mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-26 23:34:08 +02:00
Semantic search implementation
- Only implemented for hosts as an example - URL behaviour still has to be normalized refs #4469
This commit is contained in:
parent
dac61eda19
commit
d33cec78de
@ -25,6 +25,7 @@
|
|||||||
* @author Icinga Development Team <info@icinga.org>
|
* @author Icinga Development Team <info@icinga.org>
|
||||||
*/
|
*/
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
// @codingStandardsIgnoreStart
|
||||||
|
|
||||||
use Icinga\Web\Form;
|
use Icinga\Web\Form;
|
||||||
use Icinga\Web\Controller\ActionController;
|
use Icinga\Web\Controller\ActionController;
|
||||||
@ -34,69 +35,82 @@ use Icinga\Filter\Type\TextFilter;
|
|||||||
use Icinga\Application\Logger;
|
use Icinga\Application\Logger;
|
||||||
use Icinga\Module\Monitoring\Filter\Type\StatusFilter;
|
use Icinga\Module\Monitoring\Filter\Type\StatusFilter;
|
||||||
use Icinga\Module\Monitoring\Filter\UrlViewFilter;
|
use Icinga\Module\Monitoring\Filter\UrlViewFilter;
|
||||||
|
use Icinga\Module\Monitoring\DataView\HostStatus;
|
||||||
use Icinga\Web\Url;
|
use Icinga\Web\Url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application wide interface for filtering
|
||||||
|
*/
|
||||||
class FilterController extends ActionController
|
class FilterController extends ActionController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
* The current filter registry
|
||||||
|
*
|
||||||
* @var Filter
|
* @var Filter
|
||||||
*/
|
*/
|
||||||
private $registry;
|
private $registry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry point for filtering, uses the filter_domain and filter_module request parameter
|
||||||
|
* to determine which filter registry should be used
|
||||||
|
*/
|
||||||
public function indexAction()
|
public function indexAction()
|
||||||
{
|
{
|
||||||
$this->registry = new Filter();
|
$this->registry = new Filter();
|
||||||
$filter = new UrlViewFilter();
|
|
||||||
|
|
||||||
$this->view->form = new Form();
|
if ($this->getRequest()->getHeader('accept') == 'application/json') {
|
||||||
$this->view->form->addElement(
|
|
||||||
'text',
|
|
||||||
'query',
|
|
||||||
array(
|
|
||||||
'name' => 'query',
|
|
||||||
'label' => 'search',
|
|
||||||
'type' => 'search',
|
|
||||||
'data-icinga-component' => 'app/semanticsearch',
|
|
||||||
'data-icinga-target' => 'host',
|
|
||||||
'helptext' => 'Filter test'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
$this->view->form->addElement(
|
|
||||||
'submit',
|
|
||||||
'btn_submit',
|
|
||||||
array(
|
|
||||||
'name' => 'submit'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
$this->setupQueries();
|
|
||||||
$this->view->form->setRequest($this->getRequest());
|
|
||||||
|
|
||||||
if ($this->view->form->isSubmittedAndValid()) {
|
|
||||||
$tree = $this->registry->createQueryTreeForFilter($this->view->form->getValue('query'));
|
|
||||||
$this->view->tree = new \Icinga\Web\Widget\FilterBadgeRenderer($tree);
|
|
||||||
$view = \Icinga\Module\Monitoring\DataView\HostAndServiceStatus::fromRequest($this->getRequest());
|
|
||||||
$cv = new \Icinga\Module\Monitoring\Filter\Backend\IdoQueryConverter($view);
|
|
||||||
$this->view->sqlString = $cv->treeToSql($tree);
|
|
||||||
$this->view->params = $cv->getParams();
|
|
||||||
} else if ($this->getRequest()->getHeader('accept') == 'application/json') {
|
|
||||||
$this->getResponse()->setHeader('Content-Type', 'application/json');
|
$this->getResponse()->setHeader('Content-Type', 'application/json');
|
||||||
|
|
||||||
|
$this->setupQueries(
|
||||||
|
$this->getParam('filter_domain', ''),
|
||||||
|
$this->getParam('filter_module', '')
|
||||||
|
);
|
||||||
|
|
||||||
$this->_helper->json($this->parse($this->getRequest()->getParam('query', '')));
|
$this->_helper->json($this->parse($this->getRequest()->getParam('query', '')));
|
||||||
|
} else {
|
||||||
|
$this->redirect('index/welcome');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function setupQueries()
|
/**
|
||||||
|
* 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')
|
||||||
{
|
{
|
||||||
$this->registry->addDomain(\Icinga\Module\Monitoring\Filter\MonitoringFilter::hostFilter());
|
$class = '\\Icinga\\Module\\' . ucfirst($module) . '\\Filter\\Registry';
|
||||||
|
$factory = strtolower($domain) . 'Filter';
|
||||||
|
$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)
|
private function parse($text)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return $this->registry->getProposalsForQuery($text);
|
$view = HostStatus::fromRequest($this->getRequest());
|
||||||
|
$urlParser = new UrlViewFilter($view);
|
||||||
|
$queryTree = $this->registry->createQueryTreeForFilter($text);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'state' => 'success',
|
||||||
|
'proposals' => $this->registry->getProposalsForQuery($text),
|
||||||
|
'urlParam' => $urlParser->fromTree($queryTree)
|
||||||
|
);
|
||||||
} catch (\Exception $exc) {
|
} catch (\Exception $exc) {
|
||||||
Logger::error($exc);
|
Logger::error($exc);
|
||||||
|
$this->getResponse()->setHttpResponseCode(500);
|
||||||
|
return array(
|
||||||
|
'state' => 'error',
|
||||||
|
'message' => 'Search service is currently not available'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// @codingStandardsIgnoreEnd
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
All critical hosts starting with 'MySql'
|
|
||||||
All services with status warning that have been checked in the last two days
|
|
||||||
Services with open Problems and with critical hosts
|
|
||||||
|
|
||||||
with services that are not ok
|
|
||||||
|
|
||||||
|
|
||||||
[(SUBJECT)] [(SPECIFIED)] [(OP)] [FILTER] [(ADDITIONAL)]
|
|
||||||
|
|
@ -26,7 +26,6 @@
|
|||||||
*/
|
*/
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
|
||||||
namespace Icinga\Filter;
|
namespace Icinga\Filter;
|
||||||
|
|
||||||
use Icinga\Filter\Query\Node;
|
use Icinga\Filter\Query\Node;
|
||||||
@ -132,10 +131,10 @@ class Domain extends QueryProposer
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->attributes as $attributeHandler) {
|
foreach ($this->attributes as $attributeHandler) {
|
||||||
if ($attributeHandler->isValidQuery($query)) {
|
if ($attributeHandler->isValidQuery($query)) {
|
||||||
$node = $attributeHandler->convertToTreeNode($query);
|
$node = $attributeHandler->convertToTreeNode($query);
|
||||||
return $node;
|
return $node;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,6 @@
|
|||||||
|
|
||||||
namespace Icinga\Filter;
|
namespace Icinga\Filter;
|
||||||
|
|
||||||
|
|
||||||
use Icinga\Filter\Query\Tree;
|
use Icinga\Filter\Query\Tree;
|
||||||
use Icinga\Filter\Query\Node;
|
use Icinga\Filter\Query\Node;
|
||||||
|
|
||||||
@ -38,7 +37,8 @@ use Icinga\Filter\Query\Node;
|
|||||||
* This class handles the top level parsing of queries, i.e.
|
* This class handles the top level parsing of queries, i.e.
|
||||||
* - Splitting queries at conjunctions and parsing them part by part
|
* - Splitting queries at conjunctions and parsing them part by part
|
||||||
* - Delegating the query parts to specific filter domains handling this filters
|
* - 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..)
|
* - 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:
|
* Filters are split in Filter Domains, Attributes and Types:
|
||||||
*
|
*
|
||||||
@ -110,7 +110,7 @@ class Filter extends QueryProposer
|
|||||||
{
|
{
|
||||||
if ($this->defaultDomain !== null) {
|
if ($this->defaultDomain !== null) {
|
||||||
return $this->defaultDomain;
|
return $this->defaultDomain;
|
||||||
} else if (count($this->domains) > 0) {
|
} elseif (count($this->domains) > 0) {
|
||||||
return $this->domains[0];
|
return $this->domains[0];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -240,7 +240,7 @@ class Filter extends QueryProposer
|
|||||||
$right = $query;
|
$right = $query;
|
||||||
do {
|
do {
|
||||||
list($left, $conjuction, $right) = $this->splitQueryAtNextConjunction($right);
|
list($left, $conjuction, $right) = $this->splitQueryAtNextConjunction($right);
|
||||||
} while($conjuction !== null);
|
} while ($conjuction !== null);
|
||||||
return $left;
|
return $left;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,7 +275,7 @@ class Filter extends QueryProposer
|
|||||||
|
|
||||||
if ($conjunction === 'AND') {
|
if ($conjunction === 'AND') {
|
||||||
$tree->insert(Node::createAndNode());
|
$tree->insert(Node::createAndNode());
|
||||||
} elseif($conjunction === 'OR') {
|
} elseif ($conjunction === 'OR') {
|
||||||
$tree->insert(Node::createOrNode());
|
$tree->insert(Node::createOrNode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
*/
|
*/
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
|
||||||
namespace Icinga\Filter;
|
namespace Icinga\Filter;
|
||||||
|
|
||||||
use Icinga\Filter\Query\Node;
|
use Icinga\Filter\Query\Node;
|
||||||
@ -88,7 +87,7 @@ class FilterAttribute extends QueryProposer
|
|||||||
if (!$this->field) {
|
if (!$this->field) {
|
||||||
$this->field = $attr;
|
$this->field = $attr;
|
||||||
}
|
}
|
||||||
foreach(func_get_args() as $arg) {
|
foreach (func_get_args() as $arg) {
|
||||||
$this->attributes[] = trim($arg);
|
$this->attributes[] = trim($arg);
|
||||||
}
|
}
|
||||||
return $this;
|
return $this;
|
||||||
@ -121,7 +120,7 @@ class FilterAttribute extends QueryProposer
|
|||||||
$query = trim($query);
|
$query = trim($query);
|
||||||
foreach ($this->attributes as $attribute) {
|
foreach ($this->attributes as $attribute) {
|
||||||
if (stripos($query, $attribute) === 0) {
|
if (stripos($query, $attribute) === 0) {
|
||||||
return $attribute;
|
return $attribute;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -134,7 +133,8 @@ class FilterAttribute extends QueryProposer
|
|||||||
*
|
*
|
||||||
* @return bool True when this query contains an attribute mapped by this filter
|
* @return bool True when this query contains an attribute mapped by this filter
|
||||||
*/
|
*/
|
||||||
public function queryHasSupportedAttribute($query) {
|
public function queryHasSupportedAttribute($query)
|
||||||
|
{
|
||||||
return $this->getMatchingAttribute($query) !== null;
|
return $this->getMatchingAttribute($query) !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,6 +230,4 @@ class FilterAttribute extends QueryProposer
|
|||||||
{
|
{
|
||||||
return new FilterAttribute($type);
|
return new FilterAttribute($type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,12 +27,14 @@
|
|||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
|
||||||
namespace Icinga\Data;
|
namespace Icinga\Filter;
|
||||||
|
|
||||||
|
|
||||||
|
use Icinga\Filter\Query\Tree;
|
||||||
|
|
||||||
interface Filterable
|
interface Filterable
|
||||||
{
|
{
|
||||||
public function isValidFilterTarget($targetOrColumn);
|
public function isValidFilterTarget($field);
|
||||||
public function resolveFilterTarget($targetOrColumn);
|
public function getMappedField($field);
|
||||||
|
public function applyFilter(Tree $filter);
|
||||||
}
|
}
|
@ -26,7 +26,6 @@
|
|||||||
*/
|
*/
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
|
||||||
namespace Icinga\Filter\Query;
|
namespace Icinga\Filter\Query;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,7 +101,8 @@ class Node
|
|||||||
* Factory method for creating operator nodes
|
* Factory method for creating operator nodes
|
||||||
*
|
*
|
||||||
* @param String $operator The operator to use
|
* @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 $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
|
* @param String $right The right side of the node, i.e. the value to use for querying
|
||||||
*
|
*
|
||||||
* @return Node An operator Node instance
|
* @return Node An operator Node instance
|
||||||
|
@ -26,9 +26,10 @@
|
|||||||
*/
|
*/
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
|
||||||
namespace Icinga\Filter\Query;
|
namespace Icinga\Filter\Query;
|
||||||
|
|
||||||
|
use Icinga\Filter\Filterable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A binary tree representing queries in an interchangeable way
|
* A binary tree representing queries in an interchangeable way
|
||||||
*
|
*
|
||||||
@ -75,7 +76,7 @@ class Tree
|
|||||||
$node->parent = $this->lastNode;
|
$node->parent = $this->lastNode;
|
||||||
if ($this->lastNode->left == null) {
|
if ($this->lastNode->left == null) {
|
||||||
$this->lastNode->left = $node;
|
$this->lastNode->left = $node;
|
||||||
} else if($this->lastNode->right == null) {
|
} elseif ($this->lastNode->right == null) {
|
||||||
$this->lastNode->right = $node;
|
$this->lastNode->right = $node;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -99,7 +100,7 @@ class Tree
|
|||||||
|
|
||||||
if ($currentNode->type != Node::TYPE_AND) {
|
if ($currentNode->type != Node::TYPE_AND) {
|
||||||
// No AND node, insert into tree
|
// No AND node, insert into tree
|
||||||
if($currentNode->parent !== null) {
|
if ($currentNode->parent !== null) {
|
||||||
$node->parent = $currentNode->parent;
|
$node->parent = $currentNode->parent;
|
||||||
if ($currentNode->parent->left === $currentNode) {
|
if ($currentNode->parent->left === $currentNode) {
|
||||||
$currentNode->parent->left = $node;
|
$currentNode->parent->left = $node;
|
||||||
@ -144,7 +145,7 @@ class Tree
|
|||||||
{
|
{
|
||||||
if ($currentNode->type === Node::TYPE_OPERATOR) {
|
if ($currentNode->type === Node::TYPE_OPERATOR) {
|
||||||
// Always insert when encountering an operator node
|
// Always insert when encountering an operator node
|
||||||
if($currentNode->parent !== null) {
|
if ($currentNode->parent !== null) {
|
||||||
$node->parent = $currentNode->parent;
|
$node->parent = $currentNode->parent;
|
||||||
if ($currentNode->parent->left === $currentNode) {
|
if ($currentNode->parent->left === $currentNode) {
|
||||||
$currentNode->parent->left = $node;
|
$currentNode->parent->left = $node;
|
||||||
@ -168,4 +169,215 @@ class Tree
|
|||||||
$this->insertOrNode($node, $currentNode->right);
|
$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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
*/
|
*/
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
|
||||||
namespace Icinga\Filter;
|
namespace Icinga\Filter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,5 +60,4 @@ abstract class QueryProposer
|
|||||||
* @return array An array containing 0..* proposal text tokens
|
* @return array An array containing 0..* proposal text tokens
|
||||||
*/
|
*/
|
||||||
abstract public function getProposalsForQuery($query);
|
abstract public function getProposalsForQuery($query);
|
||||||
|
|
||||||
}
|
}
|
@ -121,7 +121,7 @@ class BooleanFilter extends FilterType
|
|||||||
if (self::startsWith($query, $match) && $this->subFilter) {
|
if (self::startsWith($query, $match) && $this->subFilter) {
|
||||||
$subQuery = trim(substr($query, strlen($match)));
|
$subQuery = trim(substr($query, strlen($match)));
|
||||||
$proposals = $proposals + $this->subFilter->getProposalsForQuery($subQuery);
|
$proposals = $proposals + $this->subFilter->getProposalsForQuery($subQuery);
|
||||||
} else if (strtolower($query) !== strtolower($match)) {
|
} elseif (strtolower($query) !== strtolower($match)) {
|
||||||
$proposals[] = self::markDifference($match, $query);
|
$proposals[] = self::markDifference($match, $query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,9 +146,9 @@ class BooleanFilter extends FilterType
|
|||||||
foreach ($operators as $operator) {
|
foreach ($operators as $operator) {
|
||||||
if (strtolower($operator) === strtolower($query)) {
|
if (strtolower($operator) === strtolower($query)) {
|
||||||
$proposals += array_values($this->fields);
|
$proposals += array_values($this->fields);
|
||||||
} else if (self::startsWith($operator, $query)) {
|
} elseif (self::startsWith($operator, $query)) {
|
||||||
$proposals[] = self::markDifference($operator, $query);
|
$proposals[] = self::markDifference($operator, $query);
|
||||||
} else if (self::startsWith($query, $operator)) {
|
} elseif (self::startsWith($query, $operator)) {
|
||||||
$fieldPart = trim(substr($query, strlen($operator)));
|
$fieldPart = trim(substr($query, strlen($operator)));
|
||||||
$proposals = $proposals + $this->getFieldProposals($fieldPart);
|
$proposals = $proposals + $this->getFieldProposals($fieldPart);
|
||||||
}
|
}
|
||||||
@ -232,5 +232,4 @@ class BooleanFilter extends FilterType
|
|||||||
}
|
}
|
||||||
return $node;
|
return $node;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -73,7 +73,7 @@ abstract class FilterType extends QueryProposer
|
|||||||
*
|
*
|
||||||
* @return bool True when $string starts with $substring
|
* @return bool True when $string starts with $substring
|
||||||
*/
|
*/
|
||||||
static public function startsWith($string, $substring)
|
public static function startsWith($string, $substring)
|
||||||
{
|
{
|
||||||
return stripos($string, $substring) === 0;
|
return stripos($string, $substring) === 0;
|
||||||
}
|
}
|
||||||
@ -90,7 +90,7 @@ abstract class FilterType extends QueryProposer
|
|||||||
$matchingOperator = '';
|
$matchingOperator = '';
|
||||||
foreach ($this->getOperators() as $operator) {
|
foreach ($this->getOperators() as $operator) {
|
||||||
if (stripos($query, $operator) === 0) {
|
if (stripos($query, $operator) === 0) {
|
||||||
if (strlen($matchingOperator) < strlen($operator) ){
|
if (strlen($matchingOperator) < strlen($operator)) {
|
||||||
$matchingOperator = $operator;
|
$matchingOperator = $operator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,10 +26,8 @@
|
|||||||
*/
|
*/
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
|
||||||
namespace Icinga\Filter\Type;
|
namespace Icinga\Filter\Type;
|
||||||
|
|
||||||
|
|
||||||
use Icinga\Filter\Query\Node;
|
use Icinga\Filter\Query\Node;
|
||||||
|
|
||||||
class TextFilter extends FilterType
|
class TextFilter extends FilterType
|
||||||
@ -78,7 +76,7 @@ class TextFilter extends FilterType
|
|||||||
foreach ($operators as $operator) {
|
foreach ($operators as $operator) {
|
||||||
if (strtolower($operator) === strtolower($query)) {
|
if (strtolower($operator) === strtolower($query)) {
|
||||||
$proposals += array('\'' . $this->getProposalsForValues($operator) . '\'');
|
$proposals += array('\'' . $this->getProposalsForValues($operator) . '\'');
|
||||||
} else if (self::startsWith($operator, $query)) {
|
} elseif (self::startsWith($operator, $query)) {
|
||||||
$proposals[] = self::markDifference($operator, $query);
|
$proposals[] = self::markDifference($operator, $query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,10 +164,10 @@ class TextFilter extends FilterType
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (strtolower($operator)) {
|
switch (strtolower($operator)) {
|
||||||
case 'starts with':
|
case 'ends with':
|
||||||
$value = '*' . $value;
|
$value = '*' . $value;
|
||||||
break;
|
break;
|
||||||
case 'ends with':
|
case 'starts with':
|
||||||
$value = $value . '*';
|
$value = $value . '*';
|
||||||
break;
|
break;
|
||||||
case 'matches':
|
case 'matches':
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
namespace Icinga\Filter\Type;
|
namespace Icinga\Filter\Type;
|
||||||
|
|
||||||
use Icinga\Filter\Query\Node;
|
use Icinga\Filter\Query\Node;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -104,8 +105,8 @@ class TimeRangeSpecifier extends FilterType
|
|||||||
* Return a two element array with the operator and the timestring parsed from the given query part
|
* 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
|
* @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
|
* @return array An array containing the operator as the first and the string for
|
||||||
* value or (null,null) if the query is invalid
|
* strotime as the second value or (null,null) if the query is invalid
|
||||||
*/
|
*/
|
||||||
private function getOperatorAndTimeStringFromQuery($query)
|
private function getOperatorAndTimeStringFromQuery($query)
|
||||||
{
|
{
|
||||||
@ -123,9 +124,9 @@ class TimeRangeSpecifier extends FilterType
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_numeric($query[0])) {
|
if (is_numeric($query[0])) {
|
||||||
if($this->forcedPrefix) {
|
if ($this->forcedPrefix) {
|
||||||
$prefix = $this->forcedPrefix;
|
$prefix = $this->forcedPrefix;
|
||||||
} elseif($currentOperator === Node::OPERATOR_GREATER_EQ) {
|
} elseif ($currentOperator === Node::OPERATOR_GREATER_EQ) {
|
||||||
$prefix = '-';
|
$prefix = '-';
|
||||||
} else {
|
} else {
|
||||||
$prefix = '+';
|
$prefix = '+';
|
||||||
|
@ -26,43 +26,85 @@
|
|||||||
*/
|
*/
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
|
||||||
namespace Icinga\Web\Widget;
|
namespace Icinga\Web\Widget;
|
||||||
|
|
||||||
|
|
||||||
use Icinga\Filter\Query\Tree;
|
use Icinga\Filter\Query\Tree;
|
||||||
use Icinga\Filter\Query\Node;
|
use Icinga\Filter\Query\Node;
|
||||||
|
use Icinga\Module\Monitoring\Filter\UrlViewFilter;
|
||||||
|
use Icinga\Web\Url;
|
||||||
|
|
||||||
use Zend_View_Abstract;
|
use Zend_View_Abstract;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A renderer for filter badges that allow to disable specific filters
|
||||||
|
*/
|
||||||
class FilterBadgeRenderer implements Widget
|
class FilterBadgeRenderer implements Widget
|
||||||
{
|
{
|
||||||
private $tree;
|
private $tree;
|
||||||
|
/**
|
||||||
|
* @var Url
|
||||||
|
*/
|
||||||
|
private $baseUrl;
|
||||||
private $conjunctionCellar = '';
|
private $conjunctionCellar = '';
|
||||||
|
private $urlFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new badge renderer for this tree
|
||||||
|
*
|
||||||
|
* @param Tree $tree
|
||||||
|
*/
|
||||||
public function __construct(Tree $tree)
|
public function __construct(Tree $tree)
|
||||||
{
|
{
|
||||||
$this->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)
|
private function nodeToBadge(Node $node)
|
||||||
{
|
{
|
||||||
|
$basePath = $this->baseUrl->getAbsoluteUrl();
|
||||||
|
$allParams = $this->baseUrl->getParams();
|
||||||
|
|
||||||
if ($node->type === Node::TYPE_OPERATOR) {
|
if ($node->type === Node::TYPE_OPERATOR) {
|
||||||
return ' <a class="btn btn-default btn-xs">'
|
|
||||||
|
$newTree = $this->tree->withoutNode($node);
|
||||||
|
$url = $this->urlFilter->fromTree($newTree);
|
||||||
|
$url = $basePath . (empty($allParams) ? '?' : '&') . $url;
|
||||||
|
|
||||||
|
return ' <a class="btn btn-default btn-xs" href="' . $url . '">'
|
||||||
. $this->conjunctionCellar . ' '
|
. $this->conjunctionCellar . ' '
|
||||||
. ucfirst($node->left) . ' '
|
. ucfirst($node->left) . ' '
|
||||||
. $node->operator . ' '
|
. $node->operator . ' '
|
||||||
. $node->right . '</a>';
|
. $node->right . '</a>';
|
||||||
}
|
}
|
||||||
$result = '';
|
$result = '';
|
||||||
|
|
||||||
$result .= $this->nodeToBadge($node->left);
|
$result .= $this->nodeToBadge($node->left);
|
||||||
$this->conjunctionCellar = $node->type;
|
$this->conjunctionCellar = $node->type;
|
||||||
$result .= $this->nodeToBadge($node->right);
|
$result .= $this->nodeToBadge($node->right);
|
||||||
|
|
||||||
return $result;
|
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
|
* Renders this widget via the given view and returns the
|
||||||
@ -73,9 +115,11 @@ class FilterBadgeRenderer implements Widget
|
|||||||
*/
|
*/
|
||||||
public function render(Zend_View_Abstract $view)
|
public function render(Zend_View_Abstract $view)
|
||||||
{
|
{
|
||||||
|
$this->urlFilter = new UrlViewFilter();
|
||||||
if ($this->tree->root == null) {
|
if ($this->tree->root == null) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
$this->buildBaseUrl();
|
||||||
return $this->nodeToBadge($this->tree->root);
|
return $this->nodeToBadge($this->tree->root);
|
||||||
}
|
}
|
||||||
}
|
}
|
119
library/Icinga/Web/Widget/FilterBox.php
Normal file
119
library/Icinga/Web/Widget/FilterBox.php
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<?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',
|
||||||
|
'filter',
|
||||||
|
array(
|
||||||
|
'label' => 'Filter Results',
|
||||||
|
'name' => 'filter',
|
||||||
|
'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);
|
||||||
|
}
|
||||||
|
}
|
@ -38,16 +38,21 @@ use Icinga\Web\Widget\Tabextension\OutputFormat;
|
|||||||
use Icinga\Web\Widget\Tabs;
|
use Icinga\Web\Widget\Tabs;
|
||||||
use Icinga\Module\Monitoring\Backend;
|
use Icinga\Module\Monitoring\Backend;
|
||||||
use Icinga\Web\Widget\SortBox;
|
use Icinga\Web\Widget\SortBox;
|
||||||
|
use Icinga\Web\Widget\FilterBox;
|
||||||
use Icinga\Application\Config as IcingaConfig;
|
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\Notification as NotificationView;
|
||||||
use Icinga\Module\Monitoring\DataView\Downtime as DowntimeView;
|
use Icinga\Module\Monitoring\DataView\Downtime as DowntimeView;
|
||||||
use Icinga\Module\Monitoring\DataView\Contact as ContactView;
|
use Icinga\Module\Monitoring\DataView\Contact as ContactView;
|
||||||
use Icinga\Module\Monitoring\DataView\Contactgroup as ContactgroupView;
|
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\Comment as CommentView;
|
||||||
use Icinga\Module\Monitoring\DataView\Groupsummary as GroupsummaryView;
|
use Icinga\Module\Monitoring\DataView\Groupsummary as GroupsummaryView;
|
||||||
use Icinga\Module\Monitoring\DataView\EventHistory as EventHistoryView;
|
use Icinga\Module\Monitoring\DataView\EventHistory as EventHistoryView;
|
||||||
|
use Icinga\Module\Monitoring\Filter\UrlViewFilter;
|
||||||
|
use Icinga\Filter\Filterable;
|
||||||
|
|
||||||
class Monitoring_ListController extends MonitoringController
|
class Monitoring_ListController extends MonitoringController
|
||||||
{
|
{
|
||||||
@ -96,8 +101,9 @@ class Monitoring_ListController extends MonitoringController
|
|||||||
*/
|
*/
|
||||||
public function hostsAction()
|
public function hostsAction()
|
||||||
{
|
{
|
||||||
|
|
||||||
$this->compactView = 'hosts-compact';
|
$this->compactView = 'hosts-compact';
|
||||||
$query = HostAndServiceStatusView::fromRequest(
|
$dataview = HostStatusView::fromRequest(
|
||||||
$this->_request,
|
$this->_request,
|
||||||
array(
|
array(
|
||||||
'host_icon_image',
|
'host_icon_image',
|
||||||
@ -123,8 +129,9 @@ class Monitoring_ListController extends MonitoringController
|
|||||||
'host_current_check_attempt',
|
'host_current_check_attempt',
|
||||||
'host_max_check_attempts'
|
'host_max_check_attempts'
|
||||||
)
|
)
|
||||||
)->getQuery();
|
);
|
||||||
$this->view->hosts = $query->paginate();
|
$query = $dataview->getQuery();
|
||||||
|
$this->setupFilterControl($dataview);
|
||||||
$this->setupSortControl(array(
|
$this->setupSortControl(array(
|
||||||
'host_last_check' => 'Last Host Check',
|
'host_last_check' => 'Last Host Check',
|
||||||
'host_severity' => 'Host Severity',
|
'host_severity' => 'Host Severity',
|
||||||
@ -134,6 +141,8 @@ class Monitoring_ListController extends MonitoringController
|
|||||||
'host_state' => 'Hard State'
|
'host_state' => 'Hard State'
|
||||||
));
|
));
|
||||||
$this->handleFormatRequest($query);
|
$this->handleFormatRequest($query);
|
||||||
|
$this->view->hosts = $query->paginate();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -390,7 +399,8 @@ class Monitoring_ListController extends MonitoringController
|
|||||||
$this->_helper->viewRenderer($this->compactView);
|
$this->_helper->viewRenderer($this->compactView);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->_getParam('format') === 'sql'
|
|
||||||
|
if ($this->getParam('format') === 'sql'
|
||||||
&& IcingaConfig::app()->global->get('environment', 'production') === 'development') {
|
&& IcingaConfig::app()->global->get('environment', 'production') === 'development') {
|
||||||
echo '<pre>'
|
echo '<pre>'
|
||||||
. htmlspecialchars(wordwrap($query->dump()))
|
. htmlspecialchars(wordwrap($query->dump()))
|
||||||
@ -426,6 +436,17 @@ class Monitoring_ListController extends MonitoringController
|
|||||||
$this->view->sortControl->applyRequest($this->getRequest());
|
$this->view->sortControl->applyRequest($this->getRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function setupFilterControl(Filterable $dataview)
|
||||||
|
{
|
||||||
|
$parser = new UrlViewFilter($dataview);
|
||||||
|
$this->view->filterBox = new FilterBox(
|
||||||
|
$parser->parseUrl(),
|
||||||
|
'host',
|
||||||
|
'monitoring'
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all tabs for this controller
|
* Return all tabs for this controller
|
||||||
*
|
*
|
||||||
|
@ -5,8 +5,19 @@ $viewHelper = $this->getHelper('MonitoringState');
|
|||||||
<?= $this->tabs->render($this); ?>
|
<?= $this->tabs->render($this); ?>
|
||||||
<h1>Hosts Status</h1>
|
<h1>Hosts Status</h1>
|
||||||
<div data-icinga-component="app/mainDetailGrid">
|
<div data-icinga-component="app/mainDetailGrid">
|
||||||
<?= $this->sortControl->render($this); ?>
|
<div class="container">
|
||||||
<?= $this->paginationControl($hosts, null, null, array('preserve' => $this->preserve)); ?>
|
<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">
|
<table class="table table-condensed">
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -1,23 +1,50 @@
|
|||||||
<?php
|
<?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;
|
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||||
|
|
||||||
use Icinga\Data\Db\Query;
|
use Icinga\Data\Db\Query;
|
||||||
use Icinga\Application\Benchmark;
|
use Icinga\Application\Benchmark;
|
||||||
use Icinga\Exception\ProgrammingError;
|
use Icinga\Exception\ProgrammingError;
|
||||||
|
use Icinga\Filter\Query\Tree;
|
||||||
|
use Icinga\Filter\Filterable;
|
||||||
|
use Icinga\Module\Monitoring\Filter\Backend\IdoQueryConverter;
|
||||||
|
use Icinga\Module\Monitoring\Filter\UrlViewFilter;
|
||||||
|
|
||||||
abstract class AbstractQuery extends Query
|
abstract class AbstractQuery extends Query implements Filterable
|
||||||
{
|
{
|
||||||
protected $prefix;
|
protected $prefix;
|
||||||
|
|
||||||
protected $idxAliasColumn;
|
protected $idxAliasColumn;
|
||||||
protected $idxAliasTable;
|
protected $idxAliasTable;
|
||||||
protected $columnMap = array();
|
protected $columnMap = array();
|
||||||
|
|
||||||
protected $query;
|
protected $query;
|
||||||
protected $customVars = array();
|
protected $customVars = array();
|
||||||
protected $joinedVirtualTables = array();
|
protected $joinedVirtualTables = array();
|
||||||
|
|
||||||
protected $object_id = 'object_id';
|
protected $object_id = 'object_id';
|
||||||
protected $host_id = 'host_id';
|
protected $host_id = 'host_id';
|
||||||
protected $hostgroup_id = 'hostgroup_id';
|
protected $hostgroup_id = 'hostgroup_id';
|
||||||
@ -25,16 +52,62 @@ abstract class AbstractQuery extends Query
|
|||||||
protected $servicegroup_id = 'servicegroup_id';
|
protected $servicegroup_id = 'servicegroup_id';
|
||||||
protected $contact_id = 'contact_id';
|
protected $contact_id = 'contact_id';
|
||||||
protected $contactgroup_id = 'contactgroup_id';
|
protected $contactgroup_id = 'contactgroup_id';
|
||||||
|
|
||||||
protected $aggregateColumnIdx = array();
|
protected $aggregateColumnIdx = array();
|
||||||
|
|
||||||
protected $allowCustomVars = false;
|
protected $allowCustomVars = false;
|
||||||
|
|
||||||
protected function isAggregateColumn($column)
|
public function isAggregateColumn($column)
|
||||||
{
|
{
|
||||||
return array_key_exists($column, $this->aggregateColumnIdx);
|
return array_key_exists($column, $this->aggregateColumnIdx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 applyFilter(Tree $filter)
|
||||||
|
{
|
||||||
|
foreach ($filter->getAttributes() as $target) {
|
||||||
|
$this->requireColumn($target);
|
||||||
|
}
|
||||||
|
$converter = new IdoQueryConverter($this);
|
||||||
|
$converter->treeToSql($filter, $this->baseQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isValidFilterTarget($field)
|
||||||
|
{
|
||||||
|
return $this->getMappedField($field) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMappedField($field)
|
||||||
|
{
|
||||||
|
foreach ($this->columnMap as $columnSource => $columnSet) {
|
||||||
|
if (isset($columnSet[$field])) {
|
||||||
|
return $columnSet[$field];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isTimestamp($field)
|
||||||
|
{
|
||||||
|
$mapped = $this->getMappedField($field);
|
||||||
|
if ($mapped === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return stripos($mapped, 'UNIX_TIMESTAMP') !== false;
|
||||||
|
}
|
||||||
|
|
||||||
protected function init()
|
protected function init()
|
||||||
{
|
{
|
||||||
parent::init();
|
parent::init();
|
||||||
@ -65,11 +138,136 @@ abstract class AbstractQuery extends Query
|
|||||||
$this->prepareAliasIndexes();
|
$this->prepareAliasIndexes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 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 beforeCreatingCountQuery()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function beforeCreatingSelectQuery()
|
||||||
|
{
|
||||||
|
$this->setRealColumns();
|
||||||
|
$classParts = explode('\\', get_class($this));
|
||||||
|
Benchmark::measure(sprintf('%s ready to run', array_pop($classParts)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRealColumns()
|
||||||
|
{
|
||||||
|
$columns = $this->columns;
|
||||||
|
$this->columns = array();
|
||||||
|
if (empty($columns)) {
|
||||||
|
$columns = $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 getDefaultColumns()
|
||||||
|
{
|
||||||
|
reset($this->columnMap);
|
||||||
|
$table = key($this->columnMap);
|
||||||
|
return array_keys($this->columnMap[$table]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 aliasToTableName($alias)
|
||||||
|
{
|
||||||
|
return $this->idxAliasTable[$alias];
|
||||||
|
}
|
||||||
|
|
||||||
protected function isCustomVar($alias)
|
protected function isCustomVar($alias)
|
||||||
{
|
{
|
||||||
return $this->allowCustomVars && $alias[0] === '_';
|
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)
|
protected function joinCustomvar($customvar)
|
||||||
{
|
{
|
||||||
// TODO: This is not generic enough yet
|
// TODO: This is not generic enough yet
|
||||||
@ -103,200 +301,6 @@ abstract class AbstractQuery extends Query
|
|||||||
return $this;
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
public 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)
|
protected function customvarNameToTypeName($customvar)
|
||||||
{
|
{
|
||||||
// TODO: Improve this:
|
// TODO: Improve this:
|
||||||
@ -311,106 +315,27 @@ abstract class AbstractQuery extends Query
|
|||||||
return array($m[1], $m[2]);
|
return array($m[1], $m[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function prepareFilterStringForColumn($column, $value)
|
protected function hasJoinedVirtualTable($name)
|
||||||
{
|
{
|
||||||
$filter = '';
|
return array_key_exists($name, $this->joinedVirtualTables);
|
||||||
$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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMappedColumn($name)
|
protected function getCustomvarColumnName($customvar)
|
||||||
{
|
{
|
||||||
foreach ($this->columnMap as $column => $results) {
|
return $this->customVars[$customvar] . '.varvalue';
|
||||||
if (isset($results[$name])) {
|
}
|
||||||
return $results[$name];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,49 +1,73 @@
|
|||||||
<?php
|
<?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;
|
namespace Icinga\Module\Monitoring\Backend\Ido\Query;
|
||||||
|
|
||||||
class HoststatusQuery extends AbstractQuery
|
class HoststatusQuery extends AbstractQuery
|
||||||
{
|
{
|
||||||
protected $allowCustomVars = true;
|
protected $allowCustomVars = true;
|
||||||
|
|
||||||
protected $columnMap = array(
|
protected $columnMap = array(
|
||||||
'hosts' => array(
|
'hosts' => array(
|
||||||
'host' => 'ho.name1 COLLATE latin1_general_ci',
|
'host' => 'ho.name1 COLLATE latin1_general_ci',
|
||||||
'host_name' => 'ho.name1 COLLATE latin1_general_ci',
|
'host_name' => 'ho.name1 COLLATE latin1_general_ci',
|
||||||
'host_display_name' => 'h.display_name',
|
'host_display_name' => 'h.display_name',
|
||||||
'host_alias' => 'h.alias',
|
'host_alias' => 'h.alias',
|
||||||
'host_address' => 'h.address',
|
'host_address' => 'h.address',
|
||||||
'host_ipv4' => 'INET_ATON(h.address)',
|
'host_ipv4' => 'INET_ATON(h.address)',
|
||||||
'host_icon_image' => 'h.icon_image',
|
'host_icon_image' => 'h.icon_image',
|
||||||
),
|
),
|
||||||
'hoststatus' => array(
|
'hoststatus' => array(
|
||||||
'problems' => 'CASE WHEN hs.current_state = 0 THEN 0 ELSE 1 END',
|
'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',
|
'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',
|
'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_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_output' => 'hs.output',
|
||||||
'host_long_output' => 'hs.long_output',
|
'host_long_output' => 'hs.long_output',
|
||||||
'host_perfdata' => 'hs.perfdata',
|
'host_perfdata' => 'hs.perfdata',
|
||||||
'host_problem' => 'CASE WHEN hs.current_state = 0 THEN 0 ELSE 1 END',
|
'host_problem' => 'CASE WHEN hs.current_state = 0 THEN 0 ELSE 1 END',
|
||||||
'host_acknowledged' => 'hs.problem_has_been_acknowledged',
|
'host_acknowledged' => 'hs.problem_has_been_acknowledged',
|
||||||
'host_in_downtime' => 'CASE WHEN (hs.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END',
|
'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_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_does_active_checks' => 'hs.active_checks_enabled',
|
||||||
'host_accepts_passive_checks' => 'hs.passive_checks_enabled',
|
'host_accepts_passive_checks' => 'hs.passive_checks_enabled',
|
||||||
'host_last_state_change' => 'UNIX_TIMESTAMP(hs.last_state_change)',
|
'host_last_state_change' => 'UNIX_TIMESTAMP(hs.last_state_change)',
|
||||||
'host_last_hard_state' => 'hs.last_hard_state',
|
'host_last_hard_state' => 'hs.last_hard_state',
|
||||||
'host_check_command' => 'hs.check_command',
|
'host_check_command' => 'hs.check_command',
|
||||||
'host_last_check' => 'UNIX_TIMESTAMP(hs.last_check)',
|
'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_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_execution_time' => 'hs.execution_time',
|
||||||
'host_check_latency' => 'hs.latency',
|
'host_check_latency' => 'hs.latency',
|
||||||
'host_notifications_enabled' => 'hs.notifications_enabled',
|
'host_notifications_enabled' => 'hs.notifications_enabled',
|
||||||
'host_last_time_up' => 'hs.last_time_up',
|
'host_last_time_up' => 'hs.last_time_up',
|
||||||
'host_last_time_down' => 'hs.last_time_down',
|
'host_last_time_down' => 'hs.last_time_down',
|
||||||
'host_last_time_unreachable' => 'hs.last_time_unreachable',
|
'host_last_time_unreachable' => 'hs.last_time_unreachable',
|
||||||
'host_current_check_attempt' => 'hs.current_check_attempt',
|
'host_current_check_attempt' => 'hs.current_check_attempt',
|
||||||
'host_max_check_attempts' => 'hs.max_check_attempts',
|
'host_max_check_attempts' => 'hs.max_check_attempts',
|
||||||
|
|
||||||
'host_severity' => 'CASE WHEN hs.current_state = 0
|
'host_severity' => 'CASE WHEN hs.current_state = 0
|
||||||
THEN
|
THEN
|
||||||
CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL
|
CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL
|
||||||
@ -86,37 +110,35 @@ class HoststatusQuery extends AbstractQuery
|
|||||||
'contact' => 'hco.name1 COLLATE latin1_general_ci',
|
'contact' => 'hco.name1 COLLATE latin1_general_ci',
|
||||||
),
|
),
|
||||||
'services' => array(
|
'services' => array(
|
||||||
'services_cnt' => 'SUM(1)',
|
'services_cnt' => 'SUM(1)',
|
||||||
'services_ok' => 'SUM(CASE WHEN ss.current_state = 0 THEN 1 ELSE 0 END)',
|
'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_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_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_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_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' => '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_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_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_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_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_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_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_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(
|
protected $aggregateColumnIdx = array(
|
||||||
'services_cnt' => true,
|
'services_cnt' => true,
|
||||||
'services_problem' => true,
|
'services_problem' => true,
|
||||||
'services_problem_handled' => true,
|
'services_problem_handled' => true,
|
||||||
'services_problem_unhandled' => true,
|
'services_problem_unhandled' => true,
|
||||||
);
|
);
|
||||||
|
|
||||||
protected $hcgSub;
|
protected $hcgSub;
|
||||||
|
|
||||||
protected function getDefaultColumns()
|
protected function getDefaultColumns()
|
||||||
{
|
{
|
||||||
return $this->columnMap['hosts']
|
return $this->columnMap['hosts']
|
||||||
+ $this->columnMap['hoststatus'];
|
+ $this->columnMap['hoststatus'];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function joinBaseTables()
|
protected function joinBaseTables()
|
||||||
@ -126,16 +148,16 @@ class HoststatusQuery extends AbstractQuery
|
|||||||
array('ho' => $this->prefix . 'objects'),
|
array('ho' => $this->prefix . 'objects'),
|
||||||
array()
|
array()
|
||||||
)->join(
|
)->join(
|
||||||
array('hs' => $this->prefix . 'hoststatus'),
|
array('hs' => $this->prefix . 'hoststatus'),
|
||||||
'ho.' . $this->object_id . ' = hs.host_object_id AND ho.is_active = 1 AND ho.objecttype_id = 1',
|
'ho.' . $this->object_id . ' = hs.host_object_id AND ho.is_active = 1 AND ho.objecttype_id = 1',
|
||||||
array()
|
array()
|
||||||
)->join(
|
)->join(
|
||||||
array('h' => $this->prefix . 'hosts'),
|
array('h' => $this->prefix . 'hosts'),
|
||||||
'hs.host_object_id = h.host_object_id',
|
'hs.host_object_id = h.host_object_id',
|
||||||
array()
|
array()
|
||||||
);
|
);
|
||||||
$this->joinedVirtualTables = array(
|
$this->joinedVirtualTables = array(
|
||||||
'hosts' => true,
|
'hosts' => true,
|
||||||
'hoststatus' => true,
|
'hoststatus' => true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -157,14 +179,14 @@ class HoststatusQuery extends AbstractQuery
|
|||||||
's.host_object_id = h.host_object_id',
|
's.host_object_id = h.host_object_id',
|
||||||
array()
|
array()
|
||||||
)->join(
|
)->join(
|
||||||
array('so' => $this->prefix . 'objects'),
|
array('so' => $this->prefix . 'objects'),
|
||||||
"so.$this->object_id = s.service_object_id AND so.is_active = 1",
|
"so.$this->object_id = s.service_object_id AND so.is_active = 1",
|
||||||
array()
|
array()
|
||||||
)->joinLeft(
|
)->joinLeft(
|
||||||
array('ss' => $this->prefix . 'servicestatus'),
|
array('ss' => $this->prefix . 'servicestatus'),
|
||||||
"so.$this->object_id = ss.service_object_id",
|
"so.$this->object_id = ss.service_object_id",
|
||||||
array()
|
array()
|
||||||
);
|
);
|
||||||
foreach ($this->columns as $col) {
|
foreach ($this->columns as $col) {
|
||||||
$real = $this->aliasToColumnName($col);
|
$real = $this->aliasToColumnName($col);
|
||||||
if (substr($real, 0, 4) === 'SUM(') {
|
if (substr($real, 0, 4) === 'SUM(') {
|
||||||
@ -184,25 +206,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()
|
protected function joinContacts()
|
||||||
{
|
{
|
||||||
$this->hcgcSub = $this->db->select()->distinct()->from(
|
$this->hcgcSub = $this->db->select()->distinct()->from(
|
||||||
array('hcgc' => $this->prefix . 'host_contactgroups'),
|
array('hcgc' => $this->prefix . 'host_contactgroups'),
|
||||||
array('host_name' => 'ho.name1')
|
array('host_name' => 'ho.name1')
|
||||||
)->join(
|
)->join(
|
||||||
array('cgo' => $this->prefix . 'objects'),
|
array('cgo' => $this->prefix . 'objects'),
|
||||||
'hcg.contactgroup_object_id = cgo.' . $this->object_id
|
'hcg.contactgroup_object_id = cgo.' . $this->object_id
|
||||||
. ' AND cgo.is_active = 1',
|
. ' AND cgo.is_active = 1',
|
||||||
array()
|
array()
|
||||||
)->join(
|
)->join(
|
||||||
array('h' => $this->prefix . 'hosts'),
|
array('h' => $this->prefix . 'hosts'),
|
||||||
'hcg.host_id = h.host_id',
|
'hcg.host_id = h.host_id',
|
||||||
array()
|
array()
|
||||||
)->join(
|
)->join(
|
||||||
array('ho' => $this->prefix . 'objects'),
|
array('ho' => $this->prefix . 'objects'),
|
||||||
'h.host_object_id = ho.' . $this->object_id . ' AND ho.is_active = 1',
|
'h.host_object_id = ho.' . $this->object_id . ' AND ho.is_active = 1',
|
||||||
array()
|
array()
|
||||||
);
|
);
|
||||||
$this->baseQuery->join(
|
$this->baseQuery->join(
|
||||||
array('hcg' => $this->hcgSub),
|
array('hcg' => $this->hcgSub),
|
||||||
'hcg.host_name = ho.name1',
|
'hcg.host_name = ho.name1',
|
||||||
@ -212,48 +274,6 @@ class HoststatusQuery extends AbstractQuery
|
|||||||
return $this;
|
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)
|
protected function filterContactgroup($value)
|
||||||
{
|
{
|
||||||
$this->hcgSub->where(
|
$this->hcgSub->where(
|
||||||
@ -265,28 +285,6 @@ class HoststatusQuery extends AbstractQuery
|
|||||||
return $this;
|
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()
|
protected function joinContactgroups()
|
||||||
{
|
{
|
||||||
$this->hcgSub = $this->createContactgroupFilterSubselect();
|
$this->hcgSub = $this->createContactgroupFilterSubselect();
|
||||||
@ -299,44 +297,25 @@ class HoststatusQuery extends AbstractQuery
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function joinHostHostgroups()
|
protected function createContactgroupFilterSubselect()
|
||||||
{
|
{
|
||||||
$this->baseQuery->join(
|
die((string)$this->db->select()->distinct()->from(
|
||||||
array('hgm' => $this->prefix . 'hostgroup_members'),
|
array('hcg' => $this->prefix . 'host_contactgroups'),
|
||||||
'hgm.host_object_id = h.host_object_id',
|
array('object_id' => 'ho.object_id')
|
||||||
array()
|
|
||||||
)->join(
|
)->join(
|
||||||
array('hg' => $this->prefix . 'hostgroups'),
|
array('cgo' => $this->prefix . 'objects'),
|
||||||
"hgm.hostgroup_id = hg.$this->hostgroup_id",
|
'hcg.contactgroup_object_id = cgo.' . $this->object_id
|
||||||
array()
|
. ' AND cgo.is_active = 1',
|
||||||
)->join(
|
array()
|
||||||
array('hgo' => $this->prefix . 'objects'),
|
)->join(
|
||||||
'hgo.' . $this->object_id. ' = hg.hostgroup_object_id'
|
array('h' => $this->prefix . 'hosts'),
|
||||||
. ' AND hgo.is_active = 1',
|
'hcg.host_id = h.host_id',
|
||||||
array()
|
array()
|
||||||
);
|
)->join(
|
||||||
|
array('ho' => $this->prefix . 'objects'),
|
||||||
return $this;
|
'h.host_object_id = ho.' . $this->object_id . ' AND ho.is_active = 1',
|
||||||
}
|
array()
|
||||||
|
));
|
||||||
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 joinServicegroups()
|
protected function joinServicegroups()
|
||||||
@ -348,15 +327,15 @@ class HoststatusQuery extends AbstractQuery
|
|||||||
'sgm.service_object_id = s.service_object_id',
|
'sgm.service_object_id = s.service_object_id',
|
||||||
array()
|
array()
|
||||||
)->join(
|
)->join(
|
||||||
array('sg' => $this->prefix . 'servicegroups'),
|
array('sg' => $this->prefix . 'servicegroups'),
|
||||||
'sgm.servicegroup_id = sg.' . $this->servicegroup_id,
|
'sgm.servicegroup_id = sg.' . $this->servicegroup_id,
|
||||||
array()
|
array()
|
||||||
)->join(
|
)->join(
|
||||||
array('sgo' => $this->prefix . 'objects'),
|
array('sgo' => $this->prefix . 'objects'),
|
||||||
'sgo.' . $this->object_id. ' = sg.servicegroup_object_id'
|
'sgo.' . $this->object_id . ' = sg.servicegroup_object_id'
|
||||||
. ' AND sgo.is_active = 1',
|
. ' AND sgo.is_active = 1',
|
||||||
array()
|
array()
|
||||||
);
|
);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ namespace \Icinga\Module\Monitoring\Backend\Livestatus\Query;
|
|||||||
|
|
||||||
use Icinga\Data\AbstractQuery;
|
use Icinga\Data\AbstractQuery;
|
||||||
|
|
||||||
class StatusQuery extends AbstractQuery
|
class StatusQuery extends AbstractQuery implements Filterable
|
||||||
{
|
{
|
||||||
protected $available_columns = array(
|
protected $available_columns = array(
|
||||||
'host_name',
|
'host_name',
|
||||||
|
@ -28,17 +28,19 @@
|
|||||||
|
|
||||||
namespace Icinga\Module\Monitoring\Backend\Statusdat\Query;
|
namespace Icinga\Module\Monitoring\Backend\Statusdat\Query;
|
||||||
|
|
||||||
use \Icinga\Module\Monitoring\Backend\Statusdat\Criteria\Order;
|
use Icinga\Filter\Query\Tree;
|
||||||
use Icinga\Protocol\Statusdat;
|
use Icinga\Protocol\Statusdat;
|
||||||
use Icinga\Exception;
|
use Icinga\Exception;
|
||||||
use Icinga\Data\AbstractQuery;
|
use Icinga\Data\AbstractQuery;
|
||||||
use Icinga\Protocol\Statusdat\View\MonitoringObjectList as MList;
|
use Icinga\Protocol\Statusdat\View\MonitoringObjectList as MList;
|
||||||
use Icinga\Protocol\Statusdat\Query as StatusdatQuery;
|
use Icinga\Protocol\Statusdat\Query as StatusdatQuery;
|
||||||
|
use Icinga\Filter\Filterable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Query
|
* Class Query
|
||||||
* @package Icinga\Backend\Statusdat
|
* @package Icinga\Backend\Statusdat
|
||||||
*/
|
*/
|
||||||
abstract class Query extends AbstractQuery
|
abstract class Query extends AbstractQuery implements Filterable
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var null
|
* @var null
|
||||||
@ -284,7 +286,22 @@ abstract class Query extends AbstractQuery
|
|||||||
*/
|
*/
|
||||||
public function count()
|
public function count()
|
||||||
{
|
{
|
||||||
|
|
||||||
return count($this->baseQuery->getResult());
|
return count($this->baseQuery->getResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isValidFilterTarget($field)
|
||||||
|
{
|
||||||
|
// TODO: Implement isValidFilterTarget() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMappedField($field)
|
||||||
|
{
|
||||||
|
// TODO: Implement getMappedField() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyFilter(Tree $filter)
|
||||||
|
{
|
||||||
|
// TODO: Implement applyFilter() method.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,14 +5,25 @@
|
|||||||
namespace Icinga\Module\Monitoring\DataView;
|
namespace Icinga\Module\Monitoring\DataView;
|
||||||
|
|
||||||
use Icinga\Data\AbstractQuery;
|
use Icinga\Data\AbstractQuery;
|
||||||
|
use Icinga\Filter\Filterable;
|
||||||
|
use Icinga\Filter\Query\Tree;
|
||||||
use Icinga\Module\Monitoring\Backend;
|
use Icinga\Module\Monitoring\Backend;
|
||||||
|
use Icinga\Module\Monitoring\Filter\UrlViewFilter;
|
||||||
use Icinga\Web\Request;
|
use Icinga\Web\Request;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A read-only view of an underlying Query
|
* A read-only view of an underlying Query
|
||||||
*/
|
*/
|
||||||
abstract class DataView
|
abstract class DataView implements Filterable
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Sort in ascending order, default
|
||||||
|
*/
|
||||||
|
const SORT_ASC = AbstractQuery::SORT_ASC;
|
||||||
|
/**
|
||||||
|
* Sort in reverse order
|
||||||
|
*/
|
||||||
|
const SORT_DESC = AbstractQuery::SORT_DESC;
|
||||||
/**
|
/**
|
||||||
* The query used to populate the view
|
* The query used to populate the view
|
||||||
*
|
*
|
||||||
@ -20,25 +31,17 @@ abstract class DataView
|
|||||||
*/
|
*/
|
||||||
private $query;
|
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
|
* Create a new view
|
||||||
*
|
*
|
||||||
* @param Backend $ds Which backend to query
|
* @param Backend $ds Which backend to query
|
||||||
* @param array $columns Select columns
|
* @param array $columns Select columns
|
||||||
*/
|
*/
|
||||||
public function __construct(Backend $ds, array $columns = null)
|
public function __construct(Backend $ds, array $columns = null)
|
||||||
{
|
{
|
||||||
|
$filter = new UrlViewFilter($this);
|
||||||
$this->query = $ds->select()->from(static::getTableName(), $columns === null ? $this->getColumns() : $columns);
|
$this->query = $ds->select()->from(static::getTableName(), $columns === null ? $this->getColumns() : $columns);
|
||||||
|
$this->applyFilter($filter->parseUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,23 +63,16 @@ abstract class DataView
|
|||||||
*/
|
*/
|
||||||
abstract public function getColumns();
|
abstract public function getColumns();
|
||||||
|
|
||||||
/**
|
public function applyFilter(Tree $filter)
|
||||||
* 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();
|
return $this->query->applyFilter($filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create view from request
|
* Create view from request
|
||||||
*
|
*
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @param array $columns
|
* @param array $columns
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
@ -102,8 +98,8 @@ abstract class DataView
|
|||||||
/**
|
/**
|
||||||
* Create view from params
|
* Create view from params
|
||||||
*
|
*
|
||||||
* @param array $params
|
* @param array $params
|
||||||
* @param array $columns
|
* @param array $columns
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
@ -131,12 +127,12 @@ abstract class DataView
|
|||||||
*
|
*
|
||||||
* @param array $filters
|
* @param array $filters
|
||||||
*
|
*
|
||||||
* @see isValidFilterColumn()
|
* @see Filterable::isValidFilterTarget()
|
||||||
*/
|
*/
|
||||||
public function filter(array $filters)
|
public function filter(array $filters)
|
||||||
{
|
{
|
||||||
foreach ($filters as $column => $filter) {
|
foreach ($filters as $column => $filter) {
|
||||||
if ($this->isValidFilterColumn($column)) {
|
if ($this->isValidFilterTarget($column)) {
|
||||||
$this->query->where($column, $filter);
|
$this->query->where($column, $filter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,16 +146,21 @@ abstract class DataView
|
|||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isValidFilterColumn($column)
|
public function isValidFilterTarget($column)
|
||||||
{
|
{
|
||||||
return in_array($column, $this->getColumns()) || in_array($column, $this->getFilterColumns());
|
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
|
* Sort the rows, according to the specified sort column and order
|
||||||
*
|
*
|
||||||
* @param string $column Sort column
|
* @param string $column Sort column
|
||||||
* @param int $order Sort order, one of the SORT_ constants
|
* @param int $order Sort order, one of the SORT_ constants
|
||||||
*
|
*
|
||||||
* @see DataView::SORT_ASC
|
* @see DataView::SORT_ASC
|
||||||
* @see DataView::SORT_DESC
|
* @see DataView::SORT_DESC
|
||||||
@ -180,8 +181,8 @@ abstract class DataView
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$sortColumns = array(
|
$sortColumns = array(
|
||||||
'columns' => array($column),
|
'columns' => array($column),
|
||||||
'order' => $order
|
'order' => $order
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -191,6 +192,18 @@ abstract class DataView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the query which was created in the constructor
|
* Return the query which was created in the constructor
|
||||||
*
|
*
|
||||||
@ -200,4 +213,9 @@ abstract class DataView
|
|||||||
{
|
{
|
||||||
return $this->query;
|
return $this->query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getFilterDomain()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
101
modules/monitoring/library/Monitoring/DataView/HostStatus.php
Normal file
101
modules/monitoring/library/Monitoring/DataView/HostStatus.php
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<?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_name',
|
||||||
|
'host_state',
|
||||||
|
'host_state_type',
|
||||||
|
'host_last_state_change',
|
||||||
|
'host_address',
|
||||||
|
'host_handled',
|
||||||
|
'host_icon_image',
|
||||||
|
'host_acknowledged',
|
||||||
|
'host_output',
|
||||||
|
'host_long_output',
|
||||||
|
'host_in_downtime',
|
||||||
|
'host_is_flapping',
|
||||||
|
'host_last_check',
|
||||||
|
'host_next_check',
|
||||||
|
'host_notifications_enabled',
|
||||||
|
'host_unhandled_service_count',
|
||||||
|
'host_action_url',
|
||||||
|
'host_notes_url',
|
||||||
|
'host_last_comment',
|
||||||
|
'host',
|
||||||
|
'host_display_name',
|
||||||
|
'host_alias',
|
||||||
|
'host_ipv4',
|
||||||
|
'host_severity',
|
||||||
|
'host_perfdata',
|
||||||
|
'host_does_active_checks',
|
||||||
|
'host_accepts_passive_checks',
|
||||||
|
'host_last_hard_state',
|
||||||
|
'host_last_hard_state_change',
|
||||||
|
'host_last_time_up',
|
||||||
|
'host_last_time_down',
|
||||||
|
'host_last_time_unreachable'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function getTableName()
|
||||||
|
{
|
||||||
|
return 'status';
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
namespace Icinga\Module\Monitoring\DataView;
|
namespace Icinga\Module\Monitoring\DataView;
|
||||||
|
|
||||||
class HostAndServiceStatus extends DataView
|
class ServiceStatus extends DataView
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Retrieve columns provided by this view
|
* Retrieve columns provided by this view
|
||||||
@ -54,7 +54,6 @@ class HostAndServiceStatus extends DataView
|
|||||||
'host_display_name',
|
'host_display_name',
|
||||||
'host_alias',
|
'host_alias',
|
||||||
'host_ipv4',
|
'host_ipv4',
|
||||||
// 'host_problems',
|
|
||||||
'host_severity',
|
'host_severity',
|
||||||
'host_perfdata',
|
'host_perfdata',
|
||||||
'host_does_active_checks',
|
'host_does_active_checks',
|
||||||
@ -65,7 +64,6 @@ class HostAndServiceStatus extends DataView
|
|||||||
'host_last_time_down',
|
'host_last_time_down',
|
||||||
'host_last_time_unreachable',
|
'host_last_time_unreachable',
|
||||||
'service',
|
'service',
|
||||||
// 'current_state',
|
|
||||||
'service_hard_state',
|
'service_hard_state',
|
||||||
'service_perfdata',
|
'service_perfdata',
|
||||||
'service_does_active_checks',
|
'service_does_active_checks',
|
||||||
@ -78,13 +76,11 @@ class HostAndServiceStatus extends DataView
|
|||||||
'service_last_time_unknown',
|
'service_last_time_unknown',
|
||||||
'service_current_check_attempt',
|
'service_current_check_attempt',
|
||||||
'service_max_check_attempts'
|
'service_max_check_attempts'
|
||||||
// 'object_type',
|
|
||||||
// 'problems',
|
|
||||||
// 'handled',
|
|
||||||
// 'severity'
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static function getTableName()
|
public static function getTableName()
|
||||||
{
|
{
|
||||||
return 'status';
|
return 'status';
|
||||||
@ -121,13 +117,13 @@ class HostAndServiceStatus extends DataView
|
|||||||
return array('hostgroups', 'servicegroups', 'service_problems');
|
return array('hostgroups', 'servicegroups', 'service_problems');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isValidFilterColumn($column)
|
public function isValidFilterTarget($column)
|
||||||
{
|
{
|
||||||
if ($column[0] === '_'
|
if ($column[0] === '_'
|
||||||
&& preg_match('/^_(?:host|service)_/', $column)
|
&& preg_match('/^_(?:host|service)_/', $column)
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return parent::isValidFilterColumn($column);
|
return parent::isValidFilterTarget($column);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,33 +26,50 @@
|
|||||||
*/
|
*/
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
|
||||||
namespace Icinga\Module\Monitoring\Filter\Backend;
|
namespace Icinga\Module\Monitoring\Filter\Backend;
|
||||||
|
|
||||||
|
use Icinga\Data\DatasourceInterface;
|
||||||
|
use Icinga\Data\Db\Query;
|
||||||
use Icinga\Filter\Query\Tree;
|
use Icinga\Filter\Query\Tree;
|
||||||
use Icinga\Filter\Query\Node;
|
use Icinga\Filter\Query\Node;
|
||||||
|
use Icinga\Filter\Filterable;
|
||||||
use Icinga\Module\Monitoring\DataView\DataView;
|
use Icinga\Module\Monitoring\DataView\DataView;
|
||||||
|
use Icinga\Module\Monitoring\Backend\Ido\Query\AbstractQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converter class that takes a query tree and creates an SQL Query from it's state
|
||||||
|
*/
|
||||||
class IdoQueryConverter
|
class IdoQueryConverter
|
||||||
{
|
{
|
||||||
private $view;
|
/**
|
||||||
|
* The query class to use as the base for converting
|
||||||
|
*
|
||||||
|
* @var AbstractQuery
|
||||||
|
*/
|
||||||
private $query;
|
private $query;
|
||||||
private $params = array();
|
|
||||||
|
|
||||||
public function getParams()
|
/**
|
||||||
|
* 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(AbstractQuery $query)
|
||||||
{
|
{
|
||||||
return $this->params;
|
$this->query = $query;
|
||||||
}
|
|
||||||
|
|
||||||
public function __construct(DataView $view, array $initialParams = array())
|
|
||||||
{
|
|
||||||
$this->view = $view;
|
|
||||||
$this->query = $this->view->getQuery();
|
|
||||||
$this->params = $initialParams;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
private function getSqlOperator($operator)
|
||||||
{
|
{
|
||||||
switch($operator) {
|
switch($operator) {
|
||||||
@ -65,6 +82,12 @@ class IdoQueryConverter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
private function nodeToSqlQuery(Node $node)
|
||||||
{
|
{
|
||||||
if ($node->type !== Node::TYPE_OPERATOR) {
|
if ($node->type !== Node::TYPE_OPERATOR) {
|
||||||
@ -74,6 +97,12 @@ class IdoQueryConverter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
private function parseConjunctionNode(Node $node)
|
||||||
{
|
{
|
||||||
$queryString = '';
|
$queryString = '';
|
||||||
@ -88,33 +117,78 @@ class IdoQueryConverter
|
|||||||
return $queryString;
|
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)
|
private function parseOperatorNode(Node $node)
|
||||||
{
|
{
|
||||||
if (!$this->view->isValidFilterColumn($node->left) && $this->query->getMappedColumn($node->left)) {
|
if (!$this->query->isValidFilterTarget($node->left) && $this->query->getMappedField($node->left)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
$queryString = $this->query->getMappedColumn($node->left);
|
$queryString = $this->query->getMappedField($node->left);
|
||||||
$queryString .= ' ' . (is_integer($node->right) ? $node->operator : $this->getSqlOperator($node->operator));
|
if ($this->query->isAggregateColumn($node->left)) {
|
||||||
$queryString .= ' ? ';
|
$this->type = 'HAVING';
|
||||||
$this->params[] = $this->getParameterValue($node);
|
}
|
||||||
|
$queryString .= ' ' . (is_integer($node->right) ? $node->operator : $this->getSqlOperator($node->operator)) . ' ';
|
||||||
|
$queryString .= $this->getParameterValue($node);
|
||||||
return $queryString;
|
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) {
|
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;
|
||||||
|
}
|
||||||
switch($node->context) {
|
switch($node->context) {
|
||||||
case Node::CONTEXT_TIMESTRING:
|
case Node::CONTEXT_TIMESTRING:
|
||||||
return strtotime($node->right);
|
$value = strtotime($value);
|
||||||
default:
|
default:
|
||||||
return $node->right;
|
break;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
$sql = $this->nodeToSqlQuery($tree->root);
|
||||||
|
if ($this->filtersAggregate()) {
|
||||||
|
$baseQuery->having($sql);
|
||||||
|
} else {
|
||||||
|
$baseQuery->where($sql);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function treeToSql(Tree $tree)
|
/**
|
||||||
|
* 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()
|
||||||
{
|
{
|
||||||
if ($tree->root == null) {
|
return $this->type === 'HAVING';
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return $this->nodeToSqlQuery($tree->root);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,7 +26,6 @@
|
|||||||
*/
|
*/
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
|
||||||
namespace Icinga\Module\Monitoring\Filter;
|
namespace Icinga\Module\Monitoring\Filter;
|
||||||
|
|
||||||
use Icinga\Filter\Domain;
|
use Icinga\Filter\Domain;
|
||||||
@ -41,11 +40,9 @@ use Icinga\Module\Monitoring\Filter\Type\StatusFilter;
|
|||||||
* Factory class to create filter for different monitoring objects
|
* Factory class to create filter for different monitoring objects
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class MonitoringFilter
|
class Registry
|
||||||
{
|
{
|
||||||
|
public static function getNextCheckFilterType()
|
||||||
|
|
||||||
private static function getNextCheckFilterType()
|
|
||||||
{
|
{
|
||||||
$type = new TimeRangeSpecifier();
|
$type = new TimeRangeSpecifier();
|
||||||
$type->setOperator(
|
$type->setOperator(
|
||||||
@ -57,7 +54,7 @@ class MonitoringFilter
|
|||||||
return $type;
|
return $type;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function getLastCheckFilterType()
|
public static function getLastCheckFilterType()
|
||||||
{
|
{
|
||||||
$type = new TimeRangeSpecifier();
|
$type = new TimeRangeSpecifier();
|
||||||
$type->setOperator(
|
$type->setOperator(
|
||||||
@ -79,30 +76,31 @@ class MonitoringFilter
|
|||||||
FilterAttribute::create(new TextFilter())
|
FilterAttribute::create(new TextFilter())
|
||||||
->setHandledAttributes('Name', 'Hostname')
|
->setHandledAttributes('Name', 'Hostname')
|
||||||
->setField('host_name')
|
->setField('host_name')
|
||||||
)->registerAttribute(
|
)->registerAttribute(
|
||||||
FilterAttribute::create(StatusFilter::createForHost())
|
FilterAttribute::create(StatusFilter::createForHost())
|
||||||
->setHandledAttributes('State', 'Status', 'Current Status')
|
->setHandledAttributes('State', 'Status', 'Current Status')
|
||||||
->setField('host_state')
|
->setField('host_state')
|
||||||
)->registerAttribute(
|
)->registerAttribute(
|
||||||
FilterAttribute::create(new BooleanFilter(array(
|
FilterAttribute::create(new BooleanFilter(
|
||||||
'host_is_flapping' => 'Flapping',
|
array(
|
||||||
'host_problem' => 'In Problem State',
|
'host_is_flapping' => 'Flapping',
|
||||||
'host_notifications_enabled' => 'Sending Notifications',
|
'host_problem' => 'In Problem State',
|
||||||
'host_active_checks_enabled' => 'Active',
|
'host_notifications_enabled' => 'Sending Notifications',
|
||||||
'host_passive_checks_enabled' => 'Accepting Passive Checks',
|
'host_active_checks_enabled' => 'Active',
|
||||||
'host_handled' => 'Handled',
|
'host_passive_checks_enabled' => 'Accepting Passive Checks',
|
||||||
'host_in_downtime' => 'In Downtime',
|
'host_handled' => 'Handled',
|
||||||
)))
|
'host_in_downtime' => 'In Downtime',
|
||||||
)->registerAttribute(
|
)
|
||||||
FilterAttribute::create(self::getLastCheckFilterType())
|
))
|
||||||
->setHandledAttributes('Last Check', 'Check')
|
)->registerAttribute(
|
||||||
->setField('host_last_check')
|
FilterAttribute::create(self::getLastCheckFilterType())
|
||||||
)->registerAttribute(
|
->setHandledAttributes('Last Check', 'Check')
|
||||||
FilterAttribute::create(self::getNextCheckFilterType())
|
->setField('host_last_check')
|
||||||
->setHandledAttributes('Next Check')
|
)->registerAttribute(
|
||||||
->setField('host_next_check')
|
FilterAttribute::create(self::getNextCheckFilterType())
|
||||||
);
|
->setHandledAttributes('Next Check')
|
||||||
|
->setField('host_next_check')
|
||||||
|
);
|
||||||
return $domain;
|
return $domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -30,11 +30,15 @@
|
|||||||
namespace Icinga\Module\Monitoring\Filter;
|
namespace Icinga\Module\Monitoring\Filter;
|
||||||
|
|
||||||
|
|
||||||
|
use Icinga\Filter\Filterable;
|
||||||
use Icinga\Filter\Query\Tree;
|
use Icinga\Filter\Query\Tree;
|
||||||
use Icinga\Filter\Query\Node;
|
use Icinga\Filter\Query\Node;
|
||||||
use Icinga\Web\Url;
|
use Icinga\Web\Url;
|
||||||
use Icinga\Application\Logger;
|
use Icinga\Application\Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converter class that allows to create Query Trees from an request query and vice versa
|
||||||
|
*/
|
||||||
class UrlViewFilter
|
class UrlViewFilter
|
||||||
{
|
{
|
||||||
const FILTER_TARGET = 'target';
|
const FILTER_TARGET = 'target';
|
||||||
@ -42,27 +46,54 @@ class UrlViewFilter
|
|||||||
const FILTER_VALUE = 'value';
|
const FILTER_VALUE = 'value';
|
||||||
const FILTER_ERROR = 'error';
|
const FILTER_ERROR = 'error';
|
||||||
|
|
||||||
private function evaluateNode(Node $node)
|
/**
|
||||||
{
|
* An optional target filterable to use for validation and normalization
|
||||||
switch($node->type) {
|
*
|
||||||
|
* @var Filterable
|
||||||
|
*/
|
||||||
|
private $target;
|
||||||
|
|
||||||
case Node::TYPE_OPERATOR:
|
/**
|
||||||
return urlencode($node->left) . $node->operator . urlencode($node->right);
|
* Create a new ViewFilter
|
||||||
case Node::TYPE_AND:
|
*
|
||||||
return $this->evaluateNode($node->left) . '&' . $this->evaluateNode($node->right);
|
* @param Filterable $target An optional Filterable to use for validation and normalization
|
||||||
case Node::TYPE_OR:
|
*/
|
||||||
return $this->evaluateNode($node->left) . '|' . $this->evaluateNode($node->right);
|
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)
|
public function fromTree(Tree $filter)
|
||||||
{
|
{
|
||||||
return $this->evaluateNode($filter->root);
|
if ($filter->root === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if ($this->target) {
|
||||||
|
$filter = $filter->getCopyForFilterable($this->target);
|
||||||
|
}
|
||||||
|
return $this->convertNodeToUrlString($filter->root);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function parseUrl($query = "")
|
/**
|
||||||
|
* 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'];
|
$query = $query ? $query : $_SERVER['QUERY_STRING'];
|
||||||
|
|
||||||
$tokens = $this->tokenizeQuery($query);
|
$tokens = $this->tokenizeQuery($query);
|
||||||
$tree = new Tree();
|
$tree = new Tree();
|
||||||
foreach ($tokens as $token) {
|
foreach ($tokens as $token) {
|
||||||
@ -80,18 +111,70 @@ class UrlViewFilter
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $tree;
|
return $tree->getCopyForFilterable($this->target);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
private function tokenizeQuery($query)
|
||||||
{
|
{
|
||||||
$tokens = array();
|
$tokens = array();
|
||||||
$state = self::FILTER_TARGET;
|
$state = self::FILTER_TARGET;
|
||||||
|
$query = urldecode($query);
|
||||||
|
|
||||||
for ($i = 0;$i <= strlen($query); $i++) {
|
for ($i = 0; $i <= strlen($query); $i++) {
|
||||||
|
|
||||||
switch ($state) {
|
switch ($state) {
|
||||||
case self::FILTER_TARGET:
|
case self::FILTER_TARGET:
|
||||||
list($i, $state) = $this->parseTarget($query, $i, $tokens);
|
list($i, $state) = $this->parseTarget($query, $i, $tokens);
|
||||||
@ -100,7 +183,7 @@ class UrlViewFilter
|
|||||||
list($i, $state) = $this->parseValue($query, $i, $tokens);
|
list($i, $state) = $this->parseValue($query, $i, $tokens);
|
||||||
break;
|
break;
|
||||||
case self::FILTER_ERROR:
|
case self::FILTER_ERROR:
|
||||||
list($i, $state) = $this->skip($query, $i, $tokens);
|
list($i, $state) = $this->skip($query, $i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,6 +191,14 @@ class UrlViewFilter
|
|||||||
return $tokens;
|
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)
|
private function getMatchingOperator($query, $i)
|
||||||
{
|
{
|
||||||
$operatorToUse = '';
|
$operatorToUse = '';
|
||||||
@ -118,13 +209,25 @@ class UrlViewFilter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $operatorToUse;
|
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)
|
private function parseTarget($query, $currentPos, array &$tokenList)
|
||||||
{
|
{
|
||||||
$conjunctions = array('&', '|');
|
$conjunctions = array('&', '|');
|
||||||
$i = $currentPos;
|
$i = $currentPos;
|
||||||
|
|
||||||
for ($i; $i < strlen($query); $i++) {
|
for ($i; $i < strlen($query); $i++) {
|
||||||
$currentChar = $query[$i];
|
$currentChar = $query[$i];
|
||||||
// test if operator matches
|
// test if operator matches
|
||||||
@ -132,9 +235,9 @@ class UrlViewFilter
|
|||||||
|
|
||||||
// Test if we're at an operator field right now, then add the current token
|
// Test if we're at an operator field right now, then add the current token
|
||||||
// without value to the tokenlist
|
// without value to the tokenlist
|
||||||
if($operator !== '') {
|
if ($operator !== '') {
|
||||||
$tokenList[] = array(
|
$tokenList[] = array(
|
||||||
self::FILTER_TARGET => urldecode(substr($query, $currentPos, $i - $currentPos)),
|
self::FILTER_TARGET => substr($query, $currentPos, $i - $currentPos),
|
||||||
self::FILTER_OPERATOR => $operator
|
self::FILTER_OPERATOR => $operator
|
||||||
);
|
);
|
||||||
// -1 because we're currently pointing at the first character of the operator
|
// -1 because we're currently pointing at the first character of the operator
|
||||||
@ -153,7 +256,7 @@ class UrlViewFilter
|
|||||||
|
|
||||||
if (is_array($lastState)) {
|
if (is_array($lastState)) {
|
||||||
$tokenList[] = array(
|
$tokenList[] = array(
|
||||||
self::FILTER_TARGET => urldecode($lastState[self::FILTER_TARGET]),
|
self::FILTER_TARGET => $lastState[self::FILTER_TARGET],
|
||||||
self::FILTER_OPERATOR => $lastState[self::FILTER_OPERATOR],
|
self::FILTER_OPERATOR => $lastState[self::FILTER_OPERATOR],
|
||||||
);
|
);
|
||||||
return $this->parseValue($query, $currentPos, $tokenList);
|
return $this->parseValue($query, $currentPos, $tokenList);
|
||||||
@ -165,7 +268,18 @@ class UrlViewFilter
|
|||||||
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)
|
private function parseValue($query, $currentPos, array &$tokenList)
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -190,7 +304,7 @@ class UrlViewFilter
|
|||||||
array_pop($tokenList);
|
array_pop($tokenList);
|
||||||
return array($currentPos, self::FILTER_TARGET);
|
return array($currentPos, self::FILTER_TARGET);
|
||||||
}
|
}
|
||||||
$lastState[self::FILTER_VALUE] = urldecode(substr($query, $currentPos, $length));
|
$lastState[self::FILTER_VALUE] = substr($query, $currentPos, $length);
|
||||||
|
|
||||||
if (in_array($currentChar, $conjunctions)) {
|
if (in_array($currentChar, $conjunctions)) {
|
||||||
$tokenList[] = $currentChar;
|
$tokenList[] = $currentChar;
|
||||||
@ -198,7 +312,16 @@ class UrlViewFilter
|
|||||||
return array($i, self::FILTER_TARGET);
|
return array($i, self::FILTER_TARGET);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function skip($query, $currentPos, array &$tokenList)
|
/**
|
||||||
|
* 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('&', '|');
|
$conjunctions = array('&', '|');
|
||||||
for ($i = $currentPos; strlen($query); $i++) {
|
for ($i = $currentPos; strlen($query); $i++) {
|
||||||
@ -208,6 +331,4 @@ class UrlViewFilter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -16,7 +16,6 @@ class HoststatusView extends AbstractView
|
|||||||
'host_address',
|
'host_address',
|
||||||
'host_ipv4',
|
'host_ipv4',
|
||||||
'host_icon_image',
|
'host_icon_image',
|
||||||
|
|
||||||
// Hoststatus
|
// Hoststatus
|
||||||
'host_state',
|
'host_state',
|
||||||
'host_problem',
|
'host_problem',
|
||||||
@ -39,7 +38,6 @@ class HoststatusView extends AbstractView
|
|||||||
'host_last_time_unreachable',
|
'host_last_time_unreachable',
|
||||||
'host_current_check_attempt',
|
'host_current_check_attempt',
|
||||||
'host_max_check_attempts',
|
'host_max_check_attempts',
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
'services_cnt',
|
'services_cnt',
|
||||||
'services_problem',
|
'services_problem',
|
||||||
@ -65,8 +63,8 @@ class HoststatusView extends AbstractView
|
|||||||
'columns' => array(
|
'columns' => array(
|
||||||
'host_ipv4',
|
'host_ipv4',
|
||||||
'service_description'
|
'service_description'
|
||||||
),
|
),
|
||||||
'default_dir' => self::SORT_ASC
|
'default_dir' => self::SORT_ASC
|
||||||
),
|
),
|
||||||
'host_last_state_change' => array(
|
'host_last_state_change' => array(
|
||||||
'default_dir' => self::SORT_DESC
|
'default_dir' => self::SORT_DESC
|
||||||
@ -87,6 +85,6 @@ class HoststatusView extends AbstractView
|
|||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return parent::isValidFilterColumn($column);
|
return parent::isValidFilterColumn($column);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,17 @@
|
|||||||
|
|
||||||
namespace Test\Monitoring\Application\Controllers\ListController;
|
namespace Test\Monitoring\Application\Controllers\ListController;
|
||||||
|
|
||||||
require_once(dirname(__FILE__).'/../../testlib/MonitoringControllerTest.php');
|
|
||||||
|
|
||||||
require_once(dirname(__FILE__).'/../../../../library/Monitoring/DataView/DataView.php');
|
require_once realpath(__DIR__ . '/../../../../../../library/Icinga/Test/BaseTestCase.php');
|
||||||
require_once(dirname(__FILE__).'/../../../../library/Monitoring/DataView/HostAndServiceStatus.php');
|
|
||||||
require_once(dirname(__FILE__).'/../../../../library/Monitoring/DataView/Notification.php');
|
use Icinga\Test\BaseTestCase;
|
||||||
require_once(dirname(__FILE__).'/../../../../library/Monitoring/DataView/Downtime.php');
|
|
||||||
|
require_once(realpath(BaseTestCase::$moduleDir . '/monitoring/test/php/testlib/MonitoringControllerTest.php'));
|
||||||
|
require_once(realpath(BaseTestCase::$moduleDir . '/monitoring/library/Monitoring/Filter/Backend/IdoQueryConverter.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\MonitoringControllerTest;
|
||||||
use Test\Monitoring\Testlib\Datasource\TestFixture;
|
use Test\Monitoring\Testlib\Datasource\TestFixture;
|
||||||
|
@ -2,7 +2,16 @@
|
|||||||
|
|
||||||
namespace Test\Monitoring\Application\Controllers\ListController;
|
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::$moduleDir . '/monitoring/library/Monitoring/Filter/Backend/IdoQueryConverter.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\MonitoringControllerTest;
|
||||||
use Test\Monitoring\Testlib\Datasource\TestFixture;
|
use Test\Monitoring\Testlib\Datasource\TestFixture;
|
||||||
|
@ -27,6 +27,8 @@
|
|||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
namespace Test\Modules\Monitoring\Library\Filter;
|
namespace Test\Modules\Monitoring\Library\Filter;
|
||||||
|
|
||||||
|
use Icinga\Filter\Filterable;
|
||||||
|
use Icinga\Filter\Query\Tree;
|
||||||
use Icinga\Module\Monitoring\Filter\Type\StatusFilter;
|
use Icinga\Module\Monitoring\Filter\Type\StatusFilter;
|
||||||
use Icinga\Filter\Type\TimeRangeSpecifier;
|
use Icinga\Filter\Type\TimeRangeSpecifier;
|
||||||
use Icinga\Filter\Query\Node;
|
use Icinga\Filter\Query\Node;
|
||||||
@ -34,7 +36,6 @@ use Icinga\Filter\Filter;
|
|||||||
use Icinga\Filter\Type\TextFilter;
|
use Icinga\Filter\Type\TextFilter;
|
||||||
use Icinga\Filter\FilterAttribute;
|
use Icinga\Filter\FilterAttribute;
|
||||||
use Icinga\Module\Monitoring\Filter\UrlViewFilter;
|
use Icinga\Module\Monitoring\Filter\UrlViewFilter;
|
||||||
use Icinga\Protocol\Ldap\Exception;
|
|
||||||
use Icinga\Test\BaseTestCase;
|
use Icinga\Test\BaseTestCase;
|
||||||
|
|
||||||
// @codingStandardsIgnoreStart
|
// @codingStandardsIgnoreStart
|
||||||
@ -51,6 +52,25 @@ require_once realpath(BaseTestCase::$libDir .'/Filter/Type/TimeRangeSpecifier.ph
|
|||||||
require_once realpath(BaseTestCase::$moduleDir .'/monitoring/library/Monitoring/Filter/Type/StatusFilter.php');
|
require_once realpath(BaseTestCase::$moduleDir .'/monitoring/library/Monitoring/Filter/Type/StatusFilter.php');
|
||||||
require_once realpath(BaseTestCase::$moduleDir .'/monitoring/library/Monitoring/Filter/UrlViewFilter.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(Tree $filter)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
class UrlViewFilterTest extends BaseTestCase
|
class UrlViewFilterTest extends BaseTestCase
|
||||||
{
|
{
|
||||||
@ -81,7 +101,7 @@ class UrlViewFilterTest extends BaseTestCase
|
|||||||
. ' and attr5 is UP';
|
. ' and attr5 is UP';
|
||||||
|
|
||||||
$tree = $searchEngine->createQueryTreeForFilter($query);
|
$tree = $searchEngine->createQueryTreeForFilter($query);
|
||||||
$filterFactory = new UrlViewFilter();
|
$filterFactory = new UrlViewFilter(new FilterMock());
|
||||||
$uri = $filterFactory->fromTree($tree);
|
$uri = $filterFactory->fromTree($tree);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'attr1!=Hans+wurst|attr2=%2Asomething%2A&attr3=%2Abla|attr4=1&host_last_state_change>=yesterday&attr5=0',
|
'attr1!=Hans+wurst|attr2=%2Asomething%2A&attr3=%2Abla|attr4=1&host_last_state_change>=yesterday&attr5=0',
|
||||||
@ -92,7 +112,7 @@ class UrlViewFilterTest extends BaseTestCase
|
|||||||
|
|
||||||
public function testTreeFromSimpleKeyValueUrlCreation()
|
public function testTreeFromSimpleKeyValueUrlCreation()
|
||||||
{
|
{
|
||||||
$filterFactory = new UrlViewFilter();
|
$filterFactory = new UrlViewFilter(new FilterMock());
|
||||||
$tree = $filterFactory->parseUrl('attr1!=Hans+Wurst');
|
$tree = $filterFactory->parseUrl('attr1!=Hans+Wurst');
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$tree->root->type,
|
$tree->root->type,
|
||||||
@ -118,7 +138,7 @@ class UrlViewFilterTest extends BaseTestCase
|
|||||||
|
|
||||||
public function testConjunctionFilterInUrl()
|
public function testConjunctionFilterInUrl()
|
||||||
{
|
{
|
||||||
$filterFactory = new UrlViewFilter();
|
$filterFactory = new UrlViewFilter(new FilterMock());
|
||||||
$query = 'attr1!=Hans+Wurst&test=test123|bla=1';
|
$query = 'attr1!=Hans+Wurst&test=test123|bla=1';
|
||||||
$tree = $filterFactory->parseUrl($query);
|
$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($tree->root->type, Node::TYPE_AND, 'Assert the root of the filter tree to be an AND node');
|
||||||
@ -127,7 +147,7 @@ class UrlViewFilterTest extends BaseTestCase
|
|||||||
|
|
||||||
public function testImplicitConjunctionInUrl()
|
public function testImplicitConjunctionInUrl()
|
||||||
{
|
{
|
||||||
$filterFactory = new UrlViewFilter();
|
$filterFactory = new UrlViewFilter(new FilterMock());
|
||||||
$query = 'attr1!=Hans+Wurst&test=test123|bla=1|2|3';
|
$query = 'attr1!=Hans+Wurst&test=test123|bla=1|2|3';
|
||||||
$tree = $filterFactory->parseUrl($query);
|
$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($tree->root->type, Node::TYPE_AND, 'Assert the root of the filter tree to be an AND node');
|
||||||
@ -140,7 +160,7 @@ class UrlViewFilterTest extends BaseTestCase
|
|||||||
|
|
||||||
public function testMissingValuesInQueries()
|
public function testMissingValuesInQueries()
|
||||||
{
|
{
|
||||||
$filterFactory = new UrlViewFilter();
|
$filterFactory = new UrlViewFilter(new FilterMock());
|
||||||
$queryStr = 'attr1!=Hans+Wurst&test=';
|
$queryStr = 'attr1!=Hans+Wurst&test=';
|
||||||
$tree = $filterFactory->parseUrl($queryStr);
|
$tree = $filterFactory->parseUrl($queryStr);
|
||||||
$query = $filterFactory->fromTree($tree);
|
$query = $filterFactory->fromTree($tree);
|
||||||
@ -149,7 +169,7 @@ class UrlViewFilterTest extends BaseTestCase
|
|||||||
|
|
||||||
public function testErrorInQueries()
|
public function testErrorInQueries()
|
||||||
{
|
{
|
||||||
$filterFactory = new UrlViewFilter();
|
$filterFactory = new UrlViewFilter(new FilterMock());
|
||||||
$queryStr = 'test=&attr1!=Hans+Wurst';
|
$queryStr = 'test=&attr1!=Hans+Wurst';
|
||||||
$tree = $filterFactory->parseUrl($queryStr);
|
$tree = $filterFactory->parseUrl($queryStr);
|
||||||
$query = $filterFactory->fromTree($tree);
|
$query = $filterFactory->fromTree($tree);
|
||||||
@ -158,7 +178,7 @@ class UrlViewFilterTest extends BaseTestCase
|
|||||||
|
|
||||||
public function testSenselessConjunctions()
|
public function testSenselessConjunctions()
|
||||||
{
|
{
|
||||||
$filterFactory = new UrlViewFilter();
|
$filterFactory = new UrlViewFilter(new FilterMock());
|
||||||
$queryStr = 'test=&|/5/|&attr1!=Hans+Wurst';
|
$queryStr = 'test=&|/5/|&attr1!=Hans+Wurst';
|
||||||
$tree = $filterFactory->parseUrl($queryStr);
|
$tree = $filterFactory->parseUrl($queryStr);
|
||||||
$query = $filterFactory->fromTree($tree);
|
$query = $filterFactory->fromTree($tree);
|
||||||
@ -168,7 +188,7 @@ class UrlViewFilterTest extends BaseTestCase
|
|||||||
public function testRandomString()
|
public function testRandomString()
|
||||||
{
|
{
|
||||||
$filter = '';
|
$filter = '';
|
||||||
$filterFactory = new UrlViewFilter();
|
$filterFactory = new UrlViewFilter(new FilterMock());
|
||||||
|
|
||||||
for ($i=0; $i<10;$i++) {
|
for ($i=0; $i<10;$i++) {
|
||||||
$filter .= str_shuffle('&|ds& wra =!<>|dsgs=,-G');
|
$filter .= str_shuffle('&|ds& wra =!<>|dsgs=,-G');
|
||||||
|
@ -162,6 +162,8 @@ abstract class MonitoringControllerTest extends Zend_Test_PHPUnit_ControllerTest
|
|||||||
require_once('Data/Db/Query.php');
|
require_once('Data/Db/Query.php');
|
||||||
require_once('Exception/ProgrammingError.php');
|
require_once('Exception/ProgrammingError.php');
|
||||||
require_once('Web/Widget/SortBox.php');
|
require_once('Web/Widget/SortBox.php');
|
||||||
|
require_once('Web/Widget/FilterBox.php');
|
||||||
|
require_once('Web/Widget/FilterBadgeRenderer.php');
|
||||||
require_once('library/Monitoring/Backend/AbstractBackend.php');
|
require_once('library/Monitoring/Backend/AbstractBackend.php');
|
||||||
require_once('library/Monitoring/Backend.php');
|
require_once('library/Monitoring/Backend.php');
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
/*global Icinga:false, document: false, define:false require:false base_url:false console:false */
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
/**
|
/**
|
||||||
* This file is part of Icinga 2 Web.
|
* This file is part of Icinga 2 Web.
|
||||||
@ -24,50 +25,55 @@
|
|||||||
* @author Icinga Development Team <info@icinga.org>
|
* @author Icinga Development Team <info@icinga.org>
|
||||||
*/
|
*/
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
/*global Icinga:false, document: false, define:false require:false base_url:false console:false */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures that our date/time controls will work on every browser (natively or javascript based)
|
* Ensures that our date/time controls will work on every browser (natively or javascript based)
|
||||||
*/
|
*/
|
||||||
define(['jquery', 'logging', 'URIjs/URI'], function($, log, URI) {
|
define(['jquery', 'logging', 'URIjs/URI', 'components/app/container'], function($, log, URI, Container) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
return function(inputDOM) {
|
return function(inputDOM) {
|
||||||
this.inputDom = $(inputDOM);
|
this.inputDom = $(inputDOM);
|
||||||
this.form = this.inputDom.parents('form').first();
|
this.domain = this.inputDom.attr('data-icinga-filter-domain');
|
||||||
|
this.module = this.inputDom.attr('data-icinga-filter-module');
|
||||||
|
this.form = $(this.inputDom.parents('form').first());
|
||||||
this.formUrl = URI(this.form.attr('action'));
|
this.formUrl = URI(this.form.attr('action'));
|
||||||
this.lastTokens = [];
|
|
||||||
this.lastQueuedEvent = null;
|
this.lastQueuedEvent = null;
|
||||||
this.pendingRequest = null;
|
this.pendingRequest = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the input listener
|
||||||
|
*/
|
||||||
this.construct = function() {
|
this.construct = function() {
|
||||||
this.registerControlListener();
|
this.registerControlListener();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request new proposals for the given input box
|
||||||
|
*/
|
||||||
this.getProposal = function() {
|
this.getProposal = function() {
|
||||||
var text = this.inputDom.val().trim();
|
var text = this.inputDom.val().trim();
|
||||||
|
|
||||||
try {
|
|
||||||
if (this.pendingRequest) {
|
if (this.pendingRequest) {
|
||||||
this.pendingRequest.abort();
|
this.pendingRequest.abort();
|
||||||
}
|
|
||||||
this.pendingRequest = $.ajax({
|
|
||||||
data: {
|
|
||||||
'cache' : (new Date()).getTime(),
|
|
||||||
'query' : text
|
|
||||||
},
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json'
|
|
||||||
},
|
|
||||||
url: this.formUrl
|
|
||||||
}).done(this.showProposals.bind(this)).fail(function() {});
|
|
||||||
} catch(exception) {
|
|
||||||
console.log(exception);
|
|
||||||
}
|
}
|
||||||
|
this.pendingRequest = $.ajax(this.getRequestParams(text))
|
||||||
|
.done(this.showProposals.bind(this))
|
||||||
|
.fail(this.showError.bind(this));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a selected proposal to the text box
|
||||||
|
*
|
||||||
|
* String parts encapsulated in {} are parts that already exist in the input
|
||||||
|
*
|
||||||
|
* @param token The selected token
|
||||||
|
*/
|
||||||
this.applySelectedProposal = function(token) {
|
this.applySelectedProposal = function(token) {
|
||||||
var currentText = $.trim(this.inputDom.val());
|
var currentText = $.trim(this.inputDom.val());
|
||||||
|
|
||||||
var substr = token.match(/^(\{.*\})/);
|
var substr = token.match(/^(\{.*\})/);
|
||||||
if (substr !== null) {
|
if (substr !== null) {
|
||||||
token = token.substr(substr[0].length);
|
token = token.substr(substr[0].length);
|
||||||
@ -81,23 +87,63 @@ define(['jquery', 'logging', 'URIjs/URI'], function($, log, URI) {
|
|||||||
this.inputDom.focus();
|
this.inputDom.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.showProposals = function(tokens, state, args) {
|
/**
|
||||||
|
* Display an error in the box if the request failed
|
||||||
var jsonRep = args.responseText;
|
*
|
||||||
|
* @param {Object} error The error response
|
||||||
|
* @param {String} state The HTTP state as a string
|
||||||
if (tokens.length === 0) {
|
*/
|
||||||
return this.inputDom.popover('destroy');
|
this.showError = function(error, state) {
|
||||||
|
if (state === 'abort') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.inputDom.popover('destroy').popover({
|
||||||
|
content: '<div class="alert alert-danger"> ' + error.message + ' </div>',
|
||||||
|
html: true,
|
||||||
|
trigger: 'manual'
|
||||||
|
}).popover('show');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an Object containing the request information for the given query
|
||||||
|
*
|
||||||
|
* @param query
|
||||||
|
* @returns {{data: {cache: number, query: *, filter_domain: (*|Function|Function), filter_module: Function}, headers: {Accept: string}, url: *}}
|
||||||
|
*/
|
||||||
|
this.getRequestParams = function(query) {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
'cache' : (new Date()).getTime(),
|
||||||
|
'query' : query,
|
||||||
|
'filter_domain' : this.domain,
|
||||||
|
'filter_module' : this.module
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
url: this.formUrl
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback that renders the proposal list after retrieving it from the server
|
||||||
|
*
|
||||||
|
* @param {Object} response The jquery response object inheritn XHttpResponse Attributes
|
||||||
|
*/
|
||||||
|
this.showProposals = function(response) {
|
||||||
|
if (response.proposals.length === 0) {
|
||||||
|
this.inputDom.popover('destroy');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
this.lastTokens = jsonRep;
|
|
||||||
|
|
||||||
var list = $('<ul>').addClass('nav nav-stacked nav-pills');
|
var list = $('<ul>').addClass('nav nav-stacked nav-pills');
|
||||||
$.each(tokens, (function(idx, token) {
|
$.each(response.proposals, (function(idx, token) {
|
||||||
var displayToken = token.replace(/(\{|\})/g, '');
|
var displayToken = token.replace(/(\{|\})/g, '');
|
||||||
var proposal = $('<li>').
|
var proposal = $('<li>').
|
||||||
append($('<a href="#">').
|
append($('<a href="#">').
|
||||||
text(displayToken)
|
text(displayToken)
|
||||||
).appendTo(list);
|
).appendTo(list);
|
||||||
|
|
||||||
proposal.on('click', (function(ev) {
|
proposal.on('click', (function(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
@ -114,15 +160,58 @@ define(['jquery', 'logging', 'URIjs/URI'], function($, log, URI) {
|
|||||||
}).popover('show');
|
}).popover('show');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to update the current container with the entered url if it's valid
|
||||||
|
*/
|
||||||
|
this.updateFilter = function() {
|
||||||
|
var query = $.trim(this.inputDom.val());
|
||||||
|
this.pendingRequest = $.ajax(this.getRequestParams(query))
|
||||||
|
.done((function(response) {
|
||||||
|
var container = new Container($(this.inputDom));
|
||||||
|
var url = container.getContainerHref();
|
||||||
|
url += ( url.indexOf('?') === -1 ? '?' : '&' ) + response.urlParam;
|
||||||
|
container.replaceDomFromUrl(url);
|
||||||
|
}).bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register listeners for the searchbox
|
||||||
|
*
|
||||||
|
* This means:
|
||||||
|
* - Activate/Deactivate the popover on focus and blur
|
||||||
|
* - Add Url tokens and submit on enter
|
||||||
|
*/
|
||||||
this.registerControlListener = function() {
|
this.registerControlListener = function() {
|
||||||
this.inputDom.on('blur', (function() {
|
this.inputDom.on('blur', (function() {
|
||||||
$(this).popover('hide');
|
$(this).popover('hide');
|
||||||
}));
|
}));
|
||||||
this.inputDom.on('focus', updateProposalList.bind(this));
|
this.inputDom.on('focus', updateProposalList.bind(this));
|
||||||
this.inputDom.on('keyup', updateProposalList.bind(this));
|
this.inputDom.on('keyup', updateProposalList.bind(this));
|
||||||
|
this.inputDom.on('keydown', (function(keyEv) {
|
||||||
|
if ((keyEv.keyCode || keyEv.which) === 13) {
|
||||||
|
this.updateFilter();
|
||||||
|
keyEv.stopPropagation();
|
||||||
|
keyEv.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}).bind(this));
|
||||||
|
|
||||||
|
this.form.submit(function(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var updateProposalList = function() {
|
/**
|
||||||
|
* Callback to update the proposal list if a slight delay on keyPress
|
||||||
|
*
|
||||||
|
* Needs to be bound to the object scope
|
||||||
|
*
|
||||||
|
* @param {jQuery.Event} keyEv The key Event to react on
|
||||||
|
*/
|
||||||
|
var updateProposalList = function(keyEv) {
|
||||||
|
|
||||||
if (this.lastQueuedEvent) {
|
if (this.lastQueuedEvent) {
|
||||||
window.clearTimeout(this.lastQueuedEvent);
|
window.clearTimeout(this.lastQueuedEvent);
|
||||||
}
|
}
|
||||||
@ -131,6 +220,4 @@ define(['jquery', 'logging', 'URIjs/URI'], function($, log, URI) {
|
|||||||
|
|
||||||
this.construct();
|
this.construct();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
@ -294,4 +294,5 @@ class FilterTest extends BaseTestCase
|
|||||||
'Assert the root->right->right->type node to be an OPERATOR (query :"' . $query . '")'
|
'Assert the root->right->right->type node to be an OPERATOR (query :"' . $query . '")'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -14,7 +14,7 @@ class StatusdatTestLoader extends LibraryLoader
|
|||||||
require_once 'Zend/Log.php';
|
require_once 'Zend/Log.php';
|
||||||
require_once($libPath."/Data/AbstractQuery.php");
|
require_once($libPath."/Data/AbstractQuery.php");
|
||||||
require_once($libPath."/Application/Logger.php");
|
require_once($libPath."/Application/Logger.php");
|
||||||
|
require_once($libPath."/Filter/Filterable.php");
|
||||||
require_once($libPath."/Data/DatasourceInterface.php");
|
require_once($libPath."/Data/DatasourceInterface.php");
|
||||||
$statusdat = realpath($libPath."/Protocol/Statusdat/");
|
$statusdat = realpath($libPath."/Protocol/Statusdat/");
|
||||||
require_once($statusdat."/View/AccessorStrategy.php");
|
require_once($statusdat."/View/AccessorStrategy.php");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user