Merge branch 'bugfix/url-handling-refactor-4868'

This commit is contained in:
Jannis Moßhammer 2013-10-17 21:51:13 +02:00
commit d0dadc031c
126 changed files with 7693 additions and 3714 deletions

View 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

View File

@ -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
{
/**

View File

@ -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' ?>)

View 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);
}

View File

@ -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);
}
}

View 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);
}
}

View File

@ -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)) {

View File

@ -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;
}
}

View File

@ -8,7 +8,7 @@ interface DatasourceInterface
/**
* Instantiate a Query object
*
* @return AbstractQuery
* @return BaseQuery
*/
public function select();
}

View File

@ -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();
}
}

View 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';
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;

View 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;
}
}

View 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;
}
}

View 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);
}
}

View 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);
}

View 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;
}
}

View 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);
}
}
}

View 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);
}

View File

@ -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);
}

View 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;
}
}

View 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;
}
}

View 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...';
}
}
}

View 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;
}
}

View File

@ -1,8 +0,0 @@
<?php
namespace Icinga\Objects;
class Host extends Object
{
// Nothing here
}

View File

@ -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;
}
}

View File

@ -1,7 +0,0 @@
<?php
namespace Icinga\Objects;
class Service extends Object
{
}

View File

@ -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;
}
}

View File

@ -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.
}
}

View File

@ -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;
}
}
/**

View 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);
}
}

View 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);
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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());

View File

@ -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()

View File

@ -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>

View File

@ -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): ?>

View File

@ -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): ?>

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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(

View File

@ -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
)
);
}

View File

@ -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',
)
);

View File

@ -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()
{

View File

@ -2,7 +2,7 @@
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
class CustomvarQuery extends AbstractQuery
class CustomvarQuery extends IdoQuery
{
protected $object_id = 'object_id';

View File

@ -28,7 +28,7 @@ namespace Icinga\Module\Monitoring\Backend\Ido\Query;
/**
* Handling downtime queries
*/
class DowntimeQuery extends AbstractQuery
class DowntimeQuery extends IdoQuery
{
/**
* Column map

View File

@ -2,7 +2,7 @@
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
class DowntimeendhistoryQuery extends AbstractQuery
class DowntimeendhistoryQuery extends IdoQuery
{
protected $columnMap = array(
'downtimehistory' => array(

View File

@ -2,7 +2,7 @@
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
class DowntimestarthistoryQuery extends AbstractQuery
class DowntimestarthistoryQuery extends IdoQuery
{
protected $columnMap = array(
'downtimehistory' => array(

View File

@ -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);
}

View File

@ -2,7 +2,7 @@
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
class GroupsummaryQuery extends AbstractQuery
class GroupsummaryQuery extends IdoQuery
{
protected $columnMap = array(
'hoststatus' => array(

View File

@ -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'
)
);

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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'

View File

@ -2,7 +2,7 @@
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
class NotificationhistoryQuery extends AbstractQuery
class NotificationhistoryQuery extends IdoQuery
{
protected $columnMap = array(
'history' => array(

View File

@ -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'
)

View File

@ -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'];

View File

@ -2,7 +2,7 @@
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
class StatehistoryQuery extends AbstractQuery
class StatehistoryQuery extends IdoQuery
{
protected $columnMap = array(
'statehistory' => array(

View File

@ -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()

View File

@ -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',

View File

@ -64,7 +64,7 @@ abstract class GroupsummaryQuery extends Query
/**
* @var array
*/
protected $order_columns = array(
protected $orderColumns = array(
'state' => array(
'ASC' => array(
'ok ASC',

View File

@ -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.
}
}

View File

@ -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;

View File

@ -51,7 +51,9 @@ class Comment extends DataView
'comment_is_persistent',
'comment_expiration_timestamp',
'host_name',
'service_name'
'service_name',
'host',
'service'
);
}

View File

@ -40,7 +40,9 @@ class Contact extends DataView
'host_name',
'service_object_id',
'service_host_name',
'service_description'
'service_description',
'service',
'host',
);
}

View File

@ -18,7 +18,9 @@ class Contactgroup extends DataView
return array(
'contact_name',
'contactgroup_name',
'contactgroup_alias'
'contactgroup_alias',
'host',
'service'
);
}

View File

@ -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;
}
}

View File

@ -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'
);
}

View 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);
}
}

View 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
)
);
}
}

View File

@ -21,7 +21,9 @@ class Notification extends DataView
'notification_start_time',
'notification_contact',
'notification_information',
'notification_command'
'notification_command',
'host',
'service'
);
}

View File

@ -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);
}
}

View File

@ -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
)
);
}
}

View 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;
}
}

View File

@ -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;
}
}

View 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);
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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
)
);*/
}

View File

@ -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
)
);
}

View File

@ -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
)
);
}

View File

@ -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,
)
);
}

View File

@ -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
)
);
}

View File

@ -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
),
);
}

View File

@ -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
)
);
}

View File

@ -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);
}
}

View File

@ -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
)
);
}

View File

@ -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
),
);
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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(

View File

@ -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)'
);
}
}

View 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