Merge branch 'feature/drag-and-drop'

This commit is contained in:
Johannes Meyer 2019-02-27 14:03:29 +01:00
commit 4b76251f86
51 changed files with 4471 additions and 1137 deletions

View file

@ -34,7 +34,7 @@ class ProcessCommand extends Command
public function init()
{
$this->storage = new LegacyStorage($this->Config()->getSection('global'));
$this->storage = LegacyStorage::getInstance();
}
/**

View file

@ -25,23 +25,70 @@ class NodeController extends Controller
foreach ($this->storage()->listProcessNames() as $configName) {
$config = $this->storage()->loadProcess($configName);
if (! $config->hasNode($name)) {
$parents = [];
if ($config->hasNode($name)) {
foreach ($config->getNode($name)->getPaths() as $path) {
array_pop($path); // Remove the monitored node
$immediateParentName = array_pop($path); // The directly affected process
$parents[] = [$config->getNode($immediateParentName), $path];
}
}
$askedConfigs = [];
foreach ($config->getImportedNodes() as $importedNode) {
$importedConfig = $importedNode->getBpConfig();
if (isset($askedConfigs[$importedConfig->getName()])) {
continue;
} else {
$askedConfigs[$importedConfig->getName()] = true;
}
if ($importedConfig->hasNode($name)) {
$node = $importedConfig->getNode($name);
$nativePaths = $node->getPaths($config);
do {
$path = array_pop($nativePaths);
$importedNodePos = array_search($importedNode->getIdentifier(), $path, true);
if ($importedNodePos !== false) {
array_pop($path); // Remove the monitored node
$immediateParentName = array_pop($path); // The directly affected process
$importedPath = array_slice($path, $importedNodePos + 1);
// We may get multiple native paths. Though, only the right hand of the path
// is what we're interested in. The left part is not what is getting imported.
$antiDuplicator = join('|', $importedPath) . '|' . $immediateParentName;
if (isset($parents[$antiDuplicator])) {
continue;
}
foreach ($importedNode->getPaths($config) as $targetPath) {
if ($targetPath[count($targetPath) - 1] === $immediateParentName) {
array_pop($targetPath);
$parent = $importedNode;
} else {
$parent = $importedConfig->getNode($immediateParentName);
}
$parents[$antiDuplicator] = [$parent, array_merge($targetPath, $importedPath)];
}
}
} while (! empty($nativePaths));
}
}
if (empty($parents)) {
continue;
}
MonitoringState::apply($config);
$config->applySimulation($simulation);
foreach ($config->getNode($name)->getPaths() as $path) {
array_pop($path);
$node = array_pop($path);
$renderer = new TileRenderer($config, $config->getNode($node));
$renderer->setUrl(
Url::fromPath(
'businessprocess/process/show',
array('config' => $configName)
)
)->setPath($path);
foreach ($parents as $parentAndPath) {
$renderer = (new TileRenderer($config, array_shift($parentAndPath)))
->setUrl(Url::fromPath('businessprocess/process/show', ['config' => $configName]))
->setPath(array_shift($parentAndPath));
$bc = Breadcrumb::create($renderer);
$bc->getAttributes()->set('data-base-target', '_next');

View file

@ -88,12 +88,14 @@ class ProcessController extends Controller
$renderer = $this->prepareRenderer($bp, $node);
if ($this->params->get('unlocked')) {
$renderer->unlock();
}
if (! $this->showFullscreen && ($node === null || ! $renderer->rendersImportedNode())) {
if ($this->params->get('unlocked')) {
$renderer->unlock();
}
if ($bp->isEmpty() && $renderer->isLocked()) {
$this->redirectNow($this->url()->with('unlocked', true));
if ($bp->isEmpty() && $renderer->isLocked()) {
$this->redirectNow($this->url()->with('unlocked', true));
}
}
$this->handleFormatRequest($bp, $node);
@ -137,11 +139,10 @@ class ProcessController extends Controller
if (! ($this->showFullscreen || $this->view->compact)) {
$controls->add($this->getProcessTabs($bp, $renderer));
$controls->getAttributes()->add('class', 'separated');
}
if (! $this->view->compact) {
$controls->add(Html::tag('h1')->setContent($this->view->title));
}
$controls->add(Breadcrumb::create($renderer));
$controls->add(Breadcrumb::create(clone $renderer));
if (! $this->showFullscreen && ! $this->view->compact) {
$controls->add(
new RenderedProcessActionBar($bp, $renderer, $this->Auth(), $this->url())
@ -250,6 +251,13 @@ class ProcessController extends Controller
->setNode($bp->getNode($this->params->get('simulationnode')))
->setSimulation(Simulation::fromSession($this->session()))
->handleRequest();
} elseif ($action === 'move') {
$form = $this->loadForm('MoveNode')
->setProcess($bp)
->setParentNode($node)
->setSession($this->session())
->setNode($bp->getNode($this->params->get('movenode')))
->handleRequest();
}
if ($form) {

View file

@ -122,15 +122,20 @@ class AddNodeForm extends QuickForm
)
));
$display = 1;
if ($this->bp->getMetadata()->isManuallyOrdered() && !$this->bp->isEmpty()) {
$rootNodes = $this->bp->getRootNodes();
$display = end($rootNodes)->getDisplay() + 1;
}
$this->addElement('select', 'display', array(
'label' => $this->translate('Visualization'),
'required' => true,
'description' => $this->translate(
'Where to show this process'
),
'value' => $this->hasParentNode() ? '0' : '1',
'value' => $this->hasParentNode() ? '0' : "$display",
'multiOptions' => array(
'1' => $this->translate('Toplevel Process'),
"$display" => $this->translate('Toplevel Process'),
'0' => $this->translate('Subprocess only'),
)
));
@ -184,7 +189,7 @@ class AddNodeForm extends QuickForm
protected function selectHost()
{
$this->addElement('multiselect','children', [
$this->addElement('multiselect', 'children', [
'label' => $this->translate('Hosts'),
'required' => true,
'size' => 8,
@ -220,7 +225,7 @@ class AddNodeForm extends QuickForm
protected function addServicesElement($host)
{
$this->addElement('multiselect','children', [
$this->addElement('multiselect', 'children', [
'label' => $this->translate('Services'),
'required' => true,
'size' => 8,
@ -255,7 +260,7 @@ class AddNodeForm extends QuickForm
}
if (($file = $this->getSentValue('file')) || !$this->hasParentNode()) {
$this->addElement('multiselect','children', [
$this->addElement('multiselect', 'children', [
'label' => $this->translate('Process nodes'),
'required' => true,
'size' => 8,
@ -420,11 +425,13 @@ class AddNodeForm extends QuickForm
$name = '@' . $file . ':' . $name;
}
$list[$name] = (string) $node; // display name?
$list[$name] = $node->getName(); // display name?
}
}
natcasesort($list);
if (! $this->bp->getMetadata()->isManuallyOrdered()) {
natcasesort($list);
}
return $list;
}

View file

@ -126,15 +126,16 @@ class EditNodeForm extends QuickForm
)
));
$display = $this->getNode()->getDisplay() ?: 1;
$this->addElement('select', 'display', array(
'label' => $this->translate('Visualization'),
'required' => true,
'description' => $this->translate(
'Where to show this process'
),
'value' => $this->hasParentNode() ? '0' : '1',
'value' => $display,
'multiOptions' => array(
'1' => $this->translate('Toplevel Process'),
"$display" => $this->translate('Toplevel Process'),
'0' => $this->translate('Subprocess only'),
)
));
@ -358,11 +359,13 @@ class EditNodeForm extends QuickForm
foreach ($this->bp->getNodes() as $node) {
if ($node instanceof BpNode && ! isset($parents[$node->getName()])) {
$list[(string) $node] = (string) $node; // display name?
$list[$node->getName()] = $node->getName(); // display name?
}
}
natcasesort($list);
if (! $this->bp->getMetadata()->isManuallyOrdered()) {
natcasesort($list);
}
return $list;
}

View file

@ -0,0 +1,185 @@
<?php
namespace Icinga\Module\Businessprocess\Forms;
use Icinga\Application\Icinga;
use Icinga\Exception\Http\HttpException;
use Icinga\Module\Businessprocess\BpConfig;
use Icinga\Module\Businessprocess\BpNode;
use Icinga\Module\Businessprocess\Exception\ModificationError;
use Icinga\Module\Businessprocess\Modification\ProcessChanges;
use Icinga\Module\Businessprocess\Node;
use Icinga\Module\Businessprocess\Web\Form\CsrfToken;
use Icinga\Module\Businessprocess\Web\Form\QuickForm;
use Icinga\Web\Session;
use Icinga\Web\Session\SessionNamespace;
class MoveNodeForm extends QuickForm
{
/** @var BpConfig */
protected $bp;
/** @var Node */
protected $node;
/** @var BpNode */
protected $parentNode;
/** @var SessionNamespace */
protected $session;
public function __construct($options = null)
{
parent::__construct($options);
// Zend's plugin loader reverses the order of added prefix paths thus trying our paths first before trying
// Zend paths
$this->addPrefixPaths(array(
array(
'prefix' => 'Icinga\\Web\\Form\\Element\\',
'path' => Icinga::app()->getLibraryDir('Icinga/Web/Form/Element'),
'type' => static::ELEMENT
),
array(
'prefix' => 'Icinga\\Web\\Form\\Decorator\\',
'path' => Icinga::app()->getLibraryDir('Icinga/Web/Form/Decorator'),
'type' => static::DECORATOR
)
));
}
public function setup()
{
$this->addElement(
'text',
'parent',
[
'allowEmpty' => true,
'filters' => ['Null'],
'validators' => [
['Callback', true, [
'callback' => function ($name) {
return empty($name) || $this->bp->hasBpNode($name);
},
'messages' => [
'callbackValue' => $this->translate('No process found with name %value%')
]
]]
]
]
);
$this->addElement(
'number',
'from',
[
'required' => true,
'min' => 0
]
);
$this->addElement(
'number',
'to',
[
'required' => true,
'min' => 0
]
);
$this->addElement(
'hidden',
'csrfToken',
[
'required' => true
]
);
$this->setSubmitLabel('movenode');
}
/**
* @param BpConfig $process
* @return $this
*/
public function setProcess(BpConfig $process)
{
$this->bp = $process;
return $this;
}
/**
* @param Node $node
* @return $this
*/
public function setNode(Node $node)
{
$this->node = $node;
return $this;
}
/**
* @param BpNode|null $node
* @return $this
*/
public function setParentNode(BpNode $node = null)
{
$this->parentNode = $node;
return $this;
}
/**
* @param SessionNamespace $session
* @return $this
*/
public function setSession(SessionNamespace $session)
{
$this->session = $session;
return $this;
}
public function onSuccess()
{
if (! CsrfToken::isValid($this->getValue('csrfToken'))) {
throw new HttpException(403, 'nope');
}
$changes = ProcessChanges::construct($this->bp, $this->session);
if (! $this->bp->getMetadata()->isManuallyOrdered()) {
$changes->applyManualOrder();
}
try {
$changes->moveNode(
$this->node,
$this->getValue('from'),
$this->getValue('to'),
$this->getValue('parent'),
$this->parentNode !== null ? $this->parentNode->getName() : null
);
} catch (ModificationError $e) {
$this->notifyError($e->getMessage());
Icinga::app()->getResponse()
// Web 2's JS forces a content update for non-200s. Our own JS
// can't prevent this, hence we're not making this a 400 :(
//->setHttpResponseCode(400)
->setHeader('X-Icinga-Container', 'ignore')
->sendResponse();
exit;
}
// Trigger session destruction to make sure it get's stored.
unset($changes);
$this->notifySuccess($this->getSuccessMessage($this->translate('Node order updated')));
$response = $this->getRequest()->getResponse()
->setHeader('X-Icinga-Container', 'ignore');
Session::getSession()->write();
$response->sendResponse();
exit;
}
public function hasBeenSent()
{
return true; // This form has no id
}
}

View file

@ -73,14 +73,20 @@ class ProcessForm extends QuickForm
)
));
if ($this->node !== null) {
$display = $this->node->getDisplay() ?: 1;
} else {
$display = 1;
}
$this->addElement('select', 'display', array(
'label' => $this->translate('Visualization'),
'required' => true,
'description' => $this->translate(
'Where to show this process'
),
'value' => $display,
'multiOptions' => array(
'1' => $this->translate('Toplevel Process'),
"$display" => $this->translate('Toplevel Process'),
'0' => $this->translate('Subprocess only'),
)
));
@ -97,7 +103,6 @@ class ProcessForm extends QuickForm
$this->getElement('alias')->setValue($node->getAlias());
}
$this->getElement('operator')->setValue($node->getOperator());
$this->getElement('display')->setValue($node->getDisplay());
if ($node->hasInfoUrl()) {
$this->getElement('url')->setValue($node->getInfoUrl());
}

View file

@ -10,9 +10,7 @@ $section = $this->menuSection(N_('Business Processes'), array(
));
try {
$storage = new LegacyStorage(
$this->getConfig()->getSection('global')
);
$storage = LegacyStorage::getInstance();
$prio = 0;
foreach ($storage->listProcessNames() as $name) {
@ -57,3 +55,9 @@ $this->provideRestriction(
'businessprocess/prefix',
$this->translate('Restrict access to configurations with the given prefix')
);
$this->provideJsFile('vendor/Sortable.js');
$this->provideJsFile('behavior/sortable.js');
$this->provideJsFile('vendor/jquery.fn.sortable.js');
$this->provideCssFile('state-ball.less');

View file

@ -2,12 +2,14 @@
namespace Icinga\Module\Businessprocess;
use Exception;
use Icinga\Application\Config;
use Icinga\Exception\IcingaException;
use Icinga\Exception\NotFoundError;
use Icinga\Module\Businessprocess\Exception\NestingError;
use Icinga\Module\Businessprocess\Modification\ProcessChanges;
use Icinga\Module\Businessprocess\Storage\LegacyStorage;
use Icinga\Module\Monitoring\Backend\MonitoringBackend;
use Exception;
class BpConfig
{
@ -29,6 +31,11 @@ class BpConfig
*/
protected $backend;
/**
* @var LegacyStorage
*/
protected $storage;
/** @var Metadata */
protected $metadata;
@ -81,6 +88,20 @@ class BpConfig
*/
protected $root_nodes = array();
/**
* Imported nodes
*
* @var ImportedNode[]
*/
protected $importedNodes = [];
/**
* Imported configs
*
* @var BpConfig[]
*/
protected $importedConfigs = [];
/**
* All host names { 'hostA' => true, ... }
*
@ -388,14 +409,32 @@ class BpConfig
*/
public function getRootNodes()
{
ksort($this->root_nodes, SORT_NATURAL | SORT_FLAG_CASE);
if ($this->getMetadata()->isManuallyOrdered()) {
uasort($this->root_nodes, function (BpNode $a, BpNode $b) {
$a = $a->getDisplay();
$b = $b->getDisplay();
return $a > $b ? 1 : ($a < $b ? -1 : 0);
});
} else {
ksort($this->root_nodes, SORT_NATURAL | SORT_FLAG_CASE);
}
return $this->root_nodes;
}
public function listRootNodes()
{
$names = array_keys($this->root_nodes);
natcasesort($names);
if ($this->getMetadata()->isManuallyOrdered()) {
uasort($names, function ($a, $b) {
$a = $this->root_nodes[$a]->getDisplay();
$b = $this->root_nodes[$b]->getDisplay();
return $a > $b ? 1 : ($a < $b ? -1 : 0);
});
} else {
natcasesort($names);
}
return $names;
}
@ -406,7 +445,14 @@ class BpConfig
public function hasNode($name)
{
return array_key_exists($name, $this->nodes);
if (array_key_exists($name, $this->nodes)) {
return true;
} elseif ($name[0] === '@') {
list($configName, $nodeName) = preg_split('~:\s*~', substr($name, 1), 2);
return $this->getImportedConfig($configName)->hasNode($nodeName);
}
return false;
}
public function hasRootNode($name)
@ -417,12 +463,12 @@ class BpConfig
public function createService($host, $service)
{
$node = new ServiceNode(
$this,
(object) array(
'hostname' => $host,
'service' => $service
)
);
$node->setBpConfig($this);
$this->nodes[$host . ';' . $service] = $node;
$this->hosts[$host] = true;
return $node;
@ -430,7 +476,8 @@ class BpConfig
public function createHost($host)
{
$node = new HostNode($this, (object) array('hostname' => $host));
$node = new HostNode((object) array('hostname' => $host));
$node->setBpConfig($this);
$this->nodes[$host . ';Hoststatus'] = $node;
$this->hosts[$host] = true;
return $node;
@ -454,9 +501,21 @@ class BpConfig
return $this;
}
public function listInvolvedHostNames()
public function listInvolvedHostNames(&$usedConfigs = null)
{
return array_keys($this->hosts);
$hosts = $this->hosts;
if (! empty($this->importedNodes)) {
$usedConfigs[$this->getName()] = true;
foreach ($this->importedNodes as $node) {
if (isset($usedConfigs[$node->getConfigName()])) {
continue;
}
$hosts += array_flip($node->getBpConfig()->listInvolvedHostNames($usedConfigs));
}
}
return array_keys($hosts);
}
/**
@ -469,11 +528,12 @@ class BpConfig
*/
public function createBp($name, $operator = '&')
{
$node = new BpNode($this, (object) array(
$node = new BpNode((object) array(
'name' => $name,
'operator' => $operator,
'child_names' => array(),
));
$node->setBpConfig($this);
$this->addNode($name, $node);
return $node;
@ -502,14 +562,66 @@ class BpConfig
}
$node = new ImportedNode($this, $params);
$this->importedNodes[$node->getName()] = $node;
$this->nodes[$node->getName()] = $node;
return $node;
}
public function getImportedNodes()
{
return $this->importedNodes;
}
public function getImportedConfig($name)
{
if (! isset($this->importedConfigs[$name])) {
$import = $this->storage()->loadProcess($name);
if ($this->usesSoftStates()) {
$import->useSoftStates();
} else {
$import->useHardStates();
}
$this->importedConfigs[$name] = $import;
}
return $this->importedConfigs[$name];
}
public function listInvolvedConfigs(&$configs = null)
{
if ($configs === null) {
$configs[$this->getName()] = $this;
}
foreach ($this->importedNodes as $node) {
if (! isset($configs[$node->getConfigName()])) {
$config = $node->getBpConfig();
$configs[$node->getConfigName()] = $config;
$config->listInvolvedConfigs($configs);
}
}
return $configs;
}
/**
* @param $name
* @return Node
* @throws Exception
* @return LegacyStorage
*/
protected function storage()
{
if ($this->storage === null) {
$this->storage = LegacyStorage::getInstance();
}
return $this->storage;
}
/**
* @param string $name
* @return Node
* @throws Exception
*/
public function getNode($name)
{
@ -521,6 +633,11 @@ class BpConfig
return $this->nodes[$name];
}
if ($name[0] === '@') {
list($configName, $nodeName) = preg_split('~:\s*~', substr($name, 1), 2);
return $this->getImportedConfig($configName)->getNode($nodeName);
}
// Fallback: if it is a service, create an empty one:
$this->warn(sprintf('The node "%s" doesn\'t exist', $name));
$pos = strpos($name, ';');
@ -550,11 +667,12 @@ class BpConfig
$this->calculateAllStates();
$names = array_keys($this->getUnboundNodes());
$bp = new BpNode($this, (object) array(
$bp = new BpNode((object) array(
'name' => '__unbound__',
'operator' => '&',
'child_names' => $names
));
$bp->setBpConfig($this);
$bp->setAlias($this->translate('Unbound nodes'));
return $bp;
}
@ -685,7 +803,16 @@ class BpConfig
$nodes[$name] = $name === $alias ? $name : sprintf('%s (%s)', $alias, $node);
}
natcasesort($nodes);
if ($this->getMetadata()->isManuallyOrdered()) {
uasort($nodes, function ($a, $b) {
$a = $this->nodes[$a]->getDisplay();
$b = $this->nodes[$b]->getDisplay();
return $a > $b ? 1 : ($a < $b ? -1 : 0);
});
} else {
natcasesort($nodes);
}
return $nodes;
}

View file

@ -48,12 +48,11 @@ class BpNode extends Node
protected $className = 'process';
public function __construct(BpConfig $bp, $object)
public function __construct($object)
{
$this->bp = $bp;
$this->name = $object->name;
$this->setOperator($object->operator);
$this->setChildNames($object->child_names);
$this->operator = $object->operator;
$this->childNames = $object->child_names;
}
public function getStateSummary()
@ -140,12 +139,12 @@ class BpNode extends Node
public function hasChild($name)
{
return in_array($name, $this->childNames);
return in_array($name, $this->getChildNames());
}
public function removeChild($name)
{
if (($key = array_search($name, $this->childNames)) !== false) {
if (($key = array_search($name, $this->getChildNames())) !== false) {
unset($this->childNames[$key]);
if (! empty($this->children)) {
@ -161,7 +160,7 @@ class BpNode extends Node
$tree = array();
foreach ($this->getProblematicChildren() as $child) {
$name = (string) $child;
$name = $child->getName();
$tree[$name] = array(
'node' => $child,
'children' => array()
@ -178,7 +177,7 @@ class BpNode extends Node
{
if ($this->missing === null) {
$exists = false;
$bp = $this->bp;
$bp = $this->getBpConfig();
$bp->beginLoopDetection($this->name);
foreach ($this->getChildren() as $child) {
if (! $child->isMissing()) {
@ -198,11 +197,11 @@ class BpNode extends Node
foreach ($this->getChildren() as $child) {
if ($child->isMissing()) {
$missing[(string) $child] = $child;
$missing[$child->getName()] = $child;
}
foreach ($child->getMissingChildren() as $m) {
$missing[(string) $m] = $m;
$missing[$m->getName()] = $m;
}
}
@ -276,7 +275,7 @@ class BpNode extends Node
public function hasAlias()
{
return $this->alias !== null;
return $this->getAlias() !== null;
}
public function getAlias()
@ -299,8 +298,8 @@ class BpNode extends Node
try {
$this->reCalculateState();
} catch (NestingError $e) {
$this->bp->addError(
$this->bp->translate('Nesting error detected: %s'),
$this->getBpConfig()->addError(
$this->getBpConfig()->translate('Nesting error detected: %s'),
$e->getMessage()
);
@ -314,7 +313,7 @@ class BpNode extends Node
public function getHtmlId()
{
return 'businessprocess-' . preg_replace('/[\r\n\t\s]/', '_', (string) $this);
return 'businessprocess-' . preg_replace('/[\r\n\t\s]/', '_', $this->getName());
}
protected function invertSortingState($state)
@ -327,7 +326,7 @@ class BpNode extends Node
*/
public function reCalculateState()
{
$bp = $this->bp;
$bp = $this->getBpConfig();
$sort_states = array();
$lastStateChange = 0;
@ -357,7 +356,7 @@ class BpNode extends Node
$this->setLastStateChange($lastStateChange);
switch ($this->operator) {
switch ($this->getOperator()) {
case self::OP_AND:
$sort_state = max($sort_states);
break;
@ -401,7 +400,7 @@ class BpNode extends Node
public function checkForLoops()
{
$bp = $this->bp;
$bp = $this->getBpConfig();
foreach ($this->getChildren() as $child) {
$bp->beginLoopDetection($this->name);
if ($child instanceof BpNode) {
@ -426,7 +425,11 @@ class BpNode extends Node
public function setChildNames($names)
{
natcasesort($names);
if (! $this->getBpConfig()->getMetadata()->isManuallyOrdered()) {
natcasesort($names);
$names = array_values($names);
}
$this->childNames = $names;
$this->children = null;
return $this;
@ -434,7 +437,8 @@ class BpNode extends Node
public function hasChildren($filter = null)
{
return !empty($this->childNames);
$childNames = $this->getChildNames();
return !empty($childNames);
}
public function getChildNames()
@ -446,9 +450,13 @@ class BpNode extends Node
{
if ($this->children === null) {
$this->children = array();
natcasesort($this->childNames);
foreach ($this->childNames as $name) {
$this->children[$name] = $this->bp->getNode($name);
if (! $this->getBpConfig()->getMetadata()->isManuallyOrdered()) {
$childNames = $this->getChildNames();
natcasesort($childNames);
$this->childNames = array_values($childNames);
}
foreach ($this->getChildNames() as $name) {
$this->children[$name] = $this->getBpConfig()->getNode($name);
$this->children[$name]->addParent($this);
}
}
@ -490,22 +498,22 @@ class BpNode extends Node
protected function assertNumericOperator()
{
if (! is_numeric($this->operator)) {
if (! is_numeric($this->getOperator())) {
throw new ConfigurationError('Got invalid operator: %s', $this->operator);
}
}
public function operatorHtml()
{
switch ($this->operator) {
switch ($this->getOperator()) {
case self::OP_AND:
return 'and';
return 'AND';
break;
case self::OP_OR:
return 'or';
return 'OR';
break;
case self::OP_NOT:
return 'not';
return 'NOT';
break;
default:
// MIN
@ -513,4 +521,10 @@ class BpNode extends Node
return 'min:' . $this->operator;
}
}
public function getIcon()
{
$this->icon = $this->hasParents() ? 'cubes' : 'sitemap';
return parent::getIcon();
}
}

View file

@ -2,7 +2,6 @@
namespace Icinga\Module\Businessprocess\Director;
use Icinga\Application\Config;
use Icinga\Module\Director\Hook\ShipConfigFilesHook;
use Icinga\Module\Businessprocess\Storage\LegacyStorage;
@ -12,9 +11,7 @@ class ShipConfigFiles extends ShipConfigFilesHook
{
$files = array();
$storage = new LegacyStorage(
Config::module('businessprocess')->getSection('global')
);
$storage = LegacyStorage::getInstance();
foreach ($storage->listProcesses() as $name => $title) {
$files['processes/' . $name . '.bp'] = $storage->getSource($name);

View file

@ -0,0 +1,9 @@
<?php
namespace Icinga\Module\Businessprocess\Exception;
use Icinga\Exception\IcingaException;
class ModificationError extends IcingaException
{
}

View file

@ -32,11 +32,12 @@ class HostNode extends MonitoredNode
protected $className = 'host';
public function __construct(BpConfig $bp, $object)
protected $icon = 'host';
public function __construct($object)
{
$this->name = $object->hostname . ';Hoststatus';
$this->hostname = $object->hostname;
$this->bp = $bp;
if (isset($object->state)) {
$this->setState($object->state);
} else {
@ -60,8 +61,8 @@ class HostNode extends MonitoredNode
'host' => $this->getHostname(),
);
if ($this->bp->hasBackendName()) {
$params['backend'] = $this->bp->getBackendName();
if ($this->getBpConfig()->hasBackendName()) {
$params['backend'] = $this->getBpConfig()->getBackendName();
}
return Url::fromPath('businessprocess/host/show', $params);

View file

@ -2,15 +2,13 @@
namespace Icinga\Module\Businessprocess;
use Icinga\Application\Config;
use Icinga\Module\Businessprocess\State\MonitoringState;
use Icinga\Module\Businessprocess\Storage\LegacyStorage;
use Exception;
use Icinga\Web\Url;
use ipl\Html\Html;
class ImportedNode extends Node
class ImportedNode extends BpNode
{
/** @var BpConfig */
protected $parentBp;
/** @var string */
protected $configName;
@ -18,36 +16,25 @@ class ImportedNode extends Node
protected $nodeName;
/** @var BpNode */
private $node;
protected $importedNode;
protected $className = 'subtree';
/** @var string */
protected $className = 'process subtree';
/** @var BpConfig */
private $config;
/** @var string */
protected $icon = 'download';
/**
* @inheritdoc
*/
public function __construct(BpConfig $bp, $object)
public function __construct(BpConfig $parentBp, $object)
{
$this->bp = $bp;
$this->parentBp = $parentBp;
$this->configName = $object->configName;
$this->name = '@' . $object->configName;
if (property_exists($object, 'node')) {
$this->nodeName = $object->node;
$this->name .= ':' . $object->node;
} else {
$this->useAllRootNodes();
}
$this->nodeName = $object->node;
if (isset($object->state)) {
$this->setState($object->state);
}
}
public function hasNode()
{
return $this->nodeName !== null;
parent::__construct((object) [
'name' => '@' . $this->configName . ':' . $this->nodeName,
'operator' => null,
'child_names' => null
]);
}
/**
@ -59,75 +46,52 @@ class ImportedNode extends Node
}
/**
* @inheritdoc
* @return string
*/
public function getState()
public function getNodeName()
{
if ($this->state === null) {
try {
MonitoringState::apply($this->importedConfig());
} catch (Exception $e) {
}
$this->state = $this->importedNode()->getState();
$this->setMissing(false);
}
return $this->state;
return $this->nodeName;
}
public function getIdentifier()
{
return $this->getName();
}
public function getBpConfig()
{
if ($this->bp === null) {
$this->bp = $this->parentBp->getImportedConfig($this->configName);
}
return $this->bp;
}
/**
* @inheritdoc
*/
public function getAlias()
{
return $this->importedNode()->getAlias();
}
public function getUrl()
{
$params = array(
'config' => $this->getConfigName(),
'node' => $this->importedNode()->getName()
);
return Url::fromPath('businessprocess/process/show', $params);
}
/**
* @inheritdoc
*/
public function isMissing()
{
$this->getState();
// Probably doesn't work, as we create a fake node with worse state
return $this->missing;
}
/**
* @inheritdoc
*/
public function isInDowntime()
{
if ($this->downtime === null) {
$this->getState();
$this->downtime = $this->importedNode()->isInDowntime();
if ($this->alias === null) {
$this->alias = $this->importedNode()->getAlias();
}
return $this->downtime;
return $this->alias;
}
/**
* @inheritdoc
*/
public function isAcknowledged()
public function getOperator()
{
if ($this->ack === null) {
$this->getState();
$this->downtime = $this->importedNode()->isAcknowledged();
if ($this->operator === null) {
$this->operator = $this->importedNode()->getOperator();
}
return $this->ack;
return $this->operator;
}
public function getChildNames()
{
if ($this->childNames === null) {
$this->childNames = $this->importedNode()->getChildNames();
}
return $this->childNames;
}
/**
@ -135,68 +99,15 @@ class ImportedNode extends Node
*/
protected function importedNode()
{
if ($this->node === null) {
$this->node = $this->loadImportedNode();
}
return $this->node;
}
/**
* @return BpNode
*/
protected function loadImportedNode()
{
try {
$import = $this->importedConfig();
return $import->getNode($this->nodeName);
} catch (Exception $e) {
return $this->createFailedNode($e);
}
}
protected function useAllRootNodes()
{
try {
$bp = $this->importedConfig();
$this->node = new BpNode($bp, (object) array(
'name' => $this->getName(),
'operator' => '&',
'child_names' => $bp->listRootNodes(),
));
} catch (Exception $e) {
$this->createFailedNode($e);
}
}
/**
* @return BpConfig
*/
protected function importedConfig()
{
if ($this->config === null) {
$import = $this->storage()->loadProcess($this->configName);
if ($this->bp->usesSoftStates()) {
$import->useSoftStates();
} else {
$import->useHardStates();
if ($this->importedNode === null) {
try {
$this->importedNode = $this->getBpConfig()->getBpNode($this->nodeName);
} catch (Exception $e) {
return $this->createFailedNode($e);
}
$this->config = $import;
}
return $this->config;
}
/**
* @return LegacyStorage
*/
protected function storage()
{
return new LegacyStorage(
Config::module('businessprocess')->getSection('global')
);
return $this->importedNode;
}
/**
@ -206,12 +117,13 @@ class ImportedNode extends Node
*/
protected function createFailedNode(Exception $e)
{
$this->bp->addError($e->getMessage());
$node = new BpNode($this->importedConfig(), (object) array(
$this->parentBp->addError($e->getMessage());
$node = new BpNode((object) array(
'name' => $this->getName(),
'operator' => '&',
'child_names' => array()
'child_names' => []
));
$node->setBpConfig($this->getBpConfig());
$node->setState(2);
$node->setMissing(false)
->setDowntime(false)
@ -220,21 +132,4 @@ class ImportedNode extends Node
return $node;
}
/**
* @inheritdoc
*/
public function getLink()
{
return Html::tag(
'a',
[
'href' => Url::fromPath('businessprocess/process/show', [
'config' => $this->configName,
'node' => $this->nodeName
])
],
$this->getAlias()
);
}
}

View file

@ -22,6 +22,7 @@ class Metadata
'AddToMenu' => null,
'Backend' => null,
'Statetype' => null,
'ManualOrder' => null,
// 'SLAHosts' => null
);
@ -251,6 +252,11 @@ class Metadata
return false;
}
public function isManuallyOrdered()
{
return $this->get('ManualOrder') === 'yes';
}
protected function splitCommaSeparated($string)
{
return preg_split('/\s*,\s*/', $string, -1, PREG_SPLIT_NO_EMPTY);

View file

@ -3,6 +3,7 @@
namespace Icinga\Module\Businessprocess\Modification;
use Icinga\Module\Businessprocess\BpConfig;
use Icinga\Module\Businessprocess\Exception\ModificationError;
use Icinga\Module\Businessprocess\Node;
use Icinga\Exception\ProgrammingError;
@ -48,10 +49,13 @@ abstract class NodeAction
abstract public function applyTo(BpConfig $config);
/**
* Every NodeAction must be able to tell whether it could be applied to a BusinessProcess
* Every NodeAction must be able to tell whether it can be applied to a BusinessProcess
*
* @param BpConfig $config
* @return bool
* @param BpConfig $config
*
* @throws ModificationError
*
* @return bool
*/
abstract public function appliesTo(BpConfig $config);
@ -81,6 +85,21 @@ abstract class NodeAction
return $this->getActionName() === $actionName;
}
/**
* Throw a ModificationError
*
* @param string $msg
* @param mixed ...
*
* @throws ModificationError
*/
protected function error($msg)
{
$error = ModificationError::create(func_get_args());
/** @var ModificationError $error */
throw $error;
}
/**
* Create an instance of a given actionName for a specific Node
*

View file

@ -17,11 +17,11 @@ class NodeAddChildrenAction extends NodeAction
{
$name = $this->getNodeName();
if (! $config->hasNode($name)) {
return false;
if (! $config->hasBpNode($name)) {
$this->error('Process "%s" not found', $name);
}
return $config->getNode($name) instanceof BpNode;
return true;
}
/**
@ -32,7 +32,7 @@ class NodeAddChildrenAction extends NodeAction
$node = $config->getBpNode($this->getNodeName());
foreach ($this->children as $name) {
if (! $config->hasNode($name)) {
if (! $config->hasNode($name) || $config->getNode($name)->getBpConfig()->getName() !== $config->getName()) {
if (strpos($name, ';') !== false) {
list($host, $service) = preg_split('/;/', $name, 2);

View file

@ -0,0 +1,29 @@
<?php
namespace Icinga\Module\Businessprocess\Modification;
use Icinga\Module\Businessprocess\BpConfig;
class NodeApplyManualOrderAction extends NodeAction
{
public function appliesTo(BpConfig $config)
{
return $config->getMetadata()->get('ManualOrder') !== 'yes';
}
public function applyTo(BpConfig $config)
{
$i = 0;
foreach ($config->getBpNodes() as $name => $node) {
if ($node->getDisplay() > 0) {
$node->setDisplay(++$i);
}
if ($node->hasChildren()) {
$node->setChildNames($node->getChildNames());
}
}
$config->getMetadata()->set('ManualOrder', 'yes');
}
}

View file

@ -22,7 +22,7 @@ class NodeCreateAction extends NodeAction
*/
public function setParent(Node $name)
{
$this->parentName = (string) $name;
$this->parentName = $name->getName();
}
/**
@ -72,7 +72,17 @@ class NodeCreateAction extends NodeAction
*/
public function appliesTo(BpConfig $config)
{
return ! $config->hasNode($this->getNodeName());
$name = $this->getNodeName();
if ($config->hasNode($name)) {
$this->error('A node with name "%s" already exists', $name);
}
$parent = $this->getParentName();
if ($parent !== null && !$config->hasBpNode($parent)) {
$this->error('Parent process "%s" missing', $parent);
}
return true;
}
/**
@ -91,7 +101,8 @@ class NodeCreateAction extends NodeAction
} else {
$properties['child_names'] = array();
}
$node = new BpNode($config, (object) $properties);
$node = new BpNode((object) $properties);
$node->setBpConfig($config);
foreach ($this->getProperties() as $key => $val) {
if ($key === 'parentName') {
@ -102,6 +113,15 @@ class NodeCreateAction extends NodeAction
$node->$func($val);
}
if ($node->getDisplay() > 1) {
$i = $node->getDisplay();
foreach ($config->getRootNodes() as $_ => $rootNode) {
if ($rootNode->getDisplay() >= $node->getDisplay()) {
$rootNode->setDisplay(++$i);
}
}
}
$config->addNode($name, $node);
return $node;

View file

@ -47,15 +47,21 @@ class NodeModifyAction extends NodeAction
$name = $this->getNodeName();
if (! $config->hasNode($name)) {
return false;
$this->error('Node "%s" not found', $name);
}
$node = $config->getNode($name);
foreach ($this->properties as $key => $val) {
$func = 'get' . ucfirst($key);
if ($this->formerProperties[$key] !== $node->$func()) {
return false;
$currentVal = $node->{'get' . ucfirst($key)}();
if ($this->formerProperties[$key] !== $currentVal) {
$this->error(
'Property %s of node "%s" changed its value from "%s" to "%s"',
$key,
$name,
$this->formerProperties[$key],
$currentVal
);
}
}

View file

@ -0,0 +1,212 @@
<?php
namespace Icinga\Module\Businessprocess\Modification;
use Icinga\Module\Businessprocess\BpConfig;
use Icinga\Module\Businessprocess\BpNode;
class NodeMoveAction extends NodeAction
{
/**
* @var string
*/
protected $parent;
/**
* @var string
*/
protected $newParent;
/**
* @var int
*/
protected $from;
/**
* @var int
*/
protected $to;
protected $preserveProperties = ['parent', 'newParent', 'from', 'to'];
public function setParent($name)
{
$this->parent = $name;
}
public function getParent()
{
return $this->parent;
}
public function setNewParent($name)
{
$this->newParent = $name;
}
public function getNewParent()
{
return $this->newParent;
}
public function setFrom($from)
{
$this->from = (int) $from;
}
public function getFrom()
{
return $this->from;
}
public function setTo($to)
{
$this->to = (int) $to;
}
public function getTo()
{
return $this->to;
}
public function appliesTo(BpConfig $config)
{
if (! $config->getMetadata()->isManuallyOrdered()) {
$this->error('Process configuration is not manually ordered yet');
}
$name = $this->getNodeName();
if ($this->parent !== null) {
if (! $config->hasBpNode($this->parent)) {
$this->error('Parent process "%s" missing', $this->parent);
}
$parent = $config->getBpNode($this->parent);
if (! $parent->hasChild($name)) {
$this->error('Node "%s" not found in process "%s"', $name, $this->parent);
}
$nodes = $parent->getChildNames();
if (! isset($nodes[$this->from]) || $nodes[$this->from] !== $name) {
$this->error('Node "%s" not found at position %d', $name, $this->from);
}
} else {
if (! $config->hasRootNode($name)) {
$this->error('Toplevel process "%s" not found', $name);
}
$nodes = $config->listRootNodes();
if (! isset($nodes[$this->from]) || $nodes[$this->from] !== $name) {
$this->error('Toplevel process "%s" not found at position %d', $name, $this->from);
}
}
if ($this->parent !== $this->newParent) {
if ($this->newParent !== null) {
if (! $config->hasBpNode($this->newParent)) {
$this->error('New parent process "%s" missing', $this->newParent);
} elseif ($config->getBpNode($this->newParent)->hasChild($name)) {
$this->error(
'New parent process "%s" already has a node with the name "%s"',
$this->newParent,
$name
);
}
$childrenCount = $config->getBpNode($this->newParent)->countChildren();
if ($this->to > 0 && $childrenCount < $this->to) {
$this->error(
'New parent process "%s" has not enough children. Target position %d out of range',
$this->newParent,
$this->to
);
}
} else {
if ($config->hasRootNode($name)) {
$this->error('Process "%s" is already a toplevel process', $name);
}
$childrenCount = $config->countChildren();
if ($this->to > 0 && $childrenCount < $this->to) {
$this->error(
'Process configuration has not enough toplevel processes. Target position %d out of range',
$this->to
);
}
}
}
return true;
}
public function applyTo(BpConfig $config)
{
$name = $this->getNodeName();
if ($this->parent !== null) {
$nodes = $config->getBpNode($this->parent)->getChildren();
} else {
$nodes = $config->getRootNodes();
}
$node = $nodes[$name];
$nodes = array_merge(
array_slice($nodes, 0, $this->from, true),
array_slice($nodes, $this->from + 1, null, true)
);
if ($this->parent === $this->newParent) {
$nodes = array_merge(
array_slice($nodes, 0, $this->to, true),
[$name => $node],
array_slice($nodes, $this->to, null, true)
);
} else {
if ($this->newParent !== null) {
$newNodes = $config->getBpNode($this->newParent)->getChildren();
} else {
$newNodes = $config->getRootNodes();
}
$newNodes = array_merge(
array_slice($newNodes, 0, $this->to, true),
[$name => $node],
array_slice($newNodes, $this->to, null, true)
);
if ($this->newParent !== null) {
$config->getBpNode($this->newParent)->setChildNames(array_keys($newNodes));
} else {
$config->addRootNode($name);
$i = 0;
foreach ($newNodes as $newName => $newNode) {
/** @var BpNode $newNode */
if ($newNode->getDisplay() > 0 || $newName === $name) {
$i += 1;
if ($newNode->getDisplay() !== $i) {
$newNode->setDisplay($i);
}
}
}
}
}
if ($this->parent !== null) {
$config->getBpNode($this->parent)->setChildNames(array_keys($nodes));
} else {
if ($this->newParent !== null) {
$config->removeRootNode($name);
$node->setDisplay(0);
}
$i = 0;
foreach ($nodes as $_ => $oldNode) {
/** @var BpNode $oldNode */
if ($oldNode->getDisplay() > 0) {
$i += 1;
if ($oldNode->getDisplay() !== $i) {
$oldNode->setDisplay($i);
}
}
}
}
}
}

View file

@ -40,12 +40,21 @@ class NodeRemoveAction extends NodeAction
*/
public function appliesTo(BpConfig $config)
{
$name = $this->getNodeName();
$parent = $this->getParentName();
if ($parent === null) {
return $config->hasNode($this->getNodeName());
if (!$config->hasNode($name)) {
$this->error('Toplevel process "%s" not found', $name);
}
} else {
return $config->hasNode($this->getNodeName()) && $config->hasNode($this->getParentName());
if (! $config->hasNode($parent)) {
$this->error('Parent process "%s" missing', $parent);
} elseif (! $config->getBpNode($parent)->hasChild($name)) {
$this->error('Node "%s" not found in process "%s"', $name, $parent);
}
}
return true;
}
/**
@ -57,7 +66,18 @@ class NodeRemoveAction extends NodeAction
$name = $this->getNodeName();
$parentName = $this->getParentName();
if ($parentName === null) {
$oldDisplay = $config->getBpNode($name)->getDisplay();
$config->removeNode($name);
if ($config->getMetadata()->isManuallyOrdered()) {
foreach ($config->getRootNodes() as $_ => $node) {
$nodeDisplay = $node->getDisplay();
if ($nodeDisplay > $oldDisplay) {
$node->setDisplay($node->getDisplay() - 1);
} elseif ($nodeDisplay === $oldDisplay) {
break; // Stop immediately to not make things worse ;)
}
}
}
} else {
$node = $config->getNode($name);
$parent = $config->getBpNode($parentName);

View file

@ -14,6 +14,9 @@ class ProcessChanges
/** @var Session */
protected $session;
/** @var BpConfig */
protected $config;
/** @var bool */
protected $hasBeenModified = false;
@ -47,6 +50,7 @@ class ProcessChanges
}
}
$changes->session = $session;
$changes->config = $bp;
return $changes;
}
@ -61,7 +65,7 @@ class ProcessChanges
{
$action = new NodeModifyAction($node);
$action->setNodeProperties($node, $properties);
return $this->push($action);
return $this->push($action, true);
}
/**
@ -74,7 +78,7 @@ class ProcessChanges
{
$action = new NodeAddChildrenAction($node);
$action->setChildren($children);
return $this->push($action);
return $this->push($action, true);
}
/**
@ -91,7 +95,7 @@ class ProcessChanges
if ($parent !== null) {
$action->setParent($parent);
}
return $this->push($action);
return $this->push($action, true);
}
/**
@ -117,18 +121,55 @@ class ProcessChanges
$action->setParentName($parentName);
}
return $this->push($action);
return $this->push($action, true);
}
/**
* Move the given node
*
* @param Node $node
* @param int $from
* @param int $to
* @param string $newParent
* @param string $parent
*
* @return $this
*/
public function moveNode(Node $node, $from, $to, $newParent, $parent = null)
{
$action = new NodeMoveAction($node);
$action->setParent($parent);
$action->setNewParent($newParent);
$action->setFrom($from);
$action->setTo($to);
return $this->push($action, true);
}
/**
* Apply manual order on the entire bp configuration file
*
* @return $this
*/
public function applyManualOrder()
{
return $this->push(new NodeApplyManualOrderAction(), true);
}
/**
* Add a new action to the stack
*
* @param NodeAction $change
* @param NodeAction $change
* @param bool $apply
*
* @return $this
*/
public function push(NodeAction $change)
public function push(NodeAction $change, $apply = false)
{
if ($apply && $change->appliesTo($this->config)) {
$change->applyTo($this->config);
}
$this->changes[] = $change;
$this->hasBeenModified = true;
return $this;

View file

@ -50,7 +50,7 @@ abstract class Node
*
* @var array
*/
protected $parents;
protected $parents = array();
/**
* Node identifier
@ -83,6 +83,13 @@ abstract class Node
// obsolete
protected $duration;
/**
* This node's icon
*
* @var string
*/
protected $icon;
/**
* Last state change, unix timestamp
*
@ -102,7 +109,18 @@ abstract class Node
99 => 'PENDING'
);
abstract public function __construct(BpConfig $bp, $object);
abstract public function __construct($object);
public function setBpConfig(BpConfig $bp)
{
$this->bp = $bp;
return $this;
}
public function getBpConfig()
{
return $this->bp;
}
public function setMissing($missing = true)
{
@ -286,7 +304,7 @@ abstract class Node
public function hasParents()
{
return count($this->getParents()) > 0;
return count($this->parents) > 0;
}
public function hasParentName($name)
@ -303,7 +321,7 @@ abstract class Node
public function removeParent($name)
{
$this->parents = array_filter(
$this->getParents(),
$this->parents,
function (BpNode $parent) use ($name) {
return $parent->getName() !== $name;
}
@ -317,35 +335,35 @@ abstract class Node
*/
public function getParents()
{
if ($this->parents === null) {
$this->parents = [];
foreach ($this->bp->getBpNodes() as $name => $node) {
if ($node->hasChild($this->getName())) {
$this->parents[] = $node;
}
}
}
return $this->parents;
}
/**
* @param BpConfig $rootConfig
*
* @return array
*/
public function getPaths()
public function getPaths($rootConfig = null)
{
if ($this->bp->hasRootNode($this->getName())) {
return array(array($this->getName()));
$differentConfig = false;
if ($rootConfig === null) {
$rootConfig = $this->getBpConfig();
} else {
$differentConfig = $this->getBpConfig()->getName() !== $rootConfig->getName();
}
$paths = array();
foreach ($this->getParents() as $parent) {
foreach ($parent->getPaths() as $path) {
$path[] = $this->getName();
$paths = [];
foreach ($this->parents as $parent) {
foreach ($parent->getPaths($rootConfig) as $path) {
$path[] = $differentConfig ? $this->getIdentifier() : $this->getName();
$paths[] = $path;
}
}
if (! $this instanceof ImportedNode && $this->getBpConfig()->hasRootNode($this->getName())) {
$paths[] = [$differentConfig ? $this->getIdentifier() : $this->getName()];
}
return $paths;
}
@ -379,7 +397,14 @@ abstract class Node
public function getLink()
{
return Html::tag('a', ['href' => '#'], $this->getAlias());
return Html::tag('a', ['href' => '#', 'class' => 'toggle'], Html::tag('i', [
'class' => 'icon icon-down-dir'
]));
}
public function getIcon()
{
return Html::tag('i', ['class' => 'icon icon-' . ($this->icon ?: 'attention-circled')]);
}
public function operatorHtml()
@ -392,6 +417,11 @@ abstract class Node
return $this->name;
}
public function getIdentifier()
{
return '@' . $this->getBpConfig()->getName() . ':' . $this->getName();
}
public function __toString()
{
return $this->getName();

View file

@ -37,7 +37,7 @@ class Breadcrumb extends BaseHtmlElement
'href' => Url::fromPath('businessprocess'),
'title' => mt('businessprocess', 'Show Overview')
],
Html::tag('i', ['class' => 'icon icon-dashboard'])
Html::tag('i', ['class' => 'icon icon-home'])
)
));
$breadcrumb->add(Html::tag('li')->add(
@ -46,10 +46,12 @@ class Breadcrumb extends BaseHtmlElement
$path = $renderer->getCurrentPath();
$parts = array();
while ($node = array_pop($path)) {
while ($nodeName = array_pop($path)) {
$node = $bp->getNode($nodeName);
$renderer->setParentNode($node);
array_unshift(
$parts,
static::renderNode($bp->getNode($node), $path, $renderer)
static::renderNode($node, $path, $renderer)
);
}
$breadcrumb->add($parts);
@ -69,8 +71,7 @@ class Breadcrumb extends BaseHtmlElement
// TODO: something more generic than NodeTile?
$renderer = clone($renderer);
$renderer->lock()->setIsBreadcrumb();
$p = new NodeTile($renderer, (string) $node, $node, $path);
$p->getAttributes()->add('class', $renderer->getNodeClasses($node));
$p = new NodeTile($renderer, $node, $path);
$p->setTag('li');
return $p;
}

View file

@ -2,7 +2,6 @@
namespace Icinga\Module\Businessprocess\Renderer;
use Icinga\Date\DateFormatter;
use Icinga\Exception\ProgrammingError;
use Icinga\Module\Businessprocess\BpNode;
use Icinga\Module\Businessprocess\BpConfig;
@ -11,7 +10,6 @@ use Icinga\Module\Businessprocess\Web\Url;
use ipl\Html\BaseHtmlElement;
use ipl\Html\Html;
use ipl\Html\HtmlDocument;
use ipl\Html\HtmlString;
abstract class Renderer extends HtmlDocument
{
@ -76,6 +74,17 @@ abstract class Renderer extends HtmlDocument
return $this->parent !== null;
}
public function rendersImportedNode()
{
return $this->parent !== null && $this->parent->getBpConfig()->getName() !== $this->config->getName();
}
public function setParentNode(BpNode $node)
{
$this->parent = $node;
return $this;
}
/**
* @return BpNode
*/
@ -187,6 +196,16 @@ abstract class Renderer extends HtmlDocument
return $classes;
}
/**
* @param Node $node
* @param $path
* @return string
*/
public function getId(Node $node, $path)
{
return md5((empty($path) ? '' : implode(';', $path)) . $node->getName());
}
public function setPath(array $path)
{
$this->path = $path;
@ -194,7 +213,7 @@ abstract class Renderer extends HtmlDocument
}
/**
* @return string|null
* @return array
*/
public function getPath()
{
@ -205,8 +224,11 @@ abstract class Renderer extends HtmlDocument
{
$path = $this->getPath();
if ($this->rendersSubNode()) {
$path[] = (string) $this->parent;
$path[] = $this->rendersImportedNode()
? $this->parent->getIdentifier()
: $this->parent->getName();
}
return $path;
}
@ -298,22 +320,6 @@ abstract class Renderer extends HtmlDocument
return $this->isBreadcrumb;
}
public function timeSince($time, $timeOnly = false)
{
if (! $time) {
return HtmlString::create('');
}
return Html::tag(
'span',
[
'class' => ['relative-time', 'time-since'],
'title' => DateFormatter::formatDateTime($time)
],
DateFormatter::timeSince($time, $timeOnly)
);
}
protected function createUnboundParent(BpConfig $bp)
{
return $bp->getNode('__unbound__');

View file

@ -2,7 +2,9 @@
namespace Icinga\Module\Businessprocess\Renderer;
use Icinga\Module\Businessprocess\ImportedNode;
use Icinga\Module\Businessprocess\Renderer\TileRenderer\NodeTile;
use Icinga\Module\Businessprocess\Web\Form\CsrfToken;
use ipl\Html\Html;
class TileRenderer extends Renderer
@ -16,23 +18,45 @@ class TileRenderer extends Renderer
$nodesDiv = Html::tag(
'div',
[
'class' => ['tiles', $this->howMany()],
'data-base-target' => '_next'
'class' => ['sortable', 'tiles', $this->howMany()],
'data-base-target' => '_next',
'data-sortable-disabled' => $this->isLocked() ? 'true' : 'false',
'data-sortable-data-id-attr' => 'id',
'data-sortable-direction' => 'horizontal', // Otherwise movement is buggy on small lists
'data-csrf-token' => CsrfToken::generate()
]
);
if ($this->wantsRootNodes()) {
$nodesDiv->getAttributes()->add(
'data-action-url',
$this->getUrl()->setParams(['config' => $bp->getName()])->getAbsoluteUrl()
);
} else {
$nodeName = $this->parent instanceof ImportedNode
? $this->parent->getNodeName()
: $this->parent->getName();
$nodesDiv->getAttributes()
->add('data-node-name', $nodeName)
->add('data-action-url', $this->getUrl()
->setParams([
'config' => $this->parent->getBpConfig()->getName(),
'node' => $nodeName
])
->getAbsoluteUrl());
}
$nodes = $this->getChildNodes();
$path = $this->getCurrentPath();
foreach ($nodes as $name => $node) {
$this->add(new NodeTile($this, $name, $node, $path));
$this->add(new NodeTile($this, $node, $path));
}
if ($this->wantsRootNodes()) {
$unbound = $this->createUnboundParent($bp);
if ($unbound->hasChildren()) {
$name = $unbound->getName();
$this->add(new NodeTile($this, $name, $unbound));
$this->add(new NodeTile($this, $unbound));
}
}

View file

@ -2,6 +2,7 @@
namespace Icinga\Module\Businessprocess\Renderer\TileRenderer;
use Icinga\Date\DateFormatter;
use Icinga\Module\Businessprocess\BpNode;
use Icinga\Module\Businessprocess\HostNode;
use Icinga\Module\Businessprocess\ImportedNode;
@ -9,6 +10,8 @@ use Icinga\Module\Businessprocess\MonitoredNode;
use Icinga\Module\Businessprocess\Node;
use Icinga\Module\Businessprocess\Renderer\Renderer;
use Icinga\Module\Businessprocess\ServiceNode;
use Icinga\Module\Businessprocess\Web\Component\StateBall;
use Icinga\Web\Url;
use ipl\Html\BaseHtmlElement;
use ipl\Html\Html;
@ -36,10 +39,9 @@ class NodeTile extends BaseHtmlElement
* @param Node $node
* @param null $path
*/
public function __construct(Renderer $renderer, $name, Node $node, $path = null)
public function __construct(Renderer $renderer, Node $node, $path = null)
{
$this->renderer = $renderer;
$this->name = $name;
$this->node = $node;
$this->path = $path;
}
@ -72,38 +74,45 @@ class NodeTile extends BaseHtmlElement
$attributes = $this->getAttributes();
$attributes->add('class', $renderer->getNodeClasses($node));
$attributes->add('id', 'bp-' . (string) $node);
$this->addActions();
$link = $this->getMainNodeLink();
$this->add($link);
if ($node instanceof BpNode) {
if ($renderer->isBreadcrumb()) {
$link->add($renderer->renderStateBadges($node->getStateSummary()));
} else {
$this->add(Html::tag(
'p',
['class' => 'children-count'],
$node->hasChildren()
? Html::tag(
'span',
null,
sprintf('%u %s', $node->countChildren(), mt('businessprocess', 'Children'))
)
: null
));
$this->add($renderer->renderStateBadges($node->getStateSummary()));
}
$attributes->add('id', $renderer->getId($node, $this->path));
if (! $renderer->isLocked()) {
$attributes->add('data-node-name', $node->getName());
}
if (! $renderer->isBreadcrumb()) {
$this->addDetailsActions();
if (! $renderer->isLocked()) {
$this->addActionLinks();
}
}
if (! $renderer->isLocked()) {
$this->addActionLinks();
$link = $this->getMainNodeLink();
if ($renderer->isBreadcrumb()) {
$link->prepend((new StateBall(strtolower($node->getStateName())))->addAttributes([
'title' => sprintf(
'%s %s',
$node->getStateName(),
DateFormatter::timeSince($node->getLastStateChange())
)
]));
}
$this->add($link);
if ($node instanceof BpNode && !$renderer->isBreadcrumb()) {
$this->add(Html::tag(
'p',
['class' => 'children-count'],
$node->hasChildren()
? Html::tag(
'span',
null,
sprintf('%u %s', $node->countChildren(), mt('businessprocess', 'Children'))
)
: null
));
$this->add($renderer->renderStateBadges($node->getStateSummary()));
}
return parent::render();
@ -121,26 +130,21 @@ class NodeTile extends BaseHtmlElement
protected function buildBaseNodeUrl(Node $node)
{
$path = $this->path;
$name = $this->name; // TODO: ??
$renderer = $this->renderer;
$url = $this->renderer->getBaseUrl();
$bp = $renderer->getBusinessProcess();
$params = array(
'config' => $node instanceof ImportedNode ?
$node->getConfigName() :
$bp->getName()
);
if ($name !== null) {
$params['node'] = $name;
$p = $url->getParams();
if ($node instanceof ImportedNode
&& $this->renderer->getBusinessProcess()->getName() === $node->getBpConfig()->getName()
) {
$p->set('node', $node->getNodeName());
} elseif ($this->renderer->rendersImportedNode()) {
$p->set('node', $node->getIdentifier());
} else {
$p->set('node', $node->getName());
}
$url = $renderer->getBaseUrl();
$p = $url->getParams();
$p->mergeValues($params);
if (! empty($path)) {
$p->addValues('path', $path);
if (! empty($this->path)) {
$p->addValues('path', $this->path);
}
return $url;
@ -151,31 +155,6 @@ class NodeTile extends BaseHtmlElement
return $this->buildBaseNodeUrl($node);
}
protected function makeMonitoredNodeUrl(MonitoredNode $node)
{
$path = $this->path;
$name = $this->name; // TODO: ??
$renderer = $this->renderer;
$bp = $renderer->getBusinessProcess();
$params = array(
'config' => $bp->getName()
);
if ($name !== null) {
$params['node'] = $node->getName();
}
$url = $renderer->getBaseUrl();
$p = $url->getParams();
$p->mergeValues($params);
if (! empty($path)) {
$p->addValues('path', $path);
}
return $url;
}
/**
* @return BaseHtmlElement
*/
@ -189,11 +168,7 @@ class NodeTile extends BaseHtmlElement
$link = Html::tag('a', ['href' => $url, 'data-base-target' => '_next'], $node->getHostname());
} else {
$link = Html::tag('a', ['href' => $url], $node->getAlias());
if ($node instanceof ImportedNode) {
$link->getAttributes()->add('data-base-target', '_next');
} else {
$link->getAttributes()->add('data-base-target', '_self');
}
$link->getAttributes()->add('data-base-target', '_self');
}
return $link;
@ -260,15 +235,29 @@ class NodeTile extends BaseHtmlElement
protected function addActionLinks()
{
$node = $this->node;
$renderer = $this->renderer;
if ($node instanceof MonitoredNode) {
$parent = $this->renderer->getParentNode();
if ($parent !== null) {
$baseUrl = Url::fromPath('businessprocess/process/show', [
'config' => $parent->getBpConfig()->getName(),
'node' => $parent instanceof ImportedNode
? $parent->getNodeName()
: $parent->getName(),
'unlocked' => true
]);
} else {
$baseUrl = Url::fromPath('businessprocess/process/show', [
'config' => $this->node->getBpConfig()->getName(),
'unlocked' => true
]);
}
if ($this->node instanceof MonitoredNode) {
$this->actions()->add(Html::tag(
'a',
[
'href' => $renderer->getUrl()
'href' => $baseUrl
->with('action', 'simulation')
->with('simulationnode', $this->name),
->with('simulationnode', $this->node->getName()),
'title' => mt(
'businessprocess',
'Show the business impact of this node by simulating a specific state'
@ -280,9 +269,9 @@ class NodeTile extends BaseHtmlElement
$this->actions()->add(Html::tag(
'a',
[
'href' => $renderer->getUrl()
'href' => $baseUrl
->with('action', 'editmonitored')
->with('editmonitorednode', $node->getName()),
->with('editmonitorednode', $this->node->getName()),
'title' => mt('businessprocess', 'Modify this monitored node')
],
Html::tag('i', ['class' => 'icon icon-edit'])
@ -290,18 +279,18 @@ class NodeTile extends BaseHtmlElement
}
if (! $this->renderer->getBusinessProcess()->getMetadata()->canModify()
|| $node->getName() === '__unbound__'
|| $this->node->getName() === '__unbound__'
) {
return;
}
if ($node instanceof BpNode) {
if ($this->node instanceof BpNode) {
$this->actions()->add(Html::tag(
'a',
[
'href' => $renderer->getUrl()
'href' => $baseUrl
->with('action', 'edit')
->with('editnode', $node->getName()),
->with('editnode', $this->node->getName()),
'title' => mt('businessprocess', 'Modify this business process node')
],
Html::tag('i', ['class' => 'icon icon-edit'])
@ -310,10 +299,16 @@ class NodeTile extends BaseHtmlElement
$this->actions()->add(Html::tag(
'a',
[
'href' => $renderer->getUrl()->with([
'action' => 'add',
'node' => $node->getName()
]),
'href' => $this->node instanceof ImportedNode
? $baseUrl->with([
'config' => $this->node->getConfigName(),
'node' => $this->node->getNodeName(),
'action' => 'add'
])
: $baseUrl->with([
'node' => $this->node->getName(),
'action' => 'add'
]),
'title' => mt('businessprocess', 'Add a new sub-node to this business process')
],
Html::tag('i', ['class' => 'icon icon-plus'])
@ -322,13 +317,13 @@ class NodeTile extends BaseHtmlElement
$params = array(
'action' => 'delete',
'deletenode' => $node->getName(),
'deletenode' => $this->node->getName(),
);
$this->actions()->add(Html::tag(
'a',
[
'href' => $renderer->getUrl()->with($params),
'href' => $baseUrl->with($params),
'title' => mt('businessprocess', 'Delete this node')
],
Html::tag('i', ['class' => 'icon icon-cancel'])

View file

@ -2,9 +2,13 @@
namespace Icinga\Module\Businessprocess\Renderer;
use Icinga\Module\Businessprocess\BpNode;
use Icinga\Date\DateFormatter;
use Icinga\Module\Businessprocess\BpConfig;
use Icinga\Module\Businessprocess\BpNode;
use Icinga\Module\Businessprocess\ImportedNode;
use Icinga\Module\Businessprocess\Node;
use Icinga\Module\Businessprocess\Web\Component\StateBall;
use Icinga\Module\Businessprocess\Web\Form\CsrfToken;
use ipl\Html\BaseHtmlElement;
use ipl\Html\Html;
@ -16,15 +20,45 @@ class TreeRenderer extends Renderer
public function render()
{
$bp = $this->config;
$this->add(Html::tag(
'div',
$htmlId = $bp->getHtmlId();
$tree = Html::tag(
'ul',
[
'id' => $bp->getHtmlId(),
'class' => 'bp'
'id' => $htmlId,
'class' => ['bp', 'sortable', $this->wantsRootNodes() ? '' : 'process'],
'data-sortable-disabled' => $this->isLocked() ? 'true' : 'false',
'data-sortable-data-id-attr' => 'id',
'data-sortable-direction' => 'vertical',
'data-sortable-group' => json_encode([
'name' => $this->wantsRootNodes() ? 'root' : $htmlId,
'put' => 'function:rowPutAllowed'
]),
'data-sortable-invert-swap' => 'true',
'data-is-root-config' => $this->wantsRootNodes() ? 'true' : 'false',
'data-csrf-token' => CsrfToken::generate()
],
$this->renderBp($bp)
));
);
if ($this->wantsRootNodes()) {
$tree->getAttributes()->add(
'data-action-url',
$this->getUrl()->setParams(['config' => $bp->getName()])->getAbsoluteUrl()
);
} else {
$nodeName = $this->parent instanceof ImportedNode
? $this->parent->getNodeName()
: $this->parent->getName();
$tree->getAttributes()
->add('data-node-name', $nodeName)
->add('data-action-url', $this->getUrl()
->setParams([
'config' => $this->parent->getBpConfig()->getName(),
'node' => $nodeName
])
->getAbsoluteUrl());
}
$this->add($tree);
return parent::render();
}
@ -42,22 +76,16 @@ class TreeRenderer extends Renderer
}
foreach ($nodes as $name => $node) {
$html[] = $this->renderNode($bp, $node);
if ($node instanceof BpNode) {
$html[] = $this->renderNode($bp, $node);
} else {
$html[] = $this->renderChild($bp, $node);
}
}
return $html;
}
/**
* @param Node $node
* @param $path
* @return string
*/
protected function getId(Node $node, $path)
{
return md5(implode(';', $path) . (string) $node);
}
protected function getStateClassNames(Node $node)
{
$state = strtolower($node->getStateName());
@ -77,11 +105,24 @@ class TreeRenderer extends Renderer
/**
* @param Node $node
* @param array $path
* @return BaseHtmlElement[]
*/
public function getNodeIcons(Node $node)
public function getNodeIcons(Node $node, array $path = null)
{
$icons = array();
$icons = [];
if (empty($path) && $node instanceof BpNode) {
$icons[] = Html::tag('i', ['class' => 'icon icon-sitemap']);
} else {
$icons[] = $node->getIcon();
}
$icons[] = (new StateBall(strtolower($node->getStateName())))->addAttributes([
'title' => sprintf(
'%s %s',
$node->getStateName(),
DateFormatter::timeSince($node->getLastStateChange())
)
]);
if ($node->isInDowntime()) {
$icons[] = Html::tag('i', ['class' => 'icon icon-moon']);
}
@ -100,17 +141,16 @@ class TreeRenderer extends Renderer
*/
public function renderNode(BpConfig $bp, Node $node, $path = array())
{
$table = Html::tag(
'table',
$htmlId = $this->getId($node, $path);
$li = Html::tag(
'li',
[
'id' => $this->getId($node, $path),
'class' => array(
'bp',
$node->getObjectClassName()
)
'id' => $htmlId,
'class' => ['bp', 'movable', $node->getObjectClassName()],
'data-node-name' => $node->getName()
]
);
$attributes = $table->getAttributes();
$attributes = $li->getAttributes();
$attributes->add('class', $this->getStateClassNames($node));
if ($node->isHandled()) {
$attributes->add('class', 'handled');
@ -121,78 +161,88 @@ class TreeRenderer extends Renderer
$attributes->add('class', 'node');
}
$tbody = Html::tag('tbody');
$table->add($tbody);
$tr = Html::tag('tr');
$tbody->add($tr);
$div = Html::tag('div');
$li->add($div);
$div->add($node->getLink());
$div->add($this->getNodeIcons($node, $path));
$div->add(Html::tag('span', null, $node->getAlias()));
if ($node instanceof BpNode) {
$tr->add(Html::tag(
'th',
['rowspan' => $node->countChildren() + 1 + ($this->isLocked() ? 0 : 1)],
Html::tag('span', ['class' => 'op'], $node->operatorHtml())
));
$div->add(Html::tag('span', ['class' => 'op'], $node->operatorHtml()));
}
$td = Html::tag('td');
$tr->add($td);
if ($node instanceof BpNode && $node->hasInfoUrl()) {
$td->add($this->createInfoAction($node));
$div->add($this->createInfoAction($node));
}
if (! $this->isLocked()) {
$td->add($this->getActionIcons($bp, $node));
$differentConfig = $node->getBpConfig()->getName() !== $this->getBusinessProcess()->getName();
if (! $this->isLocked() && !$differentConfig) {
$div->add($this->getActionIcons($bp, $node));
}
$ul = Html::tag('ul', [
'class' => ['bp', 'sortable'],
'data-sortable-disabled' => ($this->isLocked() || $differentConfig) ? 'true' : 'false',
'data-sortable-invert-swap' => 'true',
'data-sortable-data-id-attr' => 'id',
'data-sortable-draggable' => '.movable',
'data-sortable-direction' => 'vertical',
'data-sortable-group' => json_encode([
'name' => $htmlId, // Unique, so that the function below is the only deciding factor
'put' => 'function:rowPutAllowed'
]),
'data-csrf-token' => CsrfToken::generate(),
'data-action-url' => $this->getUrl()
->setParams([
'config' => $node->getBpConfig()->getName(),
'node' => $node instanceof ImportedNode
? $node->getNodeName()
: $node->getName()
])
->getAbsoluteUrl()
]);
$li->add($ul);
$path[] = $differentConfig ? $node->getIdentifier() : $node->getName();
foreach ($node->getChildren() as $name => $child) {
if ($child instanceof BpNode) {
$ul->add($this->renderNode($bp, $child, $path));
} else {
$ul->add($this->renderChild($bp, $child, $path));
}
}
return $li;
}
protected function renderChild($bp, Node $node, $path = null)
{
$li = Html::tag('li', [
'class' => 'movable',
'id' => $this->getId($node, $path ?: []),
'data-node-name' => $node->getName()
]);
$li->add($this->getNodeIcons($node, $path));
$link = $node->getLink();
$link->getAttributes()->set('data-base-target', '_next');
$link->add($this->getNodeIcons($node));
$li->add($link);
if ($node->hasChildren()) {
$link->add($this->renderStateBadges($node->getStateSummary()));
if (! $this->isLocked() && $node->getBpConfig()->getName() === $this->getBusinessProcess()->getName()) {
$li->add($this->getActionIcons($bp, $node));
}
if ($time = $node->getLastStateChange()) {
$since = $this->timeSince($time)->prepend(
sprintf(' (%s ', $node->getStateName())
)->add(')');
$link->add($since);
}
$td->add($link);
foreach ($node->getChildren() as $name => $child) {
$tbody->add(Html::tag(
'tr',
null,
Html::tag(
'td',
null,
$this->renderNode($bp, $child, $this->getCurrentPath())
)
));
}
if (! $this->isLocked() && $node instanceof BpNode && $bp->getMetadata()->canModify()) {
$tbody->add(Html::tag(
'tr',
null,
Html::tag(
'td',
null,
$this->renderAddNewNode($node)
)
));
}
return $table;
return $li;
}
protected function getActionIcons(BpConfig $bp, Node $node)
{
if ($node instanceof BpNode) {
if ($bp->getMetadata()->canModify()) {
return $this->createEditAction($bp, $node);
return [$this->createEditAction($bp, $node), $this->renderAddNewNode($node)];
} else {
return '';
}
@ -204,7 +254,7 @@ class TreeRenderer extends Renderer
protected function createEditAction(BpConfig $bp, BpNode $node)
{
return $this->actionIcon(
'wrench',
'edit',
$this->getUrl()->with(array(
'action' => 'edit',
'editnode' => $node->getName()
@ -243,7 +293,7 @@ class TreeRenderer extends Renderer
[
'href' => $url,
'title' => $title,
'style' => 'float: right'
'class' => 'action-link'
],
Html::tag('i', ['class' => 'icon icon-' . $icon])
);
@ -251,16 +301,12 @@ class TreeRenderer extends Renderer
protected function renderAddNewNode($parent)
{
return Html::tag(
'a',
[
'href' => $this->getUrl()
->with('action', 'add')
->with('node', $parent->getName()),
'title' => mt('businessprocess', 'Add a new business process node'),
'class' => 'addnew icon-plus'
],
mt('businessprocess', 'Add')
return $this->actionIcon(
'plus',
$this->getUrl()
->with('action', 'add')
->with('node', $parent->getName()),
mt('businessprocess', 'Add a new business process node')
);
}
}

View file

@ -12,12 +12,13 @@ class ServiceNode extends MonitoredNode
protected $className = 'service';
public function __construct(BpConfig $bp, $object)
protected $icon = 'service';
public function __construct($object)
{
$this->name = $object->hostname . ';' . $object->service;
$this->hostname = $object->hostname;
$this->service = $object->service;
$this->bp = $bp;
if (isset($object->state)) {
$this->setState($object->state);
} else {
@ -47,8 +48,8 @@ class ServiceNode extends MonitoredNode
'service' => $this->getServiceDescription()
);
if ($this->bp->hasBackendName()) {
$params['backend'] = $this->bp->getBackendName();
if ($this->getBpConfig()->hasBackendName()) {
$params['backend'] = $this->getBpConfig()->getBackendName();
}
return Url::fromPath('businessprocess/service/show', $params);

View file

@ -93,13 +93,16 @@ class MonitoringState
Benchmark::measure('Retrieved states for ' . count($serviceStatus) . ' services in ' . $config->getName());
foreach ($serviceStatus as $row) {
$this->handleDbRow($row, $config);
$configs = $config->listInvolvedConfigs();
foreach ($configs as $cfg) {
foreach ($serviceStatus as $row) {
$this->handleDbRow($row, $cfg);
}
foreach ($hostStatus as $row) {
$this->handleDbRow($row, $cfg);
}
}
foreach ($hostStatus as $row) {
$this->handleDbRow($row, $config);
}
// TODO: Union, single query?
Benchmark::measure('Got states for business process ' . $config->getName());

View file

@ -22,6 +22,9 @@ class LegacyConfigParser
/** @var BpConfig */
protected $config;
/** @var array */
protected $missingNodes = [];
/**
* LegacyConfigParser constructor
*
@ -77,6 +80,8 @@ class LegacyConfigParser
$parser->parseLine($line);
}
$parser->resolveMissingNodes();
Benchmark::measure('Business process ' . $name . ' loaded');
return $config;
}
@ -99,11 +104,28 @@ class LegacyConfigParser
$this->parseLine($line);
}
$this->resolveMissingNodes();
fclose($fh);
unset($this->currentLineNumber);
unset($this->currentFilename);
}
/**
* Resolve previously missed business process nodes
*
* @throws ConfigurationError In case a referenced process does not exist
*/
protected function resolveMissingNodes()
{
foreach ($this->missingNodes as $name => $parents) {
foreach ($parents as $parent) {
/** @var BpNode $parent */
$parent->addChild($this->config->getNode($name));
}
}
}
public static function readMetadataFromFileHeader($name, $filename)
{
$metadata = new Metadata($name);
@ -298,47 +320,44 @@ class LegacyConfigParser
// New feature: $minWarn = $m[2];
$value = $m[3];
}
$cmps = preg_split('~\s*\\' . $op . '\s*~', $value, -1, PREG_SPLIT_NO_EMPTY);
$childNames = array();
$node = new BpNode((object) array(
'name' => $name,
'operator' => $op_name,
'child_names' => []
));
$node->setBpConfig($bp);
$cmps = preg_split('~\s*\\' . $op . '\s*~', $value, -1, PREG_SPLIT_NO_EMPTY);
foreach ($cmps as $val) {
if (strpos($val, ';') !== false) {
if ($bp->hasNode($val)) {
$childNames[] = $val;
continue;
}
list($host, $service) = preg_split('~;~', $val, 2);
if ($service === 'Hoststatus') {
$bp->createHost($host);
$node->addChild($bp->getNode($val));
} else {
$bp->createService($host, $service);
list($host, $service) = preg_split('~;~', $val, 2);
if ($service === 'Hoststatus') {
$node->addChild($bp->createHost($host));
} else {
$node->addChild($bp->createService($host, $service));
}
}
}
if ($val[0] === '@') {
} elseif ($val[0] === '@') {
if (strpos($val, ':') === false) {
throw new ConfigurationError(
"I'm unable to import full external configs, a node needs to be provided for '%s'",
$val
);
// TODO: this might work:
// $node = $bp->createImportedNode(substr($val, 1));
} else {
list($config, $nodeName) = preg_split('~:\s*~', substr($val, 1), 2);
$node = $bp->createImportedNode($config, $nodeName);
$node->addChild($bp->createImportedNode($config, $nodeName));
}
$val = $node->getName();
} elseif ($bp->hasNode($val)) {
$node->addChild($bp->getNode($val));
} else {
$this->missingNodes[$val][] = $node;
}
$childNames[] = $val;
}
$node = new BpNode($bp, (object) array(
'name' => $name,
'operator' => $op_name,
'child_names' => $childNames
));
$bp->addNode($name, $node);
}

View file

@ -4,6 +4,7 @@ namespace Icinga\Module\Businessprocess\Storage;
use Icinga\Module\Businessprocess\BpNode;
use Icinga\Module\Businessprocess\BpConfig;
use Icinga\Module\Businessprocess\ImportedNode;
class LegacyConfigRenderer
{
@ -110,6 +111,10 @@ class LegacyConfigRenderer
$cfg = '';
foreach ($node->getChildBpNodes() as $name => $child) {
if ($child instanceof ImportedNode) {
continue;
}
$cfg .= $this->requireRenderedBpNode($child) . "\n";
}

View file

@ -9,6 +9,13 @@ use Icinga\Exception\SystemPermissionException;
class LegacyStorage extends Storage
{
/**
* All parsed configurations
*
* @var BpConfig[]
*/
protected $configs = [];
/** @var string */
protected $configDir;
@ -116,10 +123,14 @@ class LegacyStorage extends Storage
*/
public function loadProcess($name)
{
return LegacyConfigParser::parseFile(
$name,
$this->getFilename($name)
);
if (! isset($this->configs[$name])) {
$this->configs[$name] = LegacyConfigParser::parseFile(
$name,
$this->getFilename($name)
);
}
return $this->configs[$name];
}
/**
@ -146,6 +157,10 @@ class LegacyStorage extends Storage
*/
public function loadMetadata($name)
{
if (isset($this->configs[$name])) {
return $this->configs[$name]->getMetadata();
}
return LegacyConfigParser::readMetadataFromFileHeader(
$name,
$this->getFilename($name)

View file

@ -2,12 +2,18 @@
namespace Icinga\Module\Businessprocess\Storage;
use Icinga\Application\Config;
use Icinga\Data\ConfigObject;
use Icinga\Module\Businessprocess\BpConfig;
use Icinga\Module\Businessprocess\Metadata;
abstract class Storage
{
/**
* @var static
*/
protected static $instance;
/**
* @var ConfigObject
*/
@ -27,6 +33,15 @@ abstract class Storage
{
}
public static function getInstance()
{
if (static::$instance === null) {
static::$instance = new static(Config::module('businessprocess')->getSection('global'));
}
return static::$instance;
}
/**
* All processes readable by the current user
*

View file

@ -16,27 +16,34 @@ class RenderedProcessActionBar extends ActionBar
$meta = $config->getMetadata();
if ($renderer instanceof TreeRenderer) {
$this->add(Html::tag(
$link = Html::tag(
'a',
[
'href' => $url->with('mode', 'tile'),
'title' => mt('businessprocess', 'Switch to Tile view'),
'class' => 'icon-dashboard'
],
mt('businessprocess', 'Tiles')
));
'title' => mt('businessprocess', 'Switch to Tile view')
]
);
} else {
$this->add(Html::tag(
$link = Html::tag(
'a',
[
'href' => $url->with('mode', 'tree'),
'title' => mt('businessprocess', 'Switch to Tree view'),
'class' => 'icon-sitemap'
],
mt('businessprocess', 'Tree')
));
'title' => mt('businessprocess', 'Switch to Tree view')
]
);
}
$link->add([
Html::tag('i', ['class' => 'icon icon-dashboard' . ($renderer instanceof TreeRenderer ? '' : ' active')]),
Html::tag('i', ['class' => 'icon icon-sitemap' . ($renderer instanceof TreeRenderer ? ' active' : '')])
]);
$this->add(
Html::tag('div', ['class' => 'view-toggle'])
->add(Html::tag('span', null, mt('businessprocess', 'View')))
->add($link)
);
$this->add(Html::tag(
'a',
[
@ -51,15 +58,28 @@ class RenderedProcessActionBar extends ActionBar
$hasChanges = $config->hasSimulations() || $config->hasBeenChanged();
if ($renderer->isLocked()) {
$this->add(Html::tag(
'a',
[
'href' => $url->with('unlocked', true),
'title' => mt('businessprocess', 'Click to unlock editing for this process'),
'class' => 'icon-lock'
],
mt('businessprocess', 'Editing locked')
));
if (! $renderer->wantsRootNodes() && $renderer->rendersImportedNode()) {
$span = Html::tag('span', [
'class' => 'disabled',
'title' => mt(
'businessprocess',
'Imported processes can only be changed in their original configuration'
)
]);
$span->add(Html::tag('i', ['class' => 'icon icon-lock']))
->add(mt('businessprocess', 'Editing Locked'));
$this->add($span);
} else {
$this->add(Html::tag(
'a',
[
'href' => $url->with('unlocked', true),
'title' => mt('businessprocess', 'Click to unlock editing for this process'),
'class' => 'icon-lock'
],
mt('businessprocess', 'Unlock Editing')
));
}
} elseif (! $hasChanges) {
$this->add(Html::tag(
'a',
@ -68,7 +88,7 @@ class RenderedProcessActionBar extends ActionBar
'title' => mt('businessprocess', 'Click to lock editing for this process'),
'class' => 'icon-lock-open'
],
mt('businessprocess', 'Editing unlocked')
mt('businessprocess', 'Lock Editing')
));
}
@ -91,9 +111,9 @@ class RenderedProcessActionBar extends ActionBar
[
'href' => $url->with('action', 'add'),
'title' => mt('businessprocess', 'Add a new business process node'),
'class' => 'icon-plus'
'class' => 'icon-plus button-link'
],
mt('businessprocess', 'Add')
mt('businessprocess', 'Add Node')
));
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Icinga\Module\Businessprocess\Web\Component;
use ipl\Html\BaseHtmlElement;
class StateBall extends BaseHtmlElement
{
const SIZE_TINY = 'xs';
const SIZE_SMALL = 's';
const SIZE_MEDIUM = 'm';
const SIZE_LARGE = 'l';
protected $tag = 'div';
public function __construct($state = 'none', $size = self::SIZE_SMALL)
{
$state = \trim($state);
if (empty($state)) {
$state = 'none';
}
$size = \trim($size);
if (empty($size)) {
$size = self::SIZE_MEDIUM;
}
$this->defaultAttributes = ['class' => "state-ball state-$state size-$size"];
}
}

View file

@ -262,9 +262,7 @@ class Controller extends ModuleController
protected function storage()
{
if ($this->storage === null) {
$this->storage = new LegacyStorage(
$this->Config()->getSection('global')
);
$this->storage = LegacyStorage::getInstance();
}
return $this->storage;

View file

@ -17,7 +17,7 @@ class CsrfToken
return false;
}
list($seed, $token) = explode('|', $elementValue);
list($seed, $token) = explode('|', $token);
if (!is_numeric($seed)) {
return false;

View file

@ -6,13 +6,74 @@ a:focus {
}
}
.action-bar a {
.action-bar {
display: flex;
align-items: center;
font-size: 1.3em;
color: @icinga-blue;
&:hover::before {
text-decoration: none;
> a {
&:hover::before {
text-decoration: none;
}
&:not(:last-child) {
margin-right: 1em;
}
&.button-link {
color: white;
background: @icinga-blue;
&:active, &:focus {
text-decoration: none;
}
&:last-child {
margin-left: auto;
}
}
}
> div.view-toggle {
margin-right: 1em;
span {
color: @gray;
margin-right: .5em;
}
a {
display: inline-block;
i {
padding: .25em .5em;
border: 1px solid @icinga-blue;
&:before {
margin-right: 0;
}
&.active {
color: white;
background-color: @icinga-blue;
}
&:first-of-type {
border-top-left-radius: .25em;
border-bottom-left-radius: .25em;
}
&:last-of-type {
border-top-right-radius: .25em;
border-bottom-right-radius: .25em;
}
}
}
}
span.disabled {
color: @gray;
}
margin-right: 1em;
}
form a {
@ -23,103 +84,158 @@ div.bp {
margin-bottom: 4px;
}
div.bp.sortable > .sortable-ghost {
opacity: 0.5;
}
.simulation div.bp {
border-right: 1em solid @colorCriticalHandled;
padding-right: 1em;
background: white;
}
table.bp {
/* Business process table styling starts here */
width: 100%;
/* TreeView */
@vertical-tree-item-gap: .5em;
ul.bp {
margin: 0;
padding: 0;
color: @text-color;
border-collapse: collapse;
border-spacing: 0;
box-sizing: border-box;
font-size: 1em;
font-weight: normal;
table-layout: fixed;
list-style-type: none;
/* Reset all paddings and margins, just to be on the safe side */
th, td {
padding: 0;
margin: 0;
.action-link {
font-size: 1.3em;
line-height: 1;
}
/* Left outer margin on nested BPs */
table.bp {
// cursors!!!1
&:not([data-sortable-disabled="true"]) {
.movable {
cursor: grab;
width: 99.6%;
margin-left: .4%;
margin-top: 4px;
&.sortable-chosen {
cursor: grabbing;
}
}
&.progress .movable {
cursor: wait;
}
}
&[data-sortable-disabled="true"] {
li.process > div {
cursor: pointer;
}
}
.time-since {
display: none;
}
}
// ghost style
&.sortable > li.sortable-ghost {
position: relative;
overflow: hidden;
max-height: 30em;
background-color: @gray-lighter;
border: .2em dotted @gray-light;
border-left-width: 0;
border-right-width: 0;
mix-blend-mode: hard-light;
table.bp th {
font-weight: bold;
}
/* END of font settings */
/* No focus outline on our links, look ugly */
table.bp a:focus {
outline: none;
}
/* No link underlining */
table.bp a, table.bp a:hover {
text-decoration: none;
}
/* White font for all hovered objects */
table.bp.hovered {
color: white;
> tbody > tr > td > a > .time-since {
display: inline;
}
}
table.bp.handled.hovered {
color: #0a0a0a;
}
table.bp a {
color: inherit;
}
/* Show a pointer when hovering th, highlighting is JS-triggered */
table.bp tr th {
cursor: pointer;
}
/* Expand / collapse styling */
table.bp.process {
position: relative;
> tbody > tr:first-child > td:before {
content: '\e81d';
font-family: ifont;
position: absolute;
font-size: 1.5em;
margin-left: -0.8em;
-webkit-transition: -webkit-transform 0.3s;
-moz-transition: -moz-transform 0.3s;
-o-transition: -o-transform 0.3s;
transition: transform 0.3s;
&.process:after {
// TODO: Only apply if content overflows?
content: " ";
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50%;
background: linear-gradient(transparent, white);
}
}
&.collapsed {
// header style
li.process > div {
padding: .291666667em 0;
border-bottom: 1px solid @gray-light;
> tbody > tr:first-child > td:before {
> a.toggle {
min-width: 1.25em; // So that process icons align with their node's icons
color: @gray;
}
> span {
font-size: 1.25em;
&.op {
padding: .1em .5em;
border-radius: .5em;
background-color: @gray-light;
font-weight: bold;
font-size: 1em;
color: white;
}
}
}
// subprocess style
li.process > ul {
padding-left: 2em;
list-style-type: none;
&.sortable {
min-height: 1em; // Required to be able to move items back to an otherwise empty list
}
}
// vertical layout
> li {
padding: @vertical-tree-item-gap 0;
&:first-child {
margin-top: @vertical-tree-item-gap;
}
&.process {
padding-bottom: 0;
&:first-child {
margin-top: 0;
padding-top: 0;
}
}
}
// horizontal layout
li.process > div,
li:not(.process) {
display: flex;
align-items: center;
padding-left: .25em;
> * {
margin-right: .5em;
}
> a.action-link {
margin-left: auto; // Let the first action link move everything to the right
& + a.action-link {
margin-left: 0; // But really only the first one
}
}
}
// collapse handling
li.process {
// toggle, default
> div > a.toggle > i:before {
-webkit-transition: -webkit-transform 0.3s;
-moz-transition: -moz-transform 0.3s;
-o-transition: -o-transform 0.3s;
transition: transform 0.3s;
}
// toggle, collapsed
&.collapsed > div > a.toggle > i:before {
-moz-transform:rotate(-90deg);
-ms-transform:rotate(-90deg);
-o-transform:rotate(-90deg);
@ -127,214 +243,31 @@ table.bp.process {
transform:rotate(-90deg);
}
table.bp, th span {
display: none;
&.collapsed {
margin-bottom: (@vertical-tree-item-gap * 2);
> ul.bp {
display: none;
}
}
}
}
table.bp th > a, table.bp td > a, table.bp td > span {
display: block;
text-decoration: none;
}
table.bp span.op {
width: 1.5em;
min-height: 1.5em;
margin-top: 1em;
display: block;
line-height: 2em;
-moz-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
-o-transform: rotate(-90deg);
-webkit-transform: rotate(-90deg);
transform: rotate(-90deg);
}
table.bp .icon {
float: left;
margin-right: 0.4em;
}
table.bp.node {
td:before {
font-family: ifont;
z-index: 1;
font-size: 1.25em;
position: absolute;
margin-left: 1.25em;
margin-top: 0.25em;
// hover style
li.process:hover > div {
background-color: #dae4e6;
}
}
table.bp.node.subtree td:before {
content: '\e80e';
}
table.bp.node.service td:before {
content: '\e840';
}
table.bp.node.host td:before {
content: '\e866';
}
/* Border defaults */
table.bp {
border-width: 0;
border-style: solid;
border-color: transparent;
}
table.bp tr, table.bp tbody, table.bp th, table.bp td, table.bp.node td > a, table.node.missing td > span {
border-width: 0;
border-style: inherit;
border-color: inherit;
}
table.bp td > a, table.node.missing td > span {
height: 2.5em;
line-height: 2.5em;
padding-left: 0.5em;
display: block;
}
table.bp.node td > a:last-child, table.node.missing td > span {
padding-left: 2.5em;
background-repeat: no-repeat;
background-position: 0.5em 0.5em;
border-left-width: 0.8em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
table.bp.node.handled td > a:last-child, table.bp.node.ok td > a:last-child,
table.node.missing td > span, table.bp.node.up td > a:last-child
{
border-left-width: 0.3em;
background-position: 1em 0.5em;
padding-left: 3em;
}
table.bp th {
border-left-width: 0.8em;
width: 1.8em;
}
table.process.missing th span {
display: none;
}
table.bp.handled > tbody > tr > th, table.bp.ok > tbody > tr > th {
border-left-width: 0.3em;
width: 2em;
}
/* Operator: upper line */
table.bp.operator > tbody > tr:first-child > * {
border-top-width: 1px;
border-top-style: solid;
}
table.bp.operator.hovered > tbody > tr:first-child > * {
border-top-style: solid;
}
/* Set colors based on element state */
table.bp {
&.ok { border-color: @colorOk; }
&.up { border-color: @colorOk; }
&.warning { border-color: @colorWarning; }
&.warning.handled { border-color: @colorWarningHandled; }
&.critical { border-color: @colorCritical; }
&.critical.handled { border-color: @colorCriticalHandled; }
&.down { border-color: @colorCritical; }
&.down.handled { border-color: @colorCriticalHandled; }
&.unknown { border-color: @colorUnknown; }
&.unknown.handled { border-color: @colorUnknownHandled; }
&.unreachable { border-color: @colorUnknown; }
&.unreachable.handled { border-color: @colorUnknownHandled; }
&.pending { border-color: @colorPending; }
&.missing { border-color: #ccc; }
&.hovered {
&.ok > tbody > tr > {
th, td > a { background-color: @colorOk; }
}
&.up > tbody > tr > {
th, td > a { background-color: @colorOk; }
}
&.warning > tbody > tr > {
th, td > a { background-color: @colorWarning; }
}
&.warning.handled > tbody > tr {
> th, > td > a { background-color: @colorWarningHandled; }
}
&.critical > tbody > tr > {
th, td > a { background-color: @colorCritical; }
}
&.critical.handled > tbody > tr > {
th, td > a { background-color: @colorCriticalHandled; }
}
&.down > tbody > tr > {
th, td > a { background-color: @colorCritical; }
}
&.down.handled > tbody > tr > {
th, td > a { background-color: @colorCriticalHandled; }
}
&.unknown > tbody > tr > {
th, td > a { background-color: @colorUnknown; }
}
&.unknown.handled > tbody > tr > {
th, td > a { background-color: @colorUnknownHandled; }
}
&.unreachable > tbody > tr > {
th, td > a { background-color: @colorUnknown; }
}
&.unreachable.handled > tbody > tr > {
th, td > a { background-color: @colorUnreachableHandled; }
}
&.pending > tbody > tr > {
th, td > a { background-color: @colorPending; }
}
&.missing > tbody > tr > {
th, td > a, td > span { background-color: #ccc; }
}
}
}
/* Reduce font size after the 3rd level... */
table.bp table.bp table.bp table.bp {
font-size: 0.95em;
}
/* ...and keep it constant afterwards */
table.bp table.bp table.bp table.bp table.bp {
font-size: 1em;
}
/* Transitions */
div.knightrider table.bp {
// That's ugly, I know
.transition(@val1, @val2) {
transition: @val1, @val2;
-moz-transition: @val1, @val2;
-o-transition: @val1, @val2;
-webkit-transition: @val1, @val2;
}
> tbody > tr > td > a:last-child, > tbody > tr > td > span, > tbody > tr > th {
// Fade out
.transition(color 0.5s 0.1s step-start,
background-color 0.5s 0.1s ease-out
);
li:not(.process):hover {
background-color: #dae4e6;
}
&.hovered > tbody > tr {
> td > a:last-child, > td > span, > th {
// Fade in
.transition(color 0.0s 0.0s step-start,
background-color 0.0s 0.0s ease-out
);
li.process > div > .state-ball,
li:not(.process) > .state-ball {
border: .15em solid white;
&.size-s {
width: 7em/6em;
height: 7em/6em;
line-height: 7em/6em;
}
}
}
@ -458,6 +391,11 @@ td > a > .badges {
clear: both;
}
.tiles.sortable > .sortable-ghost {
opacity: 0.5;
border: .2em dashed black;
}
.tiles > div {
color: white;
width: 12em;
@ -627,38 +565,6 @@ td > a > .badges {
list-style: none;
overflow: hidden;
padding: 0;
.badges {
background-color: transparent;
border-radius: 0;
display: inline-block;
padding: 0 0 0 0.5em;
.badge {
line-height: 1.25em;
font-size: 0.8em;
border: 1px solid white;
}
}
}
.breadcrumb {
> .critical a { background: @colorCritical; }
> .critical.handled a { background: @colorCriticalHandled; }
> .unknown a { background: @colorUnknown; }
> .unknown.handled a { background: @colorUnknownHandled; }
> .warning a { background: @colorWarning; }
> .warning.handled a { background: @colorWarningHandled; }
> .ok a { background: @colorOk; }
}
.breadcrumb {
> .critical a:after { border-left-color: @colorCritical; }
> .critical.handled a:after { border-left-color: @colorCriticalHandled; }
> .unknown a:after { border-left-color: @colorUnknown; }
> .unknown.handled a:after { border-left-color: @colorUnknownHandled; }
> .warning a:after { border-left-color: @colorWarning; }
> .warning.handled a:after { border-left-color: @colorWarningHandled; }
> .ok a:after { border-left-color: @colorOk; }
}
.breadcrumb:after {
@ -676,22 +582,50 @@ td > a > .badges {
}
.breadcrumb li a {
color: white;
color: @icinga-blue;
margin: 0;
font-size: 1.2em;
text-decoration: none;
padding-left: 2em;
line-height: 2.5em;
background: @icinga-blue;
position: relative;
display: block;
float: left;
&:focus {
outline: none;
}
> .state-ball {
margin-right: .5em;
border: .15em solid white;
&.size-s {
width: 7em/6em;
height: 7em/6em;
line-height: 7em/6em;
}
}
}
.breadcrumb li {
border: 1px solid @gray-lighter;
&:first-of-type {
border-radius: .25em;
}
&:last-of-type {
border-radius: .25em;
border: 1px solid @icinga-blue;
background: @icinga-blue;
padding-right: 1.2em;
a {
color: white;
}
}
}
.breadcrumb li a:before, .breadcrumb li a:after {
.breadcrumb li:not(:last-of-type) a:before, .breadcrumb li:not(:last-of-type) a:after {
content: " ";
display: block;
width: 0;
@ -704,30 +638,29 @@ td > a > .badges {
left: 100%;
}
.breadcrumb li a:before {
border-left: 1.2em solid white;
.breadcrumb li:not(:last-of-type) a:before {
border-left: 1.2em solid @gray-lighter;
margin-left: 1px;
z-index: 1;
}
.breadcrumb li a:after {
border-left: 1.2em solid @icinga-blue;
.breadcrumb li:not(:last-of-type) a:after {
border-left: 1.2em solid white;
z-index: 2;
}
.tabs > .dropdown-nav-item > ul {
z-index: 100;
}
.breadcrumb li:first-child a {
padding-left: 1em;
padding-right: 0.5em;
}
.breadcrumb li:last-child a {
cursor: default;
}
.breadcrumb li:last-child a:hover {
}
.breadcrumb li:not(:last-child) a:hover { background: @text-color; color: white; }
.breadcrumb li:not(:last-child) a:hover:after { border-left-color: @text-color; }
.breadcrumb li:not(:last-child) a:hover { background: @icinga-blue; color: white; }
.breadcrumb li:not(:last-child) a:hover:after { border-left-color: @icinga-blue !important; }
.breadcrumb li:last-child:hover, .breadcrumb li:last-child a:hover { background: @icinga-blue; border-color: @icinga-blue !important; }
.breadcrumb li a:focus {
text-decoration: underline;

View file

@ -0,0 +1,58 @@
.state-ball {
border-radius: 50%;
display: inline-block;
text-align: center;
vertical-align: middle;
&.state-critical,
&.state-down {
background-color: @color-critical;
}
&.state-unknown {
background-color: @color-unknown;
}
&.state-warning {
background-color: @color-warning;
}
&.state-ok,
&.state-up {
background-color: @color-ok;
}
&.state-pending {
background-color: @color-pending;
}
&.size-xs {
line-height: 0.75em;
height: 0.75em;
width: 0.75em;
}
&.size-s {
line-height: 1em;
height: 1em;
width: 1em;
}
&.size-m {
line-height: 2em;
height: 2em;
width: 2em;
}
&.size-l {
line-height: 2.5em;
height: 2.5em;
width: 2.5em;
}
> i {
color: white;
font-style: normal;
text-transform: uppercase;
}
}

View file

@ -0,0 +1,47 @@
/*! Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
(function(Icinga, $) {
'use strict';
Icinga.Behaviors = Icinga.Behaviors || {};
var Sortable = function (icinga) {
Icinga.EventListener.call(this, icinga);
this.on('rendered', this.onRendered, this);
};
Sortable.prototype = new Icinga.EventListener();
Sortable.prototype.onRendered = function(e) {
$(e.target).find('.sortable').each(function() {
var $el = $(this);
var options = {
scroll: $el.closest('.container')[0],
onMove: function (/**Event*/ event, /**Event*/ originalEvent) {
if (typeof this.options['filter'] !== 'undefined' && $(event.related).is(this.options['filter'])) {
// Assumes the filtered item is either at the very start or end of the list and prevents the
// user from dropping other items before (if at the very start) or after it.
return false;
}
}
};
$.each($el.data(), function (i, v) {
if (i.length > 8 && i.startsWith('sortable')) {
options[i.charAt(8).toLowerCase() + i.substr(9)] = v;
}
});
if (typeof options.group !== 'undefined' && typeof options.group.put === 'string' && options.group.put.startsWith('function:')) {
var module = icinga.module($el.closest('.icinga-module').data('icingaModule'));
options.group.put = module.object[options.group.put.substr(9)];
}
$(this).sortable(options);
});
};
Icinga.Behaviors.Sortable = Sortable;
})(Icinga, jQuery);

View file

@ -21,20 +21,20 @@
/**
* Tell Icinga about our event handlers
*/
this.module.on('beforerender', this.rememberOpenedBps);
this.module.on('rendered', this.onRendered);
this.module.on('rendered', this.onRendered);
this.module.on('click', 'table.bp.process > tbody > tr:first-child > td > a:last-child', this.processTitleClick);
this.module.on('click', 'table.bp > tbody > tr:first-child > th', this.processOperatorClick);
this.module.on('focus', 'form input, form textarea, form select', this.formElementFocus);
this.module.on('mouseenter', 'table.bp > tbody > tr > td > a', this.procMouseOver);
this.module.on('mouseenter', 'table.bp > tbody > tr > th', this.procMouseOver);
this.module.on('mouseenter', 'table.node.missing > tbody > tr > td > span', this.procMouseOver);
this.module.on('mouseleave', 'div.bp', this.procMouseOut);
this.module.on('click', 'li.process a.toggle', this.processToggleClick);
this.module.on('click', 'li.process > div', this.processHeaderClick);
this.module.on('end', 'ul.sortable', this.rowDropped);
this.module.on('click', 'div.tiles > div', this.tileClick);
this.module.on('click', '.dashboard-tile', this.dashboardTileClick);
this.module.on('end', 'div.tiles.sortable', this.tileDropped);
this.module.on('choose', '.sortable', this.suspendAutoRefresh);
this.module.on('unchoose', '.sortable', this.resumeAutoRefresh);
this.module.icinga.logger.debug('BP module initialized');
},
@ -42,39 +42,40 @@
onRendered: function (event) {
var $container = $(event.currentTarget);
this.fixFullscreen($container);
this.fixOpenedBps($container);
this.restoreCollapsedBps($container);
this.highlightFormErrors($container);
this.hideInactiveFormDescriptions($container);
this.fixTileLinksOnDashboard($container);
},
processTitleClick: function (event) {
processToggleClick: function (event) {
event.stopPropagation();
var $el = $(event.currentTarget).closest('table.bp');
$el.toggleClass('collapsed');
},
processOperatorClick: function (event) {
event.stopPropagation();
var $el = $(event.currentTarget).closest('table.bp');
var $li = $(event.currentTarget).closest('li.process');
$li.toggleClass('collapsed');
// Click on arrow
$el.removeClass('collapsed');
var children = $el.find('> tbody > tr > td > table.bp.process');
if (children.length === 0) {
$el.toggleClass('collapsed');
var $bpUl = $(event.currentTarget).closest('.content > ul.bp');
if (! $bpUl.length || !$bpUl.data('isRootConfig')) {
return;
}
if (children.filter('.collapsed').length) {
children.removeClass('collapsed');
} else {
children.each(function(idx, el) {
var $el = $(el);
$el.addClass('collapsed');
$el.find('table.bp.process').addClass('collapsed');
});
var bpName = $bpUl.attr('id');
if (typeof this.idCache[bpName] === 'undefined') {
this.idCache[bpName] = [];
}
var index = this.idCache[bpName].indexOf($li.attr('id'));
if ($li.is('.collapsed')) {
if (index === -1) {
this.idCache[bpName].push($li.attr('id'));
}
} else if (index !== -1) {
this.idCache[bpName].splice(index, 1);
}
},
processHeaderClick: function (event) {
this.processToggleClick(event);
},
hideInactiveFormDescriptions: function($container) {
@ -89,73 +90,110 @@
$(event.currentTarget).find('> .bp-link > a').first().trigger('click');
},
/**
* Add 'hovered' class to hovered title elements
*
* TODO: Skip on tablets
*/
procMouseOver: function (event) {
suspendAutoRefresh: function(event) {
// TODO: If there is a better approach some time, let me know
$(event.originalEvent.from).closest('.container').data('lastUpdate', (new Date()).getTime() + 3600 * 1000);
event.stopPropagation();
var $hovered = $(event.currentTarget);
var $el = $hovered.closest('table.bp');
},
if ($el.is('.operator')) {
if (!$hovered.closest('tr').is('tr:first-child')) {
// Skip hovered space between cols
return;
}
} else {
// return;
resumeAutoRefresh: function(event) {
var $container = $(event.originalEvent.from).closest('.container');
$container.data('lastUpdate', (new Date()).getTime() - ($container.data('icingaRefresh') || 10) * 1000);
event.stopPropagation();
},
tileDropped: function(event) {
var evt = event.originalEvent;
if (evt.oldIndex !== evt.newIndex) {
var $source = $(evt.from);
var actionUrl = [
$source.data('actionUrl'),
'action=move',
'movenode=' + $(evt.item).data('nodeName')
].join('&');
var data = {
csrfToken: $source.data('csrfToken'),
movenode: 'movenode', // That's the submit button..
parent: $(evt.to).data('nodeName') || '',
from: evt.oldIndex,
to: evt.newIndex
};
var $container = $source.closest('.container');
var req = icinga.loader.loadUrl(actionUrl, $container, data, 'POST');
req.complete(function (req, textStatus) {
icinga.loader.loadUrl(
$container.data('icingaUrl'), $container, undefined, undefined, undefined, true);
});
}
},
$('table.bp.hovered').not($el.parents('table.bp')).removeClass('hovered'); // not self & parents
$el.addClass('hovered');
$el.parents('table.bp').addClass('hovered');
rowDropped: function(event) {
var evt = event.originalEvent,
$source = $(evt.from),
$target = $(evt.to);
if (evt.oldIndex !== evt.newIndex || !$target.is($source)) {
var $root = $target.closest('.content > ul.bp');
$root.addClass('progress')
.find('ul.bp')
.add($root)
.each(function() {
$(this).data('sortable').option('disabled', true);
});
var data = {
csrfToken: $target.data('csrfToken'),
movenode: 'movenode', // That's the submit button..
parent: $target.closest('.process').data('nodeName') || '',
from: evt.oldIndex,
to: evt.newIndex
};
var actionUrl = [
$source.data('actionUrl'),
'action=move',
'movenode=' + $(evt.item).data('nodeName')
].join('&');
var $container = $target.closest('.container');
var req = icinga.loader.loadUrl(actionUrl, $container, data, 'POST');
req.complete(function (req, textStatus) {
icinga.loader.loadUrl(
$container.data('icingaUrl'), $container, undefined, undefined, undefined, true);
});
event.stopPropagation();
}
},
/**
* Remove 'hovered' class from hovered title elements
* Called by Sortable.js while in Tree-View
*
* TODO: Skip on tablets
*/
procMouseOut: function (event) {
$('table.bp.hovered').removeClass('hovered');
},
/**
* Handle clicks on operator or title element
* See group option on the sortable elements.
*
* Title shows subelement, operator unfolds all subelements
* @param to
* @param from
* @param item
* @param event
* @returns boolean
*/
titleClicked: function (event) {
var self = this;
event.stopPropagation();
event.preventDefault();
var $el = $(event.currentTarget),
affected = []
$container = $el.closest('.container');
if ($el.hasClass('operator')) {
$affected = $el.closest('table').children('tbody')
.children('tr.children').children('td').children('table');
// Only if there are child BPs
if ($affected.find('th.operator').length < 1) {
$affected = $el.closest('table');
}
} else {
$affected = $el.closest('table');
rowPutAllowed: function(to, from, item, event) {
if (to.options.group.name === 'root') {
return $(item).is('.process');
}
$affected.each(function (key, el) {
var $bptable = $(el).closest('table');
$bptable.toggleClass('collapsed');
if ($bptable.hasClass('collapsed')) {
$bptable.find('table').addClass('collapsed');
}
// Otherwise we're facing a nesting error next
var $item = $(item),
childrenNames = $item.find('.process').map(function () {
return $(this).data('nodeName');
}).get();
childrenNames.push($item.data('nodeName'));
var loopDetected = $(to.el).parents('.process').toArray().some(function (parent) {
return childrenNames.indexOf($(parent).data('nodeName')) !== -1;
});
/*$container.data('refreshParams', {
opened: self.listOpenedBps($container)
});*/
return !loopDetected;
},
fixTileLinksOnDashboard: function($container) {
@ -185,48 +223,23 @@
}
},
fixOpenedBps: function($container) {
var $bpDiv = $container.find('div.bp');
var bpName = $bpDiv.attr('id');
if (typeof this.idCache[bpName] === 'undefined') {
return;
}
var $procs = $bpDiv.find('table.process');
$.each(this.idCache[bpName], function(idx, id) {
var $el = $('#' + id);
$procs = $procs.not($el);
$el.parents('table.process').each(function (idx, el) {
$procs = $procs.not($(el));
});
});
$procs.addClass('collapsed');
},
/**
* Get a list of all currently opened BPs.
*
* Only get the deepest nodes to keep requests as small as possible
*/
rememberOpenedBps: function (event) {
var ids = [];
var $bpDiv = $(event.currentTarget).find('div.bp');
var $bpName = $bpDiv.attr('id');
$bpDiv.find('table.process')
.not('table.process.collapsed')
.not('table.process.collapsed table.process')
.each(function (key, el) {
ids.push($(el).attr('id'));
});
if (ids.length === 0) {
restoreCollapsedBps: function($container) {
var $bpUl = $container.find('.content > ul.bp');
if (! $bpUl.length || !$bpUl.data('isRootConfig')) {
return;
}
this.idCache[$bpName] = ids;
var bpName = $bpUl.attr('id');
if (typeof this.idCache[bpName] === 'undefined') {
return;
}
var _this = this;
$bpUl.find('li.process')
.filter(function () {
return _this.idCache[bpName].indexOf(this.id) !== -1;
})
.addClass('collapsed');
},
/** BEGIN Form handling, borrowed from Director **/

2349
public/js/vendor/Sortable.js vendored Normal file

File diff suppressed because it is too large Load diff

76
public/js/vendor/jquery.fn.sortable.js vendored Normal file
View file

@ -0,0 +1,76 @@
(function (factory) {
"use strict";
var sortable,
jq,
_this = this
;
if (typeof define === "function" && define.amd) {
try {
define(["sortablejs", "jquery"], function(Sortable, $) {
sortable = Sortable;
jq = $;
checkErrors();
factory(Sortable, $);
});
} catch(err) {
checkErrors();
}
return;
} else if (typeof exports === 'object') {
try {
sortable = require('sortablejs');
jq = require('jquery');
} catch(err) { }
}
if (typeof jQuery === 'function' || typeof $ === 'function') {
jq = jQuery || $;
}
if (typeof Sortable !== 'undefined') {
sortable = Sortable;
}
function checkErrors() {
if (!jq) {
throw new Error('jQuery is required for jquery-sortablejs');
}
if (!sortable) {
throw new Error('SortableJS is required for jquery-sortablejs (https://github.com/SortableJS/Sortable)');
}
}
checkErrors();
factory(sortable, jq);
})(function (Sortable, $) {
"use strict";
$.fn.sortable = function (options) {
var retVal,
args = arguments;
this.each(function () {
var $el = $(this),
sortable = $el.data('sortable');
if (!sortable && (options instanceof Object || !options)) {
sortable = new Sortable(this, options);
$el.data('sortable', sortable);
} else if (sortable) {
if (options === 'destroy') {
sortable.destroy();
$el.removeData('sortable');
} else if (options === 'widget') {
retVal = sortable;
} else if (typeof sortable[options] === 'function') {
retVal = sortable[options].apply(sortable, [].slice.call(args, 1));
} else if (options in sortable.options) {
retVal = sortable.option.apply(sortable, args);
}
}
});
return (retVal === void 0) ? this : retVal;
};
});

View file

@ -20,7 +20,7 @@ class HostNodeTest extends BaseTestCase
{
$this->assertEquals(
'localhost;Hoststatus',
(string) $this->localhost()
$this->localhost()->getName()
);
}
@ -57,9 +57,9 @@ class HostNodeTest extends BaseTestCase
protected function localhost()
{
$bp = new BpConfig();
return new HostNode($bp, (object) array(
return (new HostNode((object) array(
'hostname' => 'localhost',
'state' => 0,
));
)))->setBpConfig($bp);
}
}

View file

@ -12,8 +12,8 @@ class AndOperatorTest extends BaseTestCase
{
$storage = new LegacyStorage($this->emptyConfigSection());
$expressions = array(
'a = b',
'a = b & c & d',
'a = b;c',
'a = b;c & c;d & d;e',
);
foreach ($expressions as $expression) {
@ -27,9 +27,9 @@ class AndOperatorTest extends BaseTestCase
public function testThreeTimesCriticalIsCritical()
{
$bp = $this->getBp();
$bp->setNodeState('b', 2);
$bp->setNodeState('c', 2);
$bp->setNodeState('d', 2);
$bp->setNodeState('b;c', 2);
$bp->setNodeState('c;d', 2);
$bp->setNodeState('d;e', 2);
$this->assertEquals(
'CRITICAL',
@ -40,9 +40,9 @@ class AndOperatorTest extends BaseTestCase
public function testTwoTimesCriticalAndOkIsCritical()
{
$bp = $this->getBp();
$bp->setNodeState('b', 2);
$bp->setNodeState('c', 0);
$bp->setNodeState('d', 2);
$bp->setNodeState('b;c', 2);
$bp->setNodeState('c;d', 0);
$bp->setNodeState('d;e', 2);
$this->assertEquals(
'CRITICAL',
@ -53,9 +53,9 @@ class AndOperatorTest extends BaseTestCase
public function testCriticalAndWarningAndOkIsCritical()
{
$bp = $this->getBp();
$bp->setNodeState('b', 2);
$bp->setNodeState('c', 1);
$bp->setNodeState('d', 0);
$bp->setNodeState('b;c', 2);
$bp->setNodeState('c;d', 1);
$bp->setNodeState('d;e', 0);
$this->assertEquals(
'CRITICAL',
@ -66,9 +66,9 @@ class AndOperatorTest extends BaseTestCase
public function testUnknownAndWarningAndOkIsUnknown()
{
$bp = $this->getBp();
$bp->setNodeState('b', 0);
$bp->setNodeState('c', 1);
$bp->setNodeState('d', 3);
$bp->setNodeState('b;c', 0);
$bp->setNodeState('c;d', 1);
$bp->setNodeState('d;e', 3);
$this->assertEquals(
'UNKNOWN',
@ -79,9 +79,9 @@ class AndOperatorTest extends BaseTestCase
public function testTwoTimesWarningAndOkIsWarning()
{
$bp = $this->getBp();
$bp->setNodeState('b', 0);
$bp->setNodeState('c', 1);
$bp->setNodeState('d', 1);
$bp->setNodeState('b;c', 0);
$bp->setNodeState('c;d', 1);
$bp->setNodeState('d;e', 1);
$this->assertEquals(
'WARNING',
@ -92,9 +92,9 @@ class AndOperatorTest extends BaseTestCase
public function testThreeTimesOkIsOk()
{
$bp = $this->getBp();
$bp->setNodeState('b', 0);
$bp->setNodeState('c', 0);
$bp->setNodeState('d', 0);
$bp->setNodeState('b;c', 0);
$bp->setNodeState('c;d', 0);
$bp->setNodeState('d;e', 0);
$this->assertEquals(
'OK',
@ -203,7 +203,7 @@ class AndOperatorTest extends BaseTestCase
protected function getBp()
{
$storage = new LegacyStorage($this->emptyConfigSection());
$expression = 'a = b & c & d';
$expression = 'a = b;c & c;d & d;e';
$bp = $storage->loadFromString('dummy', $expression);
$bp->createBp('b');
$bp->createBp('c');

View file

@ -12,8 +12,8 @@ class MinOperatorTest extends BaseTestCase
{
$storage = new LegacyStorage($this->emptyConfigSection());
$expressions = array(
'a = 1 of: b',
'a = 2 of: b + c + d',
'a = 1 of: b;c',
'a = 2 of: b;c + c;d + d;e',
);
$this->getName();
foreach ($expressions as $expression) {
@ -26,9 +26,9 @@ class MinOperatorTest extends BaseTestCase
public function testTwoOfThreeTimesCriticalAreAtLeastCritical()
{
$bp = $this->getBp();
$bp->setNodeState('b', 2);
$bp->setNodeState('c', 2);
$bp->setNodeState('d', 2);
$bp->setNodeState('b;c', 2);
$bp->setNodeState('c;d', 2);
$bp->setNodeState('d;e', 2);
$this->assertEquals(
'CRITICAL',
@ -39,9 +39,9 @@ class MinOperatorTest extends BaseTestCase
public function testTwoOfTwoTimesCriticalAndUnknownAreAtLeastCritical()
{
$bp = $this->getBp();
$bp->setNodeState('b', 2);
$bp->setNodeState('c', 3);
$bp->setNodeState('d', 2);
$bp->setNodeState('b;c', 2);
$bp->setNodeState('c;d', 3);
$bp->setNodeState('d;e', 2);
$this->assertEquals(
'CRITICAL',
@ -52,9 +52,9 @@ class MinOperatorTest extends BaseTestCase
public function testTwoOfCriticalAndWarningAndOkAreAtLeastCritical()
{
$bp = $this->getBp();
$bp->setNodeState('b', 2);
$bp->setNodeState('c', 1);
$bp->setNodeState('d', 0);
$bp->setNodeState('b;c', 2);
$bp->setNodeState('c;d', 1);
$bp->setNodeState('d;e', 0);
$this->assertEquals(
'CRITICAL',
@ -65,9 +65,9 @@ class MinOperatorTest extends BaseTestCase
public function testTwoOfUnknownAndWarningAndCriticalAreAtLeastCritical()
{
$bp = $this->getBp();
$bp->setNodeState('b', 2);
$bp->setNodeState('c', 1);
$bp->setNodeState('d', 3);
$bp->setNodeState('b;c', 2);
$bp->setNodeState('c;d', 1);
$bp->setNodeState('d;e', 3);
$this->assertEquals(
'CRITICAL',
@ -78,9 +78,9 @@ class MinOperatorTest extends BaseTestCase
public function testTwoOfTwoTimesWarningAndUnknownAreAtLeastUnknown()
{
$bp = $this->getBp();
$bp->setNodeState('b', 3);
$bp->setNodeState('c', 1);
$bp->setNodeState('d', 1);
$bp->setNodeState('b;c', 3);
$bp->setNodeState('c;d', 1);
$bp->setNodeState('d;e', 1);
$this->assertEquals(
'UNKNOWN',
@ -91,9 +91,9 @@ class MinOperatorTest extends BaseTestCase
public function testTwoOfThreeTimesOkAreAtLeastOk()
{
$bp = $this->getBp();
$bp->setNodeState('b', 0);
$bp->setNodeState('c', 0);
$bp->setNodeState('d', 0);
$bp->setNodeState('b;c', 0);
$bp->setNodeState('c;d', 0);
$bp->setNodeState('d;e', 0);
$this->assertEquals(
'OK',
@ -114,8 +114,8 @@ class MinOperatorTest extends BaseTestCase
public function testTenWithOnlyTwoCritical()
{
$bp = $this->getBp(10, 8, 0);
$bp->setNodeState('b', 2);
$bp->setNodeState('c', 2);
$bp->setNodeState('b;c', 2);
$bp->setNodeState('c;d', 2);
$this->assertEquals(
'OK',
@ -126,9 +126,9 @@ class MinOperatorTest extends BaseTestCase
public function testTenWithThreeCritical()
{
$bp = $this->getBp(10, 8, 0);
$bp->setNodeState('b', 2);
$bp->setNodeState('c', 2);
$bp->setNodeState('d', 2);
$bp->setNodeState('b;c', 2);
$bp->setNodeState('c;d', 2);
$bp->setNodeState('d;e', 2);
$this->assertEquals(
'CRITICAL',
@ -139,9 +139,9 @@ class MinOperatorTest extends BaseTestCase
public function testTenWithThreeWarning()
{
$bp = $this->getBp(10, 8, 0);
$bp->setNodeState('b', 1);
$bp->setNodeState('c', 1);
$bp->setNodeState('d', 1);
$bp->setNodeState('b;c', 1);
$bp->setNodeState('c;d', 1);
$bp->setNodeState('d;e', 1);
$this->assertEquals(
'WARNING',
@ -157,14 +157,13 @@ class MinOperatorTest extends BaseTestCase
$names = array();
$a = 97;
for ($i = 1; $i <= $count; $i++) {
$names[] = chr($a + $i);
$names[] = chr($a + $i) . ';' . chr($a + $i + 1);
}
$storage = new LegacyStorage($this->emptyConfigSection());
$expression = sprintf('a = %d of: %s', $min, join(' + ', $names));
$bp = $storage->loadFromString('dummy', $expression);
foreach ($names as $n) {
$bp->createBp($n);
if ($defaultState !== null) {
$bp->setNodeState($n, $defaultState);
}

View file

@ -12,10 +12,10 @@ class NotOperatorTest extends BaseTestCase
{
$storage = new LegacyStorage($this->emptyConfigSection());
$expressions = array(
'a = !b',
'a = ! b',
'a = b ! c ! d',
'a = ! b ! c ! d !',
'a = !b;c',
'a = ! b;c',
'a = b;c ! c;d ! d;e',
'a = ! b;c ! c;d ! d;e !',
);
foreach ($expressions as $expression) {
@ -29,10 +29,10 @@ class NotOperatorTest extends BaseTestCase
public function testASimpleNegationGivesTheCorrectResult()
{
$storage = new LegacyStorage($this->emptyConfigSection());
$expression = 'a = !b';
$expression = 'a = !b;c';
$bp = $storage->loadFromString('dummy', $expression);
$a = $bp->getNode('a');
$b = $bp->createBp('b')->setState(3);
$b = $bp->getNode('b;c')->setState(3);
$this->assertEquals(
'OK',
$a->getStateName()
@ -49,9 +49,9 @@ class NotOperatorTest extends BaseTestCase
public function testThreeTimesCriticalIsOk()
{
$bp = $this->getBp();
$bp->setNodeState('b', 2);
$bp->setNodeState('c', 2);
$bp->setNodeState('d', 2);
$bp->setNodeState('b;c', 2);
$bp->setNodeState('c;d', 2);
$bp->setNodeState('d;e', 2);
$this->assertEquals(
'OK',
@ -62,9 +62,9 @@ class NotOperatorTest extends BaseTestCase
public function testThreeTimesUnknownIsOk()
{
$bp = $this->getBp();
$bp->setNodeState('b', 3);
$bp->setNodeState('c', 3);
$bp->setNodeState('d', 3);
$bp->setNodeState('b;c', 3);
$bp->setNodeState('c;d', 3);
$bp->setNodeState('d;e', 3);
$this->assertEquals(
'OK',
@ -75,9 +75,9 @@ class NotOperatorTest extends BaseTestCase
public function testThreeTimesWarningIsWarning()
{
$bp = $this->getBp();
$bp->setNodeState('b', 1);
$bp->setNodeState('c', 1);
$bp->setNodeState('d', 1);
$bp->setNodeState('b;c', 1);
$bp->setNodeState('c;d', 1);
$bp->setNodeState('d;e', 1);
$this->assertEquals(
'WARNING',
@ -88,9 +88,9 @@ class NotOperatorTest extends BaseTestCase
public function testThreeTimesOkIsCritical()
{
$bp = $this->getBp();
$bp->setNodeState('b', 0);
$bp->setNodeState('c', 0);
$bp->setNodeState('d', 0);
$bp->setNodeState('b;c', 0);
$bp->setNodeState('c;d', 0);
$bp->setNodeState('d;e', 0);
$this->assertEquals(
'CRITICAL',
@ -101,9 +101,9 @@ class NotOperatorTest extends BaseTestCase
public function testNotOkAndWarningAndCriticalIsOk()
{
$bp = $this->getBp();
$bp->setNodeState('b', 0);
$bp->setNodeState('c', 1);
$bp->setNodeState('d', 2);
$bp->setNodeState('b;c', 0);
$bp->setNodeState('c;d', 1);
$bp->setNodeState('d;e', 2);
$this->assertEquals(
'OK',
@ -114,9 +114,9 @@ class NotOperatorTest extends BaseTestCase
public function testNotWarningAndUnknownAndCriticalIsOk()
{
$bp = $this->getBp();
$bp->setNodeState('b', 3);
$bp->setNodeState('c', 2);
$bp->setNodeState('d', 1);
$bp->setNodeState('b;c', 3);
$bp->setNodeState('c;d', 2);
$bp->setNodeState('d;e', 1);
$this->assertEquals(
'OK',
@ -127,9 +127,9 @@ class NotOperatorTest extends BaseTestCase
public function testNotTwoTimesWarningAndOkIsWarning()
{
$bp = $this->getBp();
$bp->setNodeState('b', 0);
$bp->setNodeState('c', 1);
$bp->setNodeState('d', 1);
$bp->setNodeState('b;c', 0);
$bp->setNodeState('c;d', 1);
$bp->setNodeState('d;e', 1);
$this->assertEquals(
'WARNING',
@ -143,11 +143,8 @@ class NotOperatorTest extends BaseTestCase
protected function getBp()
{
$storage = new LegacyStorage($this->emptyConfigSection());
$expression = 'a = ! b ! c ! d';
$expression = 'a = ! b;c ! c;d ! d;e';
$bp = $storage->loadFromString('dummy', $expression);
$bp->createBp('b');
$bp->createBp('c');
$bp->createBp('d');
return $bp;
}

View file

@ -12,8 +12,8 @@ class OrOperatorTest extends BaseTestCase
{
$storage = new LegacyStorage($this->emptyConfigSection());
$expressions = array(
'a = b',
'a = b | c | d',
'a = b;c',
'a = b;c | c;d | d;e',
);
foreach ($expressions as $expression) {
@ -27,9 +27,9 @@ class OrOperatorTest extends BaseTestCase
public function testThreeTimesCriticalIsCritical()
{
$bp = $this->getBp();
$bp->setNodeState('b', 2);
$bp->setNodeState('c', 2);
$bp->setNodeState('d', 2);
$bp->setNodeState('b;c', 2);
$bp->setNodeState('c;d', 2);
$bp->setNodeState('d;e', 2);
$this->assertEquals(
'CRITICAL',
@ -40,9 +40,9 @@ class OrOperatorTest extends BaseTestCase
public function testTwoTimesCriticalOrUnknownIsUnknown()
{
$bp = $this->getBp();
$bp->setNodeState('b', 2);
$bp->setNodeState('c', 3);
$bp->setNodeState('d', 2);
$bp->setNodeState('b;c', 2);
$bp->setNodeState('c;d', 3);
$bp->setNodeState('d;e', 2);
$this->assertEquals(
'UNKNOWN',
@ -53,9 +53,9 @@ class OrOperatorTest extends BaseTestCase
public function testCriticalOrWarningOrOkIsOk()
{
$bp = $this->getBp();
$bp->setNodeState('b', 2);
$bp->setNodeState('c', 1);
$bp->setNodeState('d', 0);
$bp->setNodeState('b;c', 2);
$bp->setNodeState('c;d', 1);
$bp->setNodeState('d;e', 0);
$this->assertEquals(
'OK',
@ -66,9 +66,9 @@ class OrOperatorTest extends BaseTestCase
public function testUnknownOrWarningOrCriticalIsWarning()
{
$bp = $this->getBp();
$bp->setNodeState('b', 2);
$bp->setNodeState('c', 1);
$bp->setNodeState('d', 3);
$bp->setNodeState('b;c', 2);
$bp->setNodeState('c;d', 1);
$bp->setNodeState('d;e', 3);
$this->assertEquals(
'WARNING',
@ -79,9 +79,9 @@ class OrOperatorTest extends BaseTestCase
public function testTwoTimesWarningAndOkIsOk()
{
$bp = $this->getBp();
$bp->setNodeState('b', 0);
$bp->setNodeState('c', 1);
$bp->setNodeState('d', 1);
$bp->setNodeState('b;c', 0);
$bp->setNodeState('c;d', 1);
$bp->setNodeState('d;e', 1);
$this->assertEquals(
'OK',
@ -92,9 +92,9 @@ class OrOperatorTest extends BaseTestCase
public function testThreeTimesWarningIsWarning()
{
$bp = $this->getBp();
$bp->setNodeState('b', 1);
$bp->setNodeState('c', 1);
$bp->setNodeState('d', 1);
$bp->setNodeState('b;c', 1);
$bp->setNodeState('c;d', 1);
$bp->setNodeState('d;e', 1);
$this->assertEquals(
'WARNING',
@ -108,11 +108,8 @@ class OrOperatorTest extends BaseTestCase
protected function getBp()
{
$storage = new LegacyStorage($this->emptyConfigSection());
$expression = 'a = b | c | d';
$expression = 'a = b;c | c;d | d;e';
$bp = $storage->loadFromString('dummy', $expression);
$bp->createBp('b');
$bp->createBp('c');
$bp->createBp('d');
return $bp;
}

View file

@ -47,10 +47,10 @@ class ServiceNodeTest extends BaseTestCase
protected function pingOnLocalhost()
{
$bp = new BpConfig();
return new ServiceNode($bp, (object) array(
return (new ServiceNode((object) array(
'hostname' => 'localhost',
'service' => 'ping <> pong',
'state' => 0,
));
)))->setBpConfig($bp);
}
}