Merge branch 'feature/nested-apply-rules-12033'

This commit is contained in:
Thomas Gelf 2016-10-25 00:02:22 +00:00
commit d804ebb321
28 changed files with 1523 additions and 682 deletions

View File

@ -7,7 +7,6 @@ use Icinga\Module\Director\Web\Form\QuickSubForm;
class AssignListSubForm extends QuickSubForm
{
protected $object;
public function setObject($object)
@ -16,20 +15,15 @@ class AssignListSubForm extends QuickSubForm
return $this;
}
public function setValue($value)
{
var_dump($value);
}
public function setup()
{
$idx = -1;
if ($this->object && $this->object->supportsAssignments()) {
// $this->setElementValue('assignlist', $object->assignments()->getFormValues());
foreach ($this->object->assignments()->getFormValues() as $values) {
foreach ($this->object->assignments()->getValues() as $values) {
$idx++;
$sub = new AssignmentSubForm();
$sub = $this->loadForm('assignmentSub');
$sub->setObject($this->object);
$sub->setup();
$sub->populate($values);
@ -38,7 +32,7 @@ class AssignListSubForm extends QuickSubForm
}
$idx++;
$sub = new AssignmentSubForm();
$sub = $this->loadForm('assignmentSub');
$sub->setObject($this->object);
$sub->setup();
$this->addSubForm($sub, $idx);
@ -48,6 +42,7 @@ class AssignListSubForm extends QuickSubForm
'ignore' => true,
));
$this->getElement('addmore')->setDecorators(array('ViewHelper'));
}
public function loadDefaultDecorators()
@ -56,7 +51,7 @@ class AssignListSubForm extends QuickSubForm
'FormElements',
array('HtmlTag', array(
'tag' => 'ul',
'class' => 'assign-rule'
'class' => 'assign-rule required'
)),
array('Fieldset', array(
'legend' => 'Assignment rules',

View File

@ -24,44 +24,13 @@ class AssignmentSubForm extends QuickSubForm
'class' => 'assign-type',
'value' => 'assign'
));
$this->addElement('select', 'property', array(
'label' => $this->translate('Property'),
'class' => 'assign-property autosubmit',
'multiOptions' => $this->optionalEnum(IcingaHost::enumProperties($this->object->getConnection(), 'host.'))
));
$this->addElement('select', 'operator', array(
'label' => $this->translate('Operator'),
'multiOptions' => array(
'=' => '=',
'!=' => '!=',
'>' => '>',
'>=' => '>=',
'<=' => '<=',
'<' => '<',
),
'required' => $this->valueIsEmpty($this->getValue('property')),
'value' => '=',
'class' => 'assign-operator',
$this->addElement('dataFilter', 'filter_string', array(
'columns' => IcingaHost::enumProperties($this->db)
));
$this->addElement('text', 'expression', array(
'label' => $this->translate('Expression'),
'placeholder' => $this->translate('Expression'),
'class' => 'assign-expression',
'required' => !$this->valueIsEmpty($this->getValue('property'))
));
/*
$this->addElement('submit', 'remove', array(
'label' => '-',
'ignore' => true
));
$this->addElement('submit', 'add', array(
'label' => '+',
'ignore' => true
));
*/
foreach ($this->getElements() as $el) {
$el->setDecorators(array('ViewHelper'));
$el->setDecorators(array('ViewHelper', 'Errors'));
}
}

View File

@ -2,6 +2,7 @@
namespace Icinga\Module\Director\Forms;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Web\Form\DirectorObjectForm;
class IcingaHostGroupForm extends DirectorObjectForm
@ -23,12 +24,14 @@ class IcingaHostGroupForm extends DirectorObjectForm
protected function addAssignmentElements()
{
$sub = new AssignListSubForm();
$sub->setObject($this->object());
$sub->setup();
$sub->setOrder(30);
$this->addSubForm($sub, 'assignlist');
$this->addAssignFilter(array(
'columns' => IcingaHost::enumProperties($this->db, 'host.'),
'required' => true,
'description' => $this->translate(
'This allows you to configure an assignment filter. Please feel'
. ' free to combine as many nested operators as you want'
)
));
return $this;
}

View File

@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Forms;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaService;
use Icinga\Module\Director\Web\Form\DirectorObjectForm;
class IcingaNotificationForm extends DirectorObjectForm
@ -41,31 +43,40 @@ class IcingaNotificationForm extends DirectorObjectForm
return $this;
}
$this->addElement(
'select',
'apply_to',
array(
'label' => $this->translate('Apply to'),
'description' => $this->translate(
'Whether this notification should affect hosts or services'
),
'required' => true,
'multiOptions' => $this->optionalEnum(
array(
'host' => $this->translate('Hosts'),
'service' => $this->translate('Services'),
)
$this->addElement('select', 'apply_to', array(
'label' => $this->translate('Apply to'),
'description' => $this->translate(
'Whether this notification should affect hosts or services'
),
'required' => true,
'class' => 'autosubmit',
'multiOptions' => $this->optionalEnum(
array(
'host' => $this->translate('Hosts'),
'service' => $this->translate('Services'),
)
)
);
));
$sub = new AssignListSubForm();
$sub->setObject($this->getObject());
$sub->setup();
$sub->setOrder(30);
$applyTo = $this->getSentOrObjectValue('apply_to');
$this->addSubForm($sub, 'assignlist');
if ($applyTo === 'host') {
$columns = IcingaHost::enumProperties($this->db, 'host.');
} elseif ($applyTo === 'service') {
// TODO: Also add host properties
$columns = IcingaService::enumProperties($this->db, 'service.');
} else {
return $this;
}
$this->addAssignFilter(array(
'columns' => $columns,
'required' => true,
'description' => $this->translate(
'This allows you to configure an assignment filter. Please feel'
. ' free to combine as many nested operators as you want'
)
));
return $this;
}

View File

@ -157,16 +157,14 @@ class IcingaServiceForm extends DirectorObjectForm
protected function addAssignmentElements()
{
if (!$this->object || !$this->object->isApplyRule()) {
return $this;
}
$sub = new AssignListSubForm();
$sub->setObject($this->getObject());
$sub->setup();
$sub->setOrder(30);
$this->addSubForm($sub, 'assignlist');
$this->addAssignFilter(array(
'columns' => IcingaHost::enumProperties($this->db, 'host.'),
'required' => true,
'description' => $this->translate(
'This allows you to configure an assignment filter. Please feel'
. ' free to combine as many nested operators as you want'
)
));
return $this;
}

View File

@ -47,7 +47,7 @@ class IcingaNotificationTable extends IcingaObjectTable
$htm .= ' ' . $v->qlink(
'Create apply-rule',
'director/notification/add',
array('apply' => $row->notification),
array('apply' => $row->notification, 'type' => 'apply'),
array('class' => 'icon-plus')
);
@ -72,23 +72,12 @@ class IcingaNotificationTable extends IcingaObjectTable
protected function appliedOnes($id)
{
if ($this->connection()->isPgsql()) {
$nameCol = "s.object_name || COALESCE(': ' || ARRAY_TO_STRING(ARRAY_AGG("
. "a.assign_type || ' where ' || a.filter_string"
. " ORDER BY a.assign_type, a.filter_string), ', '), '')";
} else {
$nameCol = "s.object_name || COALESCE(': ' || GROUP_CONCAT("
. "a.assign_type || ' where ' || a.filter_string"
. " ORDER BY a.assign_type, a.filter_string SEPARATOR ', '"
. "), '')";
}
$db = $this->connection()->getConnection();
$query = $db->select()->from(
array('s' => 'icinga_notification'),
array(
'id' => 's.id',
'objectname' => $nameCol,
'objectname' => 's.object_name',
)
)->join(
array('i' => 'icinga_notification_inheritance'),
@ -97,11 +86,6 @@ class IcingaNotificationTable extends IcingaObjectTable
)->where('i.parent_notification_id = ?', $id)
->where('s.object_type = ?', 'apply');
$query->joinLeft(
array('a' => 'icinga_notification_assignment'),
'a.notification_id = s.id',
array()
)->group('s.id');
return $db->fetchPairs($query);
}

View File

@ -2,6 +2,9 @@
namespace Icinga\Module\Director\Tables;
use Icinga\Data\Filter\Filter;
use Icinga\Exception\IcingaException;
use Icinga\Module\Director\IcingaConfig\AssignRenderer;
use Icinga\Module\Director\Web\Table\QuickTable;
class IcingaServiceTable extends QuickTable
@ -13,9 +16,9 @@ class IcingaServiceTable extends QuickTable
public function getColumns()
{
return array(
'id' => 's.id',
'service' => 's.object_name',
'object_type' => 's.object_type',
'id' => 's.id',
'service' => 's.object_name',
'object_type' => 's.object_type',
'check_command_id' => 's.check_command_id',
);
}
@ -47,21 +50,34 @@ class IcingaServiceTable extends QuickTable
if (empty($extra)) {
if ($row->check_command_id) {
$htm .= ' ' . $v->qlink(
'Create apply-rule',
'director/service/add',
array('apply' => $row->service),
array('class' => 'icon-plus')
);
'Create apply-rule',
'director/service/add',
array('apply' => $row->service),
array('class' => 'icon-plus')
);
}
} else {
$htm .= '. Related apply rules: <ul class="apply-rules">';
foreach ($extra as $id => $service) {
$htm .= '<li>'
. $v->qlink($service, 'director/service', array('id' => $id))
. '</li>';
$htm .= '. Related apply rules: <table class="apply-rules">';
foreach ($extra as $service) {
$href = $v->url('director/service', array('id' => $service->id));
$htm .= "<tr href=\"$href\">";
try {
$prettyFilter = AssignRenderer::forFilter(
Filter::fromQueryString($service->assign_filter)
)->renderAssign();
}
catch (IcingaException $e) {
// ignore errors in filter rendering
$prettyFilter = 'Error in Filter rendering: ' . $e->getMessage();
}
$htm .= "<td><a href=\"$href\">" . $service->object_name . '</a></td>';
$htm .= '<td>' . $prettyFilter . '</td>';
$htm .= '<tr>';
}
$htm .= '</ul>';
$htm .= '</table>';
$htm .= $v->qlink(
'Add more',
'director/service/add',
@ -109,23 +125,18 @@ class IcingaServiceTable extends QuickTable
$query = $db->select()->from(
array('s' => 'icinga_service'),
array(
'id' => 's.id',
'objectname' => $nameCol,
'id' => 's.id',
'object_name' => 's.object_name',
'assign_filter' => 's.assign_filter',
)
)->join(
array('i' => 'icinga_service_inheritance'),
'i.service_id = s.id',
array()
)->where('i.parent_service_id = ?', $id)
->where('s.object_type = ?', 'apply');
->where('s.object_type = ?', 'apply');
$query->joinLeft(
array('a' => 'icinga_service_assignment'),
'a.service_id = s.id',
array()
)->group('s.id');
return $db->fetchPairs($query);
return $db->fetchAll($query);
}
public function getBaseQuery()
@ -134,6 +145,6 @@ class IcingaServiceTable extends QuickTable
's.object_type IN (?)',
array('template')
)->order('CASE WHEN s.check_command_id IS NULL THEN 1 ELSE 0 END')
->order('s.object_name');
->order('s.object_name');
}
}

View File

@ -0,0 +1,453 @@
<?php
use Icinga\Data\Filter\Filter;
use Icinga\Data\Filter\FilterChain;
use Icinga\Data\Filter\FilterExpression;
use Icinga\Exception\ProgrammingError;
use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Objects\IcingaObjectGroup;
use Icinga\Module\Director\Web\Form\IconHelper;
/**
* View helper for extensible sets
*
* Avoid complaints about class names:
* @codingStandardsIgnoreStart
*/
class Zend_View_Helper_FormDataFilter extends Zend_View_Helper_FormElement
{
private $currentId;
private $fieldName;
private $cachedColumnSelect;
private $query;
/**
* Generates an 'extensible set' element.
*
* @codingStandardsIgnoreEnd
*
* @param string|array $name If a string, the element name. If an
* array, all other parameters are ignored, and the array elements
* are used in place of added parameters.
*
* @param mixed $value The element value.
*
* @param array $attribs Attributes for the element tag.
*
* @return string The element XHTML.
*/
public function formDataFilter($name, $value = null, $attribs = null)
{
$info = $this->_getInfo($name, $value, $attribs);
extract($info); // id, name, value, attribs, options, listsep, disable
if (array_key_exists('columns', $attribs)) {
$this->setColumns($attribs['columns']);
unset($attribs['columns']);
}
// TODO: check for columns in attribs, preserve & remove them from the
// array use attribs? class etc? disabled?
// override _getInfo?
$this->fieldName = $name;
if ($value === null) {
$value = $this->emptyExpression();
} elseif (is_string($value)) {
$value = Filter::fromQueryString($value);
}
return $this->beginRoot()
. $this->renderFilter($value)
. $this->endRoot();
}
protected function renderFilter(Filter $filter)
{
if ($filter instanceof FilterChain) {
return $this->renderFilterChain($filter);
} elseif ($filter instanceof FilterExpression) {
return $this->renderFilterExpression($filter);
} else {
throw new ProgrammingError('Got a Filter being neither expression nor chain');
}
}
protected function beginRoot()
{
return '<ul class="filter-root">';
}
protected function endRoot()
{
return '</ul>';
}
protected function renderFilterChain(FilterChain $filter)
{
$parts = array();
foreach ($filter->filters() as $f) {
$parts[] = $this->renderFilter($f);
}
return $this->beginChain($filter)
. implode('', $parts)
. $this->endChain($filter);
}
protected function beginChain(FilterChain $filter)
{
$list = $filter->isEmpty() ? '' : '<ul>' . "\n";
return '<li class="filter-chain"><span class="handle"> </span>'
. $this->selectOperator($filter)
. $this->removeLink($filter)
. $this->addLink($filter)
. ($filter->count() === 1 ? $this->stripLink($filter) : '')
. $list;
}
protected function endChain(FilterChain $filter)
{
$list = $filter->isEmpty() ? '' : "</ul>\n";
return $list . "</li>\n";
}
protected function beginExpression(FilterExpression $filter)
{
return '<div class="filter-expression">' . "\n";
}
protected function endExpression(FilterExpression $filter)
{
return "</div>\n";
}
protected function beginElement(FilterExpression $filter)
{
return '<div class="expression-wrapper">' . "\n";
}
protected function endElement(FilterExpression $filter)
{
return "</div>\n";
}
protected function filterExpressionHtml(FilterExpression $filter)
{
return $this->selectColumn($filter)
. $this->selectSign($filter)
. $this->beginElement($filter)
. $this->element($filter)
. $this->endElement($filter)
. $this->removeLink($filter)
. $this->expandLink($filter);
}
protected function renderFilterExpression(FilterExpression $filter)
{
return $this->beginExpression($filter)
. $this->filterExpressionHtml($filter)
. $this->endExpression($filter);
}
protected function element(FilterExpression $filter = null)
{
if ($filter) {
// TODO: Make this configurable
$type = 'host';
$filter = clone($filter);
$filter->setExpression(json_decode($filter->getExpression()));
$dummy = IcingaObject::createByType($type);
$col = $filter->getColumn();
if ($dummy->hasProperty($col)) {
if ($dummy->propertyIsBoolean($col)) {
return $this->boolean($filter);
}
}
if ($col === 'groups' && $dummy->supportsGroups()) {
return $this->selectGroup($type, $filter);
}
}
return $this->text($filter);
}
protected function selectGroup($type, Filter $filter)
{
$available = IcingaObjectGroup::enumForType($type);
return $this->select(
$this->elementId('value', $filter),
$this->optionalEnum($available),
$filter->getExpression()
);
}
protected function boolean(Filter $filter = null)
{
$value = $filter === null ? '' : $filter->getExpression();
$el = new Icinga\Module\Director\Web\Form\Element\Boolean(
$this->elementId('value', $filter),
array(
'value' => $value,
'decorators' => array('ViewHelper'),
)
);
return $el;
}
protected function text(Filter $filter = null)
{
$value = $filter === null ? '' : $filter->getExpression();
if (is_array($value)) {
return $this->view->formExtensibleSet(
$this->elementId('value', $filter),
$value
);
$value = '(' . implode('|', $value) . ')';
}
return $this->view->formText(
$this->elementId('value', $filter),
$value
);
}
protected function emptyExpression()
{
return Filter::expression('', '=', '');
}
protected function arrayForSelect($array, $flip = false)
{
$res = array();
foreach ($array as $k => $v) {
if (is_int($k)) {
$res[$v] = ucwords(str_replace('_', ' ', $v));
} elseif ($flip) {
$res[$v] = $k;
} else {
$res[$k] = $v;
}
}
// sort($res);
return $res;
}
protected function elementId($field, Filter $filter = null)
{
$prefix = $this->fieldName . '[id_';
$suffix = '][' . $field . ']';
return $prefix . $filter->getId() . $suffix;
}
protected function selectOperator(Filter $filter = null)
{
$ops = array(
'AND' => 'AND',
'OR' => 'OR',
'NOT' => 'NOT'
);
return $this->view->formSelect(
$this->elementId('operator', $filter),
$filter === null ? null : $filter->getOperatorName(),
array(
'class' => 'operator autosubmit',
),
$ops
);
return $this->select(
$this->elementId('operator', $filter),
$ops,
$filter === null ? null : $filter->getOperatorName(),
array('class' => 'operator autosubmit')
);
}
protected function selectSign(Filter $filter = null)
{
$signs = array(
'=' => '=',
'!=' => '!=',
'>' => '>',
'<' => '<',
'>=' => '>=',
'<=' => '<=',
'in' => 'in',
'true' => 'is true (or set)',
);
if ($filter === null) {
$sign = null;
} else {
if ($filter->getExpression() === true) {
$sign = 'true';
} elseif (is_array($filter->getExpression())) {
$sign = 'in';
} else {
$sign = $filter->getSign();
}
}
$class = 'sign autosubmit';
if (strlen($sign) > 3) {
$class .= ' wide';
}
return $this->select(
$this->elementId('sign', $filter),
$signs,
$sign,
array('class' => $class)
);
}
public function setColumns(array $columns = null)
{
$this->cachedColumnSelect = $columns ? $this->arrayForSelect($columns) : null;
return $this;
}
protected function selectColumn(Filter $filter = null)
{
$active = $filter === null ? null : $filter->getColumn();
if (! $this->hasColumnList()) {
return $this->view->formText(
$this->elementId('column', $filter),
$active
);
}
$cols = $this->getColumnList();
if ($active && !isset($cols[$active])) {
$cols[$active] = str_replace(
'_',
' ',
ucfirst(ltrim($active, '_'))
); // ??
}
$cols = $this->optionalEnum($cols);
return $this->select(
$this->elementId('column', $filter),
$cols,
$active,
array('class' => 'column autosubmit')
);
}
protected function optionalEnum($enum)
{
return array_merge(
array(null => $this->view->translate('- please choose -')),
$enum
);
}
protected function hasColumnList()
{
return $this->cachedColumnSelect !== null || $this->query !== null;
}
protected function getColumnList()
{
if ($this->cachedColumnSelect === null) {
$this->fetchColumnList();
}
return $this->cachedColumnSelect;
}
protected function fetchColumnList()
{
if ($this->query instanceof FilterColumns) {
$this->cachedColumnSelect = $this->arrayForSelect(
$this->query->getFilterColumns(),
true
);
asort($this->cachedColumnSelect);
} elseif ($this->cachedColumnSelect === null) {
throw new ProgrammingError('No columns set nor does the query provide any');
}
}
protected function select($name, $list, $selected, $attributes = null)
{
return $this->view->formSelect($name, $selected, $attributes, $list);
}
protected function removeLink(Filter $filter)
{
return $this->filterActionButton(
$filter,
'cancel',
t('Remove this part of your filter')
);
}
protected function addLink(Filter $filter)
{
return $this->filterActionButton(
$filter,
'plus',
t('Add another filter')
);
}
protected function expandLink(Filter $filter)
{
return $this->filterActionButton(
$filter,
'angle-double-right',
t('Wrap this expression into an operator')
);
}
protected function stripLink(Filter $filter)
{
return $this->filterActionButton(
$filter,
'minus',
t('Strip this operator, preserve child nodes')
);
}
protected function filterActionButton(Filter $filter, $action, $title)
{
return $this->iconButton(
$this->getActionButtonName($filter),
$action,
$title
);
}
protected function getActionButtonName(Filter $filter)
{
return sprintf(
'%s[id_%s][action]',
$this->fieldName,
$filter->getId()
);
}
protected function iconButton($name, $icon, $title)
{
return $this->view->formSubmit(
$name,
IconHelper::instance()->iconCharacter($icon),
array('class' => 'icon-button', 'title' => $title)
);
}
}

View File

@ -54,6 +54,23 @@ class AssignRenderer
}
}
protected function renderEquals($column, $expression)
{
if ($column === 'groups') {
return sprintf(
'%s in %s',
$expression,
$column
);
} else {
return sprintf(
'%s == %s',
$column,
$expression
);
}
}
protected function renderFilterExpression($filter)
{
$column = $filter->getColumn();
@ -67,11 +84,7 @@ class AssignRenderer
} elseif ($filter instanceof FilterMatch) {
if (strpos($expression, '*') === false) {
return sprintf(
'%s == %s',
$column,
$expression
);
return $this->renderEquals($column, $expression);
} else {
return sprintf(
'match(%s, %s)',

View File

@ -139,6 +139,8 @@ class IcingaHost extends IcingaObject
$properties[$props] = $hostProperties;
}
$properties['groups'] = 'Groups';
if (!empty($hostVars)) {
$properties[$vars] = $hostVars;
}

View File

@ -25,6 +25,7 @@ class IcingaNotification extends IcingaObject
'notification_interval' => null,
'period_id' => null,
'zone_id' => null,
'assign_filter' => null,
);
protected $supportsCustomVars = true;

View File

@ -3,6 +3,7 @@
namespace Icinga\Module\Director\Objects;
use Icinga\Module\Director\CustomVariable\CustomVariables;
use Icinga\Module\Director\IcingaConfig\AssignRenderer;
use Icinga\Module\Director\Data\Db\DbObject;
use Icinga\Module\Director\Db\Cache\PrefetchCache;
use Icinga\Module\Director\Db;
@ -385,6 +386,38 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
return $this->supportsSets;
}
public function setAssignments($value)
{
return IcingaObjectLegacyAssignments::applyToObject($this, $value);
}
/**
* @codingStandardsIgnoreStart
*/
public function setAssign_filter($filter)
{
if (! $this->supportsAssignments()) {
if ($this->hasProperty('object_type')) {
$type = $this->object_type;
} else {
$type = get_class($this);
}
throw new ProgrammingError(
'I can only assign for applied objects or objects with native'
. ' support for assigments, got %s',
$type
);
}
// @codingStandardsIgnoreEnd
if ($filter instanceof Filter) {
$filter = $filter->toQueryString();
}
return $this->reallySet('assign_filter', $filter);
}
/**
* It sometimes makes sense to defer lookups for related properties. This
* kind of lazy-loading allows us to for example set host = 'localhost' and
@ -440,10 +473,6 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
return true;
}
if ($this->supportsAssignments() && $this->assignments !== null && $this->assignments()->hasBeenModified()) {
return true;
}
foreach ($this->loadedRelatedSets as $set) {
if ($set->hasBeenModified()) {
return true;
@ -626,21 +655,6 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
return $this->arguments()->toPlainObject();
}
protected function setAssignments($value)
{
$this->assignments()->setValues($value);
return $this;
}
public function assignments()
{
if ($this->assignments === null) {
$this->assignments = new IcingaObjectAssignments($this);
}
return $this->assignments;
}
protected function getRanges()
{
return $this->ranges()->getValues();
@ -1213,11 +1227,6 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
return $fields;
}
protected function getAssignments()
{
return $this->assignments()->getValues();
}
public function hasProperty($key)
{
if ($this->propertyIsRelatedSet($key)) {
@ -1264,8 +1273,7 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
->storeImports()
->storeRanges()
->storeRelatedSets()
->storeArguments()
->storeAssignments();
->storeArguments();
}
protected function beforeStore()
@ -1330,15 +1338,6 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
return $this;
}
protected function storeAssignments()
{
if ($this->supportsAssignments()) {
$this->assignments !== null && $this->assignments()->store();
}
return $this;
}
protected function storeRelatedSets()
{
foreach ($this->loadedRelatedSets as $set) {
@ -1895,15 +1894,6 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
);
}
protected function renderAssignments()
{
if ($this->supportsAssignments()) {
return $this->assignments()->toConfigString();
} else {
return '';
}
}
protected function renderLegacyObjectHeader()
{
$type = strtolower($this->getType());
@ -1933,6 +1923,17 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
return $str;
}
/**
* @codingStandardsIgnoreStart
*/
public function renderAssign_Filter()
{
// @codingStandardsIgnoreEnd
return ' ' . AssignRenderer::forFilter(
Filter::fromQueryString($this->assign_filter)
)->renderAssign() . "\n";
}
public function toLegacyConfigString()
{
$str = implode(array(
@ -1946,7 +1947,6 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
//$this->renderMultiRelations(),
//$this->renderCustomExtensions(),
//$this->renderCustomVars(),
//$this->renderAssignments(),
$this->renderLegacySuffix()
));
@ -1993,7 +1993,6 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
$this->renderMultiRelations(),
$this->renderCustomExtensions(),
$this->renderCustomVars(),
$this->renderAssignments(),
$this->renderSuffix()
));
@ -2235,10 +2234,6 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
);
}
if ($this->supportsAssignments()) {
$props['assignments'] = $this->assignments()->getPlain();
}
if ($this->supportsCustomVars()) {
if ($resolved) {
$props['vars'] = $this->getResolvedVars();
@ -2414,10 +2409,6 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
}
}
if ($this->supportsAssignments()) {
$props['assignments'] = $this->assignments()->getUnmodifiedPlain();
}
foreach ($this->relatedSets() as $property => $set) {
if ($set->isEmpty()) {
continue;
@ -2465,7 +2456,6 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
unset($this->ranges);
unset($this->arguments);
parent::__destruct();
}
}

View File

@ -1,313 +0,0 @@
<?php
namespace Icinga\Module\Director\Objects;
use Icinga\Data\Filter\Filter;
use Icinga\Exception\ProgrammingError;
use Icinga\Module\Director\IcingaConfig\AssignRenderer;
use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c;
class IcingaObjectAssignments
{
protected $object;
protected $stored;
protected $current;
public function __construct(IcingaObject $object)
{
if (! $object->supportsAssignments()) {
throw new ProgrammingError(
'I can only assign for applied objects, got %s',
$object->object_type
);
}
$this->object = $object;
}
public function store()
{
if ($this->hasBeenModified()) {
$this->reallyStore();
return true;
}
return false;
}
public function setValues($values)
{
if (is_string($values)) {
return $this->setValues(array($values));
}
$this->current = array();
if (is_object($values)) {
$values = (array) $values;
}
ksort($values);
foreach ((array) $values as $type => $value) {
if (is_numeric($type)) {
$this->addRule($value);
} else {
if (is_string($value)) {
$this->addRule($value, $type);
continue;
}
foreach ($value as $key => $strings) {
$this->addRule($strings, $type);
}
}
}
return $this;
}
public function getFormValues()
{
$result = array();
foreach ($this->getCurrent() as $rule) {
$f = array(
'assign_type' => $rule['assign_type']
);
$filter = Filter::fromQueryString($rule['filter_string']);
if (!$filter->isExpression()) {
throw new IcingaException(
'We currently support only flat filters in our forms, got %',
(string) $filter
);
}
$f['property'] = $filter->getColumn();
$f['operator'] = $filter->getSign();
$f['expression'] = trim(stripcslashes($filter->getExpression()), '"');
$result[] = $f;
}
return $result;
}
public function setFormValues($values)
{
$rows = array();
foreach ($values as $key => $val) {
if (! is_numeric($key)) {
// Skip buttons or similar
continue;
}
if (!array_key_exists($val['assign_type'], $rows)) {
$rows[$val['assign_type']] = array();
}
if (empty($val['property'])) {
continue;
}
if (is_numeric($val['expression'])) {
$expression = $val['expression'];
} else {
$expression = '"' . addcslashes($val['expression'], '"') . '"';
}
$rows[$val['assign_type']][] = $this->rerenderFilter(
implode('', array(
$val['property'],
$val['operator'],
$expression,
))
);
}
return $this->setValues($rows);
}
protected function addRule($string, $type = 'assign')
{
// TODO: validate
$this->current[] = array(
'assign_type' => $type,
'filter_string' => $this->rerenderFilter($string)
);
return $this;
}
public function getValues()
{
return $this->getCurrent();
}
public function getUnmodifiedValues()
{
return $this->getStored();
}
public function toConfigString()
{
return $this->renderRules($this->getCurrent());
}
public function toUnmodifiedConfigString()
{
return $this->renderRules($this->getStored());
}
protected function renderRules($rules)
{
if (empty($rules)) {
return '';
}
$filters = array();
foreach ($rules as $rule) {
$filters[] = AssignRenderer::forFilter(
Filter::fromQueryString($rule['filter_string'])
)->render($rule['assign_type']);
}
return "\n " . implode("\n ", $filters) . "\n";
}
public function getPlain()
{
if ($this->current === null) {
if (! $this->object->hasBeenLoadedFromDb()) {
return array();
}
$this->current = $this->getStored();
}
return $this->createPlain($this->current);
}
public function getUnmodifiedPlain()
{
if (! $this->object->hasBeenLoadedFromDb()) {
return array();
}
return $this->createPlain($this->getStored());
}
public function hasBeenModified()
{
if ($this->current === null) {
return false;
}
return json_encode($this->getCurrent()) !== json_encode($this->getStored());
}
protected function getCurrent()
{
if ($this->current === null) {
$this->current = $this->getStored();
}
return $this->current;
}
protected function getStored()
{
if ($this->stored === null) {
$this->stored = $this->loadFromDb();
}
return $this->stored;
}
protected function rerenderFilter($string)
{
return rawurldecode(Filter::fromQueryString($string)->toQueryString());
}
protected function createPlain($dbRows)
{
$result = array();
foreach ($dbRows as $row) {
if (! array_key_exists($row['assign_type'], $result)) {
$result[$row['assign_type']] = array();
}
$result[$row['assign_type']][] = $row['filter_string'];
}
return $result;
}
protected function getDb()
{
return $this->object->getDb();
}
protected function loadFromDb()
{
$db = $this->getDb();
$object = $this->object;
$query = $db->select()->from(
$this->getTableName(),
array('assign_type', 'filter_string')
)->where($this->createWhere())->order('assign_type', 'filter_string');
$this->stored = array();
foreach ($db->fetchAll($query) as $row) {
$this->stored[] = (array) $row;
}
return $this->stored;
}
protected function createWhere()
{
return $this->getRelationColumn()
. ' = '
. $this->getObjectId();
}
protected function getObjectId()
{
return (int) $this->object->id;
}
protected function getRelationColumn()
{
return $this->object->getShortTableName() . '_id';
}
protected function getTableName()
{
return $this->object->getTableName() . '_assignment';
}
protected function reallyStore()
{
$db = $this->getDb();
$table = $this->getTableName();
$objectId = $this->object->id;
$relationCol = $this->getRelationColumn();
$db->delete($table, $this->createWhere());
foreach ($this->getCurrent() as $row) {
$data = (array) $row;
$data[$relationCol] = $objectId;
$db->insert($table, $data);
}
$this->stored = $this->current;
return $this;
}
}

View File

@ -2,6 +2,9 @@
namespace Icinga\Module\Director\Objects;
use Icinga\Application\Config;
use Icinga\Exception\IcingaException;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\IcingaConfig\IcingaConfig;
abstract class IcingaObjectGroup extends IcingaObject
@ -9,15 +12,44 @@ abstract class IcingaObjectGroup extends IcingaObject
protected $supportsImports = true;
protected $defaultProperties = array(
'id' => null,
'object_name' => null,
'object_type' => null,
'disabled' => 'n',
'display_name' => null,
'id' => null,
'object_name' => null,
'object_type' => null,
'disabled' => 'n',
'display_name' => null,
'assign_filter' => null,
);
public function getRenderingZone(IcingaConfig $config = null)
{
return $this->connection->getDefaultGlobalZoneName();
}
public static function enumForType($type, Db $connection = null)
{
if ($connection === null) {
// TODO: not nice :(
$connection = Db::fromResourceName(
Config::module('director')->get('db', 'resource')
);
}
// Last resort defense against potentiall lousy checks:
if (! ctype_alpha($type)) {
throw new IcingaException(
'Holy shit, you should never have reached this'
);
}
$db = $connection->getDbAdapter();
$select = $db->select()->from(
'icinga_' . $type . 'group',
array(
'name' => 'object_name',
'display' => 'COALESCE(display_name, object_name)'
)
)->where('object_type = ?', 'object')->order('display');
return $db->fetchPairs($select);
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace Icinga\Module\Director\Objects;
use Icinga\Exception\ProgrammingError;
/**
* This class is required for historical reasons
*
* Objects with assignments in your activity log would otherwise not be able
* to render themselves
*/
class IcingaObjectLegacyAssignments
{
public static function applyToObject(IcingaObject $object, $values)
{
if (! $object->supportsAssignments()) {
throw new ProgrammingError(
'I can only assign for applied objects, got %s',
$object->object_type
);
}
if ($values === null) {
return $object;
}
if (! is_array($values)) {
static::throwCompatError();
}
if (empty($values)) {
return $object;
}
$assigns = array();
$ignores = array();
foreach ($values as $type => $value) {
if (strpos($value, '|') !== false || strpos($value, '&' !== false)) {
$value = '(' . $value . ')';
}
if ($type === 'assign') {
$assigns[] = $value;
} elseif ($type === 'ignore') {
$ignores[] = $value;
} else {
static::throwCompatError();
}
}
$assign = implode('|', $assigns);
$ignore = implode('&', $ignores);
if (empty($assign)) {
$filter = $ignore;
} elseif (empty($ignore)) {
$filter = $assign;
} else {
if (count($assigns) === 1) {
$filter = $assign . '&' . $ignore;
} else {
$filter = '(' . $assign . ')&(' . $ignore . ')';
}
}
$object->assign_filter = $filter;
return $object;
}
protected static function throwCompatError()
{
throw new ProgrammingError(
'You ran into an unexpected compatibility issue. Please report'
. ' this with details helping us to reproduce this to the'
. ' Icinga project'
);
}
}

View File

@ -2,6 +2,7 @@
namespace Icinga\Module\Director\Objects;
use Icinga\Data\Filter\Filter;
use Icinga\Exception\ProgrammingError;
use Icinga\Module\Director\IcingaConfig\IcingaConfig;
use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c;
@ -41,6 +42,7 @@ class IcingaService extends IcingaObject
'use_agent' => null,
'apply_for' => null,
'use_var_overrides' => null,
'assign_filter' => null,
);
protected $relations = array(
@ -193,21 +195,6 @@ class IcingaService extends IcingaObject
return parent::renderObjectHeader();
}
protected function renderAssignments()
{
if (! $this->hasBeenAssignedToHostTemplate()) {
return parent::renderAssignments();
}
// TODO: use assignment renderer?
$filter = sprintf(
'assign where %s in host.templates',
c::renderString($this->host)
);
return "\n " . $filter . "\n";
}
protected function hasBeenAssignedToHostTemplate()
{
return $this->host_id && $this->getRelatedObject(
@ -238,18 +225,30 @@ class IcingaService extends IcingaObject
protected function renderCustomExtensions()
{
// A hand-crafted command endpoint overrides use_agent
$output = '';
if ($this->hasBeenAssignedToHostTemplate()) {
// TODO: use assignment renderer?
$filter = sprintf(
'assign where %s in host.templates',
c::renderString($this->host)
);
$output .= "\n " . $filter . "\n";
}
// A hand-crafted command endpoint overrides use_agent
if ($this->command_endpoint_id !== null) {
return '';
return $output;
}
// In case use_agent isn't defined, do nothing
// TODO: what if we inherit use_agent and override it with 'n'?
if ($this->use_agent !== 'y') {
return '';
return $output;
}
return c::renderKeyValue('command_endpoint', 'host_name');
return $output . c::renderKeyValue('command_endpoint', 'host_name');
}
/**

View File

@ -551,9 +551,6 @@ abstract class DirectorObjectForm extends QuickForm
$post = $this->getRequest()->getPost();
// ?? $this->populate($post);
if (array_key_exists('assignlist', $post)) {
$object->assignments()->setFormValues($post['assignlist']);
}
foreach ($post as $key => $value) {
$el = $this->getElement($key);
@ -564,10 +561,6 @@ abstract class DirectorObjectForm extends QuickForm
}
if ($object instanceof IcingaObject) {
if ($object->supportsAssignments()) {
$this->setElementValue('assignlist', $object->assignments()->getFormValues());
}
$this->handleProperties($object, $values);
$this->handleCustomVars($object, $post);
$this->handleRanges($object, $values);
@ -1136,6 +1129,71 @@ abstract class DirectorObjectForm extends QuickForm
return $this;
}
/**
* Add an assign_filter form element
*
* Forms should use this helper method for objects using the typical
* assign_filter column
*
* @param array $properties Form element properties
*
* @return self
*/
protected function addAssignFilter($properties)
{
if (!$this->object || !$this->object->supportsAssignments()) {
return $this;
}
$this->addFilterElement('assign_filter', $properties);
$el = $this->getElement('assign_filter');
$this->addDisplayGroup(array($el), 'assign', array(
'decorators' => array(
'FormElements',
array('HtmlTag', array('tag' => 'dl')),
'Fieldset',
),
'order' => 30,
'legend' => $this->translate('Assign where')
));
return $this;
}
/**
* Add a dataFilter element with fitting decorators
*
* TODO: Evaluate whether parts or all of this could be moved to the element
* class.
*
* @param string $name Element name
* @param array $properties Form element properties
*
* @return self
*/
protected function addFilterElement($name, $properties)
{
$this->addElement('dataFilter', $name, $properties);
$el = $this->getElement($name);
$ddClass = 'full-width';
if (array_key_exists('required', $properties) && $properties['required']) {
$ddClass .= ' required';
}
$el->clearDecorators()
->addDecorator('ViewHelper')
->addDecorator('Errors')
->addDecorator('Description', array('tag' => 'p', 'class' => 'description'))
->addDecorator('HtmlTag', array(
'tag' => 'dd',
'class' => $ddClass,
));
return $this;
}
protected function addEventFilterElements()
{
$this->addElement('extensibleSet', 'states', array(

View File

@ -0,0 +1,330 @@
<?php
namespace Icinga\Module\Director\Web\Form\Element;
use Icinga\Data\Filter\Filter;
use Icinga\Module\Director\Web\Form\IconHelper;
use Exception;
/**
* Input control for extensible sets
*/
class DataFilter extends FormElement
{
/**
* Default form view helper to use for rendering
* @var string
*/
public $helper = 'formDataFilter';
private $addTo;
private $removeFilter;
private $stripFilter;
private $filter;
public function getValue()
{
$value = parent::getValue();
if ($value !== null && $this->isEmpty($value)) {
$value = null;
}
return $value;
}
protected function isEmpty(Filter $filter)
{
return $filter->isEmpty() || $this->isEmptyExpression($filter);
}
protected function isEmptyExpression($filter)
{
return $filter->isExpression() &&
$filter->getColumn() === '' &&
$filter->getExpression() === '""'; // -> json_encode('')
}
/**
* @codingStandardsIgnoreStart
*/
protected function _filterValue(&$value, &$key)
{
// @codingStandardsIgnoreEnd
try {
if ($value instanceof Filter) {
// OK
} elseif (is_string($value)) {
$value = Filter::fromQueryString($value);
} else {
$value = $this->arrayToFilter($value);
}
} catch (Exception $e) {
$value = null;
// TODO: getFile, getLine
// Hint: cannot addMessage at it would loop through getValue
$this->addErrorMessage($e->getMessage());
$this->_isErrorForced = true;
}
}
/**
* This method transforms filter form data into a filter
* and reacts on pressed buttons
*/
protected function arrayToFilter($array)
{
if ($array === null) {
return null;
return Filter::matchAll();
}
$this->filter = null;
foreach ($array as $id => $entry) {
$filterId = $this->idToFilterId($id);
$sub = $this->entryToFilter($entry);
$this->checkEntryForActions($filterId, $entry);
$parentId = $this->parentIdFor($filterId);
if ($this->filter === null) {
$this->filter = $sub;
} else {
$this->filter->getById($parentId)->addFilter($sub);
}
}
$this->removeFilterIfRequested()
->stripFilterIfRequested()
->addNewFilterIfRequested()
->fixNotsWithMultipleChildren();
return $this->filter;
}
protected function removeFilterIfRequested()
{
if ($this->removeFilter !== null) {
if ($this->filter->getById($this->removeFilter)->isRootNode()) {
$this->filter = $this->emptyExpression();
} else {
$this->filter->removeId($this->removeFilter);
}
}
return $this;
}
protected function stripFilterIfRequested()
{
if ($this->stripFilter !== null) {
$strip = $this->stripFilter;
$subId = $strip . '-1';
if ($this->filter->getId() === $strip) {
$this->filter = $this->filter->getById($strip . '-1');
} else {
$this->filter->replaceById($strip, $this->filter->getById($strip . '-1'));
}
}
return $this;
}
protected function addNewFilterIfRequested()
{
if ($this->addTo !== null) {
$parent = $this->filter->getById($this->addTo);
if ($parent->isChain()) {
if ($parent->isEmpty()) {
$parent->addFilter($this->emptyExpression());
} else {
$parent->addFilter($this->emptyExpression());
}
} else {
$replacement = Filter::matchAll(clone($parent));
if ($parent->isRootNode()) {
$this->filter = $replacement;
} else {
$this->filter->replaceById($parent->getId(), $replacement);
}
}
}
return $this;
}
protected function fixNotsWithMultipleChildren(Filter $filter = null)
{
$this->filter = $this->fixNotsWithMultipleChildrenForFilter($this->filter);
return $this;
}
protected function fixNotsWithMultipleChildrenForFilter(Filter $filter)
{
if ($filter->isChain()) {
if ($filter->getOperatorName() === 'NOT') {
if ($filter->count() > 1) {
$filter = $this->notToNotAnd($filter);
}
}
foreach ($filter->filters() as $sub) {
$filter->replaceById(
$sub->getId(),
$this->fixNotsWithMultipleChildrenForFilter($sub)
);
}
}
return $filter;
}
protected function notToNotAnd(Filter $not)
{
$and = Filter::matchAll();
foreach ($not->filters() as $sub) {
$and->addFilter(clone($sub));
}
return Filter::not($and);
}
protected function emptyExpression()
{
return Filter::expression('', '=', '');
}
protected function parentIdFor($id)
{
if (false === ($pos = strrpos($id, '-'))) {
return '0';
} else {
return substr($id, 0, $pos);
}
}
protected function idToFilterId($id)
{
if (! preg_match('/^id_(new_)?(\d+(?:-\d+)*)$/', $id, $m)) {
die('nono' . $id);
}
return $m[2];
}
protected function checkEntryForActions($filterId, $entry)
{
switch ($this->entryAction($entry)) {
case 'cancel':
$this->removeFilter = $filterId;
break;
case 'minus':
$this->stripFilter = $filterId;
break;
case 'plus':
case 'angle-double-right':
$this->addTo = $filterId;
break;
}
}
/**
* Transforms a single submitted form component from an array
* into a Filter object
*
* @param Array $entry The array as submitted through the form
*
* @return Filter
*/
protected function entryToFilter($entry)
{
if (array_key_exists('operator', $entry)) {
return Filter::chain($entry['operator']);
} else {
return $this->entryToFilterExpression($entry);
}
}
protected function entryToFilterExpression($entry)
{
if ($entry['sign'] === 'true') {
return Filter::expression(
$entry['column'],
'=',
json_encode(true)
);
} elseif ($entry['sign'] === 'in') {
if (array_key_exists('value', $entry)) {
if (is_array($entry['value'])) {
$value = array_filter($entry['value'], 'strlen');
} elseif (empty($entry['value'])) {
$value = array();
} else {
$value = array($entry['value']);
}
} else {
$value = array();
}
return Filter::expression(
$entry['column'],
'=',
json_encode($value)
);
} else {
$value = array_key_exists('value', $entry) ? $entry['value'] : null;
return Filter::expression(
$entry['column'],
$entry['sign'],
json_encode($value)
);
}
}
protected function entryAction($entry)
{
if (array_key_exists('action', $entry)) {
return IconHelper::instance()->characterIconName($entry['action']);
}
return null;
}
protected function hasIncompleteExpressions(Filter $filter)
{
if ($filter->isChain()) {
foreach ($filter->filters() as $sub) {
if ($this->hasIncompleteExpressions($sub)) {
return true;
}
}
} else {
if ($filter->isRootNode() && $this->isEmptyExpression($filter)) {
return false;
}
return $filter->getColumn() === '';
}
}
public function isValid($value, $context = null)
{
if (! $value instanceof Filter) {
// TODO: try, return false on E
$filter = $this->arrayToFilter($value);
$this->setValue($filter);
}
if ($this->hasIncompleteExpressions($filter)) {
$this->addError('The configured filter is incomplete');
return false;
}
return parent::isValid($value);
}
}

View File

@ -22,11 +22,12 @@ use Icinga\Exception\ProgrammingError;
class IconHelper
{
private $icons = array(
'minus' => 'e806',
'trash' => 'e846',
'plus' => 'e805',
'cancel' => 'e804',
'help' => 'e85b',
'minus' => 'e806',
'trash' => 'e846',
'plus' => 'e805',
'cancel' => 'e804',
'help' => 'e85b',
'angle-double-right' => 'e87b',
);
private $mappedUtf8Icons;

View File

@ -186,6 +186,7 @@ input, select, select option, textarea {
form ul.form-errors {
margin-bottom: 0.5em;
ul.errors li {
background: @color-critical;
font-weight: bold;
@ -228,6 +229,10 @@ select option {
padding-top: 0.3em;
}
select[multiple=multiple] {
height: auto;
}
label {
line-height: 2em;
}
@ -620,31 +625,6 @@ form dt label {
}
}
ul.assign-rule {
margin: 0;
padding: 0;
list-style-type: none;
select, input[type=text] {
min-width: auto;
}
select.assign-type {
width: 8em;
}
select.assign-operator {
width: 3em;
}
select.assign-property {
width: 12em;
}
input.assign-expression {
width: 12em;
}
}
form fieldset {
min-width: 36em;
}
@ -689,6 +669,10 @@ form dt {
}
}
form .errors label {
color: @color-critical;
}
form dd {
display: inline-block;
width: 63%;
@ -700,6 +684,11 @@ form dd {
border-color: @color-critical;
}
}
&.full-width {
padding: 0.5em;
width: 100%;
}
}
form dd:after {
@ -898,25 +887,25 @@ table.icinga-objects {
}
table.assignment-table {
ul.apply-rules {
table.apply-rules {
margin: 1em 0 1em 0;
padding: 0;
border: 1px solid @gray-lighter;
width: 100%;
li {
display: block;
tr {
border-bottom: 1px solid @gray-lighter;
border-left: 5px solid transparent;
a {
display: block;
float: none;
font-weight: normal;
background-color: white;
padding: 0.3em 1.5em;
&:hover {
background-color: @icinga-blue;
color: white;
text-decoration: none;
}
td {
padding: 0.5em 1em;
}
&:hover, &:active, &.active {
border-left: 5px solid @icinga-blue;
}
&:hover {
background: white;
}
}
}
@ -1101,6 +1090,147 @@ table.config-diff {
}
}
ul.assign-rule {
margin: 0;
padding: 0;
list-style-type: none;
select, input[type=text] {
min-width: auto;
}
select.assign-type {
width: 8em;
}
ul {
list-style-type: none;
padding-left: 2em;
li::before {
// icon: down-dir
font-family: 'ifont';
content: '\e81d';
// icon: right-small
content: '\e877';
margin-left: -1em;
padding-top: 0em;
float: left;
color: inherit;
}
}
}
ul.filter-root {
margin-top: 0;
width: 100%;
padding-left: 0.5em;
list-style-type: none;
ul {
padding-left: 1.5em;
list-style-type: none;
width: 100%;
}
li.filter-chain, div.filter-expression {
width: 100%;
padding: 0.3em 0.5em;
min-width: 30em;
}
ul li.filter-chain::before, ul .filter-expression::before {
font-family: 'ifont';
// Formerly: icon-down-open: e821
// icon-right-small:
content: '\e877';
float: left;
margin-left: -1.5em;
margin-top: 0.5em;
}
ul.extensible-set {
padding-left: 0;
border: none;
display: inline-block;
vertical-align: top;
li::before {
content: none;
}
}
input[type=submit].icon-button {
display: none;
font-family: 'ifont';
font-weight: normal;
background: none;
border: none;
padding: 0.2em 0.4em 0.2em 0.4em;
margin: 0 0 0 0.2em;
}
.active input[type=submit].icon-button,
li:hover input[type=submit].icon-button,
div:hover input[type=submit].icon-button
{
display: inline;
}
}
.errors > ul.filter-root {
input[type=text], select {
border-color: transparent;
border-bottom-color: @gray-lighter;
}
select.column, select.operator {
border-left-color: @color-critical;
}
}
li.filter-chain > select.operator {
min-width: 5em;
max-width: 5em;
width: 5em;
}
div.filter-expression {
.column {
min-width: 7em;
max-width: 10em;
width: 10em;
}
.sign {
min-width: 4em;
max-width: 4em;
width: 4em;
margin: 0 0.3em;
&.wide {
min-width: 6em;
max-width: 6em;
width: 6em;
}
}
div.expression-wrapper {
display: inline-block;
}
div.expression-wrapper > input[type=text],
div.expression-wrapper > select {
min-width: 7em;
width: 10em;
max-width: 10em;
}
}
.tree li a {
display: inline-block;
padding-left: 2.4em;

View File

@ -188,7 +188,7 @@
}
var $li = $input.closest('li');
var $dt = $dd.prev();
var $form = $dt.closest('form');
var $form = $dd.closest('form');
$form.find('dt, dd, li').removeClass('active');
$li.addClass('active');

View File

@ -0,0 +1,184 @@
ALTER TABLE icinga_service ADD COLUMN assign_filter TEXT;
UPDATE icinga_service s JOIN (
SELECT
service_id,
CASE WHEN COUNT(*) = 0 THEN NULL
WHEN COUNT(*) = 1 THEN sa.filter_string
ELSE GROUP_CONCAT(sa.filter_string SEPARATOR '&') END AS filter_string
FROM (
SELECT
sa_not.service_id,
CASE WHEN COUNT(*) = 0 THEN NULL
WHEN COUNT(*) = 1 THEN sa_not.filter_string
ELSE '(' || GROUP_CONCAT(sa_not.filter_string SEPARATOR '&') || ')' END AS filter_string
FROM ( SELECT
sa.service_id,
'!' || sa.filter_string AS filter_string
FROM icinga_service_assignment sa
WHERE assign_type = 'ignore'
) sa_not
GROUP BY service_id
UNION ALL
SELECT
sa_yes.service_id,
CASE WHEN COUNT(*) = 0 THEN NULL
WHEN COUNT(*) = 1 THEN sa_yes.filter_string
ELSE '(' || GROUP_CONCAT(sa_yes.filter_string SEPARATOR '|') || ')' END AS filter_string
FROM ( SELECT
sa.service_id,
sa.filter_string AS filter_string
FROM icinga_service_assignment sa
WHERE assign_type = 'assign'
) sa_yes
GROUP BY service_id
) sa GROUP BY service_id
) flat_assign ON s.id = flat_assign.service_id SET s.assign_filter = flat_assign.filter_string;
DROP TABLE icinga_service_assignment;
ALTER TABLE icinga_service_set ADD COLUMN assign_filter TEXT;
UPDATE icinga_service_set s JOIN (
SELECT
service_set_id,
CASE WHEN COUNT(*) = 0 THEN NULL
WHEN COUNT(*) = 1 THEN sa.filter_string
ELSE GROUP_CONCAT(sa.filter_string SEPARATOR '&') END AS filter_string
FROM (
SELECT
sa_not.service_set_id,
CASE WHEN COUNT(*) = 0 THEN NULL
WHEN COUNT(*) = 1 THEN sa_not.filter_string
ELSE '(' || GROUP_CONCAT(sa_not.filter_string SEPARATOR '&') || ')' END AS filter_string
FROM ( SELECT
sa.service_set_id,
'!' || sa.filter_string AS filter_string
FROM icinga_service_set_assignment sa
WHERE assign_type = 'ignore'
) sa_not
GROUP BY service_set_id
UNION ALL
SELECT
sa_yes.service_set_id,
CASE WHEN COUNT(*) = 0 THEN NULL
WHEN COUNT(*) = 1 THEN sa_yes.filter_string
ELSE '(' || GROUP_CONCAT(sa_yes.filter_string SEPARATOR '|') || ')' END AS filter_string
FROM ( SELECT
sa.service_set_id,
sa.filter_string AS filter_string
FROM icinga_service_set_assignment sa
WHERE assign_type = 'assign'
) sa_yes
GROUP BY service_set_id
) sa GROUP BY service_set_id
) flat_assign ON s.id = flat_assign.service_set_id SET s.assign_filter = flat_assign.filter_string;
DROP TABLE icinga_service_set_assignment;
ALTER TABLE icinga_notification ADD COLUMN assign_filter TEXT;
UPDATE icinga_notification s JOIN (
SELECT
notification_id,
CASE WHEN COUNT(*) = 0 THEN NULL
WHEN COUNT(*) = 1 THEN sa.filter_string
ELSE GROUP_CONCAT(sa.filter_string SEPARATOR '&') END AS filter_string
FROM (
SELECT
sa_not.notification_id,
CASE WHEN COUNT(*) = 0 THEN NULL
WHEN COUNT(*) = 1 THEN sa_not.filter_string
ELSE '(' || GROUP_CONCAT(sa_not.filter_string SEPARATOR '&') || ')' END AS filter_string
FROM ( SELECT
sa.notification_id,
'!' || sa.filter_string AS filter_string
FROM icinga_notification_assignment sa
WHERE assign_type = 'ignore'
) sa_not
GROUP BY notification_id
UNION ALL
SELECT
sa_yes.notification_id,
CASE WHEN COUNT(*) = 0 THEN NULL
WHEN COUNT(*) = 1 THEN sa_yes.filter_string
ELSE '(' || GROUP_CONCAT(sa_yes.filter_string SEPARATOR '|') || ')' END AS filter_string
FROM ( SELECT
sa.notification_id,
sa.filter_string AS filter_string
FROM icinga_notification_assignment sa
WHERE assign_type = 'assign'
) sa_yes
GROUP BY notification_id
) sa GROUP BY notification_id
) flat_assign ON s.id = flat_assign.notification_id SET s.assign_filter = flat_assign.filter_string;
DROP TABLE icinga_notification_assignment;
ALTER TABLE icinga_hostgroup ADD COLUMN assign_filter TEXT;
UPDATE icinga_hostgroup s JOIN (
SELECT
hostgroup_id,
CASE WHEN COUNT(*) = 0 THEN NULL
WHEN COUNT(*) = 1 THEN sa.filter_string
ELSE GROUP_CONCAT(sa.filter_string SEPARATOR '&') END AS filter_string
FROM (
SELECT
sa_not.hostgroup_id,
CASE WHEN COUNT(*) = 0 THEN NULL
WHEN COUNT(*) = 1 THEN sa_not.filter_string
ELSE '(' || GROUP_CONCAT(sa_not.filter_string SEPARATOR '&') || ')' END AS filter_string
FROM ( SELECT
sa.hostgroup_id,
'!' || sa.filter_string AS filter_string
FROM icinga_hostgroup_assignment sa
WHERE assign_type = 'ignore'
) sa_not
GROUP BY hostgroup_id
UNION ALL
SELECT
sa_yes.hostgroup_id,
CASE WHEN COUNT(*) = 0 THEN NULL
WHEN COUNT(*) = 1 THEN sa_yes.filter_string
ELSE '(' || GROUP_CONCAT(sa_yes.filter_string SEPARATOR '|') || ')' END AS filter_string
FROM ( SELECT
sa.hostgroup_id,
sa.filter_string AS filter_string
FROM icinga_hostgroup_assignment sa
WHERE assign_type = 'assign'
) sa_yes
GROUP BY hostgroup_id
) sa GROUP BY hostgroup_id
) flat_assign ON s.id = flat_assign.hostgroup_id SET s.assign_filter = flat_assign.filter_string;
DROP TABLE icinga_hostgroup_assignment;
ALTER TABLE icinga_servicegroup ADD COLUMN assign_filter TEXT;
INSERT INTO director_schema_migration
(schema_version, migration_time)
VALUES (120, NOW());

View File

@ -47,9 +47,7 @@ class IcingaServiceTest extends BaseTestCase
{
$service = $this->service();
$service->object_type = 'apply';
$service->assignments = array(
'host.address="127.*"'
);
$service->assign_filter = 'host.address="127.*"';
}
/**
@ -58,9 +56,7 @@ class IcingaServiceTest extends BaseTestCase
public function testRefusesAssignRulesWhenNotBeingAnApply()
{
$service = $this->service();
$service->assignments = array(
'host.address=127.*'
);
$service->assign_filter = 'host.address=127.*';
}
public function testAcceptsAndRendersFlatAssignRules()
@ -76,10 +72,7 @@ class IcingaServiceTest extends BaseTestCase
// Service apply rule rendering requires access to settings:
$service->setConnection($db);
$service->object_type = 'apply';
$service->assignments = array(
'host.address="127.*"',
'host.vars.env="test"'
);
$service->assign_filter = 'host.address="127.*"|host.vars.env="test"';
$this->assertEquals(
$this->loadRendered('service1'),
@ -87,8 +80,8 @@ class IcingaServiceTest extends BaseTestCase
);
$this->assertEquals(
'host.address="127.*"',
$service->toPlainObject()->assignments['assign'][0]
'host.address="127.*"|host.vars.env="test"',
$service->assign_filter
);
}
@ -104,10 +97,7 @@ class IcingaServiceTest extends BaseTestCase
// Service apply rule rendering requires access to settings:
$service->setConnection($db);
$service->object_type = 'apply';
$service->assignments = array(
'host.address="127.*"',
'host.vars.env="test"'
);
$service->assign_filter = 'host.address="127.*"|host.vars.env="test"';
$this->assertEquals(
$this->loadRendered('service1'),
@ -115,8 +105,8 @@ class IcingaServiceTest extends BaseTestCase
);
$this->assertEquals(
'host.address="127.*"',
$service->toPlainObject()->assignments['assign'][0]
'host.address="127.*"|host.vars.env="test"',
$service->assign_filter = 'host.address="127.*"|host.vars.env="test"'
);
}
@ -130,10 +120,7 @@ class IcingaServiceTest extends BaseTestCase
$service = $this->service();
$service->object_type = 'apply';
$service->assignments = array(
'host.address="127.*"',
'host.vars.env="test"'
);
$service->assign_filter = 'host.address="127.*"|host.vars.env="test"';
$service->store($db);
@ -144,74 +131,8 @@ class IcingaServiceTest extends BaseTestCase
);
$this->assertEquals(
'host.address="127.*"',
$service->toPlainObject()->assignments['assign'][0]
);
$service->delete();
}
public function testStaysUnmodifiedWhenSameFiltersAreSetInDifferentWays()
{
if ($this->skipForMissingDb()) {
return;
}
$db = $this->getDb();
$service = $this->service();
$service->object_type = 'apply';
$service->assignments = 'host.address="127.*"';
$service->store($db);
$this->assertFalse($service->hasBeenModified());
$service->assignments = array(
'host.address="127.*"',
);
$this->assertFalse($service->hasBeenModified());
$service->assignments = 'host.address="128.*"';
$this->assertTrue($service->hasBeenModified());
$service->store();
$this->assertFalse($service->hasBeenModified());
$service->assignments = array('assign' => 'host.address="128.*"');
$this->assertFalse($service->hasBeenModified());
$service->assignments = array(
'assign' => array(
'host.address="128.*"'
)
);
$this->assertFalse($service->hasBeenModified());
$service->assignments = array(
'assign' => array(
'host.address="128.*"'
),
'ignore' => 'host.name="localhost"'
);
$this->assertTrue($service->hasBeenModified());
$service->store();
$service = IcingaService::loadWithAutoIncId($service->id, $db);
$this->assertEquals(
'host.address="128.*"',
$service->toPlainObject()->assignments['assign'][0]
);
$this->assertEquals(
'host.name="localhost"',
$service->toPlainObject()->assignments['ignore'][0]
);
$this->assertEquals(
$this->loadRendered('service2'),
(string) $service
'host.address="127.*"|host.vars.env="test"',
$service->assign_filter
);
$service->delete();
@ -247,9 +168,7 @@ class IcingaServiceTest extends BaseTestCase
$service->setConnection($db);
$service->object_type = 'apply';
$service->display_name = 'Service: $host.vars.replaced$';
$service->assignments = array(
'host.address="127.*"',
);
$service->assign_filter = 'host.address="127.*"';
$service->{'vars.custom_var'} = '$host.vars.replaced$';
$this->assertEquals(
@ -290,9 +209,7 @@ class IcingaServiceTest extends BaseTestCase
$service = $this->service()->setConnection($db);
$service->object_type = 'apply';
$service->apply_for = 'host.vars.test1';
$service->assignments = array(
'host.vars.env="test"'
);
$service->assign_filter = 'host.vars.env="test"';
$this->assertEquals(
$this->loadRendered('service5'),
(string) $service

View File

@ -1,5 +1,6 @@
apply Service "___TEST___service" {
display_name = "Whatever service"
assign where match("127.*", host.address) || host.vars.env == "test"
vars.test1 = "string"
vars.test2 = 17
vars.test3 = false
@ -8,9 +9,6 @@ apply Service "___TEST___service" {
@this = "is"
}
assign where match("127.*", host.address)
assign where host.vars.env == "test"
import DirectorOverrideTemplate
}

View File

@ -1,5 +1,6 @@
apply Service "___TEST___service_$not_replaced$" {
display_name = "Service: " + host.vars.replaced
assign where match("127.*", host.address)
vars.custom_var = host.vars.replaced
vars.test1 = "string"
vars.test2 = 17
@ -9,8 +10,6 @@ apply Service "___TEST___service_$not_replaced$" {
@this = "is"
}
assign where match("127.*", host.address)
import DirectorOverrideTemplate
}

View File

@ -1,5 +1,6 @@
apply Service "___TEST___service" for (config in host.vars.test1) {
display_name = "Whatever service"
assign where host.vars.env == "test"
vars.test1 = "string"
vars.test2 = 17
vars.test3 = false
@ -8,8 +9,6 @@ apply Service "___TEST___service" for (config in host.vars.test1) {
@this = "is"
}
assign where host.vars.env == "test"
import DirectorOverrideTemplate
}

View File

@ -1,6 +1,7 @@
apply Service for (config in host.vars.test1) {
name = "___TEST" + config + "___service " + host.var.bla
display_name = "Whatever service"
assign where host.vars.env == "test"
vars.test1 = "string"
vars.test2 = 17
vars.test3 = false
@ -9,8 +10,6 @@ apply Service for (config in host.vars.test1) {
@this = "is"
}
assign where host.vars.env == "test"
import DirectorOverrideTemplate
}

View File

@ -1,5 +1,6 @@
apply Service for (config in host.vars.test1) {
display_name = "Whatever service"
assign where host.vars.env == "test"
vars.test1 = "string"
vars.test2 = 17
vars.test3 = false
@ -8,8 +9,6 @@ apply Service for (config in host.vars.test1) {
@this = "is"
}
assign where host.vars.env == "test"
import DirectorOverrideTemplate
}