mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-04-08 17:15:08 +02:00
Merge branch 'bugfix/url-handling-refactor-4868'
This commit is contained in:
commit
d0dadc031c
123
application/controllers/FilterController.php
Normal file
123
application/controllers/FilterController.php
Normal file
@ -0,0 +1,123 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
// @codingStandardsIgnoreStart
|
||||
|
||||
use Icinga\Web\Form;
|
||||
use Icinga\Web\Controller\ActionController;
|
||||
use Icinga\Filter\Filter;
|
||||
use Icinga\Filter\FilterAttribute;
|
||||
use Icinga\Filter\Type\TextFilter;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Web\Url;
|
||||
|
||||
/**
|
||||
* Application wide interface for filtering
|
||||
*/
|
||||
class FilterController extends ActionController
|
||||
{
|
||||
/**
|
||||
* The current filter registry
|
||||
*
|
||||
* @var Filter
|
||||
*/
|
||||
private $registry;
|
||||
|
||||
private $moduleRegistry;
|
||||
|
||||
/**
|
||||
* Entry point for filtering, uses the filter_domain and filter_module request parameter
|
||||
* to determine which filter registry should be used
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
$this->registry = new Filter();
|
||||
$query = $this->getRequest()->getParam('query', '');
|
||||
$target = $this->getRequest()->getParam('filter_domain', '');
|
||||
|
||||
if ($this->getRequest()->getHeader('accept') == 'application/json') {
|
||||
$this->getResponse()->setHeader('Content-Type', 'application/json');
|
||||
$this->setupQueries(
|
||||
$target,
|
||||
$this->getParam('filter_module', '')
|
||||
);
|
||||
$this->_helper->json($this->parse($query, $target));
|
||||
} else {
|
||||
$this->setupQueries(
|
||||
$target,
|
||||
$this->getParam('filter_module')
|
||||
);
|
||||
$urlTarget = $this->parse($query, $target);
|
||||
die(print_r($urlTarget,true));
|
||||
$this->redirect($urlTarget['urlParam']);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the query handler for the given domain and module
|
||||
*
|
||||
* @param string $domain The domain to use
|
||||
* @param string $module The module to use
|
||||
*/
|
||||
private function setupQueries($domain, $module = 'default')
|
||||
{
|
||||
$class = '\\Icinga\\Module\\' . ucfirst($module) . '\\Filter\\Registry';
|
||||
$factory = strtolower($domain) . 'Filter';
|
||||
$this->moduleRegistry = $class;
|
||||
$this->registry->addDomain($class::$factory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given query text and returns the json as expected by the semantic search box
|
||||
*
|
||||
* @param String $text The query to parse
|
||||
* @return array The result structure to be returned in json format
|
||||
*/
|
||||
private function parse($text, $target)
|
||||
{
|
||||
try {
|
||||
|
||||
$queryTree = $this->registry->createQueryTreeForFilter($text);
|
||||
$registry = $this->moduleRegistry;
|
||||
return array(
|
||||
'state' => 'success',
|
||||
'proposals' => $this->registry->getProposalsForQuery($text),
|
||||
'urlParam' => $registry::getUrlForTarget($target, $queryTree)
|
||||
);
|
||||
} catch (\Exception $exc) {
|
||||
Logger::error($exc);
|
||||
$this->getResponse()->setHttpResponseCode(500);
|
||||
return array(
|
||||
'state' => 'error',
|
||||
'message' => 'Search service is currently not available'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// @codingStandardsIgnoreEnd
|
@ -31,6 +31,8 @@
|
||||
/**
|
||||
* Helper to render main and detail contents into a container
|
||||
*/
|
||||
use Icinga\Application\Icinga;
|
||||
|
||||
class Zend_View_Helper_MainDetail extends Zend_View_Helper_Abstract
|
||||
{
|
||||
/**
|
||||
|
@ -31,10 +31,10 @@ $modules = $this->modules->paginate();
|
||||
<td>
|
||||
<? if ($module->enabled): ?>
|
||||
<i>{{OK_ICON}}</i>
|
||||
<a href="<?= $disableUrl ?>"><?= $this->escape($module->name); ?></a>
|
||||
<a href="<?= $disableUrl ?>" data-icinga-target="main"><?= $this->escape($module->name); ?></a>
|
||||
<? else: ?>
|
||||
<i>{{REMOVE_ICON}}</i>
|
||||
<a href="<?= $enableUrl ?>"><?= $this->escape($module->name); ?></a>
|
||||
<a href="<?= $enableUrl ?>" data-icinga-target="main"><?= $this->escape($module->name); ?></a>
|
||||
<? endif ?>
|
||||
(<?=
|
||||
$module->enabled ? ($module->loaded ? 'enabled' : 'failed') : 'disabled' ?>)
|
||||
|
11
application/views/scripts/filter/index.phtml
Normal file
11
application/views/scripts/filter/index.phtml
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
echo $this->form;
|
||||
|
||||
if ($this->tree) {
|
||||
echo $this->tree->render($this);
|
||||
echo '<br/><pre><code>';
|
||||
echo $this->sqlString;
|
||||
echo '</pre></code>';
|
||||
print_r($this->params);
|
||||
}
|
@ -1,400 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Data;
|
||||
|
||||
use Icinga\Exception;
|
||||
|
||||
abstract class AbstractQuery implements QueryInterface
|
||||
{
|
||||
/**
|
||||
* Sort ascending
|
||||
*/
|
||||
const SORT_ASC = 1;
|
||||
|
||||
/**
|
||||
* Sort descending
|
||||
*/
|
||||
const SORT_DESC = -1;
|
||||
|
||||
/**
|
||||
* Query data source
|
||||
*
|
||||
* @var DatasourceInterface
|
||||
*/
|
||||
protected $ds;
|
||||
|
||||
/**
|
||||
* The table you are going to query
|
||||
*/
|
||||
protected $table;
|
||||
|
||||
/**
|
||||
* The columns you are interested in. All columns if empty
|
||||
*/
|
||||
protected $columns = array();
|
||||
|
||||
/**
|
||||
* A list of filters
|
||||
*/
|
||||
protected $filters = array();
|
||||
|
||||
/**
|
||||
* The columns you're using to sort the query result
|
||||
*/
|
||||
protected $order_columns = array();
|
||||
|
||||
/**
|
||||
* Return not more than that many rows
|
||||
*/
|
||||
protected $limit_count;
|
||||
|
||||
/**
|
||||
* Result starts with this row
|
||||
*/
|
||||
protected $limit_offset;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param DatasourceInterface $ds Your data source
|
||||
*/
|
||||
public function __construct(DatasourceInterface $ds, $columns = null)
|
||||
{
|
||||
$this->ds = $ds;
|
||||
|
||||
if ($columns === null) {
|
||||
$columns = $this->getDefaultColumns();
|
||||
}
|
||||
if ($columns !== null) {
|
||||
$this->columns($columns);
|
||||
}
|
||||
|
||||
$this->init();
|
||||
}
|
||||
|
||||
public function addColumn($name, $alias = null)
|
||||
{
|
||||
// TODO: Fail if adding column twice, but allow same col with new alias
|
||||
if ($alias === null) {
|
||||
$this->columns[] = $name;
|
||||
} else {
|
||||
$this->columns[$alias] = $name;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDatasource()
|
||||
{
|
||||
return $this->ds;
|
||||
}
|
||||
|
||||
protected function getDefaultColumns()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose a table and the colums you are interested in
|
||||
*
|
||||
* Query will return all available columns if none are given here
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function from($table, $columns = null)
|
||||
{
|
||||
$this->table = $table;
|
||||
if ($columns !== null) {
|
||||
$this->columns($columns);
|
||||
} else {
|
||||
// TODO: Really?
|
||||
$this->columns = $this->getDefaultColumns();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function columns($columns)
|
||||
{
|
||||
if (is_array($columns)) {
|
||||
$this->columns = $columns;
|
||||
} else {
|
||||
$this->columns = array($columns);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use once or multiple times to filter result set
|
||||
*
|
||||
* Multiple where calls will be combined by a logical AND operation
|
||||
*
|
||||
* @param string $key Column or backend-specific search expression
|
||||
* @param string $val Search value, must be escaped automagically
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function where($key, $val = null)
|
||||
{
|
||||
$this->filters[] = array($key, $val);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort query result by the given column name
|
||||
*
|
||||
* Sort direction can be ascending (self::SORT_ASC, being the default)
|
||||
* or descending (self::SORT_DESC).
|
||||
*
|
||||
* Preferred usage:
|
||||
* <code>
|
||||
* $query->sort('column_name ASC')
|
||||
* </code>
|
||||
*
|
||||
* @param string $col Column, may contain direction separated by space
|
||||
* @param int $dir Sort direction
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function order($col, $dir = null)
|
||||
{
|
||||
if ($dir === null) {
|
||||
if (($pos = strpos($col, ' ')) === false) {
|
||||
$dir = $this->getDefaultSortDir($col);
|
||||
} else {
|
||||
$dir = strtoupper(substr($col, $pos + 1));
|
||||
if ($dir === 'DESC') {
|
||||
$dir = self::SORT_DESC;
|
||||
} else {
|
||||
$dir = self::SORT_ASC;
|
||||
}
|
||||
$col = substr($col, 0, $pos);
|
||||
}
|
||||
} else {
|
||||
if ($dir === self::SORT_DESC || strtoupper($dir) === 'DESC') {
|
||||
$dir = self::SORT_DESC;
|
||||
} else {
|
||||
$dir = self::SORT_ASC;
|
||||
}
|
||||
}
|
||||
$this->order_columns[] = array($col, $dir);
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function getDefaultSortDir($col)
|
||||
{
|
||||
return self::SORT_ASC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the result set
|
||||
*
|
||||
* @param int $count Return not more than that many rows
|
||||
* @param int $offset Result starts with this row
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
// Nur wenn keine stats, sonst im RAM!!
|
||||
// Offset gibt es nicht, muss simuliert werden
|
||||
public function limit($count = null, $offset = null)
|
||||
{
|
||||
if (! preg_match('~^\d+~', $count . $offset)) {
|
||||
throw new Exception\ProgrammingError(
|
||||
sprintf(
|
||||
'Got invalid limit: %s, %s',
|
||||
$count,
|
||||
$offset
|
||||
)
|
||||
);
|
||||
}
|
||||
$this->limit_count = (int) $count;
|
||||
$this->limit_offset = (int) $offset;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wheter at least one order column has been applied to this Query
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasOrder()
|
||||
{
|
||||
return ! empty($this->order_columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wheter a limit has been applied to this Query
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasLimit()
|
||||
{
|
||||
return $this->limit_count !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wheter a starting offset been applied to this Query
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasOffset()
|
||||
{
|
||||
return $this->limit_offset > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query limit
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getLimit()
|
||||
{
|
||||
return $this->limit_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query starting offset
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getOffset()
|
||||
{
|
||||
return $this->limit_offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the columns that have been asked for with this query
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function listColumns()
|
||||
{
|
||||
return $this->columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filters that have been applied to this query
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function listFilters()
|
||||
{
|
||||
return $this->filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend this function for things that should happen at construction time
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend this function for things that should happen before query execution
|
||||
*/
|
||||
protected function finish()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Total result size regardless of limit and offset
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return $this->ds->count($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch result as an array of objects
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fetchAll()
|
||||
{
|
||||
return $this->ds->fetchAll($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch first result row
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function fetchRow()
|
||||
{
|
||||
return $this->ds->fetchRow($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch first result column
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fetchColumn()
|
||||
{
|
||||
return $this->ds->fetchColumn($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch first column value from first result row
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function fetchOne()
|
||||
{
|
||||
return $this->ds->fetchOne($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch result as a key/value pair array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fetchPairs()
|
||||
{
|
||||
return $this->ds->fetchPairs($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a pagination adapter for this query
|
||||
*
|
||||
* @return \Zend_Paginator
|
||||
*/
|
||||
public function paginate($limit = null, $page = null)
|
||||
{
|
||||
$this->finish();
|
||||
if ($page === null && $limit === null) {
|
||||
$request = \Zend_Controller_Front::getInstance()->getRequest();
|
||||
|
||||
if ($page === null) {
|
||||
$page = $request->getParam('page', 0);
|
||||
}
|
||||
|
||||
if ($limit === null) {
|
||||
$limit = $request->getParam('limit', 20);
|
||||
}
|
||||
}
|
||||
$this->limit($limit, $page * $limit);
|
||||
|
||||
$paginator = new \Zend_Paginator(
|
||||
new \Icinga\Web\Paginator\Adapter\QueryAdapter($this)
|
||||
);
|
||||
|
||||
$paginator->setItemCountPerPage($limit);
|
||||
$paginator->setCurrentPageNumber($page);
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
|
||||
public function getColumns()
|
||||
{
|
||||
return $this->columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor. Remove $ds, just to be on the safe side
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
unset($this->ds);
|
||||
}
|
||||
}
|
484
library/Icinga/Data/BaseQuery.php
Normal file
484
library/Icinga/Data/BaseQuery.php
Normal file
@ -0,0 +1,484 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Data;
|
||||
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Exception;
|
||||
use Icinga\Filter\Filterable;
|
||||
use Icinga\Filter\Query\Node;
|
||||
use Icinga\Filter\Query\Tree;
|
||||
use Zend_Paginator;
|
||||
use Icinga\Web\Paginator\Adapter\QueryAdapter;
|
||||
|
||||
abstract class BaseQuery implements Filterable
|
||||
{
|
||||
/**
|
||||
* Sort ascending
|
||||
*/
|
||||
const SORT_ASC = 1;
|
||||
|
||||
/**
|
||||
* Sort descending
|
||||
*/
|
||||
const SORT_DESC = -1;
|
||||
|
||||
/**
|
||||
* Query data source
|
||||
*
|
||||
* @var DatasourceInterface
|
||||
*/
|
||||
protected $ds;
|
||||
|
||||
/**
|
||||
* The target of this query
|
||||
* @var string
|
||||
*/
|
||||
protected $table;
|
||||
|
||||
/**
|
||||
* The columns of the target that should be returned
|
||||
* @var array
|
||||
*/
|
||||
private $columns;
|
||||
|
||||
/**
|
||||
* The columns you're using to sort the query result
|
||||
* @var array
|
||||
*/
|
||||
private $orderColumns = array();
|
||||
|
||||
/**
|
||||
* Return not more than that many rows
|
||||
* @var int
|
||||
*/
|
||||
private $limitCount;
|
||||
|
||||
/**
|
||||
* Result starts with this row
|
||||
* @var int
|
||||
*/
|
||||
private $limitOffset;
|
||||
|
||||
/**
|
||||
* The backend independent filter to use for this query
|
||||
*
|
||||
* @var Tree
|
||||
*/
|
||||
private $filter;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param DatasourceInterface $ds Your data source
|
||||
*/
|
||||
public function __construct(DatasourceInterface $ds, array $columns = null)
|
||||
{
|
||||
$this->ds = $ds;
|
||||
$this->columns = $columns;
|
||||
$this->clearFilter();
|
||||
$this->init();
|
||||
|
||||
}
|
||||
|
||||
public function getDatasource()
|
||||
{
|
||||
return $this->ds;
|
||||
}
|
||||
|
||||
|
||||
public function setColumns(array $columns)
|
||||
{
|
||||
$this->columns = $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the target and attributes for this query
|
||||
*
|
||||
* The Query will return the default attribute the attributes parameter is omitted
|
||||
*
|
||||
* @param String $target The target of this query (tablename, objectname, depends on the concrete implementation)
|
||||
* @param array $columns An optional array of columns to select, if none are given the default
|
||||
* columnset is returned
|
||||
*
|
||||
* @return self Fluent interface
|
||||
*/
|
||||
public function from($target, array $attributes = null)
|
||||
{
|
||||
$this->table = $target;
|
||||
if ($attributes !== null) {
|
||||
$this->columns = $attributes;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a filter expression to be applied on this query.
|
||||
*
|
||||
* This is an alias to andWhere()
|
||||
* The syntax of the expression and valid parameters are to be defined by the concrete
|
||||
* backend-specific query implementation.
|
||||
*
|
||||
* @param string $expression Implementation specific search expression
|
||||
* @param mixed $parameters Implementation specific search value to use for query placeholders
|
||||
*
|
||||
* @return self Fluent Interface
|
||||
* @see BaseQuery::andWhere() This is an alias to andWhere()
|
||||
*/
|
||||
public function where($expression, $parameters = null)
|
||||
{
|
||||
return $this->andWhere($expression, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an mandatory filter expression to be applied on this query
|
||||
*
|
||||
* The syntax of the expression and valid parameters are to be defined by the concrete
|
||||
* backend-specific query implementation.
|
||||
*
|
||||
* @param string $expression Implementation specific search expression
|
||||
* @param mixed $parameters Implementation specific search value to use for query placeholders
|
||||
* @return self Fluent interface
|
||||
*/
|
||||
public function andWhere($expression, $parameters = null)
|
||||
{
|
||||
$node = $this->parseFilterExpression($expression, $parameters);
|
||||
if ($node === null) {
|
||||
Logger::debug('Ignoring invalid filter expression: %s (params: %s)', $expression, $parameters);
|
||||
return $this;
|
||||
}
|
||||
$this->filter->insert(Node::createAndNode());
|
||||
$this->filter->insert($node);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an lower priority filter expression to be applied on this query
|
||||
*
|
||||
* The syntax of the expression and valid parameters are to be defined by the concrete
|
||||
* backend-specific query implementation.
|
||||
*
|
||||
* @param string $expression Implementation specific search expression
|
||||
* @param mixed $parameters Implementation specific search value to use for query placeholders
|
||||
* @return self Fluent interface
|
||||
*/
|
||||
public function orWhere($expression, $parameters = null)
|
||||
{
|
||||
$node = $this->parseFilterExpression($expression, $parameters);
|
||||
if ($node === null) {
|
||||
Logger::debug('Ignoring invalid filter expression: %s (params: %s)', $expression, $parameters);
|
||||
return $this;
|
||||
}
|
||||
$this->filter->insert(Node::createOrNode());
|
||||
$this->filter->insert($node);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given field is a valid filter target
|
||||
*
|
||||
* The base implementation always returns true, overwrite it in concrete backend-specific
|
||||
* implementations
|
||||
*
|
||||
* @param String $field The field to test for being filterable
|
||||
* @return bool True if the field can be filtered, otherwise false
|
||||
*/
|
||||
public function isValidFilterTarget($field)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the internally used field name for the given alias
|
||||
*
|
||||
* The base implementation just returns the given field, overwrite it in concrete backend-specific
|
||||
* implementations
|
||||
*
|
||||
* @param String $field The field to test for being filterable
|
||||
* @return bool True if the field can be filtered, otherwise false
|
||||
*/
|
||||
public function getMappedField($field)
|
||||
{
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a filter to this query
|
||||
*
|
||||
* This is the implementation for the Filterable, use where instead
|
||||
*
|
||||
* @param $filter
|
||||
*/
|
||||
public function addFilter($filter)
|
||||
{
|
||||
if (is_string($filter)) {
|
||||
$this->addFilter(call_user_func_array(array($this, 'parseFilterExpression'), func_get_args()));
|
||||
} elseif ($filter instanceof Node) {
|
||||
$this->filter->insert($filter);
|
||||
}
|
||||
}
|
||||
|
||||
public function setFilter(Tree $filter)
|
||||
{
|
||||
$this->filter = $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a backend specific filter expression and return a Query\Node object
|
||||
*
|
||||
* @param $expression The expression to parse
|
||||
* @param $parameters Optional parameters for the expression
|
||||
* @return Node A query node or null if it's an invalid expression
|
||||
*/
|
||||
abstract protected function parseFilterExpression($expression, $parameters = null);
|
||||
|
||||
/**
|
||||
* Return all default columns
|
||||
*
|
||||
* @return array An array of default columns to use when none are selected
|
||||
*/
|
||||
public function getDefaultColumns()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sort query result by the given column name
|
||||
*
|
||||
* Sort direction can be ascending (self::SORT_ASC, being the default)
|
||||
* or descending (self::SORT_DESC).
|
||||
*
|
||||
* Preferred usage:
|
||||
* <code>
|
||||
* $query->sort('column_name ASC')
|
||||
* </code>
|
||||
*
|
||||
* @param string $columnOrAlias Column, may contain direction separated by space
|
||||
* @param int $dir Sort direction
|
||||
*
|
||||
* @return BaseQuery
|
||||
*/
|
||||
public function order($columnOrAlias, $dir = null)
|
||||
{
|
||||
if ($dir === null) {
|
||||
$colDirPair = explode(' ', $columnOrAlias, 2);
|
||||
if (count($colDirPair) === 1) {
|
||||
$dir = $this->getDefaultSortDir($columnOrAlias);
|
||||
} else {
|
||||
$dir = $colDirPair[1];
|
||||
$columnOrAlias = $colDirPair[0];
|
||||
}
|
||||
}
|
||||
|
||||
$dir = (strtoupper(trim($dir)) === 'DESC') ? self::SORT_DESC : self::SORT_ASC;
|
||||
|
||||
$this->orderColumns[] = array($columnOrAlias, $dir);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the default sort direction constant for the given column
|
||||
*
|
||||
* @param String $col The column to get the sort direction for
|
||||
* @return int Either SORT_ASC or SORT_DESC
|
||||
*/
|
||||
protected function getDefaultSortDir($col)
|
||||
{
|
||||
return self::SORT_ASC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the result set
|
||||
*
|
||||
* @param int $count The numeric maximum limit to apply on the query result
|
||||
* @param int $offset The offset to use for the result set
|
||||
*
|
||||
* @return BaseQuery
|
||||
*/
|
||||
public function limit($count = null, $offset = null)
|
||||
{
|
||||
$this->limitCount = $count !== null ? intval($count) : null;
|
||||
$this->limitOffset = intval($offset);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this query will be ordered explicitly
|
||||
*
|
||||
* @return bool True when an order column has been set
|
||||
*/
|
||||
public function hasOrder()
|
||||
{
|
||||
return !empty($this->orderColumns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this query will be limited explicitly
|
||||
*
|
||||
* @return bool True when an limit count has been set, otherwise false
|
||||
*/
|
||||
public function hasLimit()
|
||||
{
|
||||
return $this->limitCount !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether an offset is set or not
|
||||
*
|
||||
* @return bool True when an offset > 0 is set
|
||||
*/
|
||||
public function hasOffset()
|
||||
{
|
||||
return $this->limitOffset > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query limit
|
||||
*
|
||||
* @return int The query limit or null if none is set
|
||||
*/
|
||||
public function getLimit()
|
||||
{
|
||||
return $this->limitCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query starting offset
|
||||
*
|
||||
* @return int The query offset or null if none is set
|
||||
*/
|
||||
public function getOffset()
|
||||
{
|
||||
return $this->limitOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation specific initialization
|
||||
*
|
||||
* Overwrite this instead of __construct (it's called at the end of the construct) to
|
||||
* implement custom initialization logic on construction time
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all columns set in this query or the default columns if none are set
|
||||
*
|
||||
* @return array An array of columns
|
||||
*/
|
||||
public function getColumns()
|
||||
{
|
||||
return ($this->columns !== null) ? $this->columns : $this->getDefaultColumns();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return all columns used for ordering
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOrderColumns()
|
||||
{
|
||||
return $this->orderColumns;
|
||||
}
|
||||
|
||||
public function getFilter()
|
||||
{
|
||||
return $this->filter;
|
||||
}
|
||||
|
||||
public function clearFilter()
|
||||
{
|
||||
$this->filter = new Tree();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a pagination adapter for this query
|
||||
*
|
||||
* @return \Zend_Paginator
|
||||
*/
|
||||
public function paginate($limit = null, $page = null)
|
||||
{
|
||||
if ($page === null && $limit === null) {
|
||||
$request = \Zend_Controller_Front::getInstance()->getRequest();
|
||||
|
||||
if ($page === null) {
|
||||
$page = $request->getParam('page', 0);
|
||||
}
|
||||
|
||||
if ($limit === null) {
|
||||
$limit = $request->getParam('limit', 20);
|
||||
}
|
||||
}
|
||||
$this->limit($limit, $page * $limit);
|
||||
|
||||
$paginator = new Zend_Paginator(new QueryAdapter($this));
|
||||
|
||||
$paginator->setItemCountPerPage($limit);
|
||||
$paginator->setCurrentPageNumber($page);
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Total result size regardless of limit and offset
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return $this->ds->count($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch result as an array of objects
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fetchAll()
|
||||
{
|
||||
return $this->ds->fetchAll($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch first result row
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function fetchRow()
|
||||
{
|
||||
return $this->ds->fetchRow($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch first result column
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fetchColumn()
|
||||
{
|
||||
return $this->ds->fetchColumn($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch first column value from first result row
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function fetchOne()
|
||||
{
|
||||
return $this->ds->fetchOne($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch result as a key/value pair array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fetchPairs()
|
||||
{
|
||||
return $this->ds->fetchPairs($this);
|
||||
}
|
||||
}
|
@ -72,16 +72,10 @@ class Datasource implements DatasourceInterface
|
||||
return $this;
|
||||
}
|
||||
$result = array();
|
||||
$filters = $query->listFilters();
|
||||
$columns = $query->listColumns();
|
||||
|
||||
$columns = $query->getColumns();
|
||||
foreach ($this->data as & $row) {
|
||||
|
||||
// Skip rows that do not match applied filters
|
||||
foreach ($filters as $f) {
|
||||
if ($row->{$f[0]} !== $f[1]) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Get only desired columns if asked so
|
||||
if (empty($columns)) {
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
namespace Icinga\Data\DataArray;
|
||||
|
||||
use Icinga\Data\AbstractQuery;
|
||||
use Icinga\Data\BaseQuery;
|
||||
|
||||
class Query extends AbstractQuery
|
||||
class Query extends BaseQuery
|
||||
{
|
||||
/**
|
||||
* Remember the last count
|
||||
@ -63,16 +63,18 @@ class Query extends AbstractQuery
|
||||
*/
|
||||
public function compare(& $a, & $b, $col_num = 0)
|
||||
{
|
||||
if (! array_key_exists($col_num, $this->order_columns)) {
|
||||
$orderColumns = $this->getOrderColumns();
|
||||
if (! array_key_exists($col_num, $orderColumns)) {
|
||||
return 0;
|
||||
}
|
||||
$col = $this->order_columns[$col_num][0];
|
||||
$dir = $this->order_columns[$col_num][1];
|
||||
|
||||
$col = $orderColumns[$col_num][0];
|
||||
$dir = $orderColumns[$col_num][1];
|
||||
|
||||
//$res = strnatcmp(strtolower($a->$col), strtolower($b->$col));
|
||||
$res = strcmp(strtolower($a->$col), strtolower($b->$col));
|
||||
if ($res === 0) {
|
||||
if (array_key_exists(++$col_num, $this->order_columns)) {
|
||||
if (array_key_exists(++$col_num, $orderColumns)) {
|
||||
return $this->compare($a, $b, $col_num);
|
||||
} else {
|
||||
return 0;
|
||||
@ -84,4 +86,14 @@ class Query extends AbstractQuery
|
||||
return $res * -1;
|
||||
}
|
||||
}
|
||||
|
||||
public function parseFilterExpression($expression, $parameters = null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function applyFilter()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ interface DatasourceInterface
|
||||
/**
|
||||
* Instantiate a Query object
|
||||
*
|
||||
* @return AbstractQuery
|
||||
* @return BaseQuery
|
||||
*/
|
||||
public function select();
|
||||
}
|
||||
|
@ -2,12 +2,22 @@
|
||||
|
||||
namespace Icinga\Data\Db;
|
||||
|
||||
use Icinga\Data\AbstractQuery;
|
||||
use Icinga\Data\Optional;
|
||||
use Icinga\Data\The;
|
||||
use Icinga\Filter\Query\Node;
|
||||
use Icinga\Filter\Query\Tree;
|
||||
use Zend_Db_Select;
|
||||
use Icinga\Data\BaseQuery;
|
||||
|
||||
class Query extends AbstractQuery
|
||||
/**
|
||||
* Db/Query class for implementing database queries
|
||||
*/
|
||||
class Query extends BaseQuery
|
||||
{
|
||||
/**
|
||||
* Zend_Db_Adapter_Abstract
|
||||
*
|
||||
*
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
@ -20,19 +30,19 @@ class Query extends AbstractQuery
|
||||
/**
|
||||
* Select object
|
||||
*/
|
||||
protected $selectQuery;
|
||||
private $selectQuery;
|
||||
|
||||
/**
|
||||
* Select object used for count query
|
||||
*/
|
||||
protected $countQuery;
|
||||
private $countQuery;
|
||||
|
||||
/**
|
||||
* Allow to override COUNT(*)
|
||||
*/
|
||||
protected $countColumns;
|
||||
|
||||
protected $uglySlowConservativeCount = false;
|
||||
protected $useSubqueryCount = false;
|
||||
|
||||
protected $countCache;
|
||||
|
||||
@ -44,7 +54,36 @@ class Query extends AbstractQuery
|
||||
$this->baseQuery = $this->db->select();
|
||||
}
|
||||
|
||||
protected function getSelectQuery()
|
||||
/**
|
||||
* Return the raw base query
|
||||
*
|
||||
* Modifications on this requires a call to Query::refreshQueryObjects()
|
||||
*
|
||||
* @return Zend_Db_Select
|
||||
*
|
||||
*/
|
||||
public function getRawBaseQuery()
|
||||
{
|
||||
return $this->baseQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recreate the select and count queries
|
||||
*
|
||||
* Required when external modifications are made in the baseQuery
|
||||
*/
|
||||
public function refreshQueryObjects()
|
||||
{
|
||||
$this->createQueryObjects();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the select query and initialize it if not done yet
|
||||
*
|
||||
* @return Zend_Db_Select
|
||||
*/
|
||||
public function getSelectQuery()
|
||||
{
|
||||
if ($this->selectQuery === null) {
|
||||
$this->createQueryObjects();
|
||||
@ -56,7 +95,12 @@ class Query extends AbstractQuery
|
||||
return $this->selectQuery;
|
||||
}
|
||||
|
||||
protected function getCountQuery()
|
||||
/**
|
||||
* Return the current count query and initialize it if not done yet
|
||||
*
|
||||
* @return Zend_Db_Select
|
||||
*/
|
||||
public function getCountQuery()
|
||||
{
|
||||
if ($this->countQuery === null) {
|
||||
$this->createQueryObjects();
|
||||
@ -64,54 +108,100 @@ class Query extends AbstractQuery
|
||||
return $this->countQuery;
|
||||
}
|
||||
|
||||
protected function createQueryObjects()
|
||||
/**
|
||||
* Create the Zend_Db select query for this query
|
||||
*/
|
||||
private function createSelectQuery()
|
||||
{
|
||||
$this->beforeCreatingCountQuery();
|
||||
$this->beforeCreatingSelectQuery();
|
||||
|
||||
$this->selectQuery = clone($this->baseQuery);
|
||||
$this->selectQuery->columns($this->columns);
|
||||
$this->selectQuery->columns($this->getColumns());
|
||||
if ($this->hasOrder()) {
|
||||
foreach ($this->order_columns as $col) {
|
||||
foreach ($this->getOrderColumns() as $col) {
|
||||
$this->selectQuery->order(
|
||||
$col[0]
|
||||
. ' '
|
||||
. ( $col[1] === self::SORT_DESC ? 'DESC' : 'ASC')
|
||||
$col[0] . ' ' . (($col[1] === self::SORT_DESC) ? 'DESC' : 'ASC')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->uglySlowConservativeCount) {
|
||||
$query = clone($this->selectQuery);
|
||||
if ($this->maxCount === null) {
|
||||
$this->countQuery = $this->db->select()->from(
|
||||
$query,
|
||||
'COUNT(*)'
|
||||
);
|
||||
} else {
|
||||
$this->countQuery = $this->db->select()->from(
|
||||
$query->reset('order')->limit($this->maxCount),
|
||||
'COUNT(*)'
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Create a countquery by using the select query as a subselect and count it's result
|
||||
*
|
||||
* This is a rather naive approach and not suitable for complex queries or queries with many results
|
||||
*
|
||||
* @return Zend_Db_Select The query object representing the count
|
||||
*/
|
||||
private function createCountAsSubQuery()
|
||||
{
|
||||
$query = clone($this->selectQuery);
|
||||
if ($this->maxCount === null) {
|
||||
return $this->db->select()->from($query, 'COUNT(*)');
|
||||
} else {
|
||||
$this->countQuery = clone($this->baseQuery);
|
||||
if ($this->countColumns === null) {
|
||||
$this->countColumns = array('cnt' => 'COUNT(*)');
|
||||
}
|
||||
$this->countQuery->columns($this->countColumns);
|
||||
return $this->db->select()->from(
|
||||
$query->reset('order')->limit($this->maxCount),
|
||||
'COUNT(*)'
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function beforeCreatingCountQuery()
|
||||
/**
|
||||
* Create a custom count query based on the columns set in countColumns
|
||||
*
|
||||
* @return Zend_Db_Select The query object representing the count
|
||||
*/
|
||||
private function createCustomCountQuery()
|
||||
{
|
||||
$query = clone($this->baseQuery);
|
||||
if ($this->countColumns === null) {
|
||||
$this->countColumns = array('cnt' => 'COUNT(*)');
|
||||
}
|
||||
$query->columns($this->countColumns);
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function beforeCreatingSelectQuery()
|
||||
/**
|
||||
* Create a query using the selected operation
|
||||
*
|
||||
* @see Query::createCountAsSubQuery() Used when useSubqueryCount is true
|
||||
* @see Query::createCustomCountQuery() Called when useSubqueryCount is false
|
||||
*/
|
||||
private function createCountQuery()
|
||||
{
|
||||
if ($this->useSubqueryCount) {
|
||||
$this->countQuery = $this->createCountAsSubquery();
|
||||
} else {
|
||||
$this->countQuery = $this->createCustomCountQuery();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function beforeQueryCreation()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected function afterQueryCreation()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Zend_Db select and count query objects for this instance
|
||||
*/
|
||||
private function createQueryObjects()
|
||||
{
|
||||
$this->beforeQueryCreation();
|
||||
$this->applyFilter();
|
||||
$this->createSelectQuery();
|
||||
$this->createCountQuery();
|
||||
$this->afterQueryCreation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the database and fetch the result count of this query
|
||||
*
|
||||
* @return int The result count of this query as returned by the database
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
if ($this->countCache === null) {
|
||||
@ -120,42 +210,113 @@ class Query extends AbstractQuery
|
||||
return $this->countCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the database and return all results
|
||||
*
|
||||
* @return array An array containing subarrays with all results contained in the database
|
||||
*/
|
||||
public function fetchAll()
|
||||
{
|
||||
return $this->db->fetchAll($this->getSelectQuery());
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the database and return the next result row
|
||||
*
|
||||
* @return array An array containing the next row of the database result
|
||||
*/
|
||||
public function fetchRow()
|
||||
{
|
||||
return $this->db->fetchRow($this->getSelectQuery());
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the database and return a single column of the result
|
||||
*
|
||||
* @return array An array containing the first column of the result
|
||||
*/
|
||||
public function fetchColumn()
|
||||
{
|
||||
return $this->db->fetchCol($this->getSelectQuery());
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the database and return a single result
|
||||
*
|
||||
* @return array An associative array containing the first result
|
||||
*/
|
||||
public function fetchOne()
|
||||
{
|
||||
return $this->db->fetchOne($this->getSelectQuery());
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the database and return key=>value pairs using hte first two columns
|
||||
*
|
||||
* @return array An array containing key=>value pairs
|
||||
*/
|
||||
public function fetchPairs()
|
||||
{
|
||||
return $this->db->fetchPairs($this->getSelectQuery());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the select and count query as a textual representation
|
||||
*
|
||||
* @return string An String containing the select and count query, using unix style newlines
|
||||
* as linebreaks
|
||||
*/
|
||||
public function dump()
|
||||
{
|
||||
return "QUERY\n=====\n"
|
||||
. $this->getSelectQuery()
|
||||
. "\n\nCOUNT\n=====\n"
|
||||
. $this->getCountQuery()
|
||||
. "\n\n";
|
||||
. $this->getSelectQuery()
|
||||
. "\n\nCOUNT\n=====\n"
|
||||
. $this->getCountQuery()
|
||||
. "\n\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the select query
|
||||
*
|
||||
* The paginator expects this, so we can't use debug output here
|
||||
*
|
||||
* @return Zend_Db_Select
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return (string) $this->getSelectQuery();
|
||||
return strval($this->getSelectQuery());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a backend specific filter expression and return a Query\Node object
|
||||
*
|
||||
* @param $expression The expression to parse
|
||||
* @param $parameters Optional parameters for the expression
|
||||
*
|
||||
* @return Node A query node or null if it's an invalid expression
|
||||
*/
|
||||
protected function parseFilterExpression($expression, $parameter = null)
|
||||
{
|
||||
$splitted = explode(' ', $expression, 3);
|
||||
if (count($splitted) === 1 && $parameter) {
|
||||
return Node::createOperatorNode(Node::OPERATOR_EQUALS, $splitted[0], $parameter);
|
||||
} elseif (count($splitted) === 2 && $parameter) {
|
||||
Node::createOperatorNode($splitted[0], $splitted[1], is_string($parameter));
|
||||
return Node::createOperatorNode(Node::OPERATOR_EQUALS, $splitted[0], $parameter);
|
||||
} elseif (count($splitted) === 3) {
|
||||
if (trim($splitted[2]) === '?' && is_string($parameter)) {
|
||||
return Node::createOperatorNode($splitted[1], $splitted[0], $parameter);
|
||||
} else {
|
||||
return Node::createOperatorNode($splitted[1], $splitted[0], $splitted[2]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function applyFilter()
|
||||
{
|
||||
$parser = new TreeToSqlParser($this);
|
||||
$parser->treeToSql($this->getFilter(), $this->baseQuery);
|
||||
$this->clearFilter();
|
||||
}
|
||||
}
|
||||
|
191
library/Icinga/Data/Db/TreeToSqlParser.php
Normal file
191
library/Icinga/Data/Db/TreeToSqlParser.php
Normal file
@ -0,0 +1,191 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Data\Db;
|
||||
|
||||
use Icinga\Data\BaseQuery;
|
||||
use Icinga\Filter\Query\Tree;
|
||||
use Icinga\Filter\Query\Node;
|
||||
|
||||
/**
|
||||
* Converter class that takes a query tree and creates an SQL Query from it's state
|
||||
*/
|
||||
class TreeToSqlParser
|
||||
{
|
||||
/**
|
||||
* The query class to use as the base for converting
|
||||
*
|
||||
* @var AbstractQuery
|
||||
*/
|
||||
private $query;
|
||||
|
||||
/**
|
||||
* The type of the filter (WHERE or HAVING, depending whether it's an aggregate query)
|
||||
* @var string
|
||||
*/
|
||||
private $type = 'WHERE';
|
||||
|
||||
/**
|
||||
* Create a new converter from this query
|
||||
*
|
||||
* @param AbstractQuery $query The query to use for conversion
|
||||
*/
|
||||
public function __construct(BaseQuery $query)
|
||||
{
|
||||
$this->query = $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL equivalent fo the given text operator
|
||||
*
|
||||
* @param String $operator The operator from the query node
|
||||
* @return string The operator for the sql query part
|
||||
*/
|
||||
private function getSqlOperator($operator)
|
||||
{
|
||||
switch($operator) {
|
||||
case Node::OPERATOR_EQUALS:
|
||||
return 'LIKE';
|
||||
case Node::OPERATOR_EQUALS_NOT:
|
||||
return 'NOT LIKE';
|
||||
default:
|
||||
return $operator;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Query Tree node to an sql string
|
||||
*
|
||||
* @param Node $node The node to convert
|
||||
* @return string The sql string representing the node's state
|
||||
*/
|
||||
private function nodeToSqlQuery(Node $node)
|
||||
{
|
||||
if ($node->type !== Node::TYPE_OPERATOR) {
|
||||
return $this->parseConjunctionNode($node);
|
||||
} else {
|
||||
return $this->parseOperatorNode($node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an AND or OR node to an sql string
|
||||
*
|
||||
* @param Node $node The AND/OR node to parse
|
||||
* @return string The sql string representing this node
|
||||
*/
|
||||
private function parseConjunctionNode(Node $node)
|
||||
{
|
||||
$queryString = '';
|
||||
$leftQuery = $this->nodeToSqlQuery($node->left);
|
||||
$rightQuery = $this->nodeToSqlQuery($node->right);
|
||||
if ($leftQuery != '') {
|
||||
$queryString .= $leftQuery . ' ';
|
||||
}
|
||||
if ($rightQuery != '') {
|
||||
$queryString .= (($queryString !== '') ? $node->type . ' ' : ' ') . $rightQuery;
|
||||
}
|
||||
return $queryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an operator node to an sql string
|
||||
*
|
||||
* @param Node $node The operator node to parse
|
||||
* @return string The sql string representing this node
|
||||
*/
|
||||
private function parseOperatorNode(Node $node)
|
||||
{
|
||||
if (!$this->query->isValidFilterTarget($node->left) && $this->query->getMappedField($node->left)) {
|
||||
return '';
|
||||
}
|
||||
$this->query->requireColumn($node->left);
|
||||
$queryString = $this->query->getMappedField($node->left);
|
||||
|
||||
if ($this->query->isAggregateColumn($node->left)) {
|
||||
$this->type = 'HAVING';
|
||||
}
|
||||
$queryString .= ' ' . (is_integer($node->right) ?
|
||||
$node->operator : $this->getSqlOperator($node->operator)) . ' ';
|
||||
$queryString .= $this->getParameterValue($node);
|
||||
return $queryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a node value to it's sql equivalent
|
||||
*
|
||||
* This currently only detects if the node is in the timestring context and calls strtotime if so and it replaces
|
||||
* '*' with '%'
|
||||
*
|
||||
* @param Node $node The node to retrieve the sql string value from
|
||||
* @return String|int The converted and quoted value
|
||||
*/
|
||||
private function getParameterValue(Node $node) {
|
||||
$value = $node->right;
|
||||
if ($node->operator === Node::OPERATOR_EQUALS || $node->operator === Node::OPERATOR_EQUALS_NOT) {
|
||||
$value = str_replace('*', '%', $value);
|
||||
}
|
||||
if ($this->query->isTimestamp($node->left)) {
|
||||
$node->context = Node::CONTEXT_TIMESTRING;
|
||||
}
|
||||
if ($node->context === Node::CONTEXT_TIMESTRING) {
|
||||
$value = strtotime($value);
|
||||
}
|
||||
return $this->query->getDatasource()->getConnection()->quote($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the given tree to the query, either as where or as having clause
|
||||
*
|
||||
* @param Tree $tree The tree representing the filter
|
||||
* @param \Zend_Db_Select $baseQuery The query to apply the filter on
|
||||
*/
|
||||
public function treeToSql(Tree $tree, $baseQuery)
|
||||
{
|
||||
if ($tree->root == null) {
|
||||
return;
|
||||
}
|
||||
$tree->root = $tree->normalizeTree($tree->root);
|
||||
$sql = $this->nodeToSqlQuery($tree->root);
|
||||
if ($this->filtersAggregate()) {
|
||||
$baseQuery->having($sql);
|
||||
} else {
|
||||
$baseQuery->where($sql);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is an filter that should be applied after aggregation
|
||||
*
|
||||
* @return bool True when having should be used, otherwise false
|
||||
*/
|
||||
private function filtersAggregate()
|
||||
{
|
||||
return $this->type === 'HAVING';
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Data Filter
|
||||
*/
|
||||
namespace Icinga\Data;
|
||||
|
||||
use ArrayIterator;
|
||||
|
||||
/**
|
||||
* This class contains an array of filters
|
||||
*
|
||||
* @package Icinga\Data
|
||||
* @author Icinga-Web Team <info@icinga.org>
|
||||
* @copyright 2013 Icinga-Web Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
|
||||
*/
|
||||
class Filter extends ArrayIterator
|
||||
{
|
||||
|
||||
public function without($keys)
|
||||
{
|
||||
$filter = new Filter();
|
||||
$params = $this->toParams();
|
||||
if (! is_array($keys)) {
|
||||
$keys = array($keys);
|
||||
}
|
||||
foreach ($keys as $key) {
|
||||
if (array_key_exists($key, $params)) {
|
||||
unset($params[$key]);
|
||||
}
|
||||
}
|
||||
foreach ($params as $k => $v) {
|
||||
$filter[] = array($k, $v);
|
||||
}
|
||||
return $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filter as key-value array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toParams()
|
||||
{
|
||||
$params = array();
|
||||
|
||||
foreach ($this as $filter) {
|
||||
$params[$filter[0]] = $filter[1];
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
@ -1,162 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Data;
|
||||
|
||||
use Countable;
|
||||
|
||||
interface QueryInterface extends Countable
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param DatasourceInterface $ds Your data source
|
||||
*/
|
||||
public function __construct(DatasourceInterface $ds, $columns = null);
|
||||
|
||||
public function getDatasource();
|
||||
|
||||
/**
|
||||
* Choose a table and the colums you are interested in
|
||||
*
|
||||
* Query will return all available columns if none are given here
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function from($table, $columns = null);
|
||||
|
||||
public function columns($columns);
|
||||
|
||||
/**
|
||||
* Use once or multiple times to filter result set
|
||||
*
|
||||
* Multiple where calls will be combined by a logical AND operation
|
||||
*
|
||||
* @param string $key Column or backend-specific search expression
|
||||
* @param string $val Search value, must be escaped automagically
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function where($key, $val = null);
|
||||
|
||||
/**
|
||||
* Sort query result by the given column name
|
||||
*
|
||||
* Sort direction can be ascending (self::SORT_ASC, being the default)
|
||||
* or descending (self::SORT_DESC).
|
||||
*
|
||||
* Preferred usage:
|
||||
* <code>
|
||||
* $query->sort('column_name ASC')
|
||||
* </code>
|
||||
*
|
||||
* @param string $col Column, may contain direction separated by space
|
||||
* @param int $dir Sort direction
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function order($col, $dir = null);
|
||||
|
||||
/**
|
||||
* Limit the result set
|
||||
*
|
||||
* @param int $count Return not more than that many rows
|
||||
* @param int $offset Result starts with this row
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function limit($count = null, $offset = null);
|
||||
|
||||
/**
|
||||
* Wheter at least one order column has been applied to this Query
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasOrder();
|
||||
|
||||
/**
|
||||
* Wheter a limit has been applied to this Query
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasLimit();
|
||||
|
||||
/**
|
||||
* Wheter a starting offset been applied to this Query
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasOffset();
|
||||
|
||||
/**
|
||||
* Get the query limit
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getLimit();
|
||||
|
||||
/**
|
||||
* Get the query starting offset
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getOffset();
|
||||
|
||||
/**
|
||||
* Get the columns that have been asked for with this query
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function listColumns();
|
||||
|
||||
public function getColumns();
|
||||
|
||||
/**
|
||||
* Get the filters that have been applied to this query
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function listFilters();
|
||||
|
||||
|
||||
/**
|
||||
* Fetch result as an array of objects
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fetchAll();
|
||||
|
||||
/**
|
||||
* Fetch first result row
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function fetchRow();
|
||||
|
||||
/**
|
||||
* Fetch first result column
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fetchColumn();
|
||||
|
||||
/**
|
||||
* Fetch first column value from first result row
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function fetchOne();
|
||||
|
||||
/**
|
||||
* Fetch result as a key/value pair array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fetchPairs();
|
||||
|
||||
/**
|
||||
* Return a pagination adapter for this query
|
||||
*
|
||||
* @return \Zend_Paginator
|
||||
*/
|
||||
public function paginate($limit = null, $page = null);
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Icinga\File;
|
||||
|
||||
use Icinga\Data\AbstractQuery;
|
||||
use Icinga\Data\BaseQuery;
|
||||
|
||||
class Csv
|
||||
{
|
||||
@ -12,7 +12,7 @@ class Csv
|
||||
{
|
||||
}
|
||||
|
||||
public static function fromQuery(AbstractQuery $query)
|
||||
public static function fromQuery(BaseQuery $query)
|
||||
{
|
||||
$csv = new Csv();
|
||||
$csv->query = $query;
|
||||
|
141
library/Icinga/Filter/Domain.php
Normal file
141
library/Icinga/Filter/Domain.php
Normal file
@ -0,0 +1,141 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Filter;
|
||||
|
||||
use Icinga\Filter\Query\Node;
|
||||
|
||||
/**
|
||||
* A Filter domain represents an object that supports filter operations and is basically a
|
||||
* container for filter attribute
|
||||
*
|
||||
*/
|
||||
class Domain extends QueryProposer
|
||||
{
|
||||
/**
|
||||
* The label to filter for
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $label;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $attributes = array();
|
||||
|
||||
/**
|
||||
* Create a new domain identified by the given label
|
||||
*
|
||||
* @param $label
|
||||
*/
|
||||
public function __construct($label)
|
||||
{
|
||||
$this->label = trim($label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true when this domain handles a given query (even if it's incomplete)
|
||||
*
|
||||
* @param String $query The query to test this domain with
|
||||
* @return bool True if this domain can handle the query
|
||||
*/
|
||||
public function handlesQuery($query)
|
||||
{
|
||||
$query = trim($query);
|
||||
return stripos($query, $this->label) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an attribute to be handled for this filter domain
|
||||
*
|
||||
* @param FilterAttribute $attr The attribute object to add to the filter
|
||||
* @return self Fluent interface
|
||||
*/
|
||||
public function registerAttribute(FilterAttribute $attr)
|
||||
{
|
||||
$this->attributes[] = $attr;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return proposals for the given query part
|
||||
*
|
||||
* @param String $query The part of the query that this specifier should parse
|
||||
* @return array An array containing 0..* proposal text tokens
|
||||
*/
|
||||
public function getProposalsForQuery($query)
|
||||
{
|
||||
$query = trim($query);
|
||||
if ($this->handlesQuery($query)) {
|
||||
// remove domain portion of the query
|
||||
$query = trim(substr($query, strlen($this->label)));
|
||||
}
|
||||
|
||||
$proposals = array();
|
||||
foreach ($this->attributes as $attributeHandler) {
|
||||
$proposals = array_merge($proposals, $attributeHandler->getProposalsForQuery($query));
|
||||
}
|
||||
|
||||
return $proposals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the label identifying this domain
|
||||
*
|
||||
* @return string the label for this domain
|
||||
*/
|
||||
public function getLabel()
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query tree node representing the given query and using the field given as
|
||||
* $leftOperand as the attribute (left leaf of the tree)
|
||||
*
|
||||
* @param String $query The query to create the node from
|
||||
* @param String $leftOperand The attribute use for the node
|
||||
* @return Node|null
|
||||
*/
|
||||
public function convertToTreeNode($query)
|
||||
{
|
||||
if ($this->handlesQuery($query)) {
|
||||
// remove domain portion of the query
|
||||
$query = trim(substr($query, strlen($this->label)));
|
||||
}
|
||||
|
||||
foreach ($this->attributes as $attributeHandler) {
|
||||
if ($attributeHandler->isValidQuery($query)) {
|
||||
$node = $attributeHandler->convertToTreeNode($query);
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
295
library/Icinga/Filter/Filter.php
Normal file
295
library/Icinga/Filter/Filter.php
Normal file
@ -0,0 +1,295 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Filter;
|
||||
|
||||
use Icinga\Filter\Query\Tree;
|
||||
use Icinga\Filter\Query\Node;
|
||||
|
||||
/**
|
||||
* Class for filter input and query parsing
|
||||
*
|
||||
* This class handles the top level parsing of queries, i.e.
|
||||
* - Splitting queries at conjunctions and parsing them part by part
|
||||
* - Delegating the query parts to specific filter domains handling this filters
|
||||
* - Building a query tree that allows to convert a filter representation into others
|
||||
* (url to string, string to url, sql..)
|
||||
*
|
||||
* Filters are split in Filter Domains, Attributes and Types:
|
||||
*
|
||||
* Attribute
|
||||
* Domain | FilterType
|
||||
* _|__ _|_ ______|____
|
||||
* / \/ \/ \
|
||||
* Host name is not 'test'
|
||||
*
|
||||
*/
|
||||
class Filter extends QueryProposer
|
||||
{
|
||||
/**
|
||||
* The default domain to use, if not set the first added domain
|
||||
*
|
||||
* @var null
|
||||
*/
|
||||
private $defaultDomain = null;
|
||||
|
||||
/**
|
||||
* An array containing all query parts that couldn't be parsed
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $ignoredQueryParts = array();
|
||||
|
||||
/**
|
||||
* An array containing all domains of this filter
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $domains = array();
|
||||
|
||||
/**
|
||||
* Create a new domain and return it
|
||||
*
|
||||
* @param String $name The field to be handled by this domain
|
||||
*
|
||||
* @return Domain The created domain object
|
||||
*/
|
||||
public function createFilterDomain($name)
|
||||
{
|
||||
$domain = new Domain(trim($name));
|
||||
|
||||
$this->domains[] = $domain;
|
||||
return $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default domain (used if no domain identifier is given to the query) to the given one
|
||||
*
|
||||
* @param Domain $domain The domain to use as the default. Will be added to the domain list if not present yet
|
||||
*/
|
||||
public function setDefaultDomain(Domain $domain)
|
||||
{
|
||||
if (!in_array($domain, $this->domains)) {
|
||||
$this->domains[] = $domain;
|
||||
}
|
||||
$this->defaultDomain = $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default domaon
|
||||
*
|
||||
* @return Domain Return either the domain that has been explicitly set as the default domain or the first
|
||||
* added. If no domain has been added yet null is returned
|
||||
*/
|
||||
public function getDefaultDomain()
|
||||
{
|
||||
if ($this->defaultDomain !== null) {
|
||||
return $this->defaultDomain;
|
||||
} elseif (count($this->domains) > 0) {
|
||||
return $this->domains[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a domain to this filter
|
||||
*
|
||||
* @param Domain $domain The domain to add
|
||||
* @return self Fluent interface
|
||||
*/
|
||||
public function addDomain(Domain $domain)
|
||||
{
|
||||
$this->domains[] = $domain;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all domains that could match the given query
|
||||
*
|
||||
* @param String $query The query to search matching domains for
|
||||
*
|
||||
* @return array An array containing 0..* domains that could handle the query
|
||||
*/
|
||||
public function getDomainsForQuery($query)
|
||||
{
|
||||
$domains = array();
|
||||
foreach ($this->domains as $domain) {
|
||||
if ($domain->handlesQuery($query)) {
|
||||
$domains[] = $domain;
|
||||
}
|
||||
}
|
||||
return $domains;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first domain matching for this query (or the default domain)
|
||||
*
|
||||
* @param String $query The query to search for a domain
|
||||
* @return Domain A matching domain or the default domain if no domain is matching
|
||||
*/
|
||||
public function getFirstDomainForQuery($query)
|
||||
{
|
||||
$domains = $this->getDomainsForQuery($query);
|
||||
if (empty($domains)) {
|
||||
$domain = $this->getDefaultDomain();
|
||||
} else {
|
||||
$domain = $domains[0];
|
||||
}
|
||||
return $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return proposals for the given query part
|
||||
*
|
||||
* @param String $query The part of the query that this specifier should parse
|
||||
*
|
||||
* @return array An array containing 0..* proposal text tokens
|
||||
*/
|
||||
public function getProposalsForQuery($query)
|
||||
{
|
||||
$query = $this->getLastQueryPart($query);
|
||||
$proposals = array();
|
||||
$domains = $this->getDomainsForQuery($query);
|
||||
foreach ($domains as $domain) {
|
||||
$proposals = array_merge($proposals, $domain->getProposalsForQuery($query));
|
||||
}
|
||||
if (empty($proposals) && $this->getDefaultDomain()) {
|
||||
foreach ($this->domains as $domain) {
|
||||
if (stripos($domain->getLabel(), $query) === 0 || $query == '') {
|
||||
$proposals[] = self::markDifference($domain->getLabel(), $query);
|
||||
}
|
||||
}
|
||||
$proposals = array_merge($proposals, $this->getDefaultDomain()->getProposalsForQuery($query));
|
||||
}
|
||||
return $proposals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the query at the next conjunction and return a 3 element array containing (left, conjunction, right)
|
||||
*
|
||||
* @param $query The query to split
|
||||
* @return array An three element tupel in the form array($left, $conjunction, $right)
|
||||
*/
|
||||
private function splitQueryAtNextConjunction($query)
|
||||
{
|
||||
$delimiter = array('AND', 'OR');
|
||||
$inStr = false;
|
||||
for ($i = 0; $i < strlen($query); $i++) {
|
||||
// Skip strings
|
||||
$char = $query[$i];
|
||||
if ($inStr) {
|
||||
if ($char == $inStr) {
|
||||
$inStr = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ($char === '\'' || $char === '"') {
|
||||
$inStr = $char;
|
||||
continue;
|
||||
}
|
||||
foreach ($delimiter as $delimiterString) {
|
||||
$delimiterLength = strlen($delimiterString);
|
||||
if (strtoupper(substr($query, $i, $delimiterLength)) === $delimiterString) {
|
||||
// Delimiter, split into left, middle, right part
|
||||
$nextPartOffset = $i + $delimiterLength;
|
||||
$left = substr($query, 0, $i);
|
||||
$conjunction = $delimiterString;
|
||||
$right = substr($query, $nextPartOffset);
|
||||
return array(trim($left), $conjunction, trim($right));
|
||||
}
|
||||
}
|
||||
}
|
||||
return array($query, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last part of the query
|
||||
*
|
||||
* Mostly required for generating query proposals
|
||||
*
|
||||
* @param $query The query to scan for the last part
|
||||
* @return mixed An string containing the rightmost query
|
||||
*/
|
||||
private function getLastQueryPart($query)
|
||||
{
|
||||
$right = $query;
|
||||
do {
|
||||
list($left, $conjuction, $right) = $this->splitQueryAtNextConjunction($right);
|
||||
} while ($conjuction !== null);
|
||||
return $left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query tree containing this filter
|
||||
*
|
||||
* Query parts that couldn't be parsed can be retrieved with Filter::getIgnoredQueryParts
|
||||
*
|
||||
* @param String $query The query string to parse into a query tree
|
||||
* @return Tree The resulting query tree (empty for invalid queries)
|
||||
*/
|
||||
public function createQueryTreeForFilter($query)
|
||||
{
|
||||
$this->ignoredQueryParts = array();
|
||||
$right = $query;
|
||||
$domain = null;
|
||||
$tree = new Tree();
|
||||
do {
|
||||
list($left, $conjunction, $right) = $this->splitQueryAtNextConjunction($right);
|
||||
$domain = $this->getFirstDomainForQuery($left);
|
||||
if ($domain === null) {
|
||||
$this->ignoredQueryParts[] = $left;
|
||||
continue;
|
||||
}
|
||||
|
||||
$node = $domain->convertToTreeNode($left);
|
||||
if (!$node) {
|
||||
$this->ignoredQueryParts[] = $left;
|
||||
continue;
|
||||
}
|
||||
$tree->insert($node);
|
||||
|
||||
if ($conjunction === 'AND') {
|
||||
$tree->insert(Node::createAndNode());
|
||||
} elseif ($conjunction === 'OR') {
|
||||
$tree->insert(Node::createOrNode());
|
||||
}
|
||||
|
||||
} while ($right !== null);
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all parts that couldn't be parsed in the last createQueryTreeForFilter run
|
||||
*
|
||||
* @return array An array containing invalid/non-parseable query strings
|
||||
*/
|
||||
public function getIgnoredQueryParts()
|
||||
{
|
||||
return $this->ignoredQueryParts;
|
||||
}
|
||||
}
|
233
library/Icinga/Filter/FilterAttribute.php
Normal file
233
library/Icinga/Filter/FilterAttribute.php
Normal file
@ -0,0 +1,233 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Filter;
|
||||
|
||||
use Icinga\Filter\Query\Node;
|
||||
use Icinga\Filter\Type\FilterType;
|
||||
|
||||
/**
|
||||
* Filter attribute class representing one possible filter for a specific domain
|
||||
*
|
||||
* These classes contain a Filter Type to determine possible operators/values etc.
|
||||
* Often the filter class directly contains the attribute and handles field => attribute mapping,
|
||||
* but one exception is the BooleanFilter, which overwrites the attribute to use for a more convenient.
|
||||
*
|
||||
* Basically, this component maps multiple attributes to one specific field.
|
||||
*/
|
||||
class FilterAttribute extends QueryProposer
|
||||
{
|
||||
/**
|
||||
* The FilterType object that handles operations on this attribute
|
||||
*
|
||||
* @var Type\FilterType
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* An array of attribute tokens to map, or empty to let the filter type choose it's own attribute
|
||||
* and skip this class
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $attributes = array();
|
||||
|
||||
/**
|
||||
* The field that is being represented by the given attributes
|
||||
*
|
||||
* @var String
|
||||
*/
|
||||
private $field;
|
||||
|
||||
/**
|
||||
* Create a new FilterAttribute using the given type as the filter Type
|
||||
*
|
||||
* @param FilterType $type The type of this filter
|
||||
*/
|
||||
public function __construct(FilterType $type)
|
||||
{
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a list of attributes to be mapped to this filter
|
||||
*
|
||||
* @param String $attr An attribute to be recognized by this filter
|
||||
* @param String ...
|
||||
*
|
||||
* @return self Fluent interface
|
||||
*/
|
||||
public function setHandledAttributes($attr)
|
||||
{
|
||||
if (!$this->field) {
|
||||
$this->field = $attr;
|
||||
}
|
||||
foreach (func_get_args() as $arg) {
|
||||
$this->attributes[] = trim($arg);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the field to be represented by this FilterAttribute
|
||||
*
|
||||
* The field is always unique while the attributes are ambiguous.
|
||||
*
|
||||
* @param String $field The field this Attribute collection maps to
|
||||
*
|
||||
* @return self Fluent Interface
|
||||
*/
|
||||
public function setField($field)
|
||||
{
|
||||
$this->field = $field;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the largest attribute that matches this query or null if none matches
|
||||
*
|
||||
* @param String $query The query to search for containing an attribute
|
||||
*
|
||||
* @return String The attribute to be used or null
|
||||
*/
|
||||
private function getMatchingAttribute($query)
|
||||
{
|
||||
$query = trim($query);
|
||||
foreach ($this->attributes as $attribute) {
|
||||
if (stripos($query, $attribute) === 0) {
|
||||
return $attribute;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this query contains an attribute mapped by this object
|
||||
*
|
||||
* @param String $query The query to search for the attribute
|
||||
*
|
||||
* @return bool True when this query contains an attribute mapped by this filter
|
||||
*/
|
||||
public function queryHasSupportedAttribute($query)
|
||||
{
|
||||
return $this->getMatchingAttribute($query) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return proposals for the given query part
|
||||
*
|
||||
* @param String $query The part of the query that this specifier should parse
|
||||
*
|
||||
* @return array An array containing 0..* proposal text tokens
|
||||
*/
|
||||
public function getProposalsForQuery($query)
|
||||
{
|
||||
$query = trim($query);
|
||||
$attribute = $this->getMatchingAttribute($query);
|
||||
|
||||
if ($attribute !== null || count($this->attributes) == 0) {
|
||||
$subQuery = trim(substr($query, strlen($attribute)));
|
||||
return $this->type->getProposalsForQuery($subQuery);
|
||||
} else {
|
||||
return $this->getAttributeProposalsForQuery($query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of possible attributes that can be used for completing the query
|
||||
*
|
||||
* @param String $query The query to fetch completion proposals for
|
||||
*
|
||||
* @return array An array containing 0..* strings with possible completions
|
||||
*/
|
||||
public function getAttributeProposalsForQuery($query)
|
||||
{
|
||||
if ($query === '') {
|
||||
if (count($this->attributes)) {
|
||||
return array($this->attributes[0]);
|
||||
} else {
|
||||
return $this->type->getProposalsForQuery($query);
|
||||
}
|
||||
}
|
||||
$proposals = array();
|
||||
foreach ($this->attributes as $attribute) {
|
||||
if (stripos($attribute, $query) === 0) {
|
||||
$proposals[] = self::markDifference($attribute, $query);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $proposals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if $query is a valid query for this filter, otherwise false
|
||||
*
|
||||
* @param String $query The query to validate
|
||||
*
|
||||
* @return bool True if $query represents a valid filter for this object, otherwise false
|
||||
*/
|
||||
public function isValidQuery($query)
|
||||
{
|
||||
$attribute = $this->getMatchingAttribute($query);
|
||||
if ($attribute === null && count($this->attributes) > 0) {
|
||||
return false;
|
||||
}
|
||||
$subQuery = trim(substr($query, strlen($attribute)));
|
||||
return $this->type->isValidQuery($subQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given query to a tree node
|
||||
*
|
||||
* @param String $query The query to convert to a tree node
|
||||
*
|
||||
* @return Node The tree node representing this query or null if the query is not valid
|
||||
*/
|
||||
public function convertToTreeNode($query)
|
||||
{
|
||||
if (!$this->isValidQuery($query)) {
|
||||
return null;
|
||||
}
|
||||
$lValue = $this->getMatchingAttribute($query);
|
||||
$subQuery = trim(substr($query, strlen($lValue)));
|
||||
|
||||
return $this->type->createTreeNode($subQuery, $this->field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to make filter creation more convenient, same as the constructor
|
||||
*
|
||||
* @param FilterType $type The filtertype to use for this attribute
|
||||
*
|
||||
* @return FilterAttribute An instance of FilterAttribute
|
||||
*/
|
||||
public static function create(FilterType $type)
|
||||
{
|
||||
return new FilterAttribute($type);
|
||||
}
|
||||
}
|
69
library/Icinga/Filter/Filterable.php
Normal file
69
library/Icinga/Filter/Filterable.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
|
||||
namespace Icinga\Filter;
|
||||
|
||||
/**
|
||||
* Interface for filterable data sources
|
||||
*/
|
||||
interface Filterable
|
||||
{
|
||||
/**
|
||||
* Return true when this field is filterable, otherwise false
|
||||
*
|
||||
* @param string $field The field to test for being filterable
|
||||
* @return boolean True when this field is filterable, otherwise false
|
||||
*/
|
||||
public function isValidFilterTarget($field);
|
||||
|
||||
/**
|
||||
* Return the internal, resolved name of the given field
|
||||
*
|
||||
* @param string $field The field to resolve
|
||||
* @return string The resolved name or null if the field is not resolvable
|
||||
*/
|
||||
public function getMappedField($field);
|
||||
|
||||
/**
|
||||
* Apply all filters of this filterable on the datasource
|
||||
*/
|
||||
public function applyFilter();
|
||||
|
||||
/**
|
||||
* Remove all filters from this datasource
|
||||
*/
|
||||
public function clearFilter();
|
||||
|
||||
/**
|
||||
* Add a filter to this datasource
|
||||
*
|
||||
* @param mixed $filter The filter to use
|
||||
*/
|
||||
public function addFilter($filter);
|
||||
}
|
143
library/Icinga/Filter/Query/Node.php
Normal file
143
library/Icinga/Filter/Query/Node.php
Normal file
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Filter\Query;
|
||||
|
||||
/**
|
||||
* Container class for the Node of a query tree
|
||||
*/
|
||||
class Node
|
||||
{
|
||||
const TYPE_AND = 'AND';
|
||||
const TYPE_OR = 'OR';
|
||||
const TYPE_OPERATOR = 'OPERATOR';
|
||||
|
||||
const OPERATOR_EQUALS = '=';
|
||||
const OPERATOR_EQUALS_NOT = '!=';
|
||||
const OPERATOR_GREATER = '>';
|
||||
const OPERATOR_LESS = '<';
|
||||
const OPERATOR_GREATER_EQ = '>=';
|
||||
const OPERATOR_LESS_EQ = '<=';
|
||||
|
||||
const CONTEXT_TIMESTRING = 'timestring';
|
||||
/**
|
||||
* Array containing all possible operators
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static public $operatorList = array(
|
||||
self::OPERATOR_EQUALS, self::OPERATOR_EQUALS_NOT, self::OPERATOR_GREATER,
|
||||
self::OPERATOR_LESS, self::OPERATOR_GREATER_EQ, self::OPERATOR_LESS_EQ
|
||||
);
|
||||
|
||||
/**
|
||||
* The type of this node
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $type = self::TYPE_OPERATOR;
|
||||
|
||||
/**
|
||||
* The operator of this node, if type is TYPE_OPERATOR
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $operator = '';
|
||||
|
||||
/**
|
||||
* The parent of this node or null if no parent exists
|
||||
*
|
||||
* @var Node
|
||||
*/
|
||||
public $parent;
|
||||
|
||||
/**
|
||||
* The left element of this Node
|
||||
*
|
||||
* @var String|Node
|
||||
*/
|
||||
public $left;
|
||||
|
||||
/**
|
||||
* The right element of this Node
|
||||
*
|
||||
* @var String|Node
|
||||
*/
|
||||
public $right;
|
||||
|
||||
/**
|
||||
* Additional information for this node (like that it represents a date)
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $context;
|
||||
|
||||
/**
|
||||
* Factory method for creating operator nodes
|
||||
*
|
||||
* @param String $operator The operator to use
|
||||
* @param String $left The left side of the node, i.e. target (mostly attribute)
|
||||
* to query for with this node
|
||||
* @param String $right The right side of the node, i.e. the value to use for querying
|
||||
*
|
||||
* @return Node An operator Node instance
|
||||
*/
|
||||
public static function createOperatorNode($operator, $left, $right)
|
||||
{
|
||||
$node = new Node();
|
||||
$node->type = self::TYPE_OPERATOR;
|
||||
$node->operator = $operator;
|
||||
$node->left = $left;
|
||||
$node->right = $right;
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method for creating an AND conjunction node
|
||||
*
|
||||
* @return Node An AND Node instance
|
||||
*/
|
||||
public static function createAndNode()
|
||||
{
|
||||
$node = new Node();
|
||||
$node->type = self::TYPE_AND;
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method for creating an OR conjunction node
|
||||
*
|
||||
* @return Node An OR Node instance
|
||||
*/
|
||||
public static function createOrNode()
|
||||
{
|
||||
$node = new Node();
|
||||
$node->type = self::TYPE_OR;
|
||||
return $node;
|
||||
}
|
||||
}
|
387
library/Icinga/Filter/Query/Tree.php
Normal file
387
library/Icinga/Filter/Query/Tree.php
Normal file
@ -0,0 +1,387 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Filter\Query;
|
||||
|
||||
use Icinga\Filter\Filterable;
|
||||
|
||||
/**
|
||||
* A binary tree representing queries in an interchangeable way
|
||||
*
|
||||
* This tree should always be created from queries and used to create queries
|
||||
* or convert query formats. Currently it doesn't support grouped expressions,
|
||||
* although this can be implemented rather easily in the tree (the problem is more or less
|
||||
* how to implement it in query languages)
|
||||
*/
|
||||
class Tree
|
||||
{
|
||||
/**
|
||||
* The curretnt root node of the Tree
|
||||
*
|
||||
* @var Node|null
|
||||
*/
|
||||
public $root;
|
||||
|
||||
/**
|
||||
* The last inserted node of the Tree
|
||||
*
|
||||
* @var Node|null
|
||||
*/
|
||||
private $lastNode;
|
||||
|
||||
/**
|
||||
* Insert a node into this tree, recognizing type and insert position
|
||||
*
|
||||
* @param Node $node The node to insert into the tree
|
||||
*/
|
||||
public function insert(Node $node)
|
||||
{
|
||||
|
||||
if ($this->root === null) {
|
||||
$this->root = $node;
|
||||
} else {
|
||||
switch ($node->type) {
|
||||
case Node::TYPE_AND:
|
||||
$this->insertAndNode($node, $this->root);
|
||||
break;
|
||||
case Node::TYPE_OR:
|
||||
$this->insertOrNode($node, $this->root);
|
||||
break;
|
||||
case Node::TYPE_OPERATOR:
|
||||
if ($this->lastNode->type == Node::TYPE_OPERATOR) {
|
||||
$this->insert(Node::createAndNode());
|
||||
}
|
||||
$node->parent = $this->lastNode;
|
||||
|
||||
if ($this->lastNode->left == null) {
|
||||
$this->lastNode->left = $node;
|
||||
} elseif ($this->lastNode->right == null) {
|
||||
$this->lastNode->right = $node;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->lastNode = $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the insert position of an AND node, using $currentNode as the parent node
|
||||
* and insert the tree
|
||||
*
|
||||
* And nodes are always with a higher priority than other nodes and only traverse down the tree
|
||||
* when encountering another AND tree on the way
|
||||
*
|
||||
* @param Node $node The node to insert
|
||||
* @param Node $currentNode The current node context
|
||||
*/
|
||||
private function insertAndNode(Node $node, Node $currentNode)
|
||||
{
|
||||
|
||||
if ($currentNode->type != Node::TYPE_AND) {
|
||||
// No AND node, insert into tree
|
||||
if ($currentNode->parent !== null) {
|
||||
$node->parent = $currentNode->parent;
|
||||
if ($currentNode->parent->left === $currentNode) {
|
||||
$currentNode->parent->left = $node;
|
||||
} else {
|
||||
$currentNode->parent->right = $node;
|
||||
}
|
||||
} else {
|
||||
$this->root = $node;
|
||||
}
|
||||
$currentNode->parent = $node;
|
||||
if ($node->left) {
|
||||
$currentNode->right = $node->left;
|
||||
}
|
||||
$node->left = $currentNode;
|
||||
$node->parent = null;
|
||||
return;
|
||||
|
||||
} elseif ($currentNode->left == null) {
|
||||
// Insert right if there's place
|
||||
$currentNode->left = $node;
|
||||
$node->parent = $currentNode;
|
||||
} elseif ($currentNode->right == null) {
|
||||
// Insert right if there's place
|
||||
$currentNode->right = $node;
|
||||
$node->parent = $currentNode;
|
||||
} else {
|
||||
// traverse down the tree if free insertion point is found
|
||||
$this->insertAndNode($node, $currentNode->right);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an OR node
|
||||
*
|
||||
* OR nodes are always inserted over operator nodes but below AND nodes
|
||||
*
|
||||
* @param Node $node The OR node to insert
|
||||
* @param Node $currentNode The current context to use for insertion
|
||||
*/
|
||||
private function insertOrNode(Node $node, Node $currentNode)
|
||||
{
|
||||
if ($currentNode->type === Node::TYPE_OPERATOR) {
|
||||
// Always insert when encountering an operator node
|
||||
if ($currentNode->parent !== null) {
|
||||
$node->parent = $currentNode->parent;
|
||||
if ($currentNode->parent->left === $currentNode) {
|
||||
$currentNode->parent->left = $node;
|
||||
} else {
|
||||
$currentNode->parent->right = $node;
|
||||
}
|
||||
} else {
|
||||
$this->root = $node;
|
||||
}
|
||||
$currentNode->parent = $node;
|
||||
$node->left = $currentNode;
|
||||
} elseif ($currentNode->left === null) {
|
||||
$currentNode->left = $node;
|
||||
$node->parent = $currentNode;
|
||||
return;
|
||||
} elseif ($currentNode->right === null) {
|
||||
$currentNode->right = $node;
|
||||
$node->parent = $currentNode;
|
||||
return;
|
||||
} else {
|
||||
$this->insertOrNode($node, $currentNode->right);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of this tree that only contains filters that can be applied for the given Filterable
|
||||
*
|
||||
* @param Filterable $filter The Filterable to test element nodes agains
|
||||
* @return Tree A copy of this tree that only contains nodes for the given filter
|
||||
*/
|
||||
public function getCopyForFilterable(Filterable $filter)
|
||||
{
|
||||
$copy = $this->createCopy();
|
||||
if (!$this->root) {
|
||||
return $copy;
|
||||
}
|
||||
|
||||
$copy->root = $this->removeInvalidFilter($copy->root, $filter);
|
||||
return $copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all tree nodes that are not applicable ot the given Filterable
|
||||
*
|
||||
* @param Node $node The root node to use
|
||||
* @param Filterable $filter The Filterable to test nodes against
|
||||
* @return Node The normalized tree node
|
||||
*/
|
||||
public function removeInvalidFilter($node, Filterable $filter)
|
||||
{
|
||||
if ($node === null) {
|
||||
return $node;
|
||||
}
|
||||
if ($node->type === Node::TYPE_OPERATOR) {
|
||||
if (!$filter->isValidFilterTarget($node->left)) {
|
||||
return null;
|
||||
} else {
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
|
||||
$node->left = $this->removeInvalidFilter($node->left, $filter);
|
||||
$node->right = $this->removeInvalidFilter($node->right, $filter);
|
||||
|
||||
if ($node->left && $node->right) {
|
||||
return $node;
|
||||
} elseif ($node->left) {
|
||||
return $node->left;
|
||||
} elseif ($node->right) {
|
||||
return $node->right;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize this tree and fix incomplete nodes
|
||||
*
|
||||
* @param Node $node The root node to normalize
|
||||
* @return Node The normalized root node
|
||||
*/
|
||||
public static function normalizeTree($node)
|
||||
{
|
||||
if ($node->type === Node::TYPE_OPERATOR) {
|
||||
return $node;
|
||||
}
|
||||
if ($node === null) {
|
||||
return null;
|
||||
}
|
||||
if ($node->left && $node->right) {
|
||||
$node->left = self::normalizeTree($node->left);
|
||||
$node->right = self::normalizeTree($node->right);
|
||||
return $node;
|
||||
} elseif ($node->left) {
|
||||
return $node->left;
|
||||
} elseif ($node->right) {
|
||||
return $node->right;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of all attributes in this tree
|
||||
*
|
||||
* @param Node $ctx The root node to use instead of the tree root
|
||||
* @return array An array of attribute names
|
||||
*/
|
||||
public function getAttributes($ctx = null)
|
||||
{
|
||||
$result = array();
|
||||
$ctx = $ctx ? $ctx : $this->root;
|
||||
if ($ctx == null) {
|
||||
return $result;
|
||||
}
|
||||
if ($ctx->type === Node::TYPE_OPERATOR) {
|
||||
$result[] = $ctx->left;
|
||||
} else {
|
||||
$result = $result + $this->getAttributes($ctx->left) + $this->getAttributes($ctx->right);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copy of this tree without the given node
|
||||
*
|
||||
* @param Node $node The node to remove
|
||||
* @return Tree A copy of the given tree
|
||||
*/
|
||||
public function withoutNode(Node $node)
|
||||
{
|
||||
$tree = $this->createCopy();
|
||||
$toRemove = $tree->findNode($node);
|
||||
if ($toRemove !== null) {
|
||||
if ($toRemove === $tree->root) {
|
||||
$tree->root = null;
|
||||
return $tree;
|
||||
}
|
||||
if ($toRemove->parent->left === $toRemove) {
|
||||
$toRemove->parent->left = null;
|
||||
} else {
|
||||
$toRemove->parent->right = null;
|
||||
}
|
||||
}
|
||||
$tree->root = $tree->normalizeTree($tree->root);
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an independent copy of this tree
|
||||
*
|
||||
* @return Tree A copy of this tree
|
||||
*/
|
||||
public function createCopy()
|
||||
{
|
||||
$tree = new Tree();
|
||||
if ($this->root === null) {
|
||||
return $tree;
|
||||
}
|
||||
|
||||
$this->copyBranch($this->root, $tree);
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the given node or branch into the given tree
|
||||
*
|
||||
* @param Node $node The node to copy
|
||||
* @param Tree $tree The tree to insert the copied node and it's subnodes to
|
||||
*/
|
||||
private function copyBranch(Node $node, Tree &$tree)
|
||||
{
|
||||
if ($node->type === Node::TYPE_OPERATOR) {
|
||||
$copy = Node::createOperatorNode($node->operator, $node->left, $node->right);
|
||||
$copy->context = $node->context;
|
||||
$tree->insert($copy);
|
||||
} else {
|
||||
if ($node->left) {
|
||||
$this->copyBranch($node->left, $tree);
|
||||
}
|
||||
$tree->insert($node->type === Node::TYPE_OR ? Node::createOrNode() : Node::createAndNode());
|
||||
if ($node->right) {
|
||||
$this->copyBranch($node->right, $tree);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for a given node in the tree and return it if exists
|
||||
*
|
||||
* @param Node $node The node to look for
|
||||
* @param Node $ctx The node to use as the root of the tree
|
||||
*
|
||||
* @return Node The node that matches $node in the tree or null
|
||||
*/
|
||||
public function findNode(Node $node, $ctx = null)
|
||||
{
|
||||
$ctx = $ctx ? $ctx : $this->root;
|
||||
if ($ctx === null) {
|
||||
return null;
|
||||
}
|
||||
if ($ctx->type === Node::TYPE_OPERATOR) {
|
||||
if ($ctx->left == $node->left && $ctx->right == $node->right && $ctx->operator == $node->operator) {
|
||||
return $ctx;
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
$result = $this->findNode($node, $ctx->left);
|
||||
if ($result === null) {
|
||||
$result = $this->findNode($node, $ctx->right);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if A node with the given attribute on the left side exists
|
||||
*
|
||||
* @param String $name The attribute to test for existence
|
||||
* @param Node $ctx The current root node
|
||||
*
|
||||
* @return bool True if a node contains $name on the left side, otherwise false
|
||||
*/
|
||||
public function hasNodeWithAttribute($name, $ctx = null)
|
||||
{
|
||||
$ctx = $ctx ? $ctx : $this->root;
|
||||
if ($ctx === null) {
|
||||
return false;
|
||||
}
|
||||
if ($ctx->type === Node::TYPE_OPERATOR) {
|
||||
return $ctx->left === $name;
|
||||
} else {
|
||||
return $this->hasNodeWithAttribute($name, $ctx->left) || $this->hasNodeWithAttribute($name, $ctx->right);
|
||||
}
|
||||
}
|
||||
}
|
63
library/Icinga/Filter/QueryProposer.php
Normal file
63
library/Icinga/Filter/QueryProposer.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Filter;
|
||||
|
||||
/**
|
||||
* Base class for Query proposers
|
||||
*
|
||||
* Query Proposer accept an query string in their getProposalsForQuery method and return
|
||||
* possible parts to complete this query
|
||||
*/
|
||||
abstract class QueryProposer
|
||||
{
|
||||
/**
|
||||
* Static helper function to encapsulate similar string parts with an {}
|
||||
*
|
||||
* @param $attribute The attribute to mark differences in
|
||||
* @param $query The query to use for determining similarities
|
||||
*
|
||||
* @return string The attribute string with similar parts encapsulated in curly braces
|
||||
*/
|
||||
public static function markDifference($attribute, $query)
|
||||
{
|
||||
if (strlen($query) === 0) {
|
||||
return $attribute;
|
||||
}
|
||||
return '{' . substr($attribute, 0, strlen($query)) . '}' . substr($attribute, strlen($query));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return proposals for the given query part
|
||||
*
|
||||
* @param String $query The part of the query that this specifier should parse
|
||||
*
|
||||
* @return array An array containing 0..* proposal text tokens
|
||||
*/
|
||||
abstract public function getProposalsForQuery($query);
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* Icinga 2 Web - Head for multiple monitoring frontends
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@ -19,41 +21,23 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Module\Monitoring\View;
|
||||
|
||||
namespace Icinga\Filter;
|
||||
|
||||
|
||||
use Icinga\Filter\Query\Tree;
|
||||
|
||||
/**
|
||||
* NotificationView
|
||||
* Interface for filter registries
|
||||
* Class Registry
|
||||
* @package Icinga\Filter
|
||||
*/
|
||||
class NotificationView extends AbstractView
|
||||
interface Registry
|
||||
{
|
||||
/**
|
||||
* Available columns provided by this view
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $availableColumns = array(
|
||||
'host_name',
|
||||
'service_description',
|
||||
'notification_type',
|
||||
'notification_reason',
|
||||
'notification_start_time',
|
||||
'notification_contact',
|
||||
'notification_information',
|
||||
'notification_command'
|
||||
);
|
||||
|
||||
/**
|
||||
* Default sorting rules
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sortDefaults = array(
|
||||
'notification_start_time' => array(
|
||||
'default_dir' => self::SORT_DESC
|
||||
)
|
||||
);
|
||||
public static function getUrlForTarget($domain, Tree $filter);
|
||||
}
|
235
library/Icinga/Filter/Type/BooleanFilter.php
Normal file
235
library/Icinga/Filter/Type/BooleanFilter.php
Normal file
@ -0,0 +1,235 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Filter\Type;
|
||||
|
||||
use Icinga\Filter\Query\Node;
|
||||
|
||||
/**
|
||||
* Boolean filter for setting flag filters (host is in problem state)
|
||||
*
|
||||
*/
|
||||
class BooleanFilter extends FilterType
|
||||
{
|
||||
/**
|
||||
* The operqator map to use
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $operators = array(
|
||||
Node::OPERATOR_EQUALS => 'Is',
|
||||
Node::OPERATOR_EQUALS_NOT => 'Is Not'
|
||||
);
|
||||
|
||||
/**
|
||||
* The fields that are supported by this filter
|
||||
*
|
||||
* These fields somehow break the mechanismn as they overwrite the field given in the
|
||||
* Attribute
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $fields = array();
|
||||
|
||||
/**
|
||||
* An TimeRangeSpecifier if a field is given
|
||||
*
|
||||
* @var TimeRangeSpecifier
|
||||
*/
|
||||
private $subFilter;
|
||||
|
||||
/**
|
||||
* An optional field to use for time information (no time filters are possible if this is not given)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $timeField;
|
||||
|
||||
/**
|
||||
* Create a new Boolean Filter handling the given field mapping
|
||||
*
|
||||
* @param array $fields The fields to use, in a internal_key => Text token mapping
|
||||
* @param String $timeField An optional time field, allows time specifiers to be appended to the query if given
|
||||
*/
|
||||
public function __construct(array $fields, $timeField = false)
|
||||
{
|
||||
$this->fields = $fields;
|
||||
if (is_string($timeField)) {
|
||||
$this->subFilter = new TimeRangeSpecifier();
|
||||
$this->timeField = $timeField;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite the text to use for operators
|
||||
*
|
||||
* @param String $positive The 'set flag' operator (default: 'is')
|
||||
* @param String $negative The 'unset flag' operator (default: 'is not')
|
||||
*/
|
||||
public function setOperators($positive, $negative)
|
||||
{
|
||||
$this->operators = array(
|
||||
Node::OPERATOR_EQUALS => $positive,
|
||||
Node::OPERATOR_EQUALS_NOT => $negative
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a proposal for completing a field given the $query string
|
||||
*
|
||||
* @param String $query The query to get the proposal from
|
||||
* @return array An array containing text tokens that could be used for completing the query
|
||||
*/
|
||||
private function getFieldProposals($query)
|
||||
{
|
||||
$proposals = array();
|
||||
foreach ($this->fields as $key => $field) {
|
||||
$match = null;
|
||||
if (self::startsWith($field, $query)) {
|
||||
$match = $field;
|
||||
} elseif (self::startsWith($key, $query)) {
|
||||
$match = $key;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (self::startsWith($query, $match) && $this->subFilter) {
|
||||
$subQuery = trim(substr($query, strlen($match)));
|
||||
$proposals = $proposals + $this->subFilter->getProposalsForQuery($subQuery);
|
||||
} elseif (strtolower($query) !== strtolower($match)) {
|
||||
$proposals[] = self::markDifference($match, $query);
|
||||
}
|
||||
}
|
||||
return $proposals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return proposals for the given query part
|
||||
*
|
||||
* @param String $query The part of the query that this specifier should parse
|
||||
*
|
||||
* @return array An array containing 0..* proposal text tokens
|
||||
*/
|
||||
public function getProposalsForQuery($query)
|
||||
{
|
||||
$proposals = array();
|
||||
$operators = $this->getOperators();
|
||||
if ($query === '') {
|
||||
return $this->getOperators();
|
||||
}
|
||||
|
||||
foreach ($operators as $operator) {
|
||||
if (strtolower($operator) === strtolower($query)) {
|
||||
$proposals += array_values($this->fields);
|
||||
} elseif (self::startsWith($operator, $query)) {
|
||||
$proposals[] = self::markDifference($operator, $query);
|
||||
} elseif (self::startsWith($query, $operator)) {
|
||||
$fieldPart = trim(substr($query, strlen($operator)));
|
||||
$proposals = $proposals + $this->getFieldProposals($fieldPart);
|
||||
}
|
||||
}
|
||||
return $proposals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return every possible operator of this Filter type
|
||||
*
|
||||
* @return array An array
|
||||
*/
|
||||
public function getOperators()
|
||||
{
|
||||
return $this->operators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true when the given query is valid for this type
|
||||
*
|
||||
* @param String $query The query to test for this filter type
|
||||
* @return bool True if the query can be parsed by this filter type
|
||||
*/
|
||||
public function isValidQuery($query)
|
||||
{
|
||||
list($field, $operator, $subQuery) = $this->getFieldValueForQuery($query);
|
||||
$valid = ($field !== null && $operator !== null);
|
||||
if ($valid && $subQuery && $this->subFilter !== null) {
|
||||
$valid = $this->subFilter->isValidQuery($subQuery);
|
||||
}
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a 3 element tupel with array(field, value, right) from the given query part
|
||||
*
|
||||
* @param String $query The query string to use
|
||||
* @return array An 3 element tupel containing the field, value and optionally the right
|
||||
* side of the query
|
||||
*/
|
||||
public function getFieldValueForQuery($query)
|
||||
{
|
||||
$operator = $this->getMatchingOperatorForQuery($query);
|
||||
if (!$operator) {
|
||||
return array(null, null, null);
|
||||
}
|
||||
$operatorList = array_flip($this->operators);
|
||||
$query = trim(substr($query, strlen($operator)));
|
||||
|
||||
$operator = $operatorList[$operator];
|
||||
foreach ($this->fields as $key => $field) {
|
||||
if (self::startsWith($query, $field)) {
|
||||
$subQuery = trim(substr($query, strlen($field)));
|
||||
return array($key, $operator === Node::OPERATOR_EQUALS ? 1 : 0, $subQuery);
|
||||
}
|
||||
}
|
||||
return array(null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query tree node representing the given query and using the field given as
|
||||
* $leftOperand as the attribute (left leaf of the tree)
|
||||
*
|
||||
* @param String $query The query to create the node from
|
||||
* @param String $leftOperand The attribute use for the node
|
||||
* @return Node|null
|
||||
*/
|
||||
public function createTreeNode($query, $leftOperand)
|
||||
{
|
||||
list($field, $value, $subQuery) = $this->getFieldValueForQuery($query);
|
||||
if ($field === null || $value === null) {
|
||||
return null;
|
||||
}
|
||||
$node = Node::createOperatorNode(Node::OPERATOR_EQUALS, $field, $value);
|
||||
if ($this->subFilter && $subQuery && $this->subFilter->isValidQuery($subQuery)) {
|
||||
$subNode = $this->subFilter->createTreeNode($subQuery, $this->timeField);
|
||||
$conjunctionNode = Node::createAndNode();
|
||||
$conjunctionNode->left = $subNode;
|
||||
$conjunctionNode->right = $node;
|
||||
$node = $conjunctionNode;
|
||||
}
|
||||
return $node;
|
||||
}
|
||||
}
|
100
library/Icinga/Filter/Type/FilterType.php
Normal file
100
library/Icinga/Filter/Type/FilterType.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
|
||||
namespace Icinga\Filter\Type;
|
||||
|
||||
use Icinga\Filter\QueryProposer;
|
||||
|
||||
/**
|
||||
* A specific type of filter
|
||||
*
|
||||
* Implementations represent specific filters like text, monitoringstatus, time, flags, etc
|
||||
*
|
||||
*/
|
||||
abstract class FilterType extends QueryProposer
|
||||
{
|
||||
/**
|
||||
* Return a list containing all operators that can appear in this filter type
|
||||
*
|
||||
* @return array An array of strings
|
||||
*/
|
||||
abstract public function getOperators();
|
||||
|
||||
/**
|
||||
* Return true if the given query is valid for this type
|
||||
*
|
||||
* @param String $query The query string to validate
|
||||
*
|
||||
* @return boolean True when the query can be converted to a tree node, otherwise false
|
||||
*/
|
||||
abstract public function isValidQuery($query);
|
||||
|
||||
/**
|
||||
* Return a tree node representing the given query that can be inserted into a query tree
|
||||
*
|
||||
* @param String $query The query to parse into a Node
|
||||
* @param String $leftOperand The field to use for the left (target) side of the node
|
||||
*
|
||||
* @return Node A tree node
|
||||
*/
|
||||
abstract public function createTreeNode($query, $leftOperand);
|
||||
|
||||
/**
|
||||
* More verbose helper method for testing whether a string starts with the second one
|
||||
*
|
||||
* @param String $string The string to use as the haystack
|
||||
* @param String $substring The string to use as the needle
|
||||
*
|
||||
* @return bool True when $string starts with $substring
|
||||
*/
|
||||
public static function startsWith($string, $substring)
|
||||
{
|
||||
return stripos($string, $substring) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the operator that matches the given query best (i.e. the one with longest matching string)
|
||||
*
|
||||
* @param String $query The query to extract the operator from
|
||||
*
|
||||
* @return string The operator contained in this query or an empty string if no operator matches
|
||||
*/
|
||||
protected function getMatchingOperatorForQuery($query)
|
||||
{
|
||||
$matchingOperator = '';
|
||||
foreach ($this->getOperators() as $operator) {
|
||||
if (stripos($query, $operator) === 0) {
|
||||
if (strlen($matchingOperator) < strlen($operator)) {
|
||||
$matchingOperator = $operator;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $matchingOperator;
|
||||
}
|
||||
}
|
209
library/Icinga/Filter/Type/TextFilter.php
Normal file
209
library/Icinga/Filter/Type/TextFilter.php
Normal file
@ -0,0 +1,209 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Filter\Type;
|
||||
|
||||
use Icinga\Filter\Query\Node;
|
||||
|
||||
class TextFilter extends FilterType
|
||||
{
|
||||
/**
|
||||
* Mapping of possible text tokens to normalized operators
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $operators = array(
|
||||
'Is' => Node::OPERATOR_EQUALS,
|
||||
'Is Not' => Node::OPERATOR_EQUALS_NOT,
|
||||
'Starts With' => Node::OPERATOR_EQUALS,
|
||||
'Ends With' => Node::OPERATOR_EQUALS,
|
||||
'Contains' => Node::OPERATOR_EQUALS,
|
||||
'=' => Node::OPERATOR_EQUALS,
|
||||
'!=' => Node::OPERATOR_EQUALS_NOT,
|
||||
'Like' => Node::OPERATOR_EQUALS,
|
||||
'Matches' => Node::OPERATOR_EQUALS
|
||||
);
|
||||
|
||||
/**
|
||||
* Return all possible operator tokens for this filter
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOperators()
|
||||
{
|
||||
return array_keys($this->operators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return proposals for the given query part
|
||||
*
|
||||
* @param String $query The part of the query that this specifier should parse
|
||||
*
|
||||
* @return array An array containing 0..* proposal text tokens
|
||||
*/
|
||||
public function getProposalsForQuery($query)
|
||||
{
|
||||
$proposals = array();
|
||||
$operators = $this->getOperators();
|
||||
if ($query === '') {
|
||||
return $this->getOperators();
|
||||
}
|
||||
foreach ($operators as $operator) {
|
||||
if (strtolower($operator) === strtolower($query)) {
|
||||
$proposals += array('\'' . $this->getProposalsForValues($operator) . '\'');
|
||||
} elseif (self::startsWith($operator, $query)) {
|
||||
$proposals[] = self::markDifference($operator, $query);
|
||||
}
|
||||
}
|
||||
|
||||
return $proposals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a (operator, value) tupel representing the given query or (null, null) if
|
||||
* the input is not valid
|
||||
*
|
||||
* @param String $query The query part to extract the operator and value from
|
||||
* @return array An array containg the operator as the first item and the value as the second
|
||||
* or (null, null) if parsing is not possible for this query
|
||||
*/
|
||||
public function getOperatorAndValueFromQuery($query)
|
||||
{
|
||||
$matchingOperator = $this->getMatchingOperatorForQuery($query);
|
||||
|
||||
if (!$matchingOperator) {
|
||||
return array(null, null);
|
||||
}
|
||||
$valuePart = trim(substr($query, strlen($matchingOperator)));
|
||||
if ($valuePart == '') {
|
||||
return array($matchingOperator, null);
|
||||
}
|
||||
$this->normalizeQuery($matchingOperator, $valuePart);
|
||||
return array($matchingOperator, $valuePart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true when the given query is valid for this type
|
||||
*
|
||||
* @param String $query The query to test for this filter type
|
||||
* @return bool True if the query can be parsed by this filter type
|
||||
*/
|
||||
public function isValidQuery($query)
|
||||
{
|
||||
list ($operator, $value) = $this->getOperatorAndValueFromQuery($query);
|
||||
return $operator !== null && $value !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query tree node representing the given query and using the field given as
|
||||
* $leftOperand as the attribute (left leaf of the tree)
|
||||
*
|
||||
* @param String $query The query to create the node from
|
||||
* @param String $leftOperand The attribute use for the node
|
||||
* @return Node|null
|
||||
*/
|
||||
public function createTreeNode($query, $leftOperand)
|
||||
{
|
||||
list ($operator, $value) = $this->getOperatorAndValueFromQuery($query);
|
||||
if ($operator === null || $value === null) {
|
||||
return null;
|
||||
}
|
||||
$node = new Node();
|
||||
$node->type = Node::TYPE_OPERATOR;
|
||||
$node->operator = $operator;
|
||||
$node->left = $leftOperand;
|
||||
$node->right = $value;
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the operator and value for the given query
|
||||
*
|
||||
* This removes quotes and adds wildcards for specific operators.
|
||||
* The operator and value will be modified in this method and can be
|
||||
* added to a QueryNode afterwards
|
||||
*
|
||||
* @param String $operator A reference to the operator string
|
||||
* @param String $value A reference to the value string
|
||||
*/
|
||||
private function normalizeQuery(&$operator, &$value)
|
||||
{
|
||||
$value = trim($value);
|
||||
|
||||
if ($value[0] == '\'' || $value[0] == '"') {
|
||||
$value = substr($value, 1);
|
||||
}
|
||||
$lastPos = strlen($value) - 1;
|
||||
if ($value[$lastPos] == '"' || $value[$lastPos] == '\'') {
|
||||
$value = substr($value, 0, -1);
|
||||
}
|
||||
|
||||
switch (strtolower($operator)) {
|
||||
case 'ends with':
|
||||
$value = '*' . $value;
|
||||
break;
|
||||
case 'starts with':
|
||||
$value = $value . '*';
|
||||
break;
|
||||
case 'matches':
|
||||
case 'contains':
|
||||
$value = '*' . $value . '*';
|
||||
break;
|
||||
}
|
||||
foreach ($this->operators as $operatorType => $type) {
|
||||
if (strtolower($operatorType) === strtolower($operator)) {
|
||||
$operator = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return generic value proposals for the given operator
|
||||
*
|
||||
* @param String $operator The operator string to create a proposal for
|
||||
* @return string The created proposals
|
||||
*/
|
||||
public function getProposalsForValues($operator)
|
||||
{
|
||||
switch (strtolower($operator)) {
|
||||
case 'starts with':
|
||||
return 'value...';
|
||||
case 'ends with':
|
||||
return '...value';
|
||||
case 'is':
|
||||
case 'is not':
|
||||
case '=':
|
||||
case '!=':
|
||||
return 'value';
|
||||
case 'matches':
|
||||
case 'contains':
|
||||
case 'like':
|
||||
return '...value...';
|
||||
}
|
||||
}
|
||||
}
|
218
library/Icinga/Filter/Type/TimeRangeSpecifier.php
Normal file
218
library/Icinga/Filter/Type/TimeRangeSpecifier.php
Normal file
@ -0,0 +1,218 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Filter\Type;
|
||||
|
||||
use Icinga\Filter\Query\Node;
|
||||
|
||||
/**
|
||||
* Filter Type for specifying time points. Uses valid inputs for strtotime as the
|
||||
* Filter value
|
||||
*
|
||||
*/
|
||||
class TimeRangeSpecifier extends FilterType
|
||||
{
|
||||
private $forcedPrefix = null;
|
||||
|
||||
/**
|
||||
* Default operator to use
|
||||
*
|
||||
* @var array A Text Token => Operator mapping for every supported operator
|
||||
*/
|
||||
private $operator = array(
|
||||
'Since' => Node::OPERATOR_GREATER_EQ,
|
||||
'Before' => Node::OPERATOR_LESS_EQ
|
||||
);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Example values that will be displayed to the user
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $timeExamples = array(
|
||||
'"5 minutes"',
|
||||
'"30 minutes"',
|
||||
'"1 hour"',
|
||||
'"6 hours"',
|
||||
'"1 day"',
|
||||
'"yesterday"',
|
||||
'"last Monday"'
|
||||
);
|
||||
|
||||
/**
|
||||
* Return proposals for the given query part
|
||||
*
|
||||
* @param String $query The part of the query that this specifier should parse
|
||||
* @return array An array containing 0..* proposal text tokens
|
||||
*/
|
||||
public function getProposalsForQuery($query)
|
||||
{
|
||||
if ($query === '') {
|
||||
return $this->getOperators();
|
||||
}
|
||||
$proposals = array();
|
||||
foreach ($this->getOperators() as $operator) {
|
||||
if (self::startsWith($query, $operator)) {
|
||||
if (!trim(substr($query, strlen($operator)))) {
|
||||
$proposals = array_merge($proposals, $this->timeExamples);
|
||||
}
|
||||
} elseif (self::startsWith($operator, $query)) {
|
||||
$proposals[] = self::markDifference($operator, $query);
|
||||
}
|
||||
}
|
||||
return $proposals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array containing the textual representation of all operators represented by this filter
|
||||
*
|
||||
* @return array An array of operator string
|
||||
*/
|
||||
public function getOperators()
|
||||
{
|
||||
return array_keys($this->operator);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a two element array with the operator and the timestring parsed from the given query part
|
||||
*
|
||||
* @param String $query The query to extract the operator and time value from
|
||||
* @return array An array containing the operator as the first and the string for
|
||||
* strotime as the second value or (null,null) if the query is invalid
|
||||
*/
|
||||
private function getOperatorAndTimeStringFromQuery($query)
|
||||
{
|
||||
$currentOperator = null;
|
||||
foreach ($this->operator as $operator => $type) {
|
||||
if (self::startsWith($query, $operator)) {
|
||||
$currentOperator = $type;
|
||||
$query = trim(substr($query, strlen($operator)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
$query = trim($query, '\'"');
|
||||
if (!$query || $currentOperator === null) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
if (is_numeric($query[0])) {
|
||||
if ($this->forcedPrefix) {
|
||||
$prefix = $this->forcedPrefix;
|
||||
} elseif ($currentOperator === Node::OPERATOR_GREATER_EQ) {
|
||||
$prefix = '-';
|
||||
} else {
|
||||
$prefix = '+';
|
||||
}
|
||||
$query = $prefix . $query;
|
||||
}
|
||||
|
||||
if (!strtotime($query)) {
|
||||
return array(null, null);
|
||||
}
|
||||
return array($currentOperator, $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the query is valid, otherwise false
|
||||
*
|
||||
* @param String $query The query string to validate
|
||||
* @return bool True if the query is valid, otherwise false
|
||||
*/
|
||||
public function isValidQuery($query)
|
||||
{
|
||||
list($operator, $timeQuery) = $this->getOperatorAndTimeStringFromQuery($query);
|
||||
return $timeQuery !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query tree node representing the given query and using the field given as
|
||||
* $leftOperand as the attribute (left leaf of the tree)
|
||||
*
|
||||
* @param String $query The query to create the node from
|
||||
* @param String $leftOperand The attribute use for the node
|
||||
* @return Node|null
|
||||
*/
|
||||
public function createTreeNode($query, $leftOperand)
|
||||
{
|
||||
list($operator, $timeQuery) = $this->getOperatorAndTimeStringFromQuery($query);
|
||||
|
||||
if ($operator === null || $timeQuery === null) {
|
||||
return null;
|
||||
}
|
||||
$node = Node::createOperatorNode($operator, $leftOperand, $timeQuery);
|
||||
$node->context = Node::CONTEXT_TIMESTRING;
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set possible operators for this query, in a 'stringtoken' => NodeOperatorConstant map
|
||||
*
|
||||
* @param array $operator The operator map to use
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function setOperator(array $operator)
|
||||
{
|
||||
$this->operator = $operator;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all implicit values ('after 30 minutes') to be in the past ('after -30 minutes')
|
||||
*
|
||||
* @param True $bool True to set all timestring in the past
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function setForcePastValue($bool = true)
|
||||
{
|
||||
if ($bool) {
|
||||
$this->forcedPrefix = '-';
|
||||
} else {
|
||||
$this->forcedPrefix = null;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all implicit values ('after 30 minutes') to be in the future ('after +30 minutes')
|
||||
*
|
||||
* @param True $bool True to set all timestring in the future
|
||||
* @return $this Fluent interface
|
||||
*/
|
||||
public function setForceFutureValue($bool = true)
|
||||
{
|
||||
if ($bool) {
|
||||
$this->forcedPrefix = '+';
|
||||
} else {
|
||||
$this->forcedPrefix = null;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Objects;
|
||||
|
||||
class Host extends Object
|
||||
{
|
||||
// Nothing here
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Objects;
|
||||
|
||||
class Object
|
||||
{
|
||||
protected $id;
|
||||
protected $name;
|
||||
protected $props;
|
||||
protected $defaults = array();
|
||||
protected $fromBackend = false;
|
||||
protected $hasBeenChanged = false;
|
||||
|
||||
protected function __construct($props = array())
|
||||
{
|
||||
$this->props = $this->defaults;
|
||||
if (! empty($props)) {
|
||||
$this->setProperties($props);
|
||||
}
|
||||
}
|
||||
|
||||
public function setProperties($props)
|
||||
{
|
||||
foreach ($props as $key => $val) {
|
||||
$this->props[$key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
protected function set($key, $val)
|
||||
{
|
||||
$this->props[$key] = $val;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function __set($key, $val)
|
||||
{
|
||||
$this->set($key, $val);
|
||||
}
|
||||
|
||||
public function __get($key)
|
||||
{
|
||||
if (array_key_exists($key, $this->props)) {
|
||||
return $this->props[$key];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function setLoadedFromBackend($loaded = true)
|
||||
{
|
||||
$this->fromBackend = $loaded;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public static function fromBackend($row)
|
||||
{
|
||||
$class = get_called_class();
|
||||
$object = new $class($row);
|
||||
$object->setLoadedFromBackend();
|
||||
return $object;
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Objects;
|
||||
|
||||
class Service extends Object
|
||||
{
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Protocol;
|
||||
|
||||
/**
|
||||
* Class AbstractQuery
|
||||
* @package Icinga\Protocol
|
||||
*/
|
||||
abstract class AbstractQuery
|
||||
{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const SORT_ASC = 1;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const SORT_DESC = -1;
|
||||
|
||||
/**
|
||||
* @param $key
|
||||
* @param null $val
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function where($key, $val = null);
|
||||
|
||||
/**
|
||||
* @param $col
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function order($col);
|
||||
|
||||
/**
|
||||
* @param null $count
|
||||
* @param null $offset
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function limit($count = null, $offset = null);
|
||||
|
||||
/**
|
||||
* @param $table
|
||||
* @param null $columns
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function from($table, $columns = null);
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasOrder()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasColumns()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getColumns()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasLimit()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasOffset()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null
|
||||
*/
|
||||
public function getLimit()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null
|
||||
*/
|
||||
public function getOffset()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
@ -28,14 +28,17 @@
|
||||
|
||||
namespace Icinga\Protocol\Statusdat;
|
||||
|
||||
use Icinga\Data\Optional;
|
||||
use Icinga\Data\The;
|
||||
use Icinga\Filter\Query\Node;
|
||||
use Icinga\Protocol;
|
||||
use Icinga\Data\AbstractQuery;
|
||||
use Icinga\Data\BaseQuery;
|
||||
|
||||
/**
|
||||
* Class Query
|
||||
* @package Icinga\Protocol\Statusdat
|
||||
*/
|
||||
class Query extends AbstractQuery
|
||||
class Query extends BaseQuery
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
@ -76,7 +79,7 @@ class Query extends AbstractQuery
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $order_columns = array();
|
||||
protected $orderColumns = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
@ -108,7 +111,7 @@ class Query extends AbstractQuery
|
||||
*/
|
||||
public function hasOrder()
|
||||
{
|
||||
return !empty($this->order_columns);
|
||||
return !empty($this->orderColumns);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -116,17 +119,11 @@ class Query extends AbstractQuery
|
||||
*/
|
||||
public function hasColumns()
|
||||
{
|
||||
return !empty($this->columns);
|
||||
$columns = $this->getColumns();
|
||||
return !empty($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getColumns()
|
||||
{
|
||||
return $this->columns;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
@ -200,7 +197,7 @@ class Query extends AbstractQuery
|
||||
$col = $col;
|
||||
}
|
||||
|
||||
$this->order_columns[] = array($col, $dir);
|
||||
$this->orderColumns[] = array($col, $dir);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
@ -222,13 +219,14 @@ class Query extends AbstractQuery
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $table
|
||||
* @param null $columns
|
||||
* @return $this
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function from($table, $columns = null)
|
||||
public function from($table, array $attributes = null)
|
||||
{
|
||||
if (isset(self::$VALID_TARGETS[$table])) {
|
||||
$this->source = $table;
|
||||
@ -275,7 +273,7 @@ class Query extends AbstractQuery
|
||||
*/
|
||||
private function orderIndices(array &$indices)
|
||||
{
|
||||
if (!empty($this->order_columns)) {
|
||||
if (!empty($this->orderColumns)) {
|
||||
foreach ($indices as $type => &$subindices) {
|
||||
$this->currentType = $type; // we're singlethreaded, so let's do it a bit dirty
|
||||
usort($subindices, array($this, "orderResult"));
|
||||
@ -293,7 +291,7 @@ class Query extends AbstractQuery
|
||||
$o1 = $this->ds->getObjectByName($this->currentType, $a);
|
||||
$o2 = $this->ds->getObjectByName($this->currentType, $b);
|
||||
$result = 0;
|
||||
foreach ($this->order_columns as $col) {
|
||||
foreach ($this->orderColumns as $col) {
|
||||
$result += $col[1] * strnatcasecmp($o1->{$col[0]}, $o2->{$col[0]});
|
||||
}
|
||||
if ($result > 0) {
|
||||
@ -396,4 +394,23 @@ class Query extends AbstractQuery
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a backend specific filter expression and return a Query\Node object
|
||||
*
|
||||
* @param $expression The expression to parse
|
||||
* @param $parameters Optional parameters for the expression
|
||||
* @return Node A query node or null if it's an invalid expression
|
||||
*/
|
||||
protected function parseFilterExpression($expression, $parameters = null)
|
||||
{
|
||||
// TODO: Implement parseFilterExpression() method.
|
||||
}
|
||||
|
||||
public function applyFilter()
|
||||
{
|
||||
// TODO: Implement applyFilter() method.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -106,23 +106,6 @@ class ActionController extends Zend_Controller_Action
|
||||
}
|
||||
}
|
||||
|
||||
private function dispatchDetailView($url)
|
||||
{
|
||||
// strip the base URL from the detail $url
|
||||
$url = substr($url, strlen($this->getRequest()->getBaseUrl()));
|
||||
// the host is mandatory, but ignored in Zend
|
||||
$req = new Request('http://ignoredhost/' . $url);
|
||||
$req->setUser($this->getRequest()->getUser());
|
||||
$req->setBaseUrl($this->getRequest()->getBaseUrl());
|
||||
$router = Zend_Controller_Front::getInstance()->getRouter();
|
||||
$router->route($req);
|
||||
Zend_Controller_Front::getInstance()->setRequest($req);
|
||||
$detailHtml = $this->view->action($req->getActionName(), $req->getControllerName(), $req->getModuleName());
|
||||
Zend_Controller_Front::getInstance()->setRequest($this->getRequest());
|
||||
$this->_helper->layout->assign('detailContent', $detailHtml);
|
||||
$this->_helper->layout->assign('detailClass', 'col-sm-12 col-xs-12 col-md-12 col-lg-6');
|
||||
$this->_helper->layout->assign('mainClass', 'col-sm-12 col-xs-12 col-md-12 col-lg-6');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the controller requires a login. That is when the controller requires authentication and the
|
||||
@ -223,30 +206,9 @@ class ActionController extends Zend_Controller_Action
|
||||
Benchmark::measure('Action::postDispatch()');
|
||||
|
||||
if ($this->_request->isXmlHttpRequest()) {
|
||||
$this->_helper->layout()->setLayout('body');
|
||||
$target = ($this->getParam('render') === 'detail') ? 'inline' : 'body';
|
||||
$this->_helper->layout()->setLayout($target);
|
||||
}
|
||||
|
||||
if ($this->getParam('detail', false)) {
|
||||
$detail = $this->getParam('detail');
|
||||
|
||||
// Zend uses the GET variables when calling getParam, therefore we have to persist the params,
|
||||
// clear the $_GET array, call the detail view with the url set in $detail and afterwards recreate
|
||||
// the $_GET array. If this is not done the following issues occur:
|
||||
//
|
||||
// - A stackoverflow issue due to infinite nested calls of buildDetailView (as the detailview has the same
|
||||
// postDispatch method) when 'detail' is not set to null
|
||||
//
|
||||
// - Params (like filters in the URL) from the detail view would be applied on all links of the master view
|
||||
// as those would be in the $_GET array after building the detail view. E.g. if you have a grid in the
|
||||
// master and a detail view filtering showing one host in detail, the pagination links of the grid would
|
||||
// contain the host filter of the detail view
|
||||
//
|
||||
$params = $_GET;
|
||||
$_GET['detail'] = null;
|
||||
$this->dispatchDetailView($detail);
|
||||
$_GET = $params;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
125
library/Icinga/Web/Widget/FilterBadgeRenderer.php
Normal file
125
library/Icinga/Web/Widget/FilterBadgeRenderer.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Web\Widget;
|
||||
|
||||
use Icinga\Filter\Query\Tree;
|
||||
use Icinga\Filter\Query\Node;
|
||||
use Icinga\Module\Monitoring\Filter\UrlViewFilter;
|
||||
use Icinga\Web\Url;
|
||||
|
||||
use Zend_View_Abstract;
|
||||
|
||||
/**
|
||||
* A renderer for filter badges that allow to disable specific filters
|
||||
*/
|
||||
class FilterBadgeRenderer implements Widget
|
||||
{
|
||||
private $tree;
|
||||
/**
|
||||
* @var Url
|
||||
*/
|
||||
private $baseUrl;
|
||||
private $conjunctionCellar = '';
|
||||
private $urlFilter;
|
||||
|
||||
/**
|
||||
* Create a new badge renderer for this tree
|
||||
*
|
||||
* @param Tree $tree
|
||||
*/
|
||||
public function __construct(Tree $tree)
|
||||
{
|
||||
$this->tree = $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a removable badge from a query tree node
|
||||
*
|
||||
* @param Node $node The node to create the badge for
|
||||
* @return string The html for the badge
|
||||
*/
|
||||
private function nodeToBadge(Node $node)
|
||||
{
|
||||
$basePath = $this->baseUrl->getAbsoluteUrl();
|
||||
$allParams = $this->baseUrl->getParams();
|
||||
|
||||
if ($node->type === Node::TYPE_OPERATOR) {
|
||||
|
||||
$newTree = $this->tree->withoutNode($node);
|
||||
$url = $this->urlFilter->fromTree($newTree);
|
||||
$url = $basePath . (empty($allParams) ? '?' : '&') . $url;
|
||||
|
||||
return ' <a class="filter-badge btn btn-default btn-xs" href="' . $url . '">'
|
||||
. $this->conjunctionCellar . ' '
|
||||
. ucfirst($node->left) . ' '
|
||||
. $node->operator . ' '
|
||||
. $node->right . '</a>';
|
||||
}
|
||||
$result = '';
|
||||
|
||||
$result .= $this->nodeToBadge($node->left);
|
||||
$this->conjunctionCellar = $node->type;
|
||||
$result .= $this->nodeToBadge($node->right);
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize $this->baseUrl with an Url instance containing all non-filter parameter
|
||||
*/
|
||||
private function buildBaseUrl()
|
||||
{
|
||||
$baseUrl = Url::fromRequest();
|
||||
foreach ($baseUrl->getParams() as $key => $param) {
|
||||
$translated = preg_replace('/[^0-9A-Za-z_]{1,2}$/', '', $key);
|
||||
if ($this->tree->hasNodeWithAttribute($translated) === true) {
|
||||
$baseUrl->removeKey($key);
|
||||
}
|
||||
}
|
||||
$this->baseUrl = $baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders this widget via the given view and returns the
|
||||
* HTML as a string
|
||||
*
|
||||
* @param \Zend_View_Abstract $view
|
||||
* @return string
|
||||
*/
|
||||
public function render(Zend_View_Abstract $view)
|
||||
{
|
||||
$this->urlFilter = new UrlViewFilter();
|
||||
if ($this->tree->root == null) {
|
||||
return '';
|
||||
}
|
||||
$this->buildBaseUrl();
|
||||
return $this->nodeToBadge($this->tree->root);
|
||||
}
|
||||
}
|
118
library/Icinga/Web/Widget/FilterBox.php
Normal file
118
library/Icinga/Web/Widget/FilterBox.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Web\Widget;
|
||||
|
||||
use Zend_View_Abstract;
|
||||
|
||||
use Icinga\Web\Form;
|
||||
use Icinga\Web\Url;
|
||||
use Icinga\Filter\Query\Tree;
|
||||
|
||||
/**
|
||||
* Widget that renders a filter input box together with an FilterBadgeRenderer widget
|
||||
*/
|
||||
class FilterBox implements Widget
|
||||
{
|
||||
/**
|
||||
* An optional initial filter to use
|
||||
*
|
||||
* @var \Icinga\Filter\Query\Tree
|
||||
*/
|
||||
private $initialFilter;
|
||||
|
||||
/**
|
||||
* The domain of the filter, set in the data-icinga-filter-domain attribute
|
||||
* @var string
|
||||
*/
|
||||
private $domain;
|
||||
|
||||
/**
|
||||
* The module of the filter, set in the data-icinga-filter-module attribute
|
||||
* @var string
|
||||
*/
|
||||
private $module;
|
||||
|
||||
/**
|
||||
* The template used for rendering the form and badges
|
||||
* @var string
|
||||
*/
|
||||
private static $TPL = <<<'EOT'
|
||||
<div class="row">
|
||||
<div class="col-md-12">{{FORM}}</div>
|
||||
<div class="col-md-12">{{BADGES}}</div>
|
||||
</div>
|
||||
EOT;
|
||||
|
||||
/**
|
||||
* Create a new FilterBox widget
|
||||
*
|
||||
* @param Tree $initialFilter The tree to use for initial population
|
||||
* @param String $domain The filter domain
|
||||
* @param String $module The filter module
|
||||
*/
|
||||
public function __construct(Tree $initialFilter, $domain, $module)
|
||||
{
|
||||
$this->initialFilter = $initialFilter;
|
||||
$this->domain = $domain;
|
||||
$this->module = $module;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render this widget
|
||||
*
|
||||
* @param Zend_View_Abstract $view The view to use for rendering the widget
|
||||
* @return string The HTML of the widget as a string
|
||||
*/
|
||||
public function render(Zend_View_Abstract $view)
|
||||
{
|
||||
|
||||
$form = new Form();
|
||||
$form->setAttrib('class', 'form-inline');
|
||||
$form->setMethod('GET');
|
||||
$form->setAction(Url::fromPath('/filter'));
|
||||
$form->setTokenDisabled();
|
||||
$form->addElement(
|
||||
'text',
|
||||
'query',
|
||||
array(
|
||||
'label' => 'Filter Results',
|
||||
'name' => 'query',
|
||||
'data-icinga-component' => 'app/semanticsearch',
|
||||
'data-icinga-filter-domain' => $this->domain,
|
||||
'data-icinga-filter-module' => $this->module
|
||||
)
|
||||
);
|
||||
$form->removeAttrib('data-icinga-component');
|
||||
|
||||
$form->setIgnoreChangeDiscarding(true);
|
||||
$badges = new FilterBadgeRenderer($this->initialFilter);
|
||||
$html = str_replace('{{FORM}}', $form->render($view), self::$TPL);
|
||||
return str_replace('{{BADGES}}', $badges->render($view), $html);
|
||||
}
|
||||
}
|
@ -77,7 +77,7 @@ class SortBox implements Widget
|
||||
/**
|
||||
* A request object used for initial form population
|
||||
*
|
||||
* @var Icinga\Web\Request
|
||||
* @var \Icinga\Web\Request
|
||||
*/
|
||||
private $request;
|
||||
|
||||
@ -166,6 +166,7 @@ class SortBox implements Widget
|
||||
$form->addElement($this->createFallbackSubmitButton());
|
||||
|
||||
if ($this->request) {
|
||||
$form->setAction($this->request->getRequestUri());
|
||||
$form->populate($this->request->getParams());
|
||||
}
|
||||
return $form->render($view);
|
||||
|
@ -221,7 +221,7 @@ class Tab implements Widget
|
||||
*/
|
||||
public function render(Zend_View_Abstract $view)
|
||||
{
|
||||
$class = $this->active ? ' class="active"' : '';
|
||||
$class = $this->active ? ' class="active" ' : '';
|
||||
$caption = $this->title;
|
||||
|
||||
if ($this->icon !== null) {
|
||||
@ -238,7 +238,8 @@ class Tab implements Widget
|
||||
$tagParams .= ' ' . $key . '="' . $value . '"';
|
||||
}
|
||||
}
|
||||
$tab = '<a' . $tagParams .' href="' . $this->url->getAbsoluteUrl() . '">' . $caption . '</a>';
|
||||
$tab = '<a' . $tagParams .' href="' . $this->url->getAbsoluteUrl()
|
||||
. '" data-icinga-target="self">' . $caption . '</a>';
|
||||
} else {
|
||||
$tab = $caption;
|
||||
}
|
||||
|
@ -38,16 +38,22 @@ use Icinga\Web\Widget\Tabextension\OutputFormat;
|
||||
use Icinga\Web\Widget\Tabs;
|
||||
use Icinga\Module\Monitoring\Backend;
|
||||
use Icinga\Web\Widget\SortBox;
|
||||
use Icinga\Web\Widget\FilterBox;
|
||||
use Icinga\Application\Config as IcingaConfig;
|
||||
|
||||
use Icinga\Module\Monitoring\DataView\DataView;
|
||||
use Icinga\Module\Monitoring\DataView\Notification as NotificationView;
|
||||
use Icinga\Module\Monitoring\DataView\Downtime as DowntimeView;
|
||||
use Icinga\Module\Monitoring\DataView\Contact as ContactView;
|
||||
use Icinga\Module\Monitoring\DataView\Contactgroup as ContactgroupView;
|
||||
use Icinga\Module\Monitoring\DataView\HostAndServiceStatus as HostAndServiceStatusView;
|
||||
use Icinga\Module\Monitoring\DataView\HostStatus as HostStatusView;
|
||||
use Icinga\Module\Monitoring\DataView\ServiceStatus as ServiceStatusView;
|
||||
use Icinga\Module\Monitoring\DataView\Comment as CommentView;
|
||||
use Icinga\Module\Monitoring\DataView\Groupsummary as GroupsummaryView;
|
||||
use Icinga\Module\Monitoring\DataView\EventHistory as EventHistoryView;
|
||||
use Icinga\Module\Monitoring\Filter\UrlViewFilter;
|
||||
use Icinga\Module\Monitoring\DataView\ServiceStatus;
|
||||
use Icinga\Filter\Filterable;
|
||||
|
||||
class Monitoring_ListController extends MonitoringController
|
||||
{
|
||||
@ -57,6 +63,7 @@ class Monitoring_ListController extends MonitoringController
|
||||
* @var Backend
|
||||
*/
|
||||
protected $backend;
|
||||
|
||||
/**
|
||||
* Compact layout name
|
||||
*
|
||||
@ -95,8 +102,9 @@ class Monitoring_ListController extends MonitoringController
|
||||
*/
|
||||
public function hostsAction()
|
||||
{
|
||||
|
||||
$this->compactView = 'hosts-compact';
|
||||
$query = HostAndServiceStatusView::fromRequest(
|
||||
$dataview = HostStatusView::fromRequest(
|
||||
$this->_request,
|
||||
array(
|
||||
'host_icon_image',
|
||||
@ -122,8 +130,10 @@ class Monitoring_ListController extends MonitoringController
|
||||
'host_current_check_attempt',
|
||||
'host_max_check_attempts'
|
||||
)
|
||||
)->getQuery();
|
||||
$this->view->hosts = $query->paginate();
|
||||
);
|
||||
$query = $dataview->getQuery();
|
||||
$this->setupFilterControl($dataview, 'host');
|
||||
|
||||
$this->setupSortControl(array(
|
||||
'host_last_check' => 'Last Host Check',
|
||||
'host_severity' => 'Host Severity',
|
||||
@ -133,6 +143,8 @@ class Monitoring_ListController extends MonitoringController
|
||||
'host_state' => 'Hard State'
|
||||
));
|
||||
$this->handleFormatRequest($query);
|
||||
$this->view->hosts = $query->paginate();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -142,6 +154,8 @@ class Monitoring_ListController extends MonitoringController
|
||||
{
|
||||
$this->compactView = 'services-compact';
|
||||
$this->view->services = $this->fetchServices();
|
||||
|
||||
$this->setupFilterControl(ServiceStatus::fromRequest($this->getRequest()), 'service');
|
||||
$this->setupSortControl(array(
|
||||
'service_last_check' => 'Last Service Check',
|
||||
'service_severity' => 'Severity',
|
||||
@ -180,6 +194,7 @@ class Monitoring_ListController extends MonitoringController
|
||||
'downtime_trigger_time'
|
||||
)
|
||||
)->getQuery();
|
||||
|
||||
$this->view->downtimes = $query->paginate();
|
||||
$this->setupSortControl(array(
|
||||
'downtime_is_in_effect' => 'Is In Effect',
|
||||
@ -369,12 +384,27 @@ class Monitoring_ListController extends MonitoringController
|
||||
|
||||
public function eventhistoryAction()
|
||||
{
|
||||
$query = EventHistoryView::fromRequest($this->_request)->getQuery();
|
||||
$query = EventHistoryView::fromRequest(
|
||||
$this->getRequest(),
|
||||
array(
|
||||
'host_name',
|
||||
'service_description',
|
||||
'object_type',
|
||||
'timestamp',
|
||||
'raw_timestamp',
|
||||
'state',
|
||||
'attempt',
|
||||
'max_attempts',
|
||||
'output',
|
||||
'type',
|
||||
'host',
|
||||
'service'
|
||||
)
|
||||
)->getQuery();
|
||||
$this->handleFormatRequest($query);
|
||||
$this->view->history = $query->paginate();
|
||||
$this->setupSortControl(
|
||||
array(
|
||||
)
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
@ -389,10 +419,11 @@ class Monitoring_ListController extends MonitoringController
|
||||
$this->_helper->viewRenderer($this->compactView);
|
||||
}
|
||||
|
||||
if ($this->_getParam('format') === 'sql'
|
||||
|
||||
if ($this->getParam('format') === 'sql'
|
||||
&& IcingaConfig::app()->global->get('environment', 'production') === 'development') {
|
||||
echo '<pre>'
|
||||
. htmlspecialchars(wordwrap($query->dump()))
|
||||
. htmlspecialchars(wordwrap($query->__toString()))
|
||||
. '</pre>';
|
||||
exit;
|
||||
}
|
||||
@ -425,6 +456,17 @@ class Monitoring_ListController extends MonitoringController
|
||||
$this->view->sortControl->applyRequest($this->getRequest());
|
||||
}
|
||||
|
||||
private function setupFilterControl(Filterable $dataview, $domain)
|
||||
{
|
||||
$parser = new UrlViewFilter($dataview);
|
||||
$this->view->filterBox = new FilterBox(
|
||||
$parser->fromRequest($this->getRequest()),
|
||||
$domain,
|
||||
'monitoring'
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all tabs for this controller
|
||||
*
|
||||
@ -432,7 +474,6 @@ class Monitoring_ListController extends MonitoringController
|
||||
*/
|
||||
private function createTabs()
|
||||
{
|
||||
|
||||
$tabs = $this->getTabs();
|
||||
$tabs->extend(new OutputFormat())
|
||||
->extend(new DashboardAction());
|
||||
|
@ -34,8 +34,8 @@ use \Icinga\Module\Monitoring\Object\Host;
|
||||
use \Icinga\Module\Monitoring\Object\Service;
|
||||
use \Icinga\Application\Benchmark;
|
||||
use \Icinga\Web\Widget\Tabextension\OutputFormat;
|
||||
use \Icinga\Web\Widget\Tabextension\DashboardAction;
|
||||
use \Icinga\Web\Widget\Tabextension\BasketAction;
|
||||
use Icinga\Web\Widget\Tabextension\DashboardAction;
|
||||
use Icinga\Module\Monitoring\Object\AbstractObject;
|
||||
use \Icinga\Web\Widget\Tabs;
|
||||
|
||||
/**
|
||||
@ -55,28 +55,16 @@ class Monitoring_ShowController extends MonitoringController
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$host = $this->_getParam('host');
|
||||
$service = $this->_getParam('service');
|
||||
$this->backend = Backend::createBackend($this->_getParam('backend'));
|
||||
$object = null;
|
||||
// TODO: Do not allow wildcards in names!
|
||||
if ($host !== null) {
|
||||
// TODO: $this->assertPermission('host/read', $host);
|
||||
if ($this->getRequest()->getActionName() !== 'host' && $service !== null && $service !== '*') {
|
||||
// TODO: $this->assertPermission('service/read', $service);
|
||||
$object = Service::fetch($this->backend, $host, $service);
|
||||
} else {
|
||||
$object = Host::fetch($this->backend, $host);
|
||||
}
|
||||
|
||||
if ($this->getRequest()->getActionName() === 'host') {
|
||||
$this->view->object = new Host($this->getRequest());
|
||||
} elseif ($this->getRequest()->getActionName() === 'service') {
|
||||
$this->view->object = new Service($this->getRequest());
|
||||
|
||||
} else {
|
||||
$this->view->object = AbstractObject::fromRequest($this->getRequest());
|
||||
}
|
||||
|
||||
$this->view->compact = $this->_getParam('view') === 'compact';
|
||||
if ($object === null) {
|
||||
// TODO: Notification, not found
|
||||
$this->redirectNow('monitoring/list/services');
|
||||
return;
|
||||
}
|
||||
$this->view->object = $object;
|
||||
$this->createTabs();
|
||||
}
|
||||
|
||||
@ -85,8 +73,7 @@ class Monitoring_ShowController extends MonitoringController
|
||||
*/
|
||||
public function serviceAction()
|
||||
{
|
||||
$this->view->object->prefetch();
|
||||
$this->view->preserve = array();
|
||||
$this->view->object->populate();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,14 +81,15 @@ class Monitoring_ShowController extends MonitoringController
|
||||
*/
|
||||
public function hostAction()
|
||||
{
|
||||
$this->view->object->prefetch();
|
||||
$this->view->preserve = array();
|
||||
$this->view->object->populate();
|
||||
}
|
||||
|
||||
public function historyAction()
|
||||
{
|
||||
$this->view->object->populate();
|
||||
$this->view->object->fetchEventHistory();
|
||||
$this->view->history = $this->view->object->eventHistory->limit(10)->paginate();
|
||||
$this->view->history = $this->view->object->eventhistory->limit(10)->paginate();
|
||||
|
||||
}
|
||||
|
||||
public function servicesAction()
|
||||
|
@ -5,8 +5,19 @@ $viewHelper = $this->getHelper('MonitoringState');
|
||||
<?= $this->tabs->render($this); ?>
|
||||
<h1>Hosts Status</h1>
|
||||
<div data-icinga-component="app/mainDetailGrid">
|
||||
<?= $this->sortControl->render($this); ?>
|
||||
<?= $this->paginationControl($hosts, null, null, array('preserve' => $this->preserve)); ?>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<?= $this->filterBox->render($this); ?>
|
||||
</div>
|
||||
<div class="col-md-7">
|
||||
<?= $this->sortControl->render($this); ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<?= $this->paginationControl($hosts, null, null, array('preserve' => $this->preserve)); ?>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-condensed">
|
||||
|
||||
<tbody>
|
||||
|
@ -5,8 +5,19 @@ $viewHelper = $this->getHelper('MonitoringState');
|
||||
<?= $this->tabs->render($this); ?>
|
||||
<h1>Services Status</h1>
|
||||
<div data-icinga-component="app/mainDetailGrid">
|
||||
<?= $this->sortControl->render($this); ?>
|
||||
<?= $this->paginationControl($services, null, null, array('preserve' => $this->preserve)); ?>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<?= $this->filterBox->render($this); ?>
|
||||
</div>
|
||||
<div class="col-md-7">
|
||||
<?= $this->sortControl->render($this); ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<?= $this->paginationControl($services, null, null, array('preserve' => $this->preserve)); ?>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-condensed">
|
||||
<tbody>
|
||||
<?php foreach ($services as $service): ?>
|
||||
|
@ -1,15 +1,23 @@
|
||||
<?= $this->tabs->render($this); ?>
|
||||
|
||||
<h1>History</h1>
|
||||
<?php if($history->count() === 0): ?>
|
||||
<div class="alert alert-info">
|
||||
No History Available For This Object
|
||||
|
||||
</div>
|
||||
<?php else: ?>
|
||||
|
||||
<?php if (!empty($history)): ?>
|
||||
<div data-icinga-component="app/mainDetailGrid">
|
||||
|
||||
<?= $this->paginationControl($history, null, null, array('preserve' => $this->preserve)); ?>
|
||||
<table class="table table-condensed">
|
||||
<tbody>
|
||||
|
||||
<?php foreach ($history as $event): ?>
|
||||
<tr>
|
||||
<td><?= date('d.m. H:i', $event->timestamp); ?></td>
|
||||
|
||||
<td><?= date('d.m. H:i', $event->raw_timestamp); ?></td>
|
||||
|
||||
<td>
|
||||
<?php if ($object instanceof Icinga\Module\Monitoring\Object\Service): ?>
|
||||
|
@ -1,251 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Module\Monitoring\Backend;
|
||||
|
||||
use Icinga\Data\DatasourceInterface;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\Application\Benchmark;
|
||||
use Zend_Config;
|
||||
|
||||
class AbstractBackend implements DatasourceInterface
|
||||
{
|
||||
protected $config;
|
||||
|
||||
public function __construct(Zend_Config $config)
|
||||
{
|
||||
if ($config === null) {
|
||||
// $config = new Zend_Config(array()); ???
|
||||
}
|
||||
$this->config = $config;
|
||||
$this->init();
|
||||
}
|
||||
|
||||
protected function init()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Backend entry point
|
||||
*
|
||||
* return self
|
||||
*/
|
||||
public function select()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Query object instance for given virtual table and desired fields
|
||||
*
|
||||
* Leave fields empty to get all available properties
|
||||
*
|
||||
* @param string $virtual_table Virtual table name
|
||||
* @param array $fields Fields
|
||||
* @throws \Icinga\Exception\ProgrammingError
|
||||
* @return self
|
||||
*/
|
||||
public function from($virtual_table, $fields = array())
|
||||
{
|
||||
$classname = $this->tableToClassName($virtual_table);
|
||||
if (!class_exists($classname)) {
|
||||
throw new ProgrammingError(
|
||||
sprintf(
|
||||
'Asking for invalid virtual table %s',
|
||||
$classname
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$query = new $classname($this, $fields);
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function hasView($virtual_table)
|
||||
{
|
||||
// TODO: This is no longer enough, have to check for Query right now
|
||||
return class_exists($this->tableToClassName($virtual_table));
|
||||
}
|
||||
|
||||
protected function tableToClassName($virtual_table)
|
||||
{
|
||||
return '\\Icinga\\Module\\Monitoring\\View\\'
|
||||
// . $this->getName()
|
||||
// . '\\'
|
||||
. ucfirst($virtual_table)
|
||||
. 'View';
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return preg_replace('~^.+\\\(.+?)$~', '$1', get_class($this));
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* UGLY temporary host fetch
|
||||
*
|
||||
* @param string $host
|
||||
* @param bool $fetchAll
|
||||
* @return mixed
|
||||
*/
|
||||
|
||||
public function fetchHost($host, $fetchAll = false)
|
||||
{
|
||||
$fields = array(
|
||||
'host_name',
|
||||
'host_address',
|
||||
'host_state',
|
||||
'host_handled',
|
||||
'host_icon_image',
|
||||
'host_in_downtime',
|
||||
'host_acknowledged',
|
||||
'host_check_command',
|
||||
'host_last_state_change',
|
||||
'host_alias',
|
||||
'host_output',
|
||||
'host_long_output',
|
||||
'host_perfdata',
|
||||
'host_notes_url',
|
||||
'host_action_url'
|
||||
);
|
||||
|
||||
if ($fetchAll === true) {
|
||||
$fields = array_merge(
|
||||
$fields,
|
||||
array(
|
||||
'host_current_check_attempt',
|
||||
'host_max_check_attempts',
|
||||
'host_attempt',
|
||||
'host_last_check',
|
||||
'host_next_check',
|
||||
'host_check_type',
|
||||
'host_last_state_change',
|
||||
'host_last_hard_state_change',
|
||||
'host_last_hard_state',
|
||||
'host_last_time_up',
|
||||
'host_last_time_down',
|
||||
'host_last_time_unreachable',
|
||||
'host_state_type',
|
||||
'host_last_notification',
|
||||
'host_next_notification',
|
||||
'host_no_more_notifications',
|
||||
'host_notifications_enabled',
|
||||
'host_problem_has_been_acknowledged',
|
||||
'host_acknowledgement_type',
|
||||
'host_current_notification_number',
|
||||
'host_passive_checks_enabled',
|
||||
'host_active_checks_enabled',
|
||||
'host_event_handler_enabled',
|
||||
'host_flap_detection_enabled',
|
||||
'host_is_flapping',
|
||||
'host_percent_state_change',
|
||||
'host_check_latency',
|
||||
'host_check_execution_time',
|
||||
'host_scheduled_downtime_depth',
|
||||
'host_failure_prediction_enabled',
|
||||
'host_process_performance_data',
|
||||
'host_obsessing',
|
||||
'host_modified_host_attributes',
|
||||
'host_event_handler',
|
||||
'host_check_command',
|
||||
'host_normal_check_interval',
|
||||
'host_retry_check_interval',
|
||||
'host_check_timeperiod_object_id',
|
||||
'host_status_update_time'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$select = $this->select()
|
||||
->from('status', $fields)
|
||||
->where('host_name', $host);
|
||||
|
||||
return $select->fetchRow();
|
||||
}
|
||||
|
||||
// UGLY temporary service fetch
|
||||
public function fetchService($host, $service, $fetchAll = false)
|
||||
{
|
||||
$fields = array(
|
||||
'service_description',
|
||||
'host_name',
|
||||
'host_address',
|
||||
'host_state',
|
||||
'host_handled',
|
||||
'host_icon_image',
|
||||
'service_state',
|
||||
'service_handled',
|
||||
'service_in_downtime',
|
||||
'service_acknowledged',
|
||||
'service_check_command',
|
||||
'service_last_state_change',
|
||||
'service_display_name',
|
||||
'service_output',
|
||||
'service_long_output',
|
||||
'service_perfdata',
|
||||
'service_action_url',
|
||||
'service_notes_url',
|
||||
'service_icon_image'
|
||||
);
|
||||
|
||||
if ($fetchAll === true) {
|
||||
$fields = array_merge(
|
||||
$fields,
|
||||
array(
|
||||
'service_current_check_attempt',
|
||||
'service_max_check_attempts',
|
||||
'service_attempt',
|
||||
'service_last_check',
|
||||
'service_next_check',
|
||||
'service_check_type',
|
||||
'service_last_state_change',
|
||||
'service_last_hard_state_change',
|
||||
'service_last_hard_state',
|
||||
'service_last_time_ok',
|
||||
'service_last_time_warning',
|
||||
'service_last_time_unknown',
|
||||
'service_last_time_critical',
|
||||
'service_state_type',
|
||||
'service_last_notification',
|
||||
'service_next_notification',
|
||||
'service_no_more_notifications',
|
||||
'service_notifications_enabled',
|
||||
'service_problem_has_been_acknowledged',
|
||||
'service_acknowledgement_type',
|
||||
'service_current_notification_number',
|
||||
'service_passive_checks_enabled',
|
||||
'service_active_checks_enabled',
|
||||
'service_event_handler_enabled',
|
||||
'service_flap_detection_enabled',
|
||||
'service_is_flapping',
|
||||
'service_percent_state_change',
|
||||
'service_check_latency',
|
||||
'service_check_execution_time',
|
||||
'service_scheduled_downtime_depth',
|
||||
'service_failure_prediction_enabled',
|
||||
'service_process_performance_data',
|
||||
'service_obsessing',
|
||||
'service_modified_service_attributes',
|
||||
'service_event_handler',
|
||||
'service_check_command',
|
||||
'service_normal_check_interval',
|
||||
'service_retry_check_interval',
|
||||
'service_check_timeperiod_object_id',
|
||||
'service_status_update_time'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$select = $this->select()
|
||||
->from('status', $fields)
|
||||
->where('service_description', $service)
|
||||
->where('host_name', $host);
|
||||
return $select->fetchRow();
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,405 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
|
||||
use Icinga\Data\Db\Query;
|
||||
use Icinga\Application\Benchmark;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
|
||||
abstract class AbstractQuery extends Query
|
||||
{
|
||||
protected $prefix;
|
||||
|
||||
protected $idxAliasColumn;
|
||||
protected $idxAliasTable;
|
||||
protected $columnMap = array();
|
||||
|
||||
protected $query;
|
||||
protected $customVars = array();
|
||||
protected $joinedVirtualTables = array();
|
||||
|
||||
protected $object_id = 'object_id';
|
||||
protected $host_id = 'host_id';
|
||||
protected $hostgroup_id = 'hostgroup_id';
|
||||
protected $service_id = 'service_id';
|
||||
protected $servicegroup_id = 'servicegroup_id';
|
||||
protected $contact_id = 'contact_id';
|
||||
protected $contactgroup_id = 'contactgroup_id';
|
||||
|
||||
protected $aggregateColumnIdx = array();
|
||||
|
||||
protected $allowCustomVars = false;
|
||||
|
||||
protected function isAggregateColumn($column)
|
||||
{
|
||||
return array_key_exists($column, $this->aggregateColumnIdx);
|
||||
}
|
||||
|
||||
protected function init()
|
||||
{
|
||||
parent::init();
|
||||
// TODO: $this->applyDbSpecificWorkarounds
|
||||
$this->prefix = $this->ds->getTablePrefix();
|
||||
|
||||
if ($this->ds->getDbType() === 'oracle') {
|
||||
$this->object_id = $this->host_id = $this->service_id
|
||||
= $this->hostgroup_id = $this->servicegroup_id
|
||||
= $this->contact_id = $this->contactgroup_id = 'id'; // REALLY?
|
||||
foreach ($this->columnMap as $table => & $columns) {
|
||||
foreach ($columns as $key => & $value) {
|
||||
$value = preg_replace('/UNIX_TIMESTAMP/', 'localts2unixts', $value);
|
||||
$value = preg_replace('/ COLLATE .+$/', '', $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($this->ds->getDbType() === 'pgsql') {
|
||||
foreach ($this->columnMap as $table => & $columns) {
|
||||
foreach ($columns as $key => & $value) {
|
||||
$value = preg_replace('/ COLLATE .+$/', '', $value);
|
||||
$value = preg_replace('/inet_aton\(([[:word:].]+)\)/i', '$1::inet - \'0.0.0.0\'', $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->joinBaseTables();
|
||||
$this->prepareAliasIndexes();
|
||||
}
|
||||
|
||||
protected function isCustomVar($alias)
|
||||
{
|
||||
return $this->allowCustomVars && $alias[0] === '_';
|
||||
}
|
||||
|
||||
protected function joinCustomvar($customvar)
|
||||
{
|
||||
// TODO: This is not generic enough yet
|
||||
list($type, $name) = $this->customvarNameToTypeName($customvar);
|
||||
$alias = ($type === 'host' ? 'hcv_' : 'scv_') . strtolower($name);
|
||||
|
||||
$this->customVars[$customvar] = $alias;
|
||||
|
||||
// TODO: extend if we allow queries with only hosts / only services
|
||||
// ($leftcol s.host_object_id vs h.host_object_id
|
||||
if ($this->hasJoinedVirtualTable('services')) {
|
||||
$leftcol = 's.' . $type . '_object_id';
|
||||
} else {
|
||||
$leftcol = 'h.' . $type . '_object_id';
|
||||
}
|
||||
$joinOn = $leftcol
|
||||
. ' = '
|
||||
. $alias
|
||||
. '.object_id'
|
||||
. ' AND '
|
||||
. $alias
|
||||
. '.varname = '
|
||||
. $this->db->quote(strtoupper($name));
|
||||
|
||||
$this->baseQuery->joinLeft(
|
||||
array($alias => $this->prefix . 'customvariablestatus'),
|
||||
$joinOn,
|
||||
array()
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function prepareAliasIndexes()
|
||||
{
|
||||
foreach ($this->columnMap as $tbl => & $cols) {
|
||||
foreach ($cols as $alias => $col) {
|
||||
$this->idxAliasTable[$alias] = $tbl;
|
||||
$this->idxAliasColumn[$alias] = preg_replace('~\n\s*~', ' ', $col);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getDefaultColumns()
|
||||
{
|
||||
reset($this->columnMap);
|
||||
$table = key($this->columnMap);
|
||||
return array_keys($this->columnMap[$table]);
|
||||
}
|
||||
|
||||
protected function joinBaseTables()
|
||||
{
|
||||
reset($this->columnMap);
|
||||
$table = key($this->columnMap);
|
||||
|
||||
$this->baseQuery = $this->db->select()->from(
|
||||
array($table => $this->prefix . $table),
|
||||
array()
|
||||
);
|
||||
|
||||
$this->joinedVirtualTables = array($table => true);
|
||||
}
|
||||
|
||||
protected function beforeCreatingCountQuery()
|
||||
{
|
||||
$this->applyAllFilters();
|
||||
}
|
||||
|
||||
protected function beforeCreatingSelectQuery()
|
||||
{
|
||||
$this->setRealColumns();
|
||||
$classParts = explode('\\', get_class($this));
|
||||
Benchmark::measure(sprintf('%s ready to run', array_pop($classParts)));
|
||||
}
|
||||
|
||||
protected function applyAllFilters()
|
||||
{
|
||||
$filters = array();
|
||||
foreach ($this->filters as $f) {
|
||||
$alias = $f[0];
|
||||
$value = $f[1];
|
||||
$this->requireColumn($alias);
|
||||
|
||||
if ($this->isCustomvar($alias)) {
|
||||
$col = $this->getCustomvarColumnName($alias);
|
||||
} elseif ($this->hasAliasName($alias)) {
|
||||
$col = $this->aliasToColumnName($alias);
|
||||
} else {
|
||||
throw new ProgrammingError(
|
||||
'If you finished here, code has been messed up'
|
||||
);
|
||||
}
|
||||
|
||||
$func = 'filter' . ucfirst($alias);
|
||||
if (method_exists($this, $func)) {
|
||||
$this->$func($value);
|
||||
return;
|
||||
}
|
||||
if ($this->isAggregateColumn($alias)) {
|
||||
$this->baseQuery->having($this->prepareFilterStringForColumn($col, $value));
|
||||
} else {
|
||||
$this->baseQuery->where($this->prepareFilterStringForColumn($col, $value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function order($col, $dir = null)
|
||||
{
|
||||
$this->requireColumn($col);
|
||||
if ($this->isCustomvar($col)) {
|
||||
// TODO: Doesn't work right now. Does it?
|
||||
$col = $this->getCustomvarColumnName($col);
|
||||
} elseif ($this->hasAliasName($col)) {
|
||||
$col = $this->aliasToColumnName($col);
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Can\'t order by column '.$col);
|
||||
}
|
||||
$this->order_columns[] = array($col, $dir);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setRealColumns()
|
||||
{
|
||||
$columns = $this->columns;
|
||||
$this->columns = array();
|
||||
if (empty($columns)) {
|
||||
$colums = $this->getDefaultColumns();
|
||||
}
|
||||
|
||||
foreach ($columns as $alias => $col) {
|
||||
$this->requireColumn($col);
|
||||
if ($this->isCustomvar($col)) {
|
||||
$name = $this->getCustomvarColumnName($col);
|
||||
} else {
|
||||
$name = $this->aliasToColumnName($col);
|
||||
}
|
||||
if (is_int($alias)) {
|
||||
$alias = $col;
|
||||
}
|
||||
|
||||
$this->columns[$alias] = preg_replace('|\n|', ' ' , $name);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function requireColumn($alias)
|
||||
{
|
||||
if ($this->hasAliasName($alias)) {
|
||||
$this->requireVirtualTable($this->aliasToTableName($alias));
|
||||
} elseif ($this->isCustomVar($alias)) {
|
||||
$this->requireCustomvar($alias);
|
||||
} else {
|
||||
throw new ProgrammingError(sprintf('Got invalid column: %s', $alias));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function hasAliasName($alias)
|
||||
{
|
||||
return array_key_exists($alias, $this->idxAliasColumn);
|
||||
}
|
||||
|
||||
protected function aliasToColumnName($alias)
|
||||
{
|
||||
return $this->idxAliasColumn[$alias];
|
||||
}
|
||||
|
||||
protected function aliasToTableName($alias)
|
||||
{
|
||||
return $this->idxAliasTable[$alias];
|
||||
}
|
||||
|
||||
protected function hasJoinedVirtualTable($name)
|
||||
{
|
||||
return array_key_exists($name, $this->joinedVirtualTables);
|
||||
}
|
||||
|
||||
protected function requireVirtualTable($name)
|
||||
{
|
||||
if ($this->hasJoinedVirtualTable($name)) {
|
||||
return $this;
|
||||
}
|
||||
return $this->joinVirtualTable($name);
|
||||
}
|
||||
|
||||
protected function joinVirtualTable($table)
|
||||
{
|
||||
$func = 'join' . ucfirst($table);
|
||||
if (method_exists($this, $func)) {
|
||||
$this->$func();
|
||||
} else {
|
||||
throw new ProgrammingError(sprintf(
|
||||
'Cannot join "%s", no such table found',
|
||||
$table
|
||||
));
|
||||
}
|
||||
$this->joinedVirtualTables[$table] = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function requireCustomvar($customvar)
|
||||
{
|
||||
if (! $this->hasCustomvar($customvar)) {
|
||||
$this->joinCustomvar($customvar);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function hasCustomvar($customvar)
|
||||
{
|
||||
return array_key_exists($customvar, $this->customVars);
|
||||
}
|
||||
|
||||
protected function getCustomvarColumnName($customvar)
|
||||
{
|
||||
return $this->customVars[$customvar] . '.varvalue';
|
||||
}
|
||||
|
||||
protected function createSubQuery($queryName, $columns = array())
|
||||
{
|
||||
$class = '\\'
|
||||
. substr(__CLASS__, 0, strrpos(__CLASS__, '\\') + 1)
|
||||
. ucfirst($queryName) . 'Query';
|
||||
$query = new $class($this->ds, $columns);
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function customvarNameToTypeName($customvar)
|
||||
{
|
||||
// TODO: Improve this:
|
||||
if (! preg_match('~^_(host|service)_([a-zA-Z0-9_]+)$~', $customvar, $m)) {
|
||||
throw new ProgrammingError(
|
||||
sprintf(
|
||||
'Got invalid custom var: "%s"',
|
||||
$customvar
|
||||
)
|
||||
);
|
||||
}
|
||||
return array($m[1], $m[2]);
|
||||
}
|
||||
|
||||
protected function prepareFilterStringForColumn($column, $value)
|
||||
{
|
||||
$filter = '';
|
||||
$filters = array();
|
||||
|
||||
$or = array();
|
||||
$and = array();
|
||||
|
||||
if (
|
||||
! is_array($value) &&
|
||||
(strpos($value, ',') !== false || strpos($value, '|') !== false)
|
||||
) {
|
||||
$value = preg_split('~[,|]~', $value, -1, PREG_SPLIT_NO_EMPTY);
|
||||
}
|
||||
if (! is_array($value)) {
|
||||
$value = array($value);
|
||||
}
|
||||
|
||||
// Go through all given values
|
||||
foreach ($value as $val) {
|
||||
if ($val === '') {
|
||||
// TODO: REALLY??
|
||||
continue;
|
||||
}
|
||||
$not = false;
|
||||
$force = false;
|
||||
$op = '=';
|
||||
$wildcard = false;
|
||||
|
||||
if ($val[0] === '-' || $val[0] === '!') {
|
||||
// Value starting with minus or !: negation
|
||||
$val = substr($val, 1);
|
||||
$not = true;
|
||||
}
|
||||
|
||||
if ($val[0] === '+') {
|
||||
// Value starting with +: enforces AND
|
||||
// TODO: depends on correct URL handling, not given in all
|
||||
// ZF versions.
|
||||
$val = substr($val, 1);
|
||||
$force = true;
|
||||
}
|
||||
if ($val[0] === '<' || $val[0] === '>') {
|
||||
$op = $val[0];
|
||||
$val = substr($val, 1);
|
||||
}
|
||||
if (strpos($val, '*') !== false) {
|
||||
$wildcard = true;
|
||||
$val = str_replace('*', '%', $val);
|
||||
}
|
||||
|
||||
$operator = null;
|
||||
switch ($op) {
|
||||
case '=':
|
||||
if ($not) {
|
||||
$operator = $wildcard ? 'NOT LIKE' : '!=';
|
||||
} else {
|
||||
$operator = $wildcard ? 'LIKE' : '=';
|
||||
}
|
||||
break;
|
||||
case '>':
|
||||
$operator = $not ? '<=' : '>';
|
||||
break;
|
||||
case '<':
|
||||
$operator = $not ? '>=' : '<';
|
||||
break;
|
||||
default:
|
||||
throw new ProgrammingError("'$op' is not a valid operator");
|
||||
}
|
||||
|
||||
if ($not || $force) {
|
||||
$and[] = $this->db->quoteInto($column . ' ' . $operator . ' ?', $val);
|
||||
} else {
|
||||
$or[] = $this->db->quoteInto($column . ' ' . $operator . ' ?', $val);
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($or)) {
|
||||
$filters[] = implode(' OR ', $or);
|
||||
}
|
||||
|
||||
if (! empty($and)) {
|
||||
$filters[] = implode(' AND ', $and);
|
||||
}
|
||||
|
||||
if (! empty($filters)) {
|
||||
$filter = '(' . implode(') AND (', $filters) . ')';
|
||||
}
|
||||
|
||||
return $filter;
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
|
||||
use Zend_Db_Select;
|
||||
|
||||
class AllcontactsQuery extends AbstractQuery
|
||||
class AllcontactsQuery extends IdoQuery
|
||||
{
|
||||
protected $columnMap = array(
|
||||
'contacts' => array(
|
||||
@ -39,9 +39,9 @@ class AllcontactsQuery extends AbstractQuery
|
||||
|
||||
protected $contacts;
|
||||
protected $contactgroups;
|
||||
protected $uglySlowConservativeCount = true;
|
||||
protected $useSubqueryCount = true;
|
||||
|
||||
protected function requireColumn($alias)
|
||||
public function requireColumn($alias)
|
||||
{
|
||||
$this->contacts->addColumn($alias);
|
||||
$this->contactgroups->addColumn($alias);
|
||||
|
@ -32,7 +32,7 @@ namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
/**
|
||||
* Query map for comments
|
||||
*/
|
||||
class CommentQuery extends AbstractQuery
|
||||
class CommentQuery extends IdoQuery
|
||||
{
|
||||
protected $columnMap = array(
|
||||
'comments' => array(
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
|
||||
class CommenthistoryQuery extends AbstractQuery
|
||||
class CommenthistoryQuery extends IdoQuery
|
||||
{
|
||||
protected $columnMap = array(
|
||||
'commenthistory' => array(
|
||||
@ -19,4 +19,3 @@ class CommenthistoryQuery extends AbstractQuery
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
|
||||
class ContactQuery extends AbstractQuery
|
||||
class ContactQuery extends IdoQuery
|
||||
{
|
||||
protected $columnMap = array(
|
||||
'contacts' => array(
|
||||
@ -33,10 +33,12 @@ class ContactQuery extends AbstractQuery
|
||||
'hosts' => array(
|
||||
'host_object_id' => 'ho.object_id',
|
||||
'host_name' => 'ho.name1 COLLATE latin1_general_ci',
|
||||
'host' => 'ho.name1 COLLATE latin1_general_ci',
|
||||
),
|
||||
'services' => array(
|
||||
'service_object_id' => 'so.object_id',
|
||||
'service_host_name' => 'so.name1 COLLATE latin1_general_ci',
|
||||
'service' => 'so.name1 COLLATE latin1_general_ci',
|
||||
'service_description' => 'so.name2 COLLATE latin1_general_ci',
|
||||
)
|
||||
);
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
|
||||
class ContactgroupQuery extends AbstractQuery
|
||||
class ContactgroupQuery extends IdoQuery
|
||||
{
|
||||
protected $columnMap = array(
|
||||
'contactgroups' => array(
|
||||
@ -32,14 +32,16 @@ class ContactgroupQuery extends AbstractQuery
|
||||
'hosts' => array(
|
||||
'host_object_id' => 'ho.object_id',
|
||||
'host_name' => 'ho.name1',
|
||||
'host' => 'ho.name1'
|
||||
),
|
||||
'services' => array(
|
||||
'service_object_id' => 'so.object_id',
|
||||
'service_host_name' => 'so.name1 COLLATE latin1_general_ci',
|
||||
'service' => 'so.name1 COLLATE latin1_general_ci',
|
||||
'service_description' => 'so.name2 COLLATE latin1_general_ci',
|
||||
)
|
||||
);
|
||||
protected $uglySlowConservativeCount = true;
|
||||
protected $useSubqueryCount = true;
|
||||
|
||||
protected function joinBaseTables()
|
||||
{
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
|
||||
class CustomvarQuery extends AbstractQuery
|
||||
class CustomvarQuery extends IdoQuery
|
||||
{
|
||||
protected $object_id = 'object_id';
|
||||
|
||||
|
@ -28,7 +28,7 @@ namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
/**
|
||||
* Handling downtime queries
|
||||
*/
|
||||
class DowntimeQuery extends AbstractQuery
|
||||
class DowntimeQuery extends IdoQuery
|
||||
{
|
||||
/**
|
||||
* Column map
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
|
||||
class DowntimeendhistoryQuery extends AbstractQuery
|
||||
class DowntimeendhistoryQuery extends IdoQuery
|
||||
{
|
||||
protected $columnMap = array(
|
||||
'downtimehistory' => array(
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
|
||||
class DowntimestarthistoryQuery extends AbstractQuery
|
||||
class DowntimestarthistoryQuery extends IdoQuery
|
||||
{
|
||||
protected $columnMap = array(
|
||||
'downtimehistory' => array(
|
||||
|
@ -5,7 +5,7 @@ namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
use \Zend_Db_Select;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
|
||||
class EventHistoryQuery extends AbstractQuery
|
||||
class EventHistoryQuery extends IdoQuery
|
||||
{
|
||||
protected $subQueries = array();
|
||||
|
||||
@ -21,27 +21,27 @@ class EventHistoryQuery extends AbstractQuery
|
||||
'host_name' => 'eho.name1 COLLATE latin1_general_ci',
|
||||
'service_description' => 'eho.name2 COLLATE latin1_general_ci',
|
||||
'object_type' => "CASE WHEN eho.objecttype_id = 1 THEN 'host' ELSE 'service' END",
|
||||
'timestamp' => 'eh.timestamp',
|
||||
'raw_timestamp' => 'eh.raw_timestamp',
|
||||
'state' => 'eh.state',
|
||||
// 'last_state' => 'eh.last_state',
|
||||
// 'last_hard_state' => 'eh.last_hard_state',
|
||||
'attempt' => 'eh.attempt',
|
||||
'max_attempts' => 'eh.max_attempts',
|
||||
'output' => 'eh.output', // we do not want long_output
|
||||
//'problems' => 'CASE WHEN eh.state = 0 OR eh.state IS NULL THEN 0 ELSE 1 END',
|
||||
'type' => 'eh.type'
|
||||
'timestamp' => 'eh.timestamp',
|
||||
'raw_timestamp' => 'UNIX_TIMESTAMP(eh.raw_timestamp)',
|
||||
'state' => 'eh.state',
|
||||
'attempt' => 'eh.attempt',
|
||||
'max_attempts' => 'eh.max_attempts',
|
||||
'output' => 'eh.output', // we do not want long_output
|
||||
'type' => 'eh.type',
|
||||
'service_host_name' => 'eho.name1 COLLATE latin1_general_ci',
|
||||
'service_description' => 'eho.name2 COLLATE latin1_general_ci'
|
||||
),
|
||||
'hostgroups' => array(
|
||||
'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
|
||||
),
|
||||
);
|
||||
|
||||
protected $uglySlowConservativeCount = true;
|
||||
protected $useSubqueryCount = true;
|
||||
protected $maxCount = 1000;
|
||||
|
||||
protected function joinBaseTables()
|
||||
{
|
||||
|
||||
// $start = date('Y-m-d H:i:s', time() - 3600 * 24 * 1);
|
||||
$start = date('Y-m-d H:i:s', time() - 3600 * 24 * 2);
|
||||
$end = date('Y-m-d H:i:s');
|
||||
@ -67,14 +67,15 @@ class EventHistoryQuery extends AbstractQuery
|
||||
$this->createSubQuery('Commenthistory', $columns),
|
||||
$this->createSubQuery('Notificationhistory', $columns)
|
||||
);
|
||||
|
||||
if ($start) {
|
||||
foreach ($this->subQueries as $query) {
|
||||
$query->where('raw_timestamp', '>' . $start);
|
||||
$query->where('timestamp > ?', $start);
|
||||
}
|
||||
}
|
||||
if ($end) {
|
||||
foreach ($this->subQueries as $query) {
|
||||
$query->where('raw_timestamp', '<' . $end);
|
||||
$query->where('timestamp < ? ', $end);
|
||||
}
|
||||
}
|
||||
$sub = $this->db->select()->union($this->subQueries, Zend_Db_Select::SQL_UNION_ALL);
|
||||
@ -88,7 +89,6 @@ class EventHistoryQuery extends AbstractQuery
|
||||
. ' AND eho.is_active = 1',
|
||||
array()
|
||||
);
|
||||
|
||||
$this->joinedVirtualTables = array('eventhistory' => true);
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
|
||||
class GroupsummaryQuery extends AbstractQuery
|
||||
class GroupsummaryQuery extends IdoQuery
|
||||
{
|
||||
protected $columnMap = array(
|
||||
'hoststatus' => array(
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
|
||||
class HostgroupQuery extends AbstractQuery
|
||||
class HostgroupQuery extends IdoQuery
|
||||
{
|
||||
protected $columnMap = array(
|
||||
'hostgroups' => array(
|
||||
@ -12,6 +12,7 @@ class HostgroupQuery extends AbstractQuery
|
||||
'id' => 'hg.hostgroup_id',
|
||||
),
|
||||
'hosts' => array(
|
||||
'host' => 'ho.name1 COLLATE latin1_general_ci',
|
||||
'host_name' => 'ho.name1 COLLATE latin1_general_ci'
|
||||
)
|
||||
);
|
||||
|
@ -1,49 +1,73 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
|
||||
class HoststatusQuery extends AbstractQuery
|
||||
class HoststatusQuery extends IdoQuery
|
||||
{
|
||||
protected $allowCustomVars = true;
|
||||
|
||||
protected $columnMap = array(
|
||||
'hosts' => array(
|
||||
'host' => 'ho.name1 COLLATE latin1_general_ci',
|
||||
'host_name' => 'ho.name1 COLLATE latin1_general_ci',
|
||||
'host_display_name' => 'h.display_name',
|
||||
'host_alias' => 'h.alias',
|
||||
'host_address' => 'h.address',
|
||||
'host_ipv4' => 'INET_ATON(h.address)',
|
||||
'host_icon_image' => 'h.icon_image',
|
||||
'host' => 'ho.name1 COLLATE latin1_general_ci',
|
||||
'host_name' => 'ho.name1 COLLATE latin1_general_ci',
|
||||
'host_display_name' => 'h.display_name',
|
||||
'host_alias' => 'h.alias',
|
||||
'host_address' => 'h.address',
|
||||
'host_ipv4' => 'INET_ATON(h.address)',
|
||||
'host_icon_image' => 'h.icon_image',
|
||||
),
|
||||
'hoststatus' => array(
|
||||
'problems' => 'CASE WHEN hs.current_state = 0 THEN 0 ELSE 1 END',
|
||||
'handled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) > 0 THEN 1 ELSE 0 END',
|
||||
'unhandled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) = 0 THEN 1 ELSE 0 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_output' => 'hs.output',
|
||||
'host_long_output' => 'hs.long_output',
|
||||
'host_perfdata' => 'hs.perfdata',
|
||||
'host_problem' => 'CASE WHEN hs.current_state = 0 THEN 0 ELSE 1 END',
|
||||
'host_acknowledged' => 'hs.problem_has_been_acknowledged',
|
||||
'host_in_downtime' => 'CASE WHEN (hs.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END',
|
||||
'host_handled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) > 0 THEN 1 ELSE 0 END',
|
||||
'host_does_active_checks' => 'hs.active_checks_enabled',
|
||||
'problems' => 'CASE WHEN hs.current_state = 0 THEN 0 ELSE 1 END',
|
||||
'handled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) > 0 THEN 1 ELSE 0 END',
|
||||
'unhandled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) = 0 THEN 1 ELSE 0 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_output' => 'hs.output',
|
||||
'host_long_output' => 'hs.long_output',
|
||||
'host_perfdata' => 'hs.perfdata',
|
||||
'host_problem' => 'CASE WHEN hs.current_state = 0 THEN 0 ELSE 1 END',
|
||||
'host_acknowledged' => 'hs.problem_has_been_acknowledged',
|
||||
'host_in_downtime' => 'CASE WHEN (hs.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END',
|
||||
'host_handled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) > 0 THEN 1 ELSE 0 END',
|
||||
'host_does_active_checks' => 'hs.active_checks_enabled',
|
||||
'host_accepts_passive_checks' => 'hs.passive_checks_enabled',
|
||||
'host_last_state_change' => 'UNIX_TIMESTAMP(hs.last_state_change)',
|
||||
'host_last_hard_state' => 'hs.last_hard_state',
|
||||
'host_check_command' => 'hs.check_command',
|
||||
'host_last_check' => 'UNIX_TIMESTAMP(hs.last_check)',
|
||||
'host_next_check' => 'CASE WHEN hs.should_be_scheduled THEN UNIX_TIMESTAMP(hs.next_check) ELSE NULL END',
|
||||
'host_check_execution_time' => 'hs.execution_time',
|
||||
'host_check_latency' => 'hs.latency',
|
||||
'host_notifications_enabled' => 'hs.notifications_enabled',
|
||||
'host_last_time_up' => 'hs.last_time_up',
|
||||
'host_last_time_down' => 'hs.last_time_down',
|
||||
'host_last_time_unreachable' => 'hs.last_time_unreachable',
|
||||
'host_current_check_attempt' => 'hs.current_check_attempt',
|
||||
'host_max_check_attempts' => 'hs.max_check_attempts',
|
||||
|
||||
'host_last_state_change' => 'UNIX_TIMESTAMP(hs.last_state_change)',
|
||||
'host_last_hard_state' => 'hs.last_hard_state',
|
||||
'host_check_command' => 'hs.check_command',
|
||||
'host_last_check' => 'UNIX_TIMESTAMP(hs.last_check)',
|
||||
'host_next_check' => 'CASE WHEN hs.should_be_scheduled THEN UNIX_TIMESTAMP(hs.next_check) ELSE NULL END',
|
||||
'host_check_execution_time' => 'hs.execution_time',
|
||||
'host_check_latency' => 'hs.latency',
|
||||
'host_notifications_enabled' => 'hs.notifications_enabled',
|
||||
'host_last_time_up' => 'hs.last_time_up',
|
||||
'host_last_time_down' => 'hs.last_time_down',
|
||||
'host_last_time_unreachable' => 'hs.last_time_unreachable',
|
||||
'host_current_check_attempt' => 'hs.current_check_attempt',
|
||||
'host_max_check_attempts' => 'hs.max_check_attempts',
|
||||
'host_severity' => 'CASE WHEN hs.current_state = 0
|
||||
THEN
|
||||
CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL
|
||||
@ -86,37 +110,34 @@ class HoststatusQuery extends AbstractQuery
|
||||
'contact' => 'hco.name1 COLLATE latin1_general_ci',
|
||||
),
|
||||
'services' => array(
|
||||
'services_cnt' => 'SUM(1)',
|
||||
'services_ok' => 'SUM(CASE WHEN ss.current_state = 0 THEN 1 ELSE 0 END)',
|
||||
'services_warning' => 'SUM(CASE WHEN ss.current_state = 1 THEN 1 ELSE 0 END)',
|
||||
'services_cnt' => 'SUM(1)',
|
||||
'services_ok' => 'SUM(CASE WHEN ss.current_state = 0 THEN 1 ELSE 0 END)',
|
||||
'services_warning' => 'SUM(CASE WHEN ss.current_state = 1 THEN 1 ELSE 0 END)',
|
||||
'services_critical' => 'SUM(CASE WHEN ss.current_state = 2 THEN 1 ELSE 0 END)',
|
||||
'services_unknown' => 'SUM(CASE WHEN ss.current_state = 3 THEN 1 ELSE 0 END)',
|
||||
'services_pending' => 'SUM(CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 1 ELSE 0 END)',
|
||||
'services_problem' => 'SUM(CASE WHEN ss.current_state > 0 THEN 1 ELSE 0 END)',
|
||||
'services_problem_handled' => 'SUM(CASE WHEN ss.current_state > 0 AND (ss.problem_has_been_acknowledged = 1 OR ss.scheduled_downtime_depth > 0) THEN 1 ELSE 0 END)',
|
||||
'services_problem_unhandled' => 'SUM(CASE WHEN ss.current_state > 0 AND (ss.problem_has_been_acknowledged = 0 AND ss.scheduled_downtime_depth = 0) THEN 1 ELSE 0 END)',
|
||||
'services_warning_handled' => 'SUM(CASE WHEN ss.current_state = 1 AND (ss.problem_has_been_acknowledged = 1 OR ss.scheduled_downtime_depth > 0) THEN 1 ELSE 0 END)',
|
||||
'services_unknown' => 'SUM(CASE WHEN ss.current_state = 3 THEN 1 ELSE 0 END)',
|
||||
'services_pending' => 'SUM(CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 1 ELSE 0 END)',
|
||||
'services_problem' => 'SUM(CASE WHEN ss.current_state > 0 THEN 1 ELSE 0 END)',
|
||||
'services_problem_handled' => 'SUM(CASE WHEN ss.current_state > 0 AND (ss.problem_has_been_acknowledged = 1 OR ss.scheduled_downtime_depth > 0) THEN 1 ELSE 0 END)',
|
||||
'services_problem_unhandled' => 'SUM(CASE WHEN ss.current_state > 0 AND (ss.problem_has_been_acknowledged = 0 AND ss.scheduled_downtime_depth = 0) THEN 1 ELSE 0 END)',
|
||||
'services_warning_handled' => 'SUM(CASE WHEN ss.current_state = 1 AND (ss.problem_has_been_acknowledged = 1 OR ss.scheduled_downtime_depth > 0) THEN 1 ELSE 0 END)',
|
||||
'services_critical_handled' => 'SUM(CASE WHEN ss.current_state = 2 AND (ss.problem_has_been_acknowledged = 1 OR ss.scheduled_downtime_depth > 0) THEN 1 ELSE 0 END)',
|
||||
'services_unknown_handled' => 'SUM(CASE WHEN ss.current_state = 3 AND (ss.problem_has_been_acknowledged = 1 OR ss.scheduled_downtime_depth > 0) THEN 1 ELSE 0 END)',
|
||||
'services_warning_unhandled' => 'SUM(CASE WHEN ss.current_state = 1 AND (ss.problem_has_been_acknowledged = 0 AND ss.scheduled_downtime_depth = 0) THEN 1 ELSE 0 END)',
|
||||
'services_unknown_handled' => 'SUM(CASE WHEN ss.current_state = 3 AND (ss.problem_has_been_acknowledged = 1 OR ss.scheduled_downtime_depth > 0) THEN 1 ELSE 0 END)',
|
||||
'services_warning_unhandled' => 'SUM(CASE WHEN ss.current_state = 1 AND (ss.problem_has_been_acknowledged = 0 AND ss.scheduled_downtime_depth = 0) THEN 1 ELSE 0 END)',
|
||||
'services_critical_unhandled' => 'SUM(CASE WHEN ss.current_state = 2 AND (ss.problem_has_been_acknowledged = 0 AND ss.scheduled_downtime_depth = 0) THEN 1 ELSE 0 END)',
|
||||
'services_unknown_unhandled' => 'SUM(CASE WHEN ss.current_state = 3 AND (ss.problem_has_been_acknowledged = 0 AND ss.scheduled_downtime_depth = 0) THEN 1 ELSE 0 END)',
|
||||
'services_unknown_unhandled' => 'SUM(CASE WHEN ss.current_state = 3 AND (ss.problem_has_been_acknowledged = 0 AND ss.scheduled_downtime_depth = 0) THEN 1 ELSE 0 END)',
|
||||
),
|
||||
);
|
||||
|
||||
protected $aggregateColumnIdx = array(
|
||||
'services_cnt' => true,
|
||||
'services_problem' => true,
|
||||
'services_problem_handled' => true,
|
||||
'services_cnt' => true,
|
||||
'services_problem' => true,
|
||||
'services_problem_handled' => true,
|
||||
'services_problem_unhandled' => true,
|
||||
);
|
||||
|
||||
protected $hcgSub;
|
||||
|
||||
protected function getDefaultColumns()
|
||||
public function getDefaultColumns()
|
||||
{
|
||||
return $this->columnMap['hosts']
|
||||
+ $this->columnMap['hoststatus'];
|
||||
return $this->columnMap['hosts'] + $this->columnMap['hoststatus'];
|
||||
}
|
||||
|
||||
protected function joinBaseTables()
|
||||
@ -126,16 +147,16 @@ class HoststatusQuery extends AbstractQuery
|
||||
array('ho' => $this->prefix . 'objects'),
|
||||
array()
|
||||
)->join(
|
||||
array('hs' => $this->prefix . 'hoststatus'),
|
||||
'ho.' . $this->object_id . ' = hs.host_object_id AND ho.is_active = 1 AND ho.objecttype_id = 1',
|
||||
array()
|
||||
)->join(
|
||||
array('h' => $this->prefix . 'hosts'),
|
||||
'hs.host_object_id = h.host_object_id',
|
||||
array()
|
||||
);
|
||||
array('hs' => $this->prefix . 'hoststatus'),
|
||||
'ho.' . $this->object_id . ' = hs.host_object_id AND ho.is_active = 1 AND ho.objecttype_id = 1',
|
||||
array()
|
||||
)->join(
|
||||
array('h' => $this->prefix . 'hosts'),
|
||||
'hs.host_object_id = h.host_object_id',
|
||||
array()
|
||||
);
|
||||
$this->joinedVirtualTables = array(
|
||||
'hosts' => true,
|
||||
'hosts' => true,
|
||||
'hoststatus' => true,
|
||||
);
|
||||
}
|
||||
@ -157,22 +178,22 @@ class HoststatusQuery extends AbstractQuery
|
||||
's.host_object_id = h.host_object_id',
|
||||
array()
|
||||
)->join(
|
||||
array('so' => $this->prefix . 'objects'),
|
||||
"so.$this->object_id = s.service_object_id AND so.is_active = 1",
|
||||
array()
|
||||
)->joinLeft(
|
||||
array('ss' => $this->prefix . 'servicestatus'),
|
||||
"so.$this->object_id = ss.service_object_id",
|
||||
array()
|
||||
);
|
||||
foreach ($this->columns as $col) {
|
||||
array('so' => $this->prefix . 'objects'),
|
||||
"so.$this->object_id = s.service_object_id AND so.is_active = 1",
|
||||
array()
|
||||
)->joinLeft(
|
||||
array('ss' => $this->prefix . 'servicestatus'),
|
||||
"so.$this->object_id = ss.service_object_id",
|
||||
array()
|
||||
);
|
||||
foreach ($this->getColumns() as $col) {
|
||||
$real = $this->aliasToColumnName($col);
|
||||
if (substr($real, 0, 4) === 'SUM(') {
|
||||
continue;
|
||||
}
|
||||
$this->baseQuery->group($real);
|
||||
}
|
||||
$this->uglySlowConservativeCount = true;
|
||||
$this->useSubqueryCount = true;
|
||||
}
|
||||
|
||||
protected function joinHostgroups()
|
||||
@ -184,25 +205,65 @@ class HoststatusQuery extends AbstractQuery
|
||||
}
|
||||
}
|
||||
|
||||
protected function joinServiceHostgroups()
|
||||
{
|
||||
$this->baseQuery->join(
|
||||
array('hgm' => $this->prefix . 'hostgroup_members'),
|
||||
'hgm.host_object_id = s.host_object_id',
|
||||
array()
|
||||
)->join(
|
||||
array('hg' => $this->prefix . 'hostgroups'),
|
||||
'hgm.hostgroup_id = hg.' . $this->hostgroup_id,
|
||||
array()
|
||||
)->join(
|
||||
array('hgo' => $this->prefix . 'objects'),
|
||||
'hgo.' . $this->object_id . ' = hg.hostgroup_object_id'
|
||||
. ' AND hgo.is_active = 1',
|
||||
array()
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function joinHostHostgroups()
|
||||
{
|
||||
$this->baseQuery->join(
|
||||
array('hgm' => $this->prefix . 'hostgroup_members'),
|
||||
'hgm.host_object_id = h.host_object_id',
|
||||
array()
|
||||
)->join(
|
||||
array('hg' => $this->prefix . 'hostgroups'),
|
||||
"hgm.hostgroup_id = hg.$this->hostgroup_id",
|
||||
array()
|
||||
)->join(
|
||||
array('hgo' => $this->prefix . 'objects'),
|
||||
'hgo.' . $this->object_id . ' = hg.hostgroup_object_id'
|
||||
. ' AND hgo.is_active = 1',
|
||||
array()
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function joinContacts()
|
||||
{
|
||||
$this->hcgcSub = $this->db->select()->distinct()->from(
|
||||
array('hcgc' => $this->prefix . 'host_contactgroups'),
|
||||
array('host_name' => 'ho.name1')
|
||||
)->join(
|
||||
array('cgo' => $this->prefix . 'objects'),
|
||||
'hcg.contactgroup_object_id = cgo.' . $this->object_id
|
||||
. ' AND cgo.is_active = 1',
|
||||
array()
|
||||
)->join(
|
||||
array('h' => $this->prefix . 'hosts'),
|
||||
'hcg.host_id = h.host_id',
|
||||
array()
|
||||
)->join(
|
||||
array('ho' => $this->prefix . 'objects'),
|
||||
'h.host_object_id = ho.' . $this->object_id . ' AND ho.is_active = 1',
|
||||
array()
|
||||
);
|
||||
array('cgo' => $this->prefix . 'objects'),
|
||||
'hcg.contactgroup_object_id = cgo.' . $this->object_id
|
||||
. ' AND cgo.is_active = 1',
|
||||
array()
|
||||
)->join(
|
||||
array('h' => $this->prefix . 'hosts'),
|
||||
'hcg.host_id = h.host_id',
|
||||
array()
|
||||
)->join(
|
||||
array('ho' => $this->prefix . 'objects'),
|
||||
'h.host_object_id = ho.' . $this->object_id . ' AND ho.is_active = 1',
|
||||
array()
|
||||
);
|
||||
$this->baseQuery->join(
|
||||
array('hcg' => $this->hcgSub),
|
||||
'hcg.host_name = ho.name1',
|
||||
@ -212,48 +273,6 @@ class HoststatusQuery extends AbstractQuery
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
protected function joinContacts()
|
||||
{
|
||||
|
||||
|
||||
$this->baseQuery->join(
|
||||
array('hc' => $this->prefix . 'host_contacts'),
|
||||
'hc.host_id = h.host_id',
|
||||
array()
|
||||
)->join(
|
||||
array('hco' => $this->prefix . 'objects'),
|
||||
'hco.' . $this->object_id. ' = hc.contact_object_id'
|
||||
. ' AND hco.is_active = 1',
|
||||
array()
|
||||
);
|
||||
|
||||
$this->baseQuery->join(
|
||||
array('hcg' => $this->prefix . 'host_contactgroups'),
|
||||
'hcg.host_id = h.host_id',
|
||||
array()
|
||||
)->join(
|
||||
array('hcgo' => $this->prefix . 'objects'),
|
||||
'hcgo.' . $this->object_id. ' = hcg.contactgroup_object_id'
|
||||
. ' AND hcgo.is_active = 1',
|
||||
array()
|
||||
);
|
||||
$this->baseQuery->join(
|
||||
array('cgm' => $this->prefix . 'contactgroup_members'),
|
||||
'cgm.contactgroup_id = cg.contactgroup_id',
|
||||
array()
|
||||
)->join(
|
||||
array('co' => $this->prefix . 'objects'),
|
||||
'cgm.contact_object_id = co.object_id AND co.is_active = 1',
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
*/
|
||||
protected function filterContactgroup($value)
|
||||
{
|
||||
$this->hcgSub->where(
|
||||
@ -265,28 +284,6 @@ class HoststatusQuery extends AbstractQuery
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
protected function createContactgroupFilterSubselect()
|
||||
{
|
||||
die((string) $this->db->select()->distinct()->from(
|
||||
array('hcg' => $this->prefix . 'host_contactgroups'),
|
||||
array('object_id' => 'ho.object_id')
|
||||
)->join(
|
||||
array('cgo' => $this->prefix . 'objects'),
|
||||
'hcg.contactgroup_object_id = cgo.' . $this->object_id
|
||||
. ' AND cgo.is_active = 1',
|
||||
array()
|
||||
)->join(
|
||||
array('h' => $this->prefix . 'hosts'),
|
||||
'hcg.host_id = h.host_id',
|
||||
array()
|
||||
)->join(
|
||||
array('ho' => $this->prefix . 'objects'),
|
||||
'h.host_object_id = ho.' . $this->object_id . ' AND ho.is_active = 1',
|
||||
array()
|
||||
));
|
||||
}
|
||||
|
||||
protected function joinContactgroups()
|
||||
{
|
||||
$this->hcgSub = $this->createContactgroupFilterSubselect();
|
||||
@ -299,44 +296,25 @@ class HoststatusQuery extends AbstractQuery
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function joinHostHostgroups()
|
||||
protected function createContactgroupFilterSubselect()
|
||||
{
|
||||
$this->baseQuery->join(
|
||||
array('hgm' => $this->prefix . 'hostgroup_members'),
|
||||
'hgm.host_object_id = h.host_object_id',
|
||||
array()
|
||||
die((string)$this->db->select()->distinct()->from(
|
||||
array('hcg' => $this->prefix . 'host_contactgroups'),
|
||||
array('object_id' => 'ho.object_id')
|
||||
)->join(
|
||||
array('hg' => $this->prefix . 'hostgroups'),
|
||||
"hgm.hostgroup_id = hg.$this->hostgroup_id",
|
||||
array()
|
||||
)->join(
|
||||
array('hgo' => $this->prefix . 'objects'),
|
||||
'hgo.' . $this->object_id. ' = hg.hostgroup_object_id'
|
||||
. ' AND hgo.is_active = 1',
|
||||
array()
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function joinServiceHostgroups()
|
||||
{
|
||||
$this->baseQuery->join(
|
||||
array('hgm' => $this->prefix . 'hostgroup_members'),
|
||||
'hgm.host_object_id = s.host_object_id',
|
||||
array()
|
||||
)->join(
|
||||
array('hg' => $this->prefix . 'hostgroups'),
|
||||
'hgm.hostgroup_id = hg.' . $this->hostgroup_id,
|
||||
array()
|
||||
)->join(
|
||||
array('hgo' => $this->prefix . 'objects'),
|
||||
'hgo.' . $this->object_id. ' = hg.hostgroup_object_id'
|
||||
. ' AND hgo.is_active = 1',
|
||||
array()
|
||||
);
|
||||
|
||||
return $this;
|
||||
array('cgo' => $this->prefix . 'objects'),
|
||||
'hcg.contactgroup_object_id = cgo.' . $this->object_id
|
||||
. ' AND cgo.is_active = 1',
|
||||
array()
|
||||
)->join(
|
||||
array('h' => $this->prefix . 'hosts'),
|
||||
'hcg.host_id = h.host_id',
|
||||
array()
|
||||
)->join(
|
||||
array('ho' => $this->prefix . 'objects'),
|
||||
'h.host_object_id = ho.' . $this->object_id . ' AND ho.is_active = 1',
|
||||
array()
|
||||
));
|
||||
}
|
||||
|
||||
protected function joinServicegroups()
|
||||
@ -348,15 +326,15 @@ class HoststatusQuery extends AbstractQuery
|
||||
'sgm.service_object_id = s.service_object_id',
|
||||
array()
|
||||
)->join(
|
||||
array('sg' => $this->prefix . 'servicegroups'),
|
||||
'sgm.servicegroup_id = sg.' . $this->servicegroup_id,
|
||||
array()
|
||||
)->join(
|
||||
array('sgo' => $this->prefix . 'objects'),
|
||||
'sgo.' . $this->object_id. ' = sg.servicegroup_object_id'
|
||||
. ' AND sgo.is_active = 1',
|
||||
array()
|
||||
);
|
||||
array('sg' => $this->prefix . 'servicegroups'),
|
||||
'sgm.servicegroup_id = sg.' . $this->servicegroup_id,
|
||||
array()
|
||||
)->join(
|
||||
array('sgo' => $this->prefix . 'objects'),
|
||||
'sgo.' . $this->object_id . ' = sg.servicegroup_object_id'
|
||||
. ' AND sgo.is_active = 1',
|
||||
array()
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -0,0 +1,572 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Data\Db\Query;
|
||||
use Icinga\Application\Benchmark;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\Filter\Query\Tree;
|
||||
use Icinga\Module\Monitoring\Filter\UrlViewFilter;
|
||||
|
||||
/**
|
||||
* Base class for Ido Queries
|
||||
*
|
||||
* This is the base class for all Ido queries and should be extended for new queries
|
||||
* The starting point for implementations is the columnMap attribute. This is an asscociative array in the
|
||||
* following form:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* array(
|
||||
* 'virtualTable' => array(
|
||||
* 'fieldalias1' => 'queryColumn1',
|
||||
* 'fieldalias2' => 'queryColumn2',
|
||||
* ....
|
||||
* ),
|
||||
* 'virtualTable2' => array(
|
||||
* 'host' => 'host_name1'
|
||||
* )
|
||||
* )
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* This allows you to select e.g. fieldalias1, which automatically calls the query code for joining 'virtualTable'. If
|
||||
* you afterwards select 'host', 'virtualTable2' will be joined. The joining logic is up to you, in order to make the
|
||||
* above example work you need to implement the joinVirtualTable() and joinVirtualTable2() method which contain your
|
||||
* custom (Zend_Db) logic for joining, filtering and querying the data you want.
|
||||
*
|
||||
*/
|
||||
abstract class IdoQuery extends Query
|
||||
{
|
||||
/**
|
||||
* The prefix to use
|
||||
*
|
||||
* @var String
|
||||
*/
|
||||
protected $prefix;
|
||||
|
||||
/**
|
||||
* The alias name for the index column
|
||||
*
|
||||
* @var String
|
||||
*/
|
||||
protected $idxAliasColumn;
|
||||
|
||||
/**
|
||||
* The table containing the index column alias
|
||||
*
|
||||
* @var String
|
||||
*/
|
||||
protected $idxAliasTable;
|
||||
|
||||
/**
|
||||
* The column map containing all filterable columns
|
||||
*
|
||||
* This must be overwritten by child classes, in the format
|
||||
* array(
|
||||
* 'virtualTable' => array(
|
||||
* 'fieldalias1' => 'queryColumn1',
|
||||
* 'fieldalias2' => 'queryColumn2',
|
||||
* ....
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $columnMap = array();
|
||||
|
||||
/**
|
||||
* Custom vars available for this query
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $customVars = array();
|
||||
|
||||
/**
|
||||
* An array with all 'virtual' tables that are already joined
|
||||
*
|
||||
* Virtual tables are the keys of the columnMap array and require a
|
||||
* join%VirtualTableName%() method to be defined in the concrete
|
||||
* query
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $joinedVirtualTables = array();
|
||||
|
||||
/**
|
||||
* The primary field name for the object table
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $object_id = 'object_id';
|
||||
|
||||
/**
|
||||
* The primary field name for the IDO host table
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $host_id = 'host_id';
|
||||
|
||||
/**
|
||||
* The primary field name for the IDO hostgroup table
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $hostgroup_id = 'hostgroup_id';
|
||||
|
||||
/**
|
||||
* The primary field name for the IDO service table
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $service_id = 'service_id';
|
||||
|
||||
/**
|
||||
* The primary field name for the IDO serviegroup table
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $servicegroup_id = 'servicegroup_id';
|
||||
|
||||
/**
|
||||
* The primary field name for the IDO contact table
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $contact_id = 'contact_id';
|
||||
|
||||
/**
|
||||
* The primary field name for the IDO contactgroup table
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $contactgroup_id = 'contactgroup_id';
|
||||
|
||||
/**
|
||||
* An array containing Column names that cause an aggregation of the query
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $aggregateColumnIdx = array();
|
||||
|
||||
/**
|
||||
* True to allow customvar filters and queries
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $allowCustomVars = false;
|
||||
|
||||
/**
|
||||
* Return true when the column is an aggregate column
|
||||
*
|
||||
* @param String $column The column to test
|
||||
* @return bool True when the column is an aggregate column
|
||||
*/
|
||||
public function isAggregateColumn($column)
|
||||
{
|
||||
return array_key_exists($column, $this->aggregateColumnIdx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Order the result by the given column
|
||||
*
|
||||
* @param string $columnOrAlias The column or column alias to order by
|
||||
* @param int $dir The sort direction or null to use default direction
|
||||
*
|
||||
* @return self Fluent interface
|
||||
*/
|
||||
public function order($columnOrAlias, $dir = null)
|
||||
{
|
||||
$this->requireColumn($columnOrAlias);
|
||||
if ($this->isCustomvar($columnOrAlias)) {
|
||||
$columnOrAlias = $this->getCustomvarColumnName($columnOrAlias);
|
||||
} elseif ($this->hasAliasName($columnOrAlias)) {
|
||||
$columnOrAlias = $this->aliasToColumnName($columnOrAlias);
|
||||
} else {
|
||||
Logger::info('Can\'t order by column '.$columnOrAlias);
|
||||
return $this;
|
||||
}
|
||||
return parent::order($columnOrAlias, $dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true when the given field can be used for filtering
|
||||
*
|
||||
* @param String $field The field to test
|
||||
* @return bool True when the field can be used for querying, otherwise false
|
||||
*/
|
||||
public function isValidFilterTarget($field)
|
||||
{
|
||||
return $this->getMappedField($field) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the resolved field for an alias
|
||||
*
|
||||
* @param String $field The alias to resolve
|
||||
* @return String The resolved alias or null if unknown
|
||||
*/
|
||||
public function getMappedField($field)
|
||||
{
|
||||
foreach ($this->columnMap as $columnSource => $columnSet) {
|
||||
if (isset($columnSet[$field])) {
|
||||
return $columnSet[$field];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if an field contains an explicit timestamp
|
||||
*
|
||||
* @param String $field The field to test for containing an timestamp
|
||||
* @return bool True when the field represents an timestamp
|
||||
*/
|
||||
public function isTimestamp($field)
|
||||
{
|
||||
$mapped = $this->getMappedField($field);
|
||||
if ($mapped === null) {
|
||||
return false;
|
||||
}
|
||||
return stripos($mapped, 'UNIX_TIMESTAMP') !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply oracle specific query initialization
|
||||
*/
|
||||
private function initializeForOracle()
|
||||
{
|
||||
// Oracle uses the reserved field 'id' for primary keys, so
|
||||
// these must be used instead of the normally defined ids
|
||||
$this->object_id = $this->host_id = $this->service_id
|
||||
= $this->hostgroup_id = $this->servicegroup_id
|
||||
= $this->contact_id = $this->contactgroup_id = 'id';
|
||||
foreach ($this->columnMap as &$columns) {
|
||||
foreach ($columns as &$value) {
|
||||
$value = preg_replace('/UNIX_TIMESTAMP/', 'localts2unixts', $value);
|
||||
$value = preg_replace('/ COLLATE .+$/', '', $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply postgresql specific query initialization
|
||||
*/
|
||||
private function initializeForPostgres()
|
||||
{
|
||||
foreach ($this->columnMap as $table => & $columns) {
|
||||
foreach ($columns as $key => & $value) {
|
||||
$value = preg_replace('/ COLLATE .+$/', '', $value);
|
||||
$value = preg_replace('/inet_aton\(([[:word:].]+)\)/i', '$1::inet - \'0.0.0.0\'', $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up this query and join the initial tables
|
||||
*
|
||||
* @see IdoQuery::initializeForPostgres For postgresql specific setup
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
parent::init();
|
||||
$this->prefix = $this->ds->getTablePrefix();
|
||||
|
||||
if ($this->ds->getDbType() === 'oracle') {
|
||||
$this->initializeForOracle();
|
||||
} elseif ($this->ds->getDbType() === 'pgsql') {
|
||||
$this->initializeForPostgres();
|
||||
}
|
||||
$this->joinBaseTables();
|
||||
$this->prepareAliasIndexes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Join the base tables for this query
|
||||
*/
|
||||
protected function joinBaseTables()
|
||||
{
|
||||
reset($this->columnMap);
|
||||
$table = key($this->columnMap);
|
||||
|
||||
$this->baseQuery = $this->db->select()->from(
|
||||
array($table => $this->prefix . $table),
|
||||
array()
|
||||
);
|
||||
|
||||
$this->joinedVirtualTables = array($table => true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the idxAliasTAble and idxAliasColumn properties
|
||||
*/
|
||||
protected function prepareAliasIndexes()
|
||||
{
|
||||
foreach ($this->columnMap as $tbl => & $cols) {
|
||||
foreach ($cols as $alias => $col) {
|
||||
$this->idxAliasTable[$alias] = $tbl;
|
||||
$this->idxAliasColumn[$alias] = preg_replace('~\n\s*~', ' ', $col);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare query execution
|
||||
*
|
||||
* @see IdoQuery::resolveColumns() For column alias resolving
|
||||
*/
|
||||
protected function beforeQueryCreation()
|
||||
{
|
||||
$this->resolveColumns();
|
||||
$classParts = explode('\\', get_class($this));
|
||||
Benchmark::measure(sprintf('%s ready to run', array_pop($classParts)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve columns aliases to their database field using the columnMap
|
||||
*
|
||||
* @return self Fluent interface
|
||||
*/
|
||||
public function resolveColumns()
|
||||
{
|
||||
$columns = $this->getColumns();
|
||||
$resolvedColumns = array();
|
||||
|
||||
foreach ($columns as $alias => $col) {
|
||||
$this->requireColumn($col);
|
||||
if ($this->isCustomvar($col)) {
|
||||
$name = $this->getCustomvarColumnName($col);
|
||||
} else {
|
||||
$name = $this->aliasToColumnName($col);
|
||||
}
|
||||
if (is_int($alias)) {
|
||||
$alias = $col;
|
||||
}
|
||||
|
||||
$resolvedColumns[$alias] = preg_replace('|\n|', ' ', $name);
|
||||
}
|
||||
$this->setColumns($resolvedColumns);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all columns that will be selected when no columns are given in the constructor or from
|
||||
*
|
||||
* @return array An array of column aliases
|
||||
*/
|
||||
public function getDefaultColumns()
|
||||
{
|
||||
reset($this->columnMap);
|
||||
$table = key($this->columnMap);
|
||||
return array_keys($this->columnMap[$table]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the query to the given alias can be used in the result set or queries
|
||||
*
|
||||
* This calls requireVirtualTable if needed
|
||||
*
|
||||
* @param $alias The alias of the column to require
|
||||
*
|
||||
* @return self Fluent interface
|
||||
* @see IdoQuery::requireVirtualTable The method initializing required joins
|
||||
* @throws \Icinga\Exception\ProgrammingError When an unknown column is requested
|
||||
*/
|
||||
public function requireColumn($alias)
|
||||
{
|
||||
if ($this->hasAliasName($alias)) {
|
||||
$this->requireVirtualTable($this->aliasToTableName($alias));
|
||||
} elseif ($this->isCustomVar($alias)) {
|
||||
$this->requireCustomvar($alias);
|
||||
} else {
|
||||
throw new ProgrammingError(sprintf('%s : Got invalid column: %s', get_called_class(), $alias));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the given alias exists
|
||||
*
|
||||
* @param String $alias The alias to test for
|
||||
* @return bool True when the alias exists, otherwise false
|
||||
*/
|
||||
protected function hasAliasName($alias)
|
||||
{
|
||||
return array_key_exists($alias, $this->idxAliasColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Require a virtual table for the given table name if not already required
|
||||
*
|
||||
* @param String $name The table name to require
|
||||
* @return self Fluent interface
|
||||
*/
|
||||
protected function requireVirtualTable($name)
|
||||
{
|
||||
if ($this->hasJoinedVirtualTable($name)) {
|
||||
return $this;
|
||||
}
|
||||
return $this->joinVirtualTable($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the method for joining a virtual table
|
||||
*
|
||||
* This requires a join$Table() method to exist
|
||||
*
|
||||
* @param String $table The table to join by calling join$Table() in the concrete implementation
|
||||
* @return self Fluent interface
|
||||
*
|
||||
* @throws \Icinga\Exception\ProgrammingError If the join method for this table does not exist
|
||||
*/
|
||||
protected function joinVirtualTable($table)
|
||||
{
|
||||
$func = 'join' . ucfirst($table);
|
||||
if (method_exists($this, $func)) {
|
||||
$this->$func();
|
||||
} else {
|
||||
throw new ProgrammingError(
|
||||
sprintf(
|
||||
'Cannot join "%s", no such table found',
|
||||
$table
|
||||
)
|
||||
);
|
||||
}
|
||||
$this->joinedVirtualTables[$table] = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the table for a specific alias
|
||||
*
|
||||
* @param String $alias The alias to request the table for
|
||||
* @return String The table for the alias or null if it doesn't exist
|
||||
*/
|
||||
protected function aliasToTableName($alias)
|
||||
{
|
||||
return isset($this->idxAliasTable[$alias]) ? $this->idxAliasTable[$alias] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the given alias denotes a custom variable
|
||||
*
|
||||
* @param String $alias The alias to test for being a customvariable
|
||||
* @return bool True if the alias is a customvariable, otherwise false
|
||||
*/
|
||||
protected function isCustomVar($alias)
|
||||
{
|
||||
return $this->allowCustomVars && $alias[0] === '_';
|
||||
}
|
||||
|
||||
protected function requireCustomvar($customvar)
|
||||
{
|
||||
if (! $this->hasCustomvar($customvar)) {
|
||||
$this->joinCustomvar($customvar);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function hasCustomvar($customvar)
|
||||
{
|
||||
return array_key_exists($customvar, $this->customVars);
|
||||
}
|
||||
|
||||
protected function joinCustomvar($customvar)
|
||||
{
|
||||
// TODO: This is not generic enough yet
|
||||
list($type, $name) = $this->customvarNameToTypeName($customvar);
|
||||
$alias = ($type === 'host' ? 'hcv_' : 'scv_') . strtolower($name);
|
||||
|
||||
$this->customVars[$customvar] = $alias;
|
||||
|
||||
// TODO: extend if we allow queries with only hosts / only services
|
||||
// ($leftcol s.host_object_id vs h.host_object_id
|
||||
if ($this->hasJoinedVirtualTable('services')) {
|
||||
$leftcol = 's.' . $type . '_object_id';
|
||||
} else {
|
||||
$leftcol = 'h.' . $type . '_object_id';
|
||||
}
|
||||
$joinOn = $leftcol
|
||||
. ' = '
|
||||
. $alias
|
||||
. '.object_id'
|
||||
. ' AND '
|
||||
. $alias
|
||||
. '.varname = '
|
||||
. $this->db->quote(strtoupper($name));
|
||||
|
||||
$this->baseQuery->joinLeft(
|
||||
array($alias => $this->prefix . 'customvariablestatus'),
|
||||
$joinOn,
|
||||
array()
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function customvarNameToTypeName($customvar)
|
||||
{
|
||||
// TODO: Improve this:
|
||||
if (! preg_match('~^_(host|service)_([a-zA-Z0-9_]+)$~', $customvar, $m)) {
|
||||
throw new ProgrammingError(
|
||||
sprintf(
|
||||
'Got invalid custom var: "%s"',
|
||||
$customvar
|
||||
)
|
||||
);
|
||||
}
|
||||
return array($m[1], $m[2]);
|
||||
}
|
||||
|
||||
protected function hasJoinedVirtualTable($name)
|
||||
{
|
||||
return array_key_exists($name, $this->joinedVirtualTables);
|
||||
}
|
||||
|
||||
protected function getCustomvarColumnName($customvar)
|
||||
{
|
||||
return $this->customVars[$customvar] . '.varvalue';
|
||||
}
|
||||
|
||||
public function aliasToColumnName($alias)
|
||||
{
|
||||
return $this->idxAliasColumn[$alias];
|
||||
}
|
||||
|
||||
protected function createSubQuery($queryName, $columns = array())
|
||||
{
|
||||
$class = '\\'
|
||||
. substr(__CLASS__, 0, strrpos(__CLASS__, '\\') + 1)
|
||||
. ucfirst($queryName) . 'Query';
|
||||
$query = new $class($this->ds, $columns);
|
||||
return $query;
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
/**
|
||||
* Notification query
|
||||
*/
|
||||
class NotificationQuery extends AbstractQuery
|
||||
class NotificationQuery extends IdoQuery
|
||||
{
|
||||
/**
|
||||
* Column map
|
||||
@ -44,7 +44,9 @@ class NotificationQuery extends AbstractQuery
|
||||
),
|
||||
'objects' => array(
|
||||
'host_name' => 'o.name1',
|
||||
'service_description' => 'o.name2'
|
||||
'service_description' => 'o.name2',
|
||||
'service' => 'o.name2',
|
||||
'host_name' => 'o.name1'
|
||||
),
|
||||
'contact' => array(
|
||||
'notification_contact' => 'c_o.name1'
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
|
||||
class NotificationhistoryQuery extends AbstractQuery
|
||||
class NotificationhistoryQuery extends IdoQuery
|
||||
{
|
||||
protected $columnMap = array(
|
||||
'history' => array(
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
|
||||
class ServicegroupQuery extends AbstractQuery
|
||||
class ServicegroupQuery extends IdoQuery
|
||||
{
|
||||
protected $columnMap = array(
|
||||
'servicegroups' => array(
|
||||
@ -10,7 +10,9 @@ class ServicegroupQuery extends AbstractQuery
|
||||
'servicegroup_alias' => 'sg.alias',
|
||||
),
|
||||
'services' => array(
|
||||
'host' => 'so.name1 COLLATE latin1_general_ci',
|
||||
'host_name' => 'so.name1 COLLATE latin1_general_ci',
|
||||
'service' => 'so.name2 COLLATE latin1_general_ci',
|
||||
'service_host_name' => 'so.name1 COLLATE latin1_general_ci',
|
||||
'service_description' => 'so.name2 COLLATE latin1_general_ci'
|
||||
)
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
|
||||
class ServicestatusQuery extends AbstractQuery
|
||||
class ServicestatusQuery extends IdoQuery
|
||||
{
|
||||
protected $allowCustomVars = true;
|
||||
|
||||
@ -46,7 +46,7 @@ class ServicestatusQuery extends AbstractQuery
|
||||
)
|
||||
);
|
||||
|
||||
protected function getDefaultColumns()
|
||||
public function getDefaultColumns()
|
||||
{
|
||||
return $this->columnMap['services']
|
||||
+ $this->columnMap['servicestatus'];
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
|
||||
class StatehistoryQuery extends AbstractQuery
|
||||
class StatehistoryQuery extends IdoQuery
|
||||
{
|
||||
protected $columnMap = array(
|
||||
'statehistory' => array(
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||
|
||||
class StatusQuery extends AbstractQuery
|
||||
class StatusQuery extends IdoQuery
|
||||
{
|
||||
protected $allowCustomVars = true;
|
||||
|
||||
@ -29,8 +29,6 @@ class StatusQuery extends AbstractQuery
|
||||
'host_acknowledged' => 'hs.problem_has_been_acknowledged',
|
||||
'host_in_downtime' => 'CASE WHEN (hs.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END',
|
||||
'host_handled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) > 0 THEN 1 ELSE 0 END',
|
||||
'host_does_active_checks' => 'hs.active_checks_enabled',
|
||||
'host_accepts_passive_checks' => 'hs.passive_checks_enabled',
|
||||
'host_last_state_change' => 'UNIX_TIMESTAMP(hs.last_state_change)',
|
||||
'host_last_hard_state' => 'hs.last_hard_state',
|
||||
'host_last_hard_state_change' => 'UNIX_TIMESTAMP(hs.last_hard_state_change)',
|
||||
@ -130,8 +128,6 @@ class StatusQuery extends AbstractQuery
|
||||
'service_acknowledged' => 'ss.problem_has_been_acknowledged',
|
||||
'service_in_downtime' => 'CASE WHEN (ss.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END',
|
||||
'service_handled' => 'CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) > 0 THEN 1 ELSE 0 END',
|
||||
'service_does_active_checks' => 'ss.active_checks_enabled',
|
||||
'service_accepts_passive_checks' => 'ss.passive_checks_enabled',
|
||||
'service_last_state_change' => 'UNIX_TIMESTAMP(ss.last_state_change)',
|
||||
'service_check_command' => 'ss.check_command',
|
||||
'service_last_time_ok' => 'ss.last_time_ok',
|
||||
@ -223,7 +219,6 @@ class StatusQuery extends AbstractQuery
|
||||
|
||||
protected function joinBaseTables()
|
||||
{
|
||||
// TODO: Shall we always add hostobject?
|
||||
$this->baseQuery = $this->db->select()->from(
|
||||
array('ho' => $this->prefix . 'objects'),
|
||||
array()
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
namespace \Icinga\Module\Monitoring\Backend\Livestatus\Query;
|
||||
|
||||
use Icinga\Data\AbstractQuery;
|
||||
use Icinga\Data\BaseQuery;
|
||||
|
||||
class StatusQuery extends AbstractQuery
|
||||
class StatusQuery extends BaseQuery implements Filterable
|
||||
{
|
||||
protected $available_columns = array(
|
||||
'host_name',
|
||||
|
@ -64,7 +64,7 @@ abstract class GroupsummaryQuery extends Query
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $order_columns = array(
|
||||
protected $orderColumns = array(
|
||||
'state' => array(
|
||||
'ASC' => array(
|
||||
'ok ASC',
|
||||
|
@ -28,17 +28,22 @@
|
||||
|
||||
namespace Icinga\Module\Monitoring\Backend\Statusdat\Query;
|
||||
|
||||
use \Icinga\Module\Monitoring\Backend\Statusdat\Criteria\Order;
|
||||
use Icinga\Data\Optional;
|
||||
use Icinga\Data\The;
|
||||
use Icinga\Filter\Query\Node;
|
||||
use Icinga\Filter\Query\Tree;
|
||||
use Icinga\Protocol\Statusdat;
|
||||
use Icinga\Exception;
|
||||
use Icinga\Data\AbstractQuery;
|
||||
use Icinga\Data\BaseQuery;
|
||||
use Icinga\Protocol\Statusdat\View\MonitoringObjectList as MList;
|
||||
use Icinga\Protocol\Statusdat\Query as StatusdatQuery;
|
||||
use Icinga\Filter\Filterable;
|
||||
|
||||
/**
|
||||
* Class Query
|
||||
* @package Icinga\Backend\Statusdat
|
||||
*/
|
||||
abstract class Query extends AbstractQuery
|
||||
abstract class Query extends BaseQuery implements Filterable
|
||||
{
|
||||
/**
|
||||
* @var null
|
||||
@ -284,7 +289,39 @@ abstract class Query extends AbstractQuery
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
|
||||
return count($this->baseQuery->getResult());
|
||||
}
|
||||
|
||||
public function isValidFilterTarget($field)
|
||||
{
|
||||
// TODO: Implement isValidFilterTarget() method.
|
||||
}
|
||||
|
||||
public function getMappedField($field)
|
||||
{
|
||||
// TODO: Implement getMappedField() method.
|
||||
}
|
||||
|
||||
public function applyFilter()
|
||||
{
|
||||
// TODO: Implement applyFilter() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a backend specific filter expression and return a Query\Node object
|
||||
*
|
||||
* @param $expression The expression to parse
|
||||
* @param $parameters Optional parameters for the expression
|
||||
* @return Node A query node or null if it's an invalid expression
|
||||
*/
|
||||
protected function parseFilterExpression($expression, $parameters = null)
|
||||
{
|
||||
// TODO: Implement parseFilterExpression() method.
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
namespace Icinga\Module\Monitoring;
|
||||
|
||||
use Icinga\Application\Config as IcingaConfig;
|
||||
use Icinga\Module\Monitoring\DataView\HostAndServiceStatus as HostAndServiceStatusView;
|
||||
use Icinga\Module\Monitoring\DataView\ServiceStatus as ServiceStatusView;
|
||||
use Icinga\Web\Controller\ActionController;
|
||||
|
||||
/**
|
||||
@ -13,6 +13,7 @@ use Icinga\Web\Controller\ActionController;
|
||||
*/
|
||||
class Controller extends ActionController
|
||||
{
|
||||
|
||||
/**
|
||||
* Retrieve services from either given parameters or request
|
||||
*
|
||||
@ -54,13 +55,13 @@ class Controller extends ActionController
|
||||
'max_check_attempts' => 'service_max_check_attempts'
|
||||
);
|
||||
if ($params === null) {
|
||||
$query = HostAndServiceStatusView::fromRequest(
|
||||
$query = ServiceStatusView::fromRequest(
|
||||
$this->_request,
|
||||
$columns
|
||||
)->getQuery();
|
||||
} else {
|
||||
$params['backend'] = $this->_request->getParam('backend');
|
||||
$query = HostAndServiceStatusView::fromParams(
|
||||
$query = ServiceStatusView::fromParams(
|
||||
$params,
|
||||
$columns
|
||||
)->getQuery();
|
||||
@ -79,8 +80,7 @@ class Controller extends ActionController
|
||||
exit;
|
||||
}
|
||||
if ($this->_getParam('format') === 'json'
|
||||
|| $this->_request->getHeader('Accept') === 'application/json')
|
||||
{
|
||||
|| $this->_request->getHeader('Accept') === 'application/json') {
|
||||
header('Content-type: application/json');
|
||||
echo json_encode($query->fetchAll());
|
||||
exit;
|
||||
|
@ -51,7 +51,9 @@ class Comment extends DataView
|
||||
'comment_is_persistent',
|
||||
'comment_expiration_timestamp',
|
||||
'host_name',
|
||||
'service_name'
|
||||
'service_name',
|
||||
'host',
|
||||
'service'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,9 @@ class Contact extends DataView
|
||||
'host_name',
|
||||
'service_object_id',
|
||||
'service_host_name',
|
||||
'service_description'
|
||||
'service_description',
|
||||
'service',
|
||||
'host',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,9 @@ class Contactgroup extends DataView
|
||||
return array(
|
||||
'contact_name',
|
||||
'contactgroup_name',
|
||||
'contactgroup_alias'
|
||||
'contactgroup_alias',
|
||||
'host',
|
||||
'service'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -4,37 +4,38 @@
|
||||
|
||||
namespace Icinga\Module\Monitoring\DataView;
|
||||
|
||||
use Icinga\Data\AbstractQuery;
|
||||
use Icinga\Data\BaseQuery;
|
||||
use Icinga\Filter\Filterable;
|
||||
use Icinga\Filter\Query\Tree;
|
||||
use Icinga\Module\Monitoring\Backend;
|
||||
use Icinga\Module\Monitoring\Filter\UrlViewFilter;
|
||||
use Icinga\Web\Request;
|
||||
|
||||
/**
|
||||
* A read-only view of an underlying Query
|
||||
*/
|
||||
abstract class DataView
|
||||
abstract class DataView implements Filterable
|
||||
{
|
||||
/**
|
||||
* Sort in ascending order, default
|
||||
*/
|
||||
const SORT_ASC = BaseQuery::SORT_ASC;
|
||||
/**
|
||||
* Sort in reverse order
|
||||
*/
|
||||
const SORT_DESC = BaseQuery::SORT_DESC;
|
||||
/**
|
||||
* The query used to populate the view
|
||||
*
|
||||
* @var AbstractQuery
|
||||
* @var BaseQuery
|
||||
*/
|
||||
private $query;
|
||||
|
||||
/**
|
||||
* Sort in ascending order, default
|
||||
*/
|
||||
const SORT_ASC = AbstractQuery::SORT_ASC;
|
||||
|
||||
/**
|
||||
* Sort in reverse order
|
||||
*/
|
||||
const SORT_DESC = AbstractQuery::SORT_DESC;
|
||||
|
||||
/**
|
||||
* Create a new view
|
||||
*
|
||||
* @param Backend $ds Which backend to query
|
||||
* @param array $columns Select columns
|
||||
* @param Backend $ds Which backend to query
|
||||
* @param array $columns Select columns
|
||||
*/
|
||||
public function __construct(Backend $ds, array $columns = null)
|
||||
{
|
||||
@ -60,30 +61,21 @@ abstract class DataView
|
||||
*/
|
||||
abstract public function getColumns();
|
||||
|
||||
/**
|
||||
* Retrieve default sorting rules for particular columns. These involve sort order and potential additional to sort
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function getSortRules();
|
||||
|
||||
public function getFilterColumns()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create view from request
|
||||
*
|
||||
* @param Request $request
|
||||
* @param array $columns
|
||||
* @param array $columns
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function fromRequest($request, array $columns = null)
|
||||
{
|
||||
|
||||
$view = new static(Backend::createBackend($request->getParam('backend')), $columns);
|
||||
$view->filter($request->getParams());
|
||||
$parser = new UrlViewFilter($view);
|
||||
$view->getQuery()->setFilter($parser->fromRequest($request));
|
||||
|
||||
$order = $request->getParam('dir');
|
||||
if ($order !== null) {
|
||||
if (strtolower($order) === 'desc') {
|
||||
@ -102,15 +94,20 @@ abstract class DataView
|
||||
/**
|
||||
* Create view from params
|
||||
*
|
||||
* @param array $params
|
||||
* @param array $columns
|
||||
* @param array $params
|
||||
* @param array $columns
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function fromParams(array $params, array $columns = null)
|
||||
{
|
||||
$view = new static(Backend::createBackend($params['backend']), $columns);
|
||||
$view->filter($params);
|
||||
|
||||
foreach ($params as $key => $value) {
|
||||
if ($view->isValidFilterTarget($key)) {
|
||||
$view->getQuery()->where($key, $value);
|
||||
}
|
||||
}
|
||||
$order = isset($params['order']) ? $params['order'] : null;
|
||||
if ($order !== null) {
|
||||
if (strtolower($order) === 'desc') {
|
||||
@ -119,6 +116,7 @@ abstract class DataView
|
||||
$order = self::SORT_ASC;
|
||||
}
|
||||
}
|
||||
|
||||
$view->sort(
|
||||
isset($params['sort']) ? $params['sort'] : null,
|
||||
$order
|
||||
@ -126,22 +124,6 @@ abstract class DataView
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter rows that match all of the given filters. If a filter is not valid, it's silently ignored
|
||||
*
|
||||
* @param array $filters
|
||||
*
|
||||
* @see isValidFilterColumn()
|
||||
*/
|
||||
public function filter(array $filters)
|
||||
{
|
||||
foreach ($filters as $column => $filter) {
|
||||
if ($this->isValidFilterColumn($column)) {
|
||||
$this->query->where($column, $filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given column is a valid filter column, i.e. the view actually provides the column or it's
|
||||
* a non-queryable filter column
|
||||
@ -150,16 +132,21 @@ abstract class DataView
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isValidFilterColumn($column)
|
||||
public function isValidFilterTarget($column)
|
||||
{
|
||||
return in_array($column, $this->getColumns()) || in_array($column, $this->getFilterColumns());
|
||||
}
|
||||
|
||||
public function getFilterColumns()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the rows, according to the specified sort column and order
|
||||
*
|
||||
* @param string $column Sort column
|
||||
* @param int $order Sort order, one of the SORT_ constants
|
||||
* @param string $column Sort column
|
||||
* @param int $order Sort order, one of the SORT_ constants
|
||||
*
|
||||
* @see DataView::SORT_ASC
|
||||
* @see DataView::SORT_DESC
|
||||
@ -180,15 +167,31 @@ abstract class DataView
|
||||
}
|
||||
} else {
|
||||
$sortColumns = array(
|
||||
'columns' => array($column),
|
||||
'order' => $order
|
||||
'columns' => array($column),
|
||||
'order' => $order
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
$order = $order === null ? (isset($sortColumns['order']) ? $sortColumns['order'] : self::SORT_ASC) : $order;
|
||||
$order = ($order === self::SORT_ASC) ? 'ASC' : 'DESC';
|
||||
|
||||
foreach ($sortColumns['columns'] as $column) {
|
||||
$this->query->order($column, $order);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve default sorting rules for particular columns. These involve sort order and potential additional to sort
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function getSortRules();
|
||||
|
||||
public function getMappedField($field)
|
||||
{
|
||||
return $this->query->getMappedField($field);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -200,4 +203,22 @@ abstract class DataView
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
public function applyFilter()
|
||||
{
|
||||
$this->query->applyFilter();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function clearFilter()
|
||||
{
|
||||
$this->query->clearFilter();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addFilter($filter)
|
||||
{
|
||||
$this->query->addFilter($filter);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,29 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Module\Monitoring\DataView;
|
||||
@ -14,16 +38,23 @@ class EventHistory extends DataView
|
||||
public function getColumns()
|
||||
{
|
||||
return array(
|
||||
'raw_timestamp',
|
||||
'timestamp',
|
||||
'host',
|
||||
'service',
|
||||
'cnt_notification',
|
||||
'cnt_hard_state',
|
||||
'cnt_soft_state',
|
||||
'cnt_downtime_start',
|
||||
'cnt_downtime_end',
|
||||
'host_name',
|
||||
'service_description',
|
||||
'object_type',
|
||||
'timestamp',
|
||||
'raw_timestamp',
|
||||
'state',
|
||||
'attempt',
|
||||
'max_attempts',
|
||||
'output',
|
||||
'type'
|
||||
'type',
|
||||
'host',
|
||||
'service'
|
||||
);
|
||||
}
|
||||
|
||||
|
113
modules/monitoring/library/Monitoring/DataView/HostStatus.php
Normal file
113
modules/monitoring/library/Monitoring/DataView/HostStatus.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Module\Monitoring\DataView;
|
||||
|
||||
use Icinga\Module\Monitoring\Filter\MonitoringFilter;
|
||||
|
||||
class HostStatus extends DataView
|
||||
{
|
||||
/**
|
||||
* Retrieve columns provided by this view
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getColumns()
|
||||
{
|
||||
return array(
|
||||
'host',
|
||||
'host_name',
|
||||
'host_alias',
|
||||
'host_address',
|
||||
'host_state',
|
||||
'host_state_type',
|
||||
'host_handled',
|
||||
'host_in_downtime',
|
||||
'host_acknowledged',
|
||||
'host_last_state_change',
|
||||
'host_last_state_change',
|
||||
'host_last_notification',
|
||||
'host_last_check',
|
||||
'host_next_check',
|
||||
'host_check_execution_time',
|
||||
'host_check_latency',
|
||||
'host_output',
|
||||
'host_long_output',
|
||||
'host_check_command',
|
||||
'host_perfdata',
|
||||
'host_passive_checks_enabled',
|
||||
'host_obsessing',
|
||||
'host_notifications_enabled',
|
||||
'host_event_handler_enabled',
|
||||
'host_flap_detection_enabled',
|
||||
'host_active_checks_enabled',
|
||||
'host_current_check_attempt',
|
||||
'host_max_check_attempts',
|
||||
'host_last_notification',
|
||||
'host_current_notification_number',
|
||||
'host_percent_state_change',
|
||||
'host_is_flapping',
|
||||
'host_last_comment',
|
||||
'host_action_url',
|
||||
'host_notes_url',
|
||||
'host_percent_state_change'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the table name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'status';
|
||||
}
|
||||
|
||||
/**
|
||||
* The sort rules for this query
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSortRules()
|
||||
{
|
||||
return array(
|
||||
'host_name' => array(
|
||||
'order' => self::SORT_ASC
|
||||
),
|
||||
'host_address' => array(
|
||||
'columns' => array(
|
||||
'host_ipv4',
|
||||
'service_description'
|
||||
),
|
||||
'order' => self::SORT_ASC
|
||||
),
|
||||
'host_last_state_change' => array(
|
||||
'order' => self::SORT_ASC
|
||||
),
|
||||
'host_severity' => array(
|
||||
'columns' => array(
|
||||
'host_severity',
|
||||
'host_last_state_change',
|
||||
),
|
||||
'order' => self::SORT_ASC
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function getFilterColumns()
|
||||
{
|
||||
return array('hostgroups', 'servicegroups', 'service_problems');
|
||||
}
|
||||
|
||||
public function isValidFilterTarget($column)
|
||||
{
|
||||
if ($column[0] === '_'
|
||||
&& preg_match('/^_(?:host|service)_/', $column)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return parent::isValidFilterTarget($column);
|
||||
}
|
||||
}
|
74
modules/monitoring/library/Monitoring/DataView/Hostgroup.php
Normal file
74
modules/monitoring/library/Monitoring/DataView/Hostgroup.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
|
||||
namespace Icinga\Module\Monitoring\DataView;
|
||||
|
||||
/**
|
||||
* View for hostgroups
|
||||
*/
|
||||
class Hostgroup extends DataView
|
||||
{
|
||||
/**
|
||||
* Retrieve columns provided by this view
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getColumns()
|
||||
{
|
||||
return array(
|
||||
'host',
|
||||
'hostgroup_name',
|
||||
'hostgroup_alias'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the table name for this view
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'hostgroup';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve default sorting rules for particular columns. These involve sort order and potential additional to sort
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSortRules()
|
||||
{
|
||||
return array(
|
||||
'hostgroup_name' => array(
|
||||
'order' => self::SORT_ASC
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -21,7 +21,9 @@ class Notification extends DataView
|
||||
'notification_start_time',
|
||||
'notification_contact',
|
||||
'notification_information',
|
||||
'notification_command'
|
||||
'notification_command',
|
||||
'host',
|
||||
'service'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
namespace Icinga\Module\Monitoring\DataView;
|
||||
|
||||
class HostAndServiceStatus extends DataView
|
||||
class ServiceStatus extends DataView
|
||||
{
|
||||
/**
|
||||
* Retrieve columns provided by this view
|
||||
@ -38,6 +38,10 @@ class HostAndServiceStatus extends DataView
|
||||
'service_action_url',
|
||||
'service_notes_url',
|
||||
'service_last_comment',
|
||||
'service_last_check',
|
||||
'service_next_check',
|
||||
'service_last_notification',
|
||||
'service_check_command',
|
||||
'host_icon_image',
|
||||
'host_acknowledged',
|
||||
'host_output',
|
||||
@ -54,22 +58,20 @@ class HostAndServiceStatus extends DataView
|
||||
'host_display_name',
|
||||
'host_alias',
|
||||
'host_ipv4',
|
||||
// 'host_problems',
|
||||
'host_severity',
|
||||
'host_perfdata',
|
||||
'host_does_active_checks',
|
||||
'host_accepts_passive_checks',
|
||||
'host_active_checks_enabled',
|
||||
'host_passive_checks_enabled',
|
||||
'host_last_hard_state',
|
||||
'host_last_hard_state_change',
|
||||
'host_last_time_up',
|
||||
'host_last_time_down',
|
||||
'host_last_time_unreachable',
|
||||
'service',
|
||||
// 'current_state',
|
||||
'service_hard_state',
|
||||
'service_perfdata',
|
||||
'service_does_active_checks',
|
||||
'service_accepts_passive_checks',
|
||||
'service_active_checks_enabled',
|
||||
'service_passive_checks_enabled',
|
||||
'service_last_hard_state',
|
||||
'service_last_hard_state_change',
|
||||
'service_last_time_ok',
|
||||
@ -78,13 +80,11 @@ class HostAndServiceStatus extends DataView
|
||||
'service_last_time_unknown',
|
||||
'service_current_check_attempt',
|
||||
'service_max_check_attempts'
|
||||
// 'object_type',
|
||||
// 'problems',
|
||||
// 'handled',
|
||||
// 'severity'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'status';
|
||||
@ -121,13 +121,13 @@ class HostAndServiceStatus extends DataView
|
||||
return array('hostgroups', 'servicegroups', 'service_problems');
|
||||
}
|
||||
|
||||
protected function isValidFilterColumn($column)
|
||||
public function isValidFilterTarget($column)
|
||||
{
|
||||
if ($column[0] === '_'
|
||||
&& preg_match('/^_(?:host|service)_/', $column)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return parent::isValidFilterColumn($column);
|
||||
return parent::isValidFilterTarget($column);
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
|
||||
namespace Icinga\Module\Monitoring\DataView;
|
||||
|
||||
|
||||
class Servicegroup extends DataView
|
||||
{
|
||||
/**
|
||||
* Retrieve columns provided by this view
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getColumns()
|
||||
{
|
||||
return array(
|
||||
'service',
|
||||
'host',
|
||||
'servicegroup_name',
|
||||
'servicegroup_alias'
|
||||
);
|
||||
}
|
||||
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'servicegroup';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve default sorting rules for particular columns. These involve sort order and potential additional to sort
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSortRules()
|
||||
{
|
||||
return array(
|
||||
'servicegroup_name' => array(
|
||||
'order' => self::SORT_ASC
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
233
modules/monitoring/library/Monitoring/Filter/Registry.php
Normal file
233
modules/monitoring/library/Monitoring/Filter/Registry.php
Normal file
@ -0,0 +1,233 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Module\Monitoring\Filter;
|
||||
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Filter\Domain;
|
||||
use Icinga\Filter\FilterAttribute;
|
||||
use Icinga\Filter\Query\Node;
|
||||
use Icinga\Filter\Query\Tree;
|
||||
use Icinga\Filter\Type\BooleanFilter;
|
||||
use Icinga\Filter\Type\TextFilter;
|
||||
use Icinga\Filter\Type\TimeRangeSpecifier;
|
||||
use Icinga\Module\Monitoring\DataView\HostStatus;
|
||||
use Icinga\Module\Monitoring\DataView\ServiceStatus;
|
||||
use Icinga\Module\Monitoring\Filter\Type\StatusFilter;
|
||||
use Icinga\Filter\Registry as FilterRegistry;
|
||||
use Icinga\Module\Monitoring\Object\Host;
|
||||
use Icinga\Web\Request;
|
||||
use Zend_Controller_Request_Exception;
|
||||
use Icinga\Web\Url;
|
||||
|
||||
/**
|
||||
* Factory class to create filter for different monitoring objects
|
||||
*
|
||||
*/
|
||||
class Registry implements FilterRegistry
|
||||
{
|
||||
/**
|
||||
* Return a TimeRangeSpecifier for the 'Next Check' query
|
||||
*
|
||||
* @return TimeRangeSpecifier
|
||||
*/
|
||||
public static function getNextCheckFilterType()
|
||||
{
|
||||
$type = new TimeRangeSpecifier();
|
||||
$type->setOperator(
|
||||
array(
|
||||
'Until' => Node::OPERATOR_LESS_EQ,
|
||||
'After' => Node::OPERATOR_GREATER_EQ
|
||||
)
|
||||
)->setForceFutureValue(true);
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a TimeRangeSpecifier for the 'Last Check' query
|
||||
*
|
||||
* @return TimeRangeSpecifier
|
||||
*/
|
||||
public static function getLastCheckFilterType()
|
||||
{
|
||||
$type = new TimeRangeSpecifier();
|
||||
$type->setOperator(
|
||||
array(
|
||||
'Older Than' => Node::OPERATOR_LESS_EQ,
|
||||
'Is Older Than' => Node::OPERATOR_LESS_EQ,
|
||||
'Newer Than' => Node::OPERATOR_GREATER_EQ,
|
||||
'Is Newer Than' => Node::OPERATOR_GREATER_EQ,
|
||||
)
|
||||
)->setForcePastValue(true);
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registry function for the host domain
|
||||
*
|
||||
* @return Domain the domain to use in the filter registry
|
||||
*/
|
||||
public static function hostFilter()
|
||||
{
|
||||
$domain = new Domain('Host');
|
||||
|
||||
$domain->registerAttribute(
|
||||
FilterAttribute::create(new TextFilter())
|
||||
->setHandledAttributes('Name', 'Hostname')
|
||||
->setField('host_name')
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(StatusFilter::createForHost())
|
||||
->setField('host_state')
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(
|
||||
new BooleanFilter(
|
||||
array(
|
||||
'host_is_flapping' => 'Flapping',
|
||||
'host_problem' => 'In Problem State',
|
||||
'host_notifications_enabled' => 'Sending Notifications',
|
||||
'host_active_checks_enabled' => 'Active',
|
||||
'host_passive_checks_enabled' => 'Accepting Passive Checks',
|
||||
'host_handled' => 'Handled',
|
||||
'host_in_downtime' => 'In Downtime',
|
||||
)
|
||||
)
|
||||
)
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(self::getLastCheckFilterType())
|
||||
->setHandledAttributes('Last Check', 'Check')
|
||||
->setField('host_last_check')
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(self::getNextCheckFilterType())
|
||||
->setHandledAttributes('Next Check')
|
||||
->setField('host_next_check')
|
||||
);
|
||||
return $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registry function for the service domain
|
||||
*
|
||||
* @return Domain the domain to use in the filter registry
|
||||
*/
|
||||
public static function serviceFilter()
|
||||
{
|
||||
$domain = new Domain('Service');
|
||||
|
||||
$domain->registerAttribute(
|
||||
FilterAttribute::create(new TextFilter())
|
||||
->setHandledAttributes('Name', 'Servicename')
|
||||
->setField('service_name')
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(StatusFilter::createForService())
|
||||
->setField('service_state')
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(StatusFilter::createForHost())
|
||||
->setHandledAttributes('Host')
|
||||
->setField('host_state')
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(
|
||||
new BooleanFilter(
|
||||
array(
|
||||
'service_is_flapping' => 'Flapping',
|
||||
'service_problem' => 'In Problem State',
|
||||
'service_notifications_enabled' => 'Sending Notifications',
|
||||
'service_active_checks_enabled' => 'Active',
|
||||
'service_passive_checks_enabled' => 'Accepting Passive Checks',
|
||||
'service_handled' => 'Handled',
|
||||
'service_in_downtime' => 'In Downtime',
|
||||
'host_in_downtime' => 'In Host Downtime'
|
||||
)
|
||||
)
|
||||
)
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(self::getLastCheckFilterType())
|
||||
->setHandledAttributes('Last Check', 'Check')
|
||||
->setField('service_last_check')
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(self::getNextCheckFilterType())
|
||||
->setHandledAttributes('Next Check')
|
||||
->setField('service_next_check')
|
||||
);
|
||||
return $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given filter to an url, using the referer as the base url and base filter
|
||||
*
|
||||
* @param $domain The domain to filter for
|
||||
* @param Tree $filter The tree representing the fiter
|
||||
*
|
||||
* @return string An url
|
||||
* @throws Zend_Controller_Request_Exception Called if no referer is available
|
||||
*/
|
||||
public static function getUrlForTarget($domain, Tree $filter)
|
||||
{
|
||||
if (!isset($_SERVER['HTTP_REFERER'])) {
|
||||
throw new Zend_Controller_Request_Exception('You can\'t use this method without an referer');
|
||||
}
|
||||
$request = Icinga::app()->getFrontController()->getRequest();
|
||||
switch ($domain) {
|
||||
case 'host':
|
||||
$view = HostStatus::fromRequest($request);
|
||||
break;
|
||||
case 'service':
|
||||
$view = ServiceStatus::fromRequest($request);
|
||||
break;
|
||||
default:
|
||||
Logger::error('Invalid filter domain requested : %s', $domain);
|
||||
throw new Exception('Unknown Domain ' . $domain);
|
||||
}
|
||||
$urlParser = new UrlViewFilter($view);
|
||||
$lastQuery = parse_url($_SERVER['HTTP_REFERER'], PHP_URL_QUERY);
|
||||
$lastPath = parse_url($_SERVER['HTTP_REFERER'], PHP_URL_PATH);
|
||||
$lastFilter = $urlParser->parseUrl($lastQuery);
|
||||
$lastParameters = array();
|
||||
|
||||
parse_str($lastQuery, $lastParameters);
|
||||
if ($lastFilter->root) {
|
||||
$filter->insert($lastFilter->root);
|
||||
}
|
||||
$params = array();
|
||||
foreach ($lastParameters as $key => $param) {
|
||||
if (!$filter->hasNodeWithAttribute($key)) {
|
||||
$params[$key] = $param;
|
||||
}
|
||||
}
|
||||
|
||||
$baseUrl = Url::fromPath($lastPath, $params);
|
||||
$urlString = $baseUrl->getRelativeUrl();
|
||||
if (stripos($urlString, '?') === false) {
|
||||
$urlString .= '?';
|
||||
} else {
|
||||
$urlString .= '&';
|
||||
}
|
||||
$urlString .= $urlParser->fromTree($filter);
|
||||
return '/' . $urlString;
|
||||
}
|
||||
}
|
@ -0,0 +1,320 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Module\Monitoring\Filter\Type;
|
||||
|
||||
use Icinga\Filter\Query\Node;
|
||||
use Icinga\Filter\Type\FilterType;
|
||||
use Icinga\Filter\Type\TextFilter;
|
||||
use Icinga\Filter\Type\TimeRangeSpecifier;
|
||||
|
||||
/**
|
||||
* Filter type for monitoring states
|
||||
*
|
||||
* It's best to use the StatusFilter::createForHost and StatusFilter::createForService
|
||||
* factory methods as those correctly initialize possible states
|
||||
*
|
||||
*/
|
||||
class StatusFilter extends FilterType
|
||||
{
|
||||
/**
|
||||
* An array containing a mapping of the textual state representation ('Ok', 'Down', etc.)
|
||||
* as the keys and the numeric value mapped by this state as the value
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $baseStates = array();
|
||||
|
||||
/**
|
||||
* An array containing all possible textual operator tokens mapped to the
|
||||
* normalized query operator
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $operators = array(
|
||||
'Is' => Node::OPERATOR_EQUALS,
|
||||
'=' => Node::OPERATOR_EQUALS,
|
||||
'!=' => Node::OPERATOR_EQUALS_NOT,
|
||||
'Is not' => Node::OPERATOR_EQUALS_NOT
|
||||
);
|
||||
|
||||
/**
|
||||
* The type of this filter ('host' or 'service')
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $type = '';
|
||||
|
||||
/**
|
||||
* The timerange subfilter that can be appended to this filter
|
||||
*
|
||||
* @var TimeRangeSpecifier
|
||||
*/
|
||||
private $subFilter;
|
||||
|
||||
/**
|
||||
* Create a new StatusFilter and initialize the internal state correctly.
|
||||
*
|
||||
* It's best to use the factory methods instead of new as a call to
|
||||
* setBaseStates is necessary on direct creation
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->subFilter = new TimeRangeSpecifier();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type for this filter (host or service)
|
||||
*
|
||||
* @param String $type Either 'host' or 'service'
|
||||
*/
|
||||
public function setType($type)
|
||||
{
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a StatusFilter instance that has been initialized for host status filters
|
||||
*
|
||||
* @return StatusFilter The ready-to-use host status filter
|
||||
*/
|
||||
public static function createForHost()
|
||||
{
|
||||
$status = new StatusFilter();
|
||||
$status->setBaseStates(
|
||||
array(
|
||||
'Up' => 0,
|
||||
'Down' => 1,
|
||||
'Unreachable' => 2,
|
||||
'Pending' => 99
|
||||
)
|
||||
);
|
||||
$status->setType('host');
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a StatusFilter instance that has been initialized for service status filters
|
||||
*
|
||||
* @return StatusFilter The ready-to-use service status filter
|
||||
*/
|
||||
public static function createForService()
|
||||
{
|
||||
$status = new StatusFilter();
|
||||
$status->setBaseStates(
|
||||
array(
|
||||
'Ok' => 0,
|
||||
'Warning' => 1,
|
||||
'Critical' => 2,
|
||||
'Unknown' => 3,
|
||||
'Pending' => 99
|
||||
|
||||
)
|
||||
);
|
||||
$status->setType('service');
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return proposals for the given query part
|
||||
*
|
||||
* @param String $query The part of the query that this specifier should parse
|
||||
*
|
||||
* @return array An array containing 0..* proposal text tokens
|
||||
*/
|
||||
public function getProposalsForQuery($query)
|
||||
{
|
||||
if ($query == '') {
|
||||
return $this->getOperators();
|
||||
}
|
||||
$proposals = array();
|
||||
foreach ($this->getOperators() as $operator) {
|
||||
if (stripos($operator, $query) === 0 && strlen($operator) < strlen($query)) {
|
||||
$proposals[] = self::markDifference($operator, $query);
|
||||
} elseif (stripos($query, $operator) === 0) {
|
||||
$subQuery = trim(substr($query, strlen($operator)));
|
||||
$proposals = $this->getValueProposalsForQuery($subQuery);
|
||||
}
|
||||
}
|
||||
return $proposals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array containing all possible states
|
||||
*
|
||||
* @return array An array containing all states mapped by this filter
|
||||
*/
|
||||
private function getAllStates()
|
||||
{
|
||||
return array_keys($this->baseStates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return possible tokens for completing a partial query that already contains an operator
|
||||
*
|
||||
* @param String $query The partial query containing the operator
|
||||
*
|
||||
* @return array An array of strings that reflect possible query completions
|
||||
*/
|
||||
private function getValueProposalsForQuery($query)
|
||||
{
|
||||
if ($query == '') {
|
||||
return $this->getAllStates();
|
||||
}
|
||||
$proposals = array();
|
||||
|
||||
foreach ($this->getAllStates() as $state) {
|
||||
if (self::startsWith($query, $state)) {
|
||||
$subQuery = trim(substr($query, strlen($state)));
|
||||
$proposals = array_merge($proposals, $this->subFilter->getProposalsForQuery($subQuery));
|
||||
} elseif (self::startsWith($state, $query)) {
|
||||
$proposals[] = self::markDifference($state, $query);
|
||||
}
|
||||
}
|
||||
return $proposals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an tuple containing the operator as the first, the value as the second and a possible subquery as the
|
||||
* third element by parsing the given query
|
||||
*
|
||||
* The subquery contains the time information for this status if given
|
||||
*
|
||||
* @param String $query The Query to parse with this filter
|
||||
*
|
||||
* @return array An array with three elements: array(operator, value, subQuery) or filled with nulls
|
||||
* if the query is not valid
|
||||
*/
|
||||
private function getOperatorValueArray($query)
|
||||
{
|
||||
$result = array(null, null, null);
|
||||
foreach ($this->getOperators() as $operator) {
|
||||
if (stripos($query, $operator) === 0) {
|
||||
$result[0] = $operator;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($result[0] === null) {
|
||||
return $result;
|
||||
}
|
||||
$subQuery = trim(substr($query, strlen($result[0])));
|
||||
|
||||
foreach ($this->getAllStates() as $state) {
|
||||
if (self::startsWith($subQuery, $state)) {
|
||||
$result[1] = $state;
|
||||
}
|
||||
}
|
||||
$result[2] = trim(substr($subQuery, strlen($result[1])));
|
||||
if ($result[2] && !$this->subFilter->isValidQuery($result[2])) {
|
||||
return array(null, null, null);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array containing the textual presentation of all possible operators
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOperators()
|
||||
{
|
||||
return array_keys($this->operators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the given query is a valid, complete query
|
||||
*
|
||||
* @param String $query The query to test for being valid and complete
|
||||
*
|
||||
* @return bool True when this query is valid, otherwise false
|
||||
*/
|
||||
public function isValidQuery($query)
|
||||
{
|
||||
$result = $this->getOperatorValueArray($query);
|
||||
return $result[0] !== null && $result[1] !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Tree Node from this filter query
|
||||
*
|
||||
* @param String $query The query to parse and turn into a Node
|
||||
* @param String $leftOperand The field to use for the status
|
||||
*
|
||||
* @return Node A node object to be added to a query tree
|
||||
*/
|
||||
public function createTreeNode($query, $leftOperand)
|
||||
{
|
||||
list($operator, $valueSymbol, $timeSpec) = $this->getOperatorValueArray($query);
|
||||
if ($operator === null || $valueSymbol === null) {
|
||||
return null;
|
||||
}
|
||||
$node = Node::createOperatorNode(
|
||||
$this->operators[$operator],
|
||||
$leftOperand,
|
||||
$this->resolveValue($valueSymbol)
|
||||
);
|
||||
if ($timeSpec) {
|
||||
$left = $node;
|
||||
$node = Node::createAndNode();
|
||||
$node->left = $left;
|
||||
|
||||
$node->right = $this->subFilter->createTreeNode($timeSpec, $this->type . '_last_state_change');
|
||||
$node->right->parent = $node;
|
||||
$node->left->parent = $node;
|
||||
}
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the numeric representation of state given to this filter
|
||||
*
|
||||
* @param String $valueSymbol The state string from the query
|
||||
*
|
||||
* @return int The numeric state mapped by $valueSymbol or null if it's an invalid state
|
||||
*/
|
||||
private function resolveValue($valueSymbol)
|
||||
{
|
||||
if (isset($this->baseStates[$valueSymbol])) {
|
||||
return $this->baseStates[$valueSymbol];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set possible states for this filter
|
||||
*
|
||||
* Only required when this filter isn't created by one of it's factory methods
|
||||
*
|
||||
* @param array $states The states in an associative statename => numeric representation array
|
||||
*/
|
||||
public function setBaseStates(array $states)
|
||||
{
|
||||
$this->baseStates = $states;
|
||||
}
|
||||
}
|
342
modules/monitoring/library/Monitoring/Filter/UrlViewFilter.php
Normal file
342
modules/monitoring/library/Monitoring/Filter/UrlViewFilter.php
Normal file
@ -0,0 +1,342 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Module\Monitoring\Filter;
|
||||
|
||||
use Icinga\Filter\Filterable;
|
||||
use Icinga\Filter\Query\Tree;
|
||||
use Icinga\Filter\Query\Node;
|
||||
use Icinga\Web\Request;
|
||||
use Icinga\Web\Url;
|
||||
use Icinga\Application\Logger;
|
||||
|
||||
/**
|
||||
* Converter class that allows to create Query Trees from an request query and vice versa
|
||||
*/
|
||||
class UrlViewFilter
|
||||
{
|
||||
const FILTER_TARGET = 'target';
|
||||
const FILTER_OPERATOR = 'operator';
|
||||
const FILTER_VALUE = 'value';
|
||||
const FILTER_ERROR = 'error';
|
||||
|
||||
/**
|
||||
* An optional target filterable to use for validation and normalization
|
||||
*
|
||||
* @var Filterable
|
||||
*/
|
||||
private $target;
|
||||
|
||||
/**
|
||||
* Create a new ViewFilter
|
||||
*
|
||||
* @param Filterable $target An optional Filterable to use for validation and normalization
|
||||
*/
|
||||
public function __construct(Filterable $target = null)
|
||||
{
|
||||
$this->target = $target;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return an URL filter string for the given query tree
|
||||
*
|
||||
* @param Tree $filter The query tree to parse
|
||||
* @return null|string The string representation of the query
|
||||
*/
|
||||
public function fromTree(Tree $filter)
|
||||
{
|
||||
if ($filter->root === null) {
|
||||
return '';
|
||||
}
|
||||
if ($this->target) {
|
||||
$filter = $filter->getCopyForFilterable($this->target);
|
||||
}
|
||||
return $this->convertNodeToUrlString($filter->root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given given url and return a query tree
|
||||
*
|
||||
* @param string $query The query to parse, if not given $_SERVER['QUERY_STRING'] is used
|
||||
* @return Tree A tree representing the valid parts of the filter
|
||||
*/
|
||||
public function parseUrl($query = '')
|
||||
{
|
||||
if (!isset($_SERVER['QUERY_STRING'])) {
|
||||
$_SERVER['QUERY_STRING'] = $query;
|
||||
}
|
||||
$query = $query ? $query : $_SERVER['QUERY_STRING'];
|
||||
|
||||
$tokens = $this->tokenizeQuery($query);
|
||||
$tree = new Tree();
|
||||
foreach ($tokens as $token) {
|
||||
if ($token === '&') {
|
||||
$tree->insert(Node::createAndNode());
|
||||
} elseif ($token === '|') {
|
||||
$tree->insert(Node::createOrNode());
|
||||
} elseif (is_array($token)) {
|
||||
$tree->insert(
|
||||
Node::createOperatorNode(
|
||||
$token[self::FILTER_OPERATOR],
|
||||
$token[self::FILTER_TARGET],
|
||||
$token[self::FILTER_VALUE]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return $tree->getCopyForFilterable($this->target);
|
||||
}
|
||||
|
||||
public function fromRequest($request)
|
||||
{
|
||||
if ($request->getParam('query')) {
|
||||
return $this->parseUrl(urldecode($request->getParam('query')));
|
||||
} else {
|
||||
return $this->parseUrl(parse_url($request->getBaseUrl(), PHP_URL_QUERY));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a tree node and it's subnodes to a request string
|
||||
*
|
||||
* @param Node $node The node to convert
|
||||
* @return null|string A string representing the node in the url form or null if it's invalid
|
||||
* ( or if the Filterable doesn't support the attribute)
|
||||
*/
|
||||
private function convertNodeToUrlString(Node $node)
|
||||
{
|
||||
$left = null;
|
||||
$right = null;
|
||||
|
||||
if ($node->type === Node::TYPE_OPERATOR) {
|
||||
if ($this->target && !$this->target->isValidFilterTarget($node->left)) {
|
||||
return null;
|
||||
}
|
||||
return urlencode($node->left) . $node->operator . urlencode($node->right);
|
||||
}
|
||||
if ($node->left) {
|
||||
$left = $this->convertNodeToUrlString($node->left);
|
||||
}
|
||||
if ($node->right) {
|
||||
$right = $this->convertNodeToUrlString($node->right);
|
||||
}
|
||||
|
||||
if ($left && !$right) {
|
||||
return null;
|
||||
} elseif ($right && !$left) {
|
||||
return $this->convertNodeToUrlString($node->right);
|
||||
} elseif (!$left && !$right) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$operator = ($node->type === Node::TYPE_AND) ? '&' : '|';
|
||||
return $left . $operator . $right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the query into seperate tokens that can be parsed seperately
|
||||
*
|
||||
* Tokens are associative arrays in the following form
|
||||
*
|
||||
* array(
|
||||
* self::FILTER_TARGET => 'Attribute',
|
||||
* self::FILTER_OPERATOR => '!=',
|
||||
* self::FILTER_VALUE => 'Value'
|
||||
* )
|
||||
*
|
||||
* @param String $query The query to tokenize
|
||||
* @return array An array of tokens
|
||||
*
|
||||
* @see self::parseTarget() The tokenize function for target=value expressions
|
||||
* @see self::parseValue() The tokenize function that only retrieves a value (e.g. target=value|value2)
|
||||
*/
|
||||
private function tokenizeQuery($query)
|
||||
{
|
||||
$tokens = array();
|
||||
$state = self::FILTER_TARGET;
|
||||
$query = urldecode($query);
|
||||
|
||||
for ($i = 0; $i <= strlen($query); $i++) {
|
||||
switch ($state) {
|
||||
case self::FILTER_TARGET:
|
||||
list($i, $state) = $this->parseTarget($query, $i, $tokens);
|
||||
break;
|
||||
case self::FILTER_VALUE:
|
||||
list($i, $state) = $this->parseValue($query, $i, $tokens);
|
||||
break;
|
||||
case self::FILTER_ERROR:
|
||||
list($i, $state) = $this->skip($query, $i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the operator matching the given query, or an empty string if none matches
|
||||
*
|
||||
* @param String $query The query to extract the operator from
|
||||
* @param integer $i The offset to use in the query string
|
||||
*
|
||||
* @return string The operator string that matches best
|
||||
*/
|
||||
private function getMatchingOperator($query, $i)
|
||||
{
|
||||
$operatorToUse = '';
|
||||
foreach (Node::$operatorList as $operator) {
|
||||
if (substr($query, $i, strlen($operator)) === $operator) {
|
||||
if (strlen($operatorToUse) < strlen($operator)) {
|
||||
$operatorToUse = $operator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $operatorToUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a new expression until the next conjunction or end and return the matching token for it
|
||||
*
|
||||
* @param String $query The query string to create a token from
|
||||
* @param Integer $currentPos The offset to use in the query string
|
||||
* @param array $tokenList The existing token list to add the token to
|
||||
*
|
||||
* @return array A two element array with the new offset in the beginning and the new
|
||||
* parse state as the second parameter
|
||||
*/
|
||||
private function parseTarget($query, $currentPos, array &$tokenList)
|
||||
{
|
||||
$conjunctions = array('&', '|');
|
||||
$i = $currentPos;
|
||||
|
||||
for ($i; $i < strlen($query); $i++) {
|
||||
$currentChar = $query[$i];
|
||||
// test if operator matches
|
||||
$operator = $this->getMatchingOperator($query, $i);
|
||||
|
||||
// Test if we're at an operator field right now, then add the current token
|
||||
// without value to the tokenlist
|
||||
if ($operator !== '') {
|
||||
$tokenList[] = array(
|
||||
self::FILTER_TARGET => substr($query, $currentPos, $i - $currentPos),
|
||||
self::FILTER_OPERATOR => $operator
|
||||
);
|
||||
// -1 because we're currently pointing at the first character of the operator
|
||||
$newOffset = $i + strlen($operator) - 1;
|
||||
return array($newOffset, self::FILTER_VALUE);
|
||||
}
|
||||
|
||||
// Implicit value token (test=1|2)
|
||||
if (in_array($currentChar, $conjunctions) || $i + 1 == strlen($query)) {
|
||||
$nrOfSymbols = count($tokenList);
|
||||
if ($nrOfSymbols <= 2) {
|
||||
return array($i, self::FILTER_TARGET);
|
||||
}
|
||||
|
||||
$lastState = &$tokenList[$nrOfSymbols-2];
|
||||
|
||||
if (is_array($lastState)) {
|
||||
$tokenList[] = array(
|
||||
self::FILTER_TARGET => $lastState[self::FILTER_TARGET],
|
||||
self::FILTER_OPERATOR => $lastState[self::FILTER_OPERATOR],
|
||||
);
|
||||
return $this->parseValue($query, $currentPos, $tokenList);
|
||||
}
|
||||
return array($i, self::FILTER_TARGET);
|
||||
}
|
||||
}
|
||||
|
||||
return array($i, self::FILTER_TARGET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the value part of a query string, starting at current pos
|
||||
*
|
||||
* This expects an token without value to be placed in the tokenList stack
|
||||
*
|
||||
* @param String $query The query string to create a token from
|
||||
* @param Integer $currentPos The offset to use in the query string
|
||||
* @param array $tokenList The existing token list to add the token to
|
||||
*
|
||||
* @return array A two element array with the new offset in the beginning and the new
|
||||
* parse state as the second parameter
|
||||
*/
|
||||
private function parseValue($query, $currentPos, array &$tokenList)
|
||||
{
|
||||
|
||||
$i = $currentPos;
|
||||
$conjunctions = array('&', '|');
|
||||
$nrOfSymbols = count($tokenList);
|
||||
|
||||
if ($nrOfSymbols == 0) {
|
||||
return array($i, self::FILTER_TARGET);
|
||||
}
|
||||
$lastState = &$tokenList[$nrOfSymbols-1];
|
||||
for ($i; $i < strlen($query); $i++) {
|
||||
$currentChar = $query[$i];
|
||||
if (in_array($currentChar, $conjunctions)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$length = $i - $currentPos;
|
||||
// No value given
|
||||
if ($length === 0) {
|
||||
array_pop($tokenList);
|
||||
array_pop($tokenList);
|
||||
return array($currentPos, self::FILTER_TARGET);
|
||||
}
|
||||
$lastState[self::FILTER_VALUE] = substr($query, $currentPos, $length);
|
||||
|
||||
if (in_array($currentChar, $conjunctions)) {
|
||||
$tokenList[] = $currentChar;
|
||||
}
|
||||
return array($i, self::FILTER_TARGET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip a query substring until the next conjunction appears
|
||||
*
|
||||
* @param String $query The query string to skip the next token
|
||||
* @param Integer $currentPos The offset to use in the query string
|
||||
*
|
||||
* @return array A two element array with the new offset in the beginning and the new
|
||||
* parse state as the second parameter
|
||||
*/
|
||||
private function skip($query, $currentPos)
|
||||
{
|
||||
$conjunctions = array('&', '|');
|
||||
for ($i = $currentPos; strlen($query); $i++) {
|
||||
$currentChar = $query[$i];
|
||||
if (in_array($currentChar, $conjunctions)) {
|
||||
return array($i, self::FILTER_TARGET);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,244 +1,188 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
*
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Module\Monitoring\Object;
|
||||
|
||||
use Icinga\Data\AbstractQuery as Query;
|
||||
use Icinga\Data\BaseQuery as Query;
|
||||
use \Icinga\Module\Monitoring\Backend;
|
||||
use Icinga\Module\Monitoring\DataView\Contact;
|
||||
use Icinga\Module\Monitoring\DataView\Contactgroup;
|
||||
use Icinga\Module\Monitoring\DataView\Downtime;
|
||||
use Icinga\Module\Monitoring\DataView\EventHistory;
|
||||
use Icinga\Module\Monitoring\DataView\Hostgroup;
|
||||
use Icinga\Module\Monitoring\DataView\HostStatus;
|
||||
use Icinga\Module\Monitoring\DataView\Comment;
|
||||
use Icinga\Module\Monitoring\DataView\Servicegroup;
|
||||
use Icinga\Module\Monitoring\DataView\ServiceStatus;
|
||||
use Icinga\Web\Request;
|
||||
|
||||
/**
|
||||
* Generic icinga object with belongings
|
||||
*/
|
||||
abstract class AbstractObject
|
||||
{
|
||||
protected $backend;
|
||||
const TYPE_HOST = 1;
|
||||
const TYPE_SERVICE = 2;
|
||||
|
||||
protected $type;
|
||||
public $type = self::TYPE_HOST;
|
||||
public $prefix = 'host_';
|
||||
|
||||
protected $name1;
|
||||
public $comments = array();
|
||||
public $downtimes = array();
|
||||
public $hostgroups = array();
|
||||
public $servicegroups = array();
|
||||
public $contacts = array();
|
||||
public $contactgroups = array();
|
||||
public $customvars = array();
|
||||
public $events = array();
|
||||
|
||||
protected $name2;
|
||||
private $request = null;
|
||||
|
||||
protected $properties;
|
||||
|
||||
protected $foreign = array();
|
||||
|
||||
public function __construct(Backend $backend, $name1, $name2 = null)
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->backend = $backend;
|
||||
$this->name1 = $name1;
|
||||
$this->name2 = $name2;
|
||||
|
||||
if ($name1 && $name2) {
|
||||
$this->type = 2;
|
||||
} elseif ($name1 && !$name2) {
|
||||
$this->type = 1;
|
||||
}
|
||||
|
||||
$this->properties = (array) $this->fetchObject();
|
||||
}
|
||||
|
||||
public static function fetch(Backend $backend, $name1, $name2 = null)
|
||||
{
|
||||
return new static($backend, $name1, $name2);
|
||||
}
|
||||
|
||||
abstract protected function fetchObject();
|
||||
|
||||
public function __isset($key)
|
||||
{
|
||||
return $this->$key !== null;
|
||||
}
|
||||
|
||||
public function __get($key)
|
||||
{
|
||||
if (isset($this->properties[$key])) {
|
||||
return $this->properties[$key];
|
||||
}
|
||||
if (array_key_exists($key, $this->foreign)) {
|
||||
if ($this->foreign[$key] === null) {
|
||||
$func = 'fetch' . ucfirst($key);
|
||||
if (! method_exists($this, $func)) {
|
||||
return null;
|
||||
}
|
||||
$this->$func($key);
|
||||
$this->request = $request;
|
||||
$properties = $this->getProperties();
|
||||
if ($properties) {
|
||||
foreach ($properties as $key => $value) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
return $this->foreign[$key];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function prefetch()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
abstract protected function getProperties();
|
||||
|
||||
abstract protected function applyObjectFilter(Query $query);
|
||||
|
||||
protected function fetchHostgroups()
|
||||
public function fetchComments()
|
||||
{
|
||||
$this->foreign['hostgroups'] = $this->applyObjectFilter(
|
||||
$this->backend->select()->from(
|
||||
'hostgroup',
|
||||
array(
|
||||
'hostgroup_name',
|
||||
'hostgroup_alias'
|
||||
)
|
||||
$this->comments = Comment::fromRequest(
|
||||
$this->request,
|
||||
array(
|
||||
'comment_internal_id',
|
||||
'comment_timestamp',
|
||||
'comment_author',
|
||||
'comment_data',
|
||||
'comment_type',
|
||||
)
|
||||
)->fetchPairs();
|
||||
)->getQuery()
|
||||
->where('comment_objecttype_id', 1)
|
||||
|
||||
->fetchAll();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function fetchServicegroups()
|
||||
public function fetchDowntimes()
|
||||
{
|
||||
$this->foreign['servicegroups'] = $this->applyObjectFilter(
|
||||
$this->backend->select()->from(
|
||||
'servicegroup',
|
||||
array(
|
||||
'servicegroup_name',
|
||||
'servicegroup_alias'
|
||||
)
|
||||
$this->downtimes = Downtime::fromRequest(
|
||||
$this->request,
|
||||
array(
|
||||
'downtime_type',
|
||||
'downtime_author_name',
|
||||
'downtime_comment_data',
|
||||
'downtime_is_fixed',
|
||||
'downtime_duration',
|
||||
'downtime_entry_time',
|
||||
'downtime_scheduled_start_time',
|
||||
'downtime_scheduled_end_time',
|
||||
'downtime_was_started',
|
||||
'downtime_actual_start_time',
|
||||
'downtime_is_in_effect',
|
||||
'downtime_triggered_by_id',
|
||||
)
|
||||
)->fetchPairs();
|
||||
)->getQuery()->fetchAll();
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function fetchContacts()
|
||||
public function fetchHostgroups()
|
||||
{
|
||||
$this->foreign['contacts'] = $this->applyObjectFilter(
|
||||
$this->backend->select()->from(
|
||||
'contact',
|
||||
array(
|
||||
'contact_name',
|
||||
'contact_alias',
|
||||
'contact_email',
|
||||
'contact_pager',
|
||||
)
|
||||
$this->hostgroups = Hostgroup::fromRequest(
|
||||
$this->request,
|
||||
array(
|
||||
'hostgroup_name',
|
||||
'hostgroup_alias'
|
||||
)
|
||||
)->fetchAll();
|
||||
)->getQuery()->fetchPairs();
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function fetchContactgroups()
|
||||
public function fetchContacts()
|
||||
{
|
||||
$this->foreign['contactgroups'] = $this->applyObjectFilter(
|
||||
$this->backend->select()->from(
|
||||
'contactgroup',
|
||||
array(
|
||||
'contactgroup_name',
|
||||
'contactgroup_alias',
|
||||
)
|
||||
$this->contacts = Contact::fromRequest(
|
||||
$this->request,
|
||||
array(
|
||||
'contact_name',
|
||||
'contact_alias',
|
||||
'contact_email',
|
||||
'contact_pager',
|
||||
)
|
||||
)->fetchAll();
|
||||
)->getQuery()
|
||||
->where('host_name', $this->host_name)
|
||||
->fetchAll();
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function fetchComments()
|
||||
public function fetchServicegroups()
|
||||
{
|
||||
$this->foreign['comments'] = $this->applyObjectFilter(
|
||||
$this->backend->select()->from(
|
||||
'comment',
|
||||
array(
|
||||
'comment_timestamp',
|
||||
'comment_author',
|
||||
'comment_data',
|
||||
'comment_type',
|
||||
'comment_internal_id'
|
||||
)
|
||||
)->where('comment_objecttype_id', $this->type)
|
||||
)->fetchAll();
|
||||
$this->servicegroups = Servicegroup::fromRequest(
|
||||
$this->request,
|
||||
array(
|
||||
'servicegroup_name',
|
||||
'servicegroup_alias',
|
||||
)
|
||||
)->getQuery()->fetchPairs();
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function fetchCustomvars()
|
||||
public function fetchContactgroups()
|
||||
{
|
||||
$this->foreign['customvars'] = $this->applyObjectFilter(
|
||||
$this->backend->select()->from(
|
||||
'customvar',
|
||||
array(
|
||||
'varname',
|
||||
'varvalue'
|
||||
)
|
||||
)->where('varname', '-*PW*,-*PASS*,-*COMMUNITY*')
|
||||
)->fetchPairs();
|
||||
$this->contactgroups = Contactgroup::fromRequest(
|
||||
$this->request,
|
||||
array(
|
||||
'contactgroup_name',
|
||||
'contactgroup_alias'
|
||||
)
|
||||
)->getQuery()->fetchAll();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fetchEventHistory()
|
||||
{
|
||||
$this->foreign['eventHistory'] = $this->applyObjectFilter(
|
||||
$this->backend->select()->from(
|
||||
'eventHistory',
|
||||
array(
|
||||
'object_type',
|
||||
'host_name',
|
||||
'service_description',
|
||||
'timestamp',
|
||||
'state',
|
||||
'attempt',
|
||||
'max_attempts',
|
||||
'output',
|
||||
'type'
|
||||
)
|
||||
$this->eventhistory = EventHistory::fromRequest(
|
||||
$this->request,
|
||||
array(
|
||||
'object_type',
|
||||
'host_name',
|
||||
'service_description',
|
||||
'timestamp',
|
||||
'raw_timestamp',
|
||||
'state',
|
||||
'attempt',
|
||||
'max_attempts',
|
||||
'output',
|
||||
'type'
|
||||
)
|
||||
);
|
||||
)->sort('timestamp', 'DESC')->getQuery();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fetchDowtimes()
|
||||
public function __get($param)
|
||||
{
|
||||
$this->foreign['downtimes'] = $this->applyObjectFilter(
|
||||
$this->backend->select()->from(
|
||||
'downtime',
|
||||
array(
|
||||
'host_name',
|
||||
'object_type',
|
||||
'service_host_name',
|
||||
'service_description',
|
||||
'downtime_type',
|
||||
'downtime_author_name',
|
||||
'downtime_comment_data',
|
||||
'downtime_is_fixed',
|
||||
'downtime_duration',
|
||||
'downtime_entry_time',
|
||||
'downtime_scheduled_start_time',
|
||||
'downtime_scheduled_end_time',
|
||||
'downtime_was_started',
|
||||
'downtime_actual_start_time',
|
||||
'downtime_actual_start_time_usec',
|
||||
'downtime_is_in_effect',
|
||||
'downtime_trigger_time',
|
||||
'downtime_triggered_by_id',
|
||||
'downtime_internal_downtime_id'
|
||||
)
|
||||
)
|
||||
)->fetchAll(9);
|
||||
return $this;
|
||||
if (substr($param, 0, strlen($this->prefix)) === $this->prefix) {
|
||||
return false;
|
||||
}
|
||||
$expandedName = $this->prefix . strtolower($param);
|
||||
return $this->$expandedName;
|
||||
}
|
||||
|
||||
public function getRequest()
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
abstract public function populate();
|
||||
|
||||
public static function fromRequest(Request $request)
|
||||
{
|
||||
if ($request->has('service') && $request->has('host')) {
|
||||
return new Service($request);
|
||||
} elseif ($request->has('host')) {
|
||||
return new Host($request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,133 +1,30 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
*
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Module\Monitoring\Object;
|
||||
|
||||
use Icinga\Data\AbstractQuery as Query;
|
||||
use Icinga\Data\BaseQuery as Query;
|
||||
use Icinga\Module\Monitoring\DataView\HostStatus;
|
||||
|
||||
/**
|
||||
* Represent a host object
|
||||
*/
|
||||
class Host extends AbstractObject
|
||||
{
|
||||
protected $foreign = array(
|
||||
'hostgroups' => null,
|
||||
'contacts' => null,
|
||||
'contactgroups' => null,
|
||||
'customvars' => null,
|
||||
'comments' => null,
|
||||
'downtimes' => null,
|
||||
'customvars' => null
|
||||
);
|
||||
|
||||
/**
|
||||
* Statename
|
||||
*/
|
||||
public function stateName()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
public $type = self::TYPE_HOST;
|
||||
public $prefix = 'host_';
|
||||
private $view = null;
|
||||
|
||||
/**
|
||||
* Filter object belongings
|
||||
*
|
||||
* @param Query $query
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
protected function applyObjectFilter(Query $query)
|
||||
{
|
||||
return $query->where('host_name', $this->name1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load foreign object data
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function prefetch()
|
||||
public function populate()
|
||||
{
|
||||
return $this->fetchHostgroups()
|
||||
$this->fetchComments()
|
||||
->fetchDowntimes()
|
||||
->fetchHostgroups()
|
||||
->fetchContacts()
|
||||
->fetchContactgroups()
|
||||
->fetchCustomvars()
|
||||
->fetchComments()
|
||||
->fetchDowtimes()
|
||||
->fetchCustomvars();
|
||||
->fetchContactGroups();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load object data
|
||||
* @return object
|
||||
*/
|
||||
protected function fetchObject()
|
||||
protected function getProperties()
|
||||
{
|
||||
return $this->backend->select()->from(
|
||||
'status',
|
||||
array(
|
||||
'host_name',
|
||||
'host_alias',
|
||||
'host_address',
|
||||
'host_state',
|
||||
'host_handled',
|
||||
'host_in_downtime',
|
||||
'in_downtime' => 'host_in_downtime',
|
||||
'host_acknowledged',
|
||||
'host_last_state_change',
|
||||
'last_state_change' => 'host_last_state_change',
|
||||
'last_notification' => 'host_last_notification',
|
||||
'last_check' => 'host_last_check',
|
||||
'next_check' => 'host_next_check',
|
||||
'check_execution_time' => 'host_check_execution_time',
|
||||
'check_latency' => 'host_check_latency',
|
||||
'output' => 'host_output',
|
||||
'long_output' => 'host_long_output',
|
||||
'check_command' => 'host_check_command',
|
||||
'perfdata' => 'host_perfdata',
|
||||
'host_icon_image',
|
||||
'passive_checks_enabled' => 'host_passive_checks_enabled',
|
||||
'obsessing' => 'host_obsessing',
|
||||
'notifications_enabled' => 'host_notifications_enabled',
|
||||
'event_handler_enabled' => 'host_event_handler_enabled',
|
||||
'flap_detection_enabled' => 'host_flap_detection_enabled',
|
||||
'active_checks_enabled' => 'host_active_checks_enabled',
|
||||
'current_check_attempt' => 'host_current_check_attempt',
|
||||
'max_check_attempts' => 'host_max_check_attempts',
|
||||
'last_notification' => 'host_last_notification',
|
||||
'current_notification_number' => 'host_current_notification_number',
|
||||
'percent_state_change' => 'host_percent_state_change',
|
||||
'is_flapping' => 'host_is_flapping',
|
||||
'last_comment' => 'host_last_comment',
|
||||
'action_url' => 'host_action_url',
|
||||
'notes_url' => 'host_notes_url',
|
||||
'percent_state_change' => 'host_percent_state_change'
|
||||
)
|
||||
)->where('host_name', $this->name1)->fetchRow();
|
||||
$this->view = HostStatus::fromRequest($this->getRequest());
|
||||
return $this->view->getQuery()->fetchRow();
|
||||
}
|
||||
}
|
||||
|
@ -1,148 +1,30 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
*
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Module\Monitoring\Object;
|
||||
|
||||
use Icinga\Data\AbstractQuery as Query;
|
||||
use Icinga\Data\BaseQuery as Query;
|
||||
use Icinga\Module\Monitoring\DataView\ServiceStatus;
|
||||
|
||||
/**
|
||||
* Represent a single service
|
||||
*/
|
||||
class Service extends AbstractObject
|
||||
{
|
||||
/**
|
||||
* Foreign references to objects
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $foreign = array(
|
||||
'servicegroups' => null,
|
||||
'contacts' => null,
|
||||
'contactgroups' => null,
|
||||
'customvars' => null,
|
||||
'comments' => null,
|
||||
'downtimes' => null,
|
||||
'customvars' => null
|
||||
);
|
||||
|
||||
/**
|
||||
* Statename
|
||||
*/
|
||||
public function stateName()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
public $type = self::TYPE_SERVICE;
|
||||
public $prefix = 'service_';
|
||||
private $view = null;
|
||||
|
||||
/**
|
||||
* Filter foreign object belongings
|
||||
*
|
||||
* @param Query $query
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
protected function applyObjectFilter(Query $query)
|
||||
public function populate()
|
||||
{
|
||||
return $query->where('service_host_name', $this->name1)
|
||||
->where('service_description', $this->name2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect foreign data
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function prefetch()
|
||||
{
|
||||
return $this->fetchServicegroups()
|
||||
$this->fetchComments()
|
||||
->fetchDowntimes()
|
||||
->fetchHostgroups()
|
||||
->fetchServicegroups()
|
||||
->fetchContacts()
|
||||
->fetchContactgroups()
|
||||
->fetchCustomvars()
|
||||
->fetchComments()
|
||||
->fetchDowtimes()
|
||||
->fetchCustomvars();
|
||||
->fetchContactGroups();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load object data
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
protected function fetchObject()
|
||||
protected function getProperties()
|
||||
{
|
||||
return $this->backend->select()->from(
|
||||
'status',
|
||||
array(
|
||||
'host_name',
|
||||
'host_alias',
|
||||
'host_address',
|
||||
'host_state',
|
||||
'host_handled',
|
||||
'host_in_downtime',
|
||||
'host_acknowledged',
|
||||
'host_last_state_change',
|
||||
'service_description',
|
||||
'service_state',
|
||||
'service_handled',
|
||||
'service_acknowledged',
|
||||
'service_in_downtime',
|
||||
'service_last_state_change',
|
||||
'last_check' => 'service_last_check',
|
||||
'next_check' => 'service_next_check',
|
||||
'check_execution_time' => 'service_check_execution_time',
|
||||
'check_latency' => 'service_check_latency',
|
||||
'output' => 'service_output',
|
||||
'long_output' => 'service_long_output',
|
||||
'check_command' => 'service_check_command',
|
||||
'perfdata' => 'service_perfdata',
|
||||
'current_check_attempt' => 'service_current_check_attempt',
|
||||
'max_check_attempts' => 'service_max_check_attempts',
|
||||
'state_type' => 'service_state_type',
|
||||
'passive_checks_enabled' => 'service_passive_checks_enabled',
|
||||
'last_state_change' => 'service_last_state_change',
|
||||
'last_notification' => 'service_last_notification',
|
||||
'current_notification_number' => 'service_current_notification_number',
|
||||
'is_flapping' => 'service_is_flapping',
|
||||
'percent_state_change' => 'service_percent_state_change',
|
||||
'in_downtime' => 'service_in_downtime',
|
||||
'passive_checks_enabled' => 'service_passive_checks_enabled',
|
||||
'obsessing' => 'service_obsessing',
|
||||
'notifications_enabled' => 'service_notifications_enabled',
|
||||
'event_handler_enabled' => 'service_event_handler_enabled',
|
||||
'flap_detection_enabled' => 'service_flap_detection_enabled',
|
||||
'active_checks_enabled' => 'service_active_checks_enabled',
|
||||
'last_comment' => 'service_last_comment',
|
||||
'action_url' => 'service_action_url',
|
||||
'notes_url' => 'service_notes_url'
|
||||
)
|
||||
)
|
||||
->where('host_name', $this->name1)
|
||||
->where('service_description', $this->name2)
|
||||
->fetchRow();
|
||||
$this->view = ServiceStatus::fromRequest($this->getRequest());
|
||||
return $this->view->getQuery()->fetchRow();
|
||||
}
|
||||
}
|
||||
|
@ -1,339 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Module\Monitoring\View;
|
||||
|
||||
use Icinga\Data\AbstractQuery;
|
||||
use Icinga\Data\Filter;
|
||||
|
||||
/**
|
||||
* AbstractView provides consistent views to our Icinga Backends
|
||||
*
|
||||
* TODO: * This could be renamed to AbstractView
|
||||
* * We might need more complex filters (let's do the simple ones first)
|
||||
*
|
||||
* You should not directly instantiate such a view but always go through the
|
||||
* Monitoring Backend. Using the Backend's select() method selecting from
|
||||
* 'virtualtable' returns a Icinga\Module\Monitoring\View\VirtualtableView instance.
|
||||
*
|
||||
* Usage example:
|
||||
* <code>
|
||||
* use \Icinga\Module\Monitoring\Backend;
|
||||
* $backend = Backend::getInstance();
|
||||
* $query = $backend->select()->from('viewname', array(
|
||||
* 'one_column',
|
||||
* 'another_column',
|
||||
* 'alias' => 'whatever_column',
|
||||
* ))->where('any_column', $search);
|
||||
*
|
||||
* print_r($query->fetchAll());
|
||||
* </code>
|
||||
*
|
||||
* What we see in the example is that:
|
||||
* * you can (and should) use a defined set of columns when issueing a query
|
||||
* * you can use proper alias names to have an influence on column names
|
||||
* in the result set
|
||||
* * the AbstractView behaves like any Query object and provides useful
|
||||
* methods such as fetchAll, count, fetchPairs and so on
|
||||
*
|
||||
* If you want to fill a dropdown form element with all your hostgroups
|
||||
* starting with "net", using the hostgroup name as the form elements value but
|
||||
* showing the hostgroup aliases in the dropdown you would probably do this as
|
||||
* follows:
|
||||
*
|
||||
* <code>
|
||||
* $pairs = $backend->select->from(
|
||||
* 'hostgroups',
|
||||
* array('hostgroup_name', 'hostgroup_alias')
|
||||
* )->where('hostgroup_name', 'net*')->fetchPairs();
|
||||
* $formElement->setMultiOptions($pairs);
|
||||
* </code>
|
||||
*
|
||||
* AbstractView is a proxy to your Backend Query. While both are Query objects
|
||||
* providing partially the same interface, they are not directly related to
|
||||
* each other.
|
||||
*/
|
||||
class AbstractView extends AbstractQuery
|
||||
{
|
||||
/**
|
||||
* Stores the backend-specific Query Object
|
||||
* @var AbstractQuery
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* All the columns provided by this View MUST be specified here
|
||||
* @var Array
|
||||
*/
|
||||
protected $availableColumns = array();
|
||||
|
||||
/**
|
||||
* Columns available for search only but not in result sets
|
||||
* @var Array
|
||||
*/
|
||||
protected $specialFilters = array();
|
||||
|
||||
/**
|
||||
* All views COULD have a generic column called 'search', if available the
|
||||
* real column name is defined here.
|
||||
* TODO: This may be subject to change as a "search" could involve multiple
|
||||
* columns
|
||||
* @var string
|
||||
*/
|
||||
protected $searchColumn;
|
||||
|
||||
/**
|
||||
* Defines default sorting rules for specific column names. This helps in
|
||||
* providing "intelligent" sort defaults for different columns (involving
|
||||
* also other columns where needed)
|
||||
* @var Array
|
||||
*/
|
||||
protected $sortDefaults = array();
|
||||
|
||||
/**
|
||||
* Filters that will be enforced (e.g. AND for SQL), will be used for auth
|
||||
* restrictions
|
||||
*
|
||||
* TODO: rename to enforcedFilters?
|
||||
*
|
||||
* @var Array
|
||||
*/
|
||||
protected $forcedFilters = array();
|
||||
|
||||
/**
|
||||
* Whether this view provides a specific column name
|
||||
*
|
||||
* @param string $column Column name
|
||||
* @return bool
|
||||
*/
|
||||
public function hasColumn($column)
|
||||
{
|
||||
return in_array($column, $this->availableColumns);
|
||||
}
|
||||
|
||||
// TODO: Not sure about this yet
|
||||
public function getDefaultColumns()
|
||||
{
|
||||
return $this->availableColumns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all available column names
|
||||
*
|
||||
* This might be useful for dynamic frontend tables or similar
|
||||
*
|
||||
* @return Array
|
||||
*/
|
||||
public function getAvailableColumns()
|
||||
{
|
||||
return $this->availableColumns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract and apply filters and sort rules from a given request object
|
||||
*
|
||||
* TODO: Enforce Icinga\Web\Request (or similar) as soon as we replaced
|
||||
* Zend_Controller_Request
|
||||
*
|
||||
* @param mixed $request The request object
|
||||
* @return self
|
||||
*/
|
||||
public function applyRequest($request)
|
||||
{
|
||||
return $this->applyRequestFilters($request)
|
||||
->applyRequestSorting($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract and apply sort column and directon from given request object
|
||||
*
|
||||
* @param mixed $request The request object
|
||||
* @return self
|
||||
*/
|
||||
protected function applyRequestSorting($request)
|
||||
{
|
||||
|
||||
return $this->order(
|
||||
// TODO: Use first sortDefaults entry if available, fall back to
|
||||
// column if not
|
||||
$request->getParam('sort', $this->getDefaultSortColumn()),
|
||||
$request->getParam('dir')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract and apply filters from a given request object
|
||||
*
|
||||
* Columns not fitting any defined available column or special filter column
|
||||
* will be silently ignored.
|
||||
*
|
||||
* @param mixed $request The request object
|
||||
* @return self
|
||||
*/
|
||||
protected function applyRequestFilters($request)
|
||||
{
|
||||
foreach ($request->getParams() as $key => $value) {
|
||||
if ($key === 'search' && $value !== '') {
|
||||
if (strpos($value, '=') === false) {
|
||||
if ($this->searchColumn !== null) {
|
||||
$this->where($this->searchColumn, $value);
|
||||
}
|
||||
} else {
|
||||
list($k, $v) = preg_split('~\s*=\s*~', $value, 2);
|
||||
if ($this->isValidFilterColumn($k)) {
|
||||
$this->where($k, $v);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ($this->isValidFilterColumn($key)) {
|
||||
$this->where($key, $value);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
// TODO: applyAuthFilters(Auth $auth = null)
|
||||
// AbstractView will enforce restrictions as provided by the Auth
|
||||
// backend. Those filters work like normal ones and have to be stored
|
||||
// by applyAuthFilters to $this->forcedFilters
|
||||
|
||||
/**
|
||||
* Apply an array of filters. This might become obsolete or even improved
|
||||
* and accept Filter objects - this is still to be defined.
|
||||
*
|
||||
* @param Array $filters Filter array
|
||||
* @return self
|
||||
*/
|
||||
public function applyFilters($filters)
|
||||
{
|
||||
foreach ($filters as $col => $filter) {
|
||||
$this->where($col, $filter);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives you a filter object with all applied filters excluding auth filters
|
||||
* Might be used to build URLs fitting query objects.
|
||||
*
|
||||
* Please take care, as Url has been improved the Filter object might
|
||||
* become subject to change
|
||||
*
|
||||
* @return Filter
|
||||
*/
|
||||
public function getAppliedFilter()
|
||||
{
|
||||
return new Filter($this->filters);
|
||||
}
|
||||
|
||||
public function hasSingleSortColumn()
|
||||
{
|
||||
return count($this->order_columns) === 1;
|
||||
}
|
||||
|
||||
public function getFirstSortColumn()
|
||||
{
|
||||
return $this->order_columns[0];
|
||||
}
|
||||
|
||||
protected function getDefaultSortColumn()
|
||||
{
|
||||
if (empty($this->sortDefaults)) {
|
||||
return $this->availableColumns[0];
|
||||
} else {
|
||||
reset($this->sortDefaults);
|
||||
return key($this->sortDefaults);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default sort direction for given column, ASCending if not defined
|
||||
*
|
||||
* @param String $col Column name
|
||||
* @return int
|
||||
*/
|
||||
protected function getDefaultSortDir($col)
|
||||
{
|
||||
if (isset($this->sortDefaults[$col]['default_dir'])) {
|
||||
return $this->sortDefaults[$col]['default_dir'];
|
||||
}
|
||||
return self::SORT_ASC;
|
||||
}
|
||||
|
||||
/**
|
||||
* getQuery gives you an instance of the Query object implementing this
|
||||
* view for the chosen backend.
|
||||
*
|
||||
* @return AbstractQuery
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
|
||||
if ($this->query === null) {
|
||||
$classParts = preg_split('|\\\|', get_class($this));
|
||||
$class = substr(
|
||||
array_pop($classParts),
|
||||
0,
|
||||
-4
|
||||
) . 'Query';
|
||||
$class = '\\' . get_class($this->ds) . '\\Query\\' . $class;
|
||||
$query = new $class($this->ds, $this->columns);
|
||||
foreach ($this->filters as $f) {
|
||||
$query->where($f[0], $f[1]);
|
||||
}
|
||||
foreach ($this->forcedFilters as $col => $filter) {
|
||||
$query->where($col, $filter);
|
||||
}
|
||||
foreach ($this->order_columns as $col) {
|
||||
|
||||
if (isset($this->sortDefaults[$col[0]]['columns'])) {
|
||||
foreach ($this->sortDefaults[$col[0]]['columns'] as $c) {
|
||||
$query->order($c, $col[1]);
|
||||
}
|
||||
} else {
|
||||
$query->order($col[0], $col[1]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->query = $query;
|
||||
}
|
||||
if ($this->hasLimit()) {
|
||||
$this->query->limit($this->getLimit(), $this->getOffset());
|
||||
}
|
||||
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
public function count()
|
||||
{
|
||||
return $this->getQuery()->count();
|
||||
}
|
||||
|
||||
public function fetchAll()
|
||||
{
|
||||
return $this->getQuery()->fetchAll();
|
||||
}
|
||||
|
||||
public function fetchRow()
|
||||
{
|
||||
return $this->getQuery()->fetchRow();
|
||||
}
|
||||
|
||||
public function fetchColumn()
|
||||
{
|
||||
return $this->getQuery()->fetchColumn();
|
||||
}
|
||||
|
||||
public function fetchPairs()
|
||||
{
|
||||
return $this->getQuery()->fetchPairs();
|
||||
}
|
||||
|
||||
public function isValidFilterColumn($column)
|
||||
{
|
||||
if (in_array($column, $this->specialFilters)) {
|
||||
return true;
|
||||
}
|
||||
return in_array($column, $this->availableColumns);
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Module\Monitoring\View;
|
||||
|
||||
class AllcontactsView extends AbstractView
|
||||
{
|
||||
protected $query;
|
||||
|
||||
protected $availableColumns = array(
|
||||
'host_name',
|
||||
'contact_name',
|
||||
'contact_alias',
|
||||
'contact_email',
|
||||
'contact_pager',
|
||||
'contact_notify_hosts',
|
||||
'contact_notify_services',
|
||||
'contact_has_host_notfications',
|
||||
'contact_has_service_notfications',
|
||||
'contact_can_submit_commands',
|
||||
'contact_notify_service_recovery',
|
||||
'contact_notify_service_warning',
|
||||
'contact_notify_service_critical',
|
||||
'contact_notify_service_unknown',
|
||||
'contact_notify_service_flapping',
|
||||
'contact_notify_service_downtime',
|
||||
'contact_notify_host_recovery',
|
||||
'contact_notify_host_down',
|
||||
'contact_notify_host_unreachable',
|
||||
'contact_notify_host_flapping',
|
||||
'contact_notify_host_downtime',
|
||||
'host_object_id',
|
||||
'host_name',
|
||||
'service_object_id',
|
||||
'service_host_name',
|
||||
'service_description',
|
||||
/* 'contact_alias',
|
||||
'contact_email',
|
||||
'contact_pager',
|
||||
'contact_notify_hosts',
|
||||
'contact_notify_services',
|
||||
*/
|
||||
);
|
||||
|
||||
/*
|
||||
protected $sortDefaults = array(
|
||||
'contact_alias' => array(
|
||||
'default_dir' => self::SORT_ASC
|
||||
)
|
||||
);*/
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Module\Monitoring\View;
|
||||
|
||||
class ContactView extends AbstractView
|
||||
{
|
||||
protected $query;
|
||||
|
||||
protected $availableColumns = array(
|
||||
'contact_name',
|
||||
'contact_alias',
|
||||
'contact_email',
|
||||
'contact_pager',
|
||||
'contact_notify_hosts',
|
||||
'contact_notify_services',
|
||||
'contact_has_host_notfications',
|
||||
'contact_has_service_notfications',
|
||||
'contact_can_submit_commands',
|
||||
'contact_notify_service_recovery',
|
||||
'contact_notify_service_warning',
|
||||
'contact_notify_service_critical',
|
||||
'contact_notify_service_unknown',
|
||||
'contact_notify_service_flapping',
|
||||
'contact_notify_service_downtime',
|
||||
'contact_notify_host_recovery',
|
||||
'contact_notify_host_down',
|
||||
'contact_notify_host_unreachable',
|
||||
'contact_notify_host_flapping',
|
||||
'contact_notify_host_downtime',
|
||||
'host_object_id',
|
||||
'host_name',
|
||||
'service_object_id',
|
||||
'service_host_name',
|
||||
'service_description',
|
||||
);
|
||||
|
||||
protected $specialFilters = array();
|
||||
|
||||
protected $sortDefaults = array(
|
||||
'contact_alias' => array(
|
||||
'default_dir' => self::SORT_ASC
|
||||
)
|
||||
);
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Module\Monitoring\View;
|
||||
|
||||
class ContactgroupView extends AbstractView
|
||||
{
|
||||
protected $query;
|
||||
|
||||
protected $availableColumns = array(
|
||||
'contactgroup_name',
|
||||
'contactgroup_alias',
|
||||
|
||||
'contact_name',
|
||||
'contact_alias',
|
||||
'contact_email',
|
||||
'contact_pager',
|
||||
'contact_notify_hosts',
|
||||
'contact_notify_services',
|
||||
'contact_has_host_notfications',
|
||||
'contact_has_service_notfications',
|
||||
'contact_can_submit_commands',
|
||||
'contact_notify_service_recovery',
|
||||
'contact_notify_service_warning',
|
||||
'contact_notify_service_critical',
|
||||
'contact_notify_service_unknown',
|
||||
'contact_notify_service_flapping',
|
||||
'contact_notify_service_downtime',
|
||||
'contact_notify_host_recovery',
|
||||
'contact_notify_host_down',
|
||||
'contact_notify_host_unreachable',
|
||||
'contact_notify_host_flapping',
|
||||
'contact_notify_host_downtime',
|
||||
|
||||
|
||||
'host_object_id',
|
||||
'host_name',
|
||||
'service_description'
|
||||
);
|
||||
|
||||
protected $specialFilters = array();
|
||||
|
||||
protected $sortDefaults = array(
|
||||
'contactgroup_alias' => array(
|
||||
'default_dir' => self::SORT_ASC
|
||||
)
|
||||
);
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Module\Monitoring\View;
|
||||
|
||||
class CustomvarView extends AbstractView
|
||||
{
|
||||
protected $query;
|
||||
|
||||
protected $availableColumns = array(
|
||||
'varname',
|
||||
'varvalue',
|
||||
'object_type',
|
||||
);
|
||||
|
||||
protected $specialFilters = array();
|
||||
|
||||
protected $sortDefaults = array(
|
||||
'varname' => array(
|
||||
'varname' => self::SORT_ASC,
|
||||
'varvalue' => self::SORT_ASC,
|
||||
)
|
||||
);
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* Icinga 2 Web - Head for multiple monitoring frontends
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
namespace Icinga\Module\Monitoring\View;
|
||||
|
||||
/**
|
||||
* Class DowntimeView
|
||||
*/
|
||||
class DowntimeView extends AbstractView
|
||||
{
|
||||
/**
|
||||
* Query object
|
||||
* @var mixed
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* Available columns
|
||||
* @var string[]
|
||||
*/
|
||||
protected $availableColumns = array(
|
||||
'host_name',
|
||||
'object_type',
|
||||
'service_host_name',
|
||||
'service_description',
|
||||
'downtime_type',
|
||||
'downtime_author_name',
|
||||
'downtime_comment_data',
|
||||
'downtime_is_fixed',
|
||||
'downtime_duration',
|
||||
'downtime_entry_time',
|
||||
'downtime_scheduled_start_time',
|
||||
'downtime_scheduled_end_time',
|
||||
'downtime_was_started',
|
||||
'downtime_actual_start_time',
|
||||
'downtime_actual_start_time_usec',
|
||||
'downtime_is_in_effect',
|
||||
'downtime_trigger_time',
|
||||
'downtime_triggered_by_id',
|
||||
'downtime_internal_downtime_id'
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters
|
||||
* @var array
|
||||
*/
|
||||
protected $specialFilters = array();
|
||||
|
||||
/**
|
||||
* Default sorting of data set
|
||||
* @var array
|
||||
*/
|
||||
protected $sortDefaults = array(
|
||||
'downtime_is_in_effect' => array(
|
||||
'default_dir' => self::SORT_DESC
|
||||
),
|
||||
'downtime_actual_start_time' => array(
|
||||
'default_dir' => self::SORT_DESC
|
||||
)
|
||||
);
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Module\Monitoring\View;
|
||||
|
||||
class EventHistoryView extends AbstractView
|
||||
{
|
||||
protected $query;
|
||||
|
||||
protected $availableColumns = array(
|
||||
'raw_timestamp',
|
||||
'timestamp',
|
||||
'host',
|
||||
'service',
|
||||
'host_name',
|
||||
'state',
|
||||
'last_state',
|
||||
'last_hard_state',
|
||||
'attempt',
|
||||
'max_attempts',
|
||||
'output',
|
||||
'type'
|
||||
);
|
||||
|
||||
protected $specialFilters = array(
|
||||
'hostgroup',
|
||||
);
|
||||
|
||||
protected $sortDefaults = array(
|
||||
'raw_timestamp' => array(
|
||||
'default_dir' => self::SORT_DESC
|
||||
),
|
||||
'timestamp' => array(
|
||||
'default_dir' => self::SORT_DESC
|
||||
),
|
||||
);
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Module\Monitoring\View;
|
||||
|
||||
class HostgroupView extends AbstractView
|
||||
{
|
||||
protected $query;
|
||||
|
||||
protected $availableColumns = array(
|
||||
'hostgroups',
|
||||
'hostgroup_name',
|
||||
'hostgroup_alias',
|
||||
);
|
||||
|
||||
protected $specialFilters = array();
|
||||
|
||||
protected $sortDefaults = array(
|
||||
'hostgroup_alias' => array(
|
||||
'default_dir' => self::SORT_ASC
|
||||
)
|
||||
);
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Module\Monitoring\View;
|
||||
|
||||
class HoststatusView extends AbstractView
|
||||
{
|
||||
protected $query;
|
||||
protected $searchColumn = 'host';
|
||||
|
||||
protected $availableColumns = array(
|
||||
// Hosts
|
||||
'host',
|
||||
'host_name',
|
||||
'host_display_name',
|
||||
'host_alias',
|
||||
'host_address',
|
||||
'host_ipv4',
|
||||
'host_icon_image',
|
||||
|
||||
// Hoststatus
|
||||
'host_state',
|
||||
'host_problem',
|
||||
'host_severity',
|
||||
'host_state_type',
|
||||
'host_output',
|
||||
'host_long_output',
|
||||
'host_perfdata',
|
||||
'host_acknowledged',
|
||||
'host_in_downtime',
|
||||
'host_handled',
|
||||
'host_does_active_checks',
|
||||
'host_accepts_passive_checks',
|
||||
'host_last_state_change',
|
||||
'host_last_hard_state',
|
||||
'host_last_hard_state_change',
|
||||
'host_notifications_enabled',
|
||||
'host_last_time_up',
|
||||
'host_last_time_down',
|
||||
'host_last_time_unreachable',
|
||||
'host_current_check_attempt',
|
||||
'host_max_check_attempts',
|
||||
|
||||
// Services
|
||||
'services_cnt',
|
||||
'services_problem',
|
||||
'services_problem_handled',
|
||||
'services_problem_unhandled',
|
||||
);
|
||||
|
||||
protected $specialFilters = array(
|
||||
'hostgroup',
|
||||
'servicegroup',
|
||||
'contact',
|
||||
'contactgroup',
|
||||
);
|
||||
|
||||
protected $sortDefaults = array(
|
||||
'host_name' => array(
|
||||
'columns' => array(
|
||||
'host_name',
|
||||
),
|
||||
'default_dir' => self::SORT_ASC
|
||||
),
|
||||
'host_address' => array(
|
||||
'columns' => array(
|
||||
'host_ipv4',
|
||||
'service_description'
|
||||
),
|
||||
'default_dir' => self::SORT_ASC
|
||||
),
|
||||
'host_last_state_change' => array(
|
||||
'default_dir' => self::SORT_DESC
|
||||
),
|
||||
'host_severity' => array(
|
||||
'columns' => array(
|
||||
'host_severity',
|
||||
'host_last_state_change',
|
||||
),
|
||||
'default_dir' => self::SORT_DESC
|
||||
)
|
||||
);
|
||||
|
||||
public function isValidFilterColumn($column)
|
||||
{
|
||||
if ($column[0] === '_'
|
||||
&& preg_match('~^_host~', $column)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return parent::isValidFilterColumn($column);
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Module\Monitoring\View;
|
||||
|
||||
class ServicegroupView extends AbstractView
|
||||
{
|
||||
protected $query;
|
||||
|
||||
protected $availableColumns = array(
|
||||
'servicegroup_name',
|
||||
'servicegroup_alias',
|
||||
);
|
||||
|
||||
protected $specialFilters = array();
|
||||
|
||||
protected $sortDefaults = array(
|
||||
'servicegroup_alias' => array(
|
||||
'default_dir' => self::SORT_ASC
|
||||
)
|
||||
);
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Monitoring\View;
|
||||
|
||||
use Icinga\Module\Monitoring\View\AbstractView;
|
||||
|
||||
class StatehistoryView extends AbstractView
|
||||
{
|
||||
protected $availableColumns = array(
|
||||
'raw_timestamp',
|
||||
'timestamp',
|
||||
'state',
|
||||
'attempt',
|
||||
'max_attempts',
|
||||
'output',
|
||||
'type'
|
||||
);
|
||||
|
||||
protected $sortDefaults = array(
|
||||
'timestamp' => array(
|
||||
'columns' => array('raw_timestamp'),
|
||||
'default_dir' => self::SORT_DESC
|
||||
),
|
||||
);
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Module\Monitoring\View;
|
||||
|
||||
class StatusView extends AbstractView
|
||||
{
|
||||
protected $query;
|
||||
// protected $searchColumn = 'host'; -> besser in der Query, 'search' mitgeben
|
||||
|
||||
protected $availableColumns = array(
|
||||
// Hosts
|
||||
'host',
|
||||
'host_name',
|
||||
'host_display_name',
|
||||
'host_alias',
|
||||
'host_address',
|
||||
'host_ipv4',
|
||||
'host_icon_image',
|
||||
|
||||
// Hoststatus
|
||||
'host_state',
|
||||
'host_problems',
|
||||
'host_severity',
|
||||
'host_state_type',
|
||||
'host_output',
|
||||
'host_long_output',
|
||||
'host_perfdata',
|
||||
'host_acknowledged',
|
||||
'host_in_downtime',
|
||||
'host_handled',
|
||||
'host_does_active_checks',
|
||||
'host_accepts_passive_checks',
|
||||
'host_last_state_change',
|
||||
'host_last_hard_state',
|
||||
'host_last_hard_state_change',
|
||||
'host_notifications_enabled',
|
||||
'host_last_time_up',
|
||||
'host_last_time_down',
|
||||
'host_last_time_unreachable',
|
||||
'host_unhandled_service_count',
|
||||
|
||||
// Services
|
||||
'service',
|
||||
'service_description',
|
||||
'service_display_name',
|
||||
|
||||
// Servicetatus
|
||||
'current_state',
|
||||
'service_state',
|
||||
'service_state_type',
|
||||
'service_hard_state',
|
||||
'service_output',
|
||||
'service_long_output',
|
||||
'service_perfdata',
|
||||
'service_acknowledged',
|
||||
'service_in_downtime',
|
||||
'service_handled',
|
||||
'service_does_active_checks',
|
||||
'service_accepts_passive_checks',
|
||||
'service_last_state_change',
|
||||
'service_last_hard_state',
|
||||
'service_last_hard_state_change',
|
||||
'service_notifications_enabled',
|
||||
'service_last_time_ok',
|
||||
'service_last_time_warning',
|
||||
'service_last_time_critical',
|
||||
'service_last_time_unknown',
|
||||
|
||||
'object_type',
|
||||
|
||||
// Status
|
||||
'problems',
|
||||
'handled',
|
||||
'severity'
|
||||
);
|
||||
|
||||
protected $specialFilters = array(
|
||||
'hostgroups',
|
||||
'servicegroups'
|
||||
);
|
||||
|
||||
protected $sortDefaults = array(
|
||||
'host_name' => array(
|
||||
'columns' => array(
|
||||
'host_name',
|
||||
),
|
||||
'default_dir' => self::SORT_ASC
|
||||
),
|
||||
'service_host_name' => array(
|
||||
'columns' => array(
|
||||
'service_host_name',
|
||||
'service_description'
|
||||
),
|
||||
'default_dir' => self::SORT_ASC
|
||||
),
|
||||
'host_address' => array(
|
||||
'columns' => array(
|
||||
'host_ipv4',
|
||||
'service_description'
|
||||
),
|
||||
'default_dir' => self::SORT_ASC
|
||||
),
|
||||
'host_last_state_change' => array(
|
||||
'default_dir' => self::SORT_DESC
|
||||
),
|
||||
'service_last_state_change' => array(
|
||||
'default_dir' => self::SORT_DESC
|
||||
),
|
||||
'severity' => array(
|
||||
'columns' => array(
|
||||
'severity',
|
||||
'host_name',
|
||||
'service_last_state_change',
|
||||
),
|
||||
'default_dir' => self::SORT_DESC
|
||||
),
|
||||
'host_severity' => array(
|
||||
'columns' => array(
|
||||
'host_severity',
|
||||
'host_last_state_change',
|
||||
),
|
||||
'default_dir' => self::SORT_DESC
|
||||
)
|
||||
);
|
||||
|
||||
public function isValidFilterColumn($column)
|
||||
{
|
||||
if ($column[0] === '_'
|
||||
&& preg_match('~^_(?:host|service)_~', $column)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return parent::isValidFilterColumn($column);
|
||||
}
|
||||
}
|
@ -2,12 +2,17 @@
|
||||
|
||||
namespace Test\Monitoring\Application\Controllers\ListController;
|
||||
|
||||
require_once(dirname(__FILE__).'/../../testlib/MonitoringControllerTest.php');
|
||||
|
||||
require_once(dirname(__FILE__).'/../../../../library/Monitoring/DataView/DataView.php');
|
||||
require_once(dirname(__FILE__).'/../../../../library/Monitoring/DataView/HostAndServiceStatus.php');
|
||||
require_once(dirname(__FILE__).'/../../../../library/Monitoring/DataView/Notification.php');
|
||||
require_once(dirname(__FILE__).'/../../../../library/Monitoring/DataView/Downtime.php');
|
||||
require_once realpath(__DIR__ . '/../../../../../../library/Icinga/Test/BaseTestCase.php');
|
||||
|
||||
use Icinga\Test\BaseTestCase;
|
||||
|
||||
require_once(realpath(BaseTestCase::$moduleDir . '/monitoring/test/php/testlib/MonitoringControllerTest.php'));
|
||||
require_once(realpath(BaseTestCase::$libDir . '/Data/Db/TreeToSqlParser.php'));
|
||||
require_once(realpath(BaseTestCase::$moduleDir . '/monitoring/library/Monitoring/DataView/DataView.php'));
|
||||
require_once(realpath(BaseTestCase::$moduleDir . '/monitoring/library/Monitoring/DataView/HostStatus.php'));
|
||||
require_once(realpath(BaseTestCase::$moduleDir . '/monitoring/library/Monitoring/DataView/Notification.php'));
|
||||
require_once(realpath(BaseTestCase::$moduleDir . '/monitoring/library/Monitoring/DataView/Downtime.php'));
|
||||
|
||||
use Test\Monitoring\Testlib\MonitoringControllerTest;
|
||||
use Test\Monitoring\Testlib\Datasource\TestFixture;
|
||||
|
@ -2,7 +2,16 @@
|
||||
|
||||
namespace Test\Monitoring\Application\Controllers\ListController;
|
||||
|
||||
require_once(dirname(__FILE__).'/../../testlib/MonitoringControllerTest.php');
|
||||
use Icinga\Test\BaseTestCase;
|
||||
|
||||
require_once realpath(__DIR__ . '/../../../../../../library/Icinga/Test/BaseTestCase.php');
|
||||
|
||||
require_once(realpath(BaseTestCase::$moduleDir . '/monitoring/test/php/testlib/MonitoringControllerTest.php'));
|
||||
require_once(realpath(BaseTestCase::$libDir . '/Data/Db/TreeToSqlParser.php'));
|
||||
require_once(realpath(BaseTestCase::$moduleDir . '/monitoring/library/Monitoring/DataView/DataView.php'));
|
||||
require_once(realpath(BaseTestCase::$moduleDir . '/monitoring/library/Monitoring/DataView/ServiceStatus.php'));
|
||||
require_once(realpath(BaseTestCase::$moduleDir . '/monitoring/library/Monitoring/DataView/Notification.php'));
|
||||
require_once(realpath(BaseTestCase::$moduleDir . '/monitoring/library/Monitoring/DataView/Downtime.php'));
|
||||
|
||||
use Test\Monitoring\Testlib\MonitoringControllerTest;
|
||||
use Test\Monitoring\Testlib\Datasource\TestFixture;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Tests\Monitoring\Backend\Statusdat;
|
||||
|
||||
use Icinga\Data\DatasourceInterface;
|
||||
use Zend_Config;
|
||||
use Tests\Icinga\Protocol\Statusdat\ReaderMock as ReaderMock;
|
||||
use \Icinga\Module\Monitoring\Backend\Statusdat\Query\ServicegroupsummaryQuery;
|
||||
@ -14,7 +15,7 @@ StatusdatTestLoader::requireLibrary();
|
||||
require_once(realpath($base."/library/Monitoring/Backend/Statusdat/Query/GroupsummaryQuery.php"));
|
||||
require_once(realpath($base."/library/Monitoring/Backend/Statusdat/Query/ServicegroupsummaryQuery.php"));
|
||||
|
||||
class BackendMock extends \Icinga\Module\Monitoring\Backend\AbstractBackend
|
||||
class BackendMock implements DatasourceInterface
|
||||
{
|
||||
public $reader;
|
||||
|
||||
@ -37,7 +38,7 @@ class ServicegroupsummaryqueryTest extends \PHPUnit_Framework_TestCase
|
||||
$backendConfig = new Zend_Config(
|
||||
array()
|
||||
);
|
||||
$backend = new BackendMock($backendConfig);
|
||||
$backend = new BackendMock();
|
||||
$backend->setReader($this->getTestDataset());
|
||||
$q = new ServicegroupsummaryQuery($backend);
|
||||
$indices = array(
|
||||
|
@ -0,0 +1,142 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Test\Modules\Monitoring\Library\Filter\Type;
|
||||
|
||||
use Icinga\Module\Monitoring\Filter\Type\StatusFilter;
|
||||
use Icinga\Filter\Type\TimeRangeSpecifier;
|
||||
use Icinga\Filter\Query\Node;
|
||||
use Icinga\Test\BaseTestCase;
|
||||
|
||||
// @codingStandardsIgnoreStart
|
||||
require_once realpath(__DIR__ . '/../../../../../../../library/Icinga/Test/BaseTestCase.php');
|
||||
require_once realpath(BaseTestCase::$libDir .'/Filter/Query/Node.php');
|
||||
require_once realpath(BaseTestCase::$libDir .'/Filter/QueryProposer.php');
|
||||
require_once realpath(BaseTestCase::$libDir .'/Filter/Type/FilterType.php');
|
||||
require_once realpath(BaseTestCase::$libDir .'/Filter/Type/TimeRangeSpecifier.php');
|
||||
require_once realpath(BaseTestCase::$moduleDir .'/monitoring/library/Monitoring/Filter/Type/StatusFilter.php');
|
||||
// @codingStandardsIgnoreEnd
|
||||
|
||||
|
||||
class StatusFilterTest extends BaseTestCase
|
||||
{
|
||||
public function testOperatorProposal()
|
||||
{
|
||||
$searchType = StatusFilter::createForHost();
|
||||
$this->assertEquals(
|
||||
$searchType->getOperators(),
|
||||
$searchType->getProposalsForQuery(''),
|
||||
'Assert all possible operators to be returned when monitoring status has no further query input'
|
||||
);
|
||||
}
|
||||
|
||||
public function testStateTypeProposal()
|
||||
{
|
||||
$searchType = StatusFilter::createForHost();
|
||||
$this->assertEquals(
|
||||
array('{Pen}ding'),
|
||||
$searchType->getProposalsForQuery('is Pen'),
|
||||
'Assert StatusFilter to complete partial queries'
|
||||
);
|
||||
}
|
||||
|
||||
public function testTimeRangeProposal()
|
||||
{
|
||||
$subFilter = new TimeRangeSpecifier();
|
||||
$searchType = StatusFilter::createForHost();
|
||||
$this->assertEquals(
|
||||
$subFilter->getOperators(),
|
||||
$searchType->getProposalsForQuery('is Pending'),
|
||||
'Assert StatusFilter to chain TimeRangeSpecifier at the end'
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
$subFilter->timeExamples,
|
||||
$searchType->getProposalsForQuery('is Pending Since'),
|
||||
'Assert TimeRange time examples to be proposed'
|
||||
);
|
||||
}
|
||||
|
||||
public function testQueryNodeCreation()
|
||||
{
|
||||
$searchType = StatusFilter::createForHost();
|
||||
$treeNode = $searchType->createTreeNode('is down', 'host_current_state');
|
||||
$this->assertEquals(
|
||||
'host_current_state',
|
||||
$treeNode->left,
|
||||
'Assert the left treenode to represent the state field given to the StatusFilter'
|
||||
);
|
||||
$this->assertEquals(
|
||||
1,
|
||||
$treeNode->right,
|
||||
'Assert the right treenode to contain the numeric status for "Down"'
|
||||
);
|
||||
$this->assertEquals(
|
||||
Node::TYPE_OPERATOR,
|
||||
$treeNode->type,
|
||||
'Assert the treenode to be an operator node'
|
||||
);
|
||||
$this->assertEquals(
|
||||
Node::OPERATOR_EQUALS,
|
||||
$treeNode->operator,
|
||||
'Assert the treenode operator to be "Equals"'
|
||||
);
|
||||
}
|
||||
|
||||
public function testQueryNodeCreationWithTime()
|
||||
{
|
||||
$searchType = StatusFilter::createForHost();
|
||||
|
||||
$treeNode = $searchType->createTreeNode('is down since yesterday', 'host_current_state');
|
||||
$this->assertEquals(
|
||||
Node::TYPE_AND,
|
||||
$treeNode->type,
|
||||
'Assert and and node to be returned when an additional time specifier is appended'
|
||||
);
|
||||
$this->assertEquals(
|
||||
Node::TYPE_OPERATOR,
|
||||
$treeNode->left->type,
|
||||
'Assert the left node to be the original query (operator)'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'host_current_state',
|
||||
$treeNode->left->left,
|
||||
'Assert the left node to be the original query (field)'
|
||||
);
|
||||
$this->assertEquals(
|
||||
Node::TYPE_OPERATOR,
|
||||
$treeNode->right->type,
|
||||
'Assert the right node to be the time specifier query (operator)'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'host_last_state_change',
|
||||
$treeNode->right->left,
|
||||
'Assert the right node to be the time specifier query (field)'
|
||||
);
|
||||
}
|
||||
}
|
209
modules/monitoring/test/php/library/Filter/UrlViewFilterTest.php
Normal file
209
modules/monitoring/test/php/library/Filter/UrlViewFilterTest.php
Normal file
@ -0,0 +1,209 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
namespace Test\Modules\Monitoring\Library\Filter;
|
||||
|
||||
use Icinga\Filter\Filterable;
|
||||
use Icinga\Filter\Query\Tree;
|
||||
use Icinga\Module\Monitoring\Filter\Type\StatusFilter;
|
||||
use Icinga\Filter\Type\TimeRangeSpecifier;
|
||||
use Icinga\Filter\Query\Node;
|
||||
use Icinga\Filter\Filter;
|
||||
use Icinga\Filter\Type\TextFilter;
|
||||
use Icinga\Filter\FilterAttribute;
|
||||
use Icinga\Module\Monitoring\Filter\UrlViewFilter;
|
||||
use Icinga\Test\BaseTestCase;
|
||||
|
||||
// @codingStandardsIgnoreStart
|
||||
require_once realpath(__DIR__ . '/../../../../../../library/Icinga/Test/BaseTestCase.php');
|
||||
require_once realpath(BaseTestCase::$libDir . '/Filter/QueryProposer.php');
|
||||
require_once realpath(BaseTestCase::$libDir . '/Filter/Filter.php');
|
||||
require_once realpath(BaseTestCase::$libDir . '/Filter/FilterAttribute.php');
|
||||
require_once realpath(BaseTestCase::$libDir . '/Filter/Domain.php');
|
||||
require_once realpath(BaseTestCase::$libDir . '/Filter/Query/Node.php');
|
||||
require_once realpath(BaseTestCase::$libDir . '/Filter/Query/Tree.php');
|
||||
require_once realpath(BaseTestCase::$libDir . '/Filter/Type/FilterType.php');
|
||||
require_once realpath(BaseTestCase::$libDir . '/Filter/Type/TextFilter.php');
|
||||
require_once realpath(BaseTestCase::$libDir .'/Filter/Type/TimeRangeSpecifier.php');
|
||||
require_once realpath(BaseTestCase::$moduleDir .'/monitoring/library/Monitoring/Filter/Type/StatusFilter.php');
|
||||
require_once realpath(BaseTestCase::$moduleDir .'/monitoring/library/Monitoring/Filter/UrlViewFilter.php');
|
||||
|
||||
class FilterMock implements Filterable
|
||||
{
|
||||
public function isValidFilterTarget($field)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getMappedField($field)
|
||||
{
|
||||
return $field;
|
||||
}
|
||||
|
||||
public function applyFilter()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function clearFilter()
|
||||
{
|
||||
// TODO: Implement clearFilter() method.
|
||||
}
|
||||
|
||||
public function addFilter($filter)
|
||||
{
|
||||
// TODO: Implement addFilter() method.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class UrlViewFilterTest extends BaseTestCase
|
||||
{
|
||||
public function testUrlParamCreation()
|
||||
{
|
||||
$searchEngine = new Filter();
|
||||
$searchEngine->createFilterDomain('host')
|
||||
->registerAttribute(
|
||||
FilterAttribute::create(new TextFilter())
|
||||
->setHandledAttributes('attr1')
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(new TextFilter())
|
||||
->setHandledAttributes('attr2')
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(new TextFilter())
|
||||
->setHandledAttributes('attr3')
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(StatusFilter::createForHost())
|
||||
->setHandledAttributes('attr4')
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(StatusFilter::createForHost())
|
||||
->setHandledAttributes('attr5')
|
||||
);
|
||||
$query = 'attr1 is not \'Hans wurst\''
|
||||
. ' or attr2 contains something '
|
||||
. ' and attr3 starts with bla'
|
||||
. ' or attr4 is DOWN since "yesterday"'
|
||||
. ' and attr5 is UP';
|
||||
|
||||
$tree = $searchEngine->createQueryTreeForFilter($query);
|
||||
$filterFactory = new UrlViewFilter(new FilterMock());
|
||||
$uri = $filterFactory->fromTree($tree);
|
||||
$this->assertEquals(
|
||||
'attr1!=Hans+wurst|attr2=%2Asomething%2A&attr3=bla%2A|attr4=1&host_last_state_change>=yesterday&attr5=0',
|
||||
$uri,
|
||||
'Assert a correct query to be returned when parsing a more complex query ("'. $query .'")'
|
||||
);
|
||||
}
|
||||
|
||||
public function testTreeFromSimpleKeyValueUrlCreation()
|
||||
{
|
||||
$filterFactory = new UrlViewFilter(new FilterMock());
|
||||
$tree = $filterFactory->parseUrl('attr1!=Hans+Wurst');
|
||||
$this->assertEquals(
|
||||
$tree->root->type,
|
||||
Node::TYPE_OPERATOR,
|
||||
'Assert one operator node to exist for a simple filter'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$tree->root->operator,
|
||||
Node::OPERATOR_EQUALS_NOT,
|
||||
'Assert the operator to be !='
|
||||
);
|
||||
$this->assertEquals(
|
||||
$tree->root->left,
|
||||
'attr1',
|
||||
'Assert the field to be set correctly'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$tree->root->right,
|
||||
'Hans Wurst',
|
||||
'Assert the value to be set correctly'
|
||||
);
|
||||
}
|
||||
|
||||
public function testConjunctionFilterInUrl()
|
||||
{
|
||||
$filterFactory = new UrlViewFilter(new FilterMock());
|
||||
$query = 'attr1!=Hans+Wurst&test=test123|bla=1';
|
||||
$tree = $filterFactory->parseUrl($query);
|
||||
$this->assertEquals($tree->root->type, Node::TYPE_AND, 'Assert the root of the filter tree to be an AND node');
|
||||
$this->assertEquals($filterFactory->fromTree($tree), $query, 'Assert the tree to map back to the query');
|
||||
}
|
||||
|
||||
public function testImplicitConjunctionInUrl()
|
||||
{
|
||||
$filterFactory = new UrlViewFilter(new FilterMock());
|
||||
$query = 'attr1!=Hans+Wurst&test=test123|bla=1|2|3';
|
||||
$tree = $filterFactory->parseUrl($query);
|
||||
$this->assertEquals($tree->root->type, Node::TYPE_AND, 'Assert the root of the filter tree to be an AND node');
|
||||
$this->assertEquals(
|
||||
'attr1!=Hans+Wurst&test=test123|bla=1|bla=2|bla=3',
|
||||
$filterFactory->fromTree($tree),
|
||||
'Assert the tree to map back to the query in an explicit form'
|
||||
);
|
||||
}
|
||||
|
||||
public function testMissingValuesInQueries()
|
||||
{
|
||||
$filterFactory = new UrlViewFilter(new FilterMock());
|
||||
$queryStr = 'attr1!=Hans+Wurst&test=';
|
||||
$tree = $filterFactory->parseUrl($queryStr);
|
||||
$query = $filterFactory->fromTree($tree);
|
||||
$this->assertEquals('attr1!=Hans+Wurst', $query, 'Assert the valid part of a query to be used');
|
||||
}
|
||||
|
||||
public function testErrorInQueries()
|
||||
{
|
||||
$filterFactory = new UrlViewFilter(new FilterMock());
|
||||
$queryStr = 'test=&attr1!=Hans+Wurst';
|
||||
$tree = $filterFactory->parseUrl($queryStr);
|
||||
$query = $filterFactory->fromTree($tree);
|
||||
$this->assertEquals('attr1!=Hans+Wurst', $query, 'Assert the valid part of a query to be used');
|
||||
}
|
||||
|
||||
public function testSenselessConjunctions()
|
||||
{
|
||||
$filterFactory = new UrlViewFilter(new FilterMock());
|
||||
$queryStr = 'test=&|/5/|&attr1!=Hans+Wurst';
|
||||
$tree = $filterFactory->parseUrl($queryStr);
|
||||
$query = $filterFactory->fromTree($tree);
|
||||
$this->assertEquals('attr1!=Hans+Wurst', $query, 'Assert the valid part of a query to be used');
|
||||
}
|
||||
|
||||
public function testRandomString()
|
||||
{
|
||||
$filter = '';
|
||||
$filterFactory = new UrlViewFilter(new FilterMock());
|
||||
|
||||
for ($i=0; $i<10;$i++) {
|
||||
$filter .= str_shuffle('&|ds& wra =!<>|dsgs=,-G');
|
||||
$tree = $filterFactory->parseUrl($filter);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user