From 98b6b2c6d03f85446f9fc9b77bb30a1ef37b6892 Mon Sep 17 00:00:00 2001 From: Christian Menapace Date: Mon, 20 May 2019 12:08:06 +0200 Subject: [PATCH] Add Service Override --- application/forms/AddNodeForm.php | 75 ++++++++++++- application/forms/EditNodeForm.php | 100 +++++++++++++++++- library/Businessprocess/BpConfig.php | 24 ++++- library/Businessprocess/BpNode.php | 19 ++++ .../Modification/NodeAddChildrenAction.php | 15 ++- library/Businessprocess/Node.php | 47 ++++++++ .../Renderer/TileRenderer/NodeTile.php | 9 ++ .../Businessprocess/Renderer/TreeRenderer.php | 57 +++++++++- library/Businessprocess/ServiceNode.php | 12 ++- .../Businessprocess/State/MonitoringState.php | 35 +++--- .../Storage/LegacyConfigParser.php | 14 ++- .../NoDuplicateChildrenValidator.php | 2 +- public/css/module.less | 58 ++++++++-- 13 files changed, 424 insertions(+), 43 deletions(-) diff --git a/application/forms/AddNodeForm.php b/application/forms/AddNodeForm.php index 591f37a..691b59d 100644 --- a/application/forms/AddNodeForm.php +++ b/application/forms/AddNodeForm.php @@ -207,6 +207,11 @@ class AddNodeForm extends QuickForm $this->addHostElement(); if ($host = $this->getSentValue('host')) { $this->addServicesElement($host); + $this->addServiceOverrideCheckbox(); + + if ($this->getSentValue('service_override') === '1') { + $this->addServiceOverrideElements(); + } } else { $this->setSubmitLabel($this->translate('Next')); } @@ -253,6 +258,36 @@ class AddNodeForm extends QuickForm ]); } + protected function addServiceOverrideCheckbox() + { + $this->addElement('checkbox', 'service_override', [ + 'ignore' => true, + 'class' => 'autosubmit', + 'label' => $this->translate('Override Service State'), + 'description' => $this->translate('Enable service state overrides') + ]); + } + + protected function addServiceOverrideElements() + { + $elements = []; + foreach ($this->enumServiceStateList() as $state => $stateName) { + if ($state === 0) { + continue; + } + $this->addElement('select', $stateName, [ + 'label' => $this->translate($stateName), + 'ignore' => true, + 'multiOptions' => $this->optionalEnum($this->enumServiceStateList()), + ]); + $elements[] = $stateName; + } + + $this->addSimpleDisplayGroup($elements, 'override_group', [ + 'legend' => $this->translate('State Overrides') + ]); + } + protected function selectProcess() { if ($this->hasParentNode()) { @@ -391,6 +426,34 @@ class AddNodeForm extends QuickForm return $services; } + protected function enumServiceStateList() + { + $serviceStateList = [ + 0 => $this->translate('OK'), + 1 => $this->translate('WARNING'), + 2 => $this->translate('CRITICAL'), + 3 => $this->translate('UNKNOWN'), + 99 => $this->translate('PENDING'), + ]; + + return $serviceStateList; + } + + protected function statesToString() + { + $stateString = null; + foreach ($this->enumServiceStateList() as $state => $stateName) { + if ($this->getValue($stateName) !== null && $this->getValue($stateName) !== '') { + if ($stateString !== null) { + $stateString .= ','; + } + $stateString .= $state . '-' . $this->getValue($stateName); + } + } + + return $stateString; + } + protected function hasProcesses() { return count($this->enumProcesses()) > 0; @@ -464,8 +527,18 @@ class AddNodeForm extends QuickForm { $changes = ProcessChanges::construct($this->bp, $this->session); switch ($this->getValue('node_type')) { - case 'host': case 'service': + if ($this->statesToString() !== null) { + $services = []; + foreach ($this->getValue('children') as $service) { + $services[] = $service . ':' . $this->statesToString(); + } + $changes->addChildrenToNode($services, $this->parent); + } else { + $changes->addChildrenToNode($this->getValue('children'), $this->parent); + } + break; + case 'host': case 'process': if ($this->hasParentNode()) { $changes->addChildrenToNode($this->getValue('children'), $this->parent); diff --git a/application/forms/EditNodeForm.php b/application/forms/EditNodeForm.php index 429a8f2..438ad59 100644 --- a/application/forms/EditNodeForm.php +++ b/application/forms/EditNodeForm.php @@ -36,6 +36,8 @@ class EditNodeForm extends QuickForm protected $host; + protected $statesOverride = []; + /** @var SessionNamespace */ protected $session; @@ -43,7 +45,8 @@ class EditNodeForm extends QuickForm { $this->host = substr($this->getNode()->getName(), 0, strpos($this->getNode()->getName(), ';')); if ($this->isService()) { - $this->service = substr($this->getNode()->getName(), strpos($this->getNode()->getName(), ';') + 1); + $this->service = $this->getNode()->getShortName(); + $this->statesOverride = $this->getNode()->getStatesOverride(); } $view = $this->getView(); @@ -190,8 +193,16 @@ class EditNodeForm extends QuickForm if ($this->getSentValue('hosts') === null) { $this->addServicesElement($this->host); + $this->addServiceOverrideCheckbox(); + if (!empty($this->statesOverride) || $this->getSentValue('service_override') === '1') { + $this->addServiceOverrideElements(); + } } elseif ($host = $this->getSentValue('hosts')) { $this->addServicesElement($host); + $this->addServiceOverrideCheckbox(); + if ($this->getSentValue('service_override') === '1') { + $this->addServiceOverrideElements(); + } } else { $this->setSubmitLabel($this->translate('Next')); } @@ -214,14 +225,58 @@ class EditNodeForm extends QuickForm { $this->addElement('select', 'children', array( 'required' => true, - 'value' => $this->getNode()->getName(), + 'value' => $this->service, 'multiOptions' => $this->enumServiceList($host), 'label' => $this->translate('Service'), 'description' => $this->translate('The service for this business process node'), - 'validators' => [[new NoDuplicateChildrenValidator($this, $this->bp, $this->parent), true]] + 'validators' => [ + ['Callback', true, [ + 'callback' => function ($value) { + if ($this->node->getShortName() === $value) { + return true; + } + return ! $this->parent->hasMatchingChild($value); + }, + 'messages' => [ + 'callbackValue' => $this->translate('%value% is already defined in this process') + ] + ]] + ] )); } + protected function addServiceOverrideCheckbox() + { + $this->addElement('checkbox', 'service_override', [ + 'ignore' => true, + 'class' => 'autosubmit', + 'value' => ! empty($this->statesOverride) ? '1' : null, + 'label' => $this->translate('Override Service State'), + 'description' => $this->translate('Enable service state overrides') + ]); + } + + protected function addServiceOverrideElements() + { + $elements = []; + foreach ($this->enumServiceStateList() as $state => $stateName) { + if ($state == 0) { + continue; + } + $this->addElement('select', $stateName, [ + 'label' => $this->translate($stateName), + 'value' => isset($this->statesOverride[$state]) ? $this->statesOverride[$state] : null, + 'ignore' => true, + 'multiOptions' => $this->optionalEnum($this->enumServiceStateList()), + ]); + $elements[] = $stateName; + } + + $this->addSimpleDisplayGroup($elements, 'override_group', [ + 'legend' => $this->translate('State Overrides') + ]); + } + protected function selectProcess() { $this->addElement('multiselect', 'children', array( @@ -341,6 +396,34 @@ class EditNodeForm extends QuickForm return $services; } + protected function enumServiceStateList() + { + $serviceStateList = [ + 0 => $this->translate('OK'), + 1 => $this->translate('WARNING'), + 2 => $this->translate('CRITICAL'), + 3 => $this->translate('UNKNOWN'), + 99 => $this->translate('PENDING'), + ]; + + return $serviceStateList; + } + + protected function statesToString() + { + $stateString = null; + foreach ($this->enumServiceStateList() as $state => $stateName) { + if ($this->getValue($stateName) !== null && $this->getValue($stateName) !== '') { + if ($stateString !== null) { + $stateString .= ','; + } + $stateString .= $state . '-' . $this->getValue($stateName); + } + } + + return $stateString; + } + protected function hasProcesses() { return count($this->enumProcesses()) > 0; @@ -405,8 +488,17 @@ class EditNodeForm extends QuickForm $changes->deleteNode($this->node, $this->parent->getName()); switch ($this->getValue('node_type')) { - case 'host': case 'service': + if ($this->statesToString() !== null) { + $changes->addChildrenToNode( + $this->getValue('children') . ':' . $this->statesToString(), + $this->parent + ); + } else { + $changes->addChildrenToNode($this->getValue('children'), $this->parent); + } + break; + case 'host': case 'process': $changes->addChildrenToNode($this->getValue('children'), $this->parent); break; diff --git a/library/Businessprocess/BpConfig.php b/library/Businessprocess/BpConfig.php index c49d262..96a0a7d 100644 --- a/library/Businessprocess/BpConfig.php +++ b/library/Businessprocess/BpConfig.php @@ -460,16 +460,21 @@ class BpConfig return array_key_exists($name, $this->root_nodes); } - public function createService($host, $service) + public function createService($host, $service, $statesOverride = null) { $node = new ServiceNode( (object) array( 'hostname' => $host, - 'service' => $service + 'service' => $service, + 'statesOverride'=> $statesOverride ) ); $node->setBpConfig($this); - $this->nodes[$host . ';' . $service] = $node; + if (isset($statesOverride)) { + $this->nodes[$host . ';' . $service . ':' . $statesOverride] = $node; + } else { + $this->nodes[$host . ';' . $service] = $node; + } $this->hosts[$host] = true; return $node; } @@ -657,6 +662,19 @@ class BpConfig ); } + public function getMatchingNodeNams($name) + { + $matching = []; + + foreach ($this->nodes as $nodeName => $node) { + if ($name === $node->getShortName()) { + $matching[] = $nodeName; + } + } + + return $matching; + } + /** * @return BpNode */ diff --git a/library/Businessprocess/BpNode.php b/library/Businessprocess/BpNode.php index 9aeaab8..b6f3721 100644 --- a/library/Businessprocess/BpNode.php +++ b/library/Businessprocess/BpNode.php @@ -142,6 +142,11 @@ class BpNode extends Node return in_array($name, $this->getChildNames()); } + public function hasMatchingChild($name) + { + return in_array($name, $this->getChildShortNames()); + } + public function removeChild($name) { if (($key = array_search($name, $this->getChildNames())) !== false) { @@ -498,6 +503,20 @@ class BpNode extends Node return $this->children; } + protected function getChildShortNames() + { + $ChidrenShortNames = array(); + + foreach ($this->getChildren() as $child) { + $name = $child->getShortName(); + array_push($ChidrenShortNames, $name); + } + + return $ChidrenShortNames; + } + + + /** * Reorder this node's children, in case manual order is not applied */ diff --git a/library/Businessprocess/Modification/NodeAddChildrenAction.php b/library/Businessprocess/Modification/NodeAddChildrenAction.php index 5d5ab29..4b9988b 100644 --- a/library/Businessprocess/Modification/NodeAddChildrenAction.php +++ b/library/Businessprocess/Modification/NodeAddChildrenAction.php @@ -34,12 +34,17 @@ class NodeAddChildrenAction extends NodeAction foreach ($this->children as $name) { if (! $config->hasNode($name) || $config->getNode($name)->getBpConfig()->getName() !== $config->getName()) { if (strpos($name, ';') !== false) { - list($host, $service) = preg_split('/;/', $name, 2); - - if ($service === 'Hoststatus') { - $config->createHost($host); + if (strpos($name, ':') !== false) { + list($host, $service, $statesOverride) = preg_split('~[;:]~', $name, 3); + $config->createService($host, $service, $statesOverride); } else { - $config->createService($host, $service); + list($host, $service) = preg_split('/;/', $name, 2); + + if ($service === 'Hoststatus') { + $config->createHost($host); + } else { + $config->createService($host, $service); + } } } elseif ($name[0] === '@' && strpos($name, ':') !== false) { list($configName, $nodeName) = preg_split('~:\s*~', substr($name, 1), 2); diff --git a/library/Businessprocess/Node.php b/library/Businessprocess/Node.php index 7849a7c..5c8f0dd 100644 --- a/library/Businessprocess/Node.php +++ b/library/Businessprocess/Node.php @@ -46,6 +46,8 @@ abstract class Node self::NODE_EMPTY => 0 ); + protected $stateOverride = []; + /** @var string Alias of the node */ protected $alias; @@ -197,6 +199,28 @@ abstract class Node return $this; } + protected function setStateOverride($state, $oveRrideState) + { + $this->stateOverride[(int) $state] = (int) $oveRrideState; + } + + public function setStatesOverride($statesOverrideString) + { + $overrides = explode(',', $statesOverrideString); + + foreach ($overrides as $overrideState) { + if (strpos($overrideState, '-') !== false) { + list($key, $value) = explode('-', $overrideState); + $this->setStateOverride($key, $value); + } + } + } + + public function getStatesOverride() + { + return $this->stateOverride; + } + /** * Forget my state * @@ -246,6 +270,24 @@ abstract class Node ); } + if (isset($this->stateOverride[$this->state])) { + return $this->stateOverride[$this->state]; + } else { + return $this->state; + } + } + + public function getRealState() + { + if ($this->state === null) { + throw new ProgrammingError( + sprintf( + 'Node %s is unable to retrieve it\'s state', + $this->name + ) + ); + } + return $this->state; } @@ -364,6 +406,11 @@ abstract class Node return $this; } + public function getShortName() + { + return $this->name; + } + public function hasParents() { return count($this->parents) > 0; diff --git a/library/Businessprocess/Renderer/TileRenderer/NodeTile.php b/library/Businessprocess/Renderer/TileRenderer/NodeTile.php index 8c16ed5..7cb0c81 100644 --- a/library/Businessprocess/Renderer/TileRenderer/NodeTile.php +++ b/library/Businessprocess/Renderer/TileRenderer/NodeTile.php @@ -113,6 +113,15 @@ class NodeTile extends BaseHtmlElement )); } + if ($node instanceof ServiceNode && $node->getRealState() !== $node->getState()) { + $this->add((new StateBall(strtolower($node->getStateName($node->getRealState()))))->addAttributes([ + 'title' => sprintf( + '%s', + $node->getStateName($node->getRealState()) + ) + ])); + } + if ($node instanceof BpNode && !$renderer->isBreadcrumb()) { $this->add(Html::tag( 'p', diff --git a/library/Businessprocess/Renderer/TreeRenderer.php b/library/Businessprocess/Renderer/TreeRenderer.php index 9322c26..6b3e4fe 100644 --- a/library/Businessprocess/Renderer/TreeRenderer.php +++ b/library/Businessprocess/Renderer/TreeRenderer.php @@ -136,6 +136,45 @@ class TreeRenderer extends Renderer return $icons; } + public function getNodeRealState(Node $node) + { + $realState = []; + $realState[] = Html::tag('i', ['class' => 'icon icon-flash']); + $realState[] = (new StateBall(strtolower($node->getStateName($node->getRealState()))))->addAttributes([ + 'title' => sprintf( + '%s', + $node->getStateName($node->getRealState()) + ) + ]); + + return $realState; + } + + public function getNodeStateOverride(Node $node) + { + $statesOverrights = []; + + $states = $node->getStatesOverride(); + foreach ($states as $originalState => $overrideState) { + $statesOverrights[] = (new StateBall(strtolower($node->getStateName($originalState))))->addAttributes([ + 'title' => sprintf( + '%s', + $node->getStateName($originalState) + ) + ]); + $statesOverrights[] = Html::tag('i', ['class' => 'icon icon-right-small']); + $statesOverrights[] = (new StateBall(strtolower($node->getStateName($overrideState))))->addAttributes([ + 'title' => sprintf( + '%s', + $node->getStateName($overrideState) + ), + 'class' => 'last' + ]); + } + + return $statesOverrights; + } + /** * @param BpConfig $bp * @param Node $node @@ -243,10 +282,24 @@ class TreeRenderer extends Renderer $link->getAttributes()->set('data-base-target', '_next'); $li->add($link); - if (! $this->isLocked() && $node->getBpConfig()->getName() === $this->getBusinessProcess()->getName()) { - $li->add($this->getActionIcons($bp, $node)); + if ($node->getRealState() !== $node->getState()) { + $li->add($this->getNodeRealState($node)); } + $leftAlign = Html::tag('div', [ + 'class' => 'left-side', + ]); + + if (!empty($node->getStatesOverride())) { + $leftAlign->add($this->getNodeStateOverride($node)); + } + + if (! $this->isLocked() && $node->getBpConfig()->getName() === $this->getBusinessProcess()->getName()) { + $leftAlign->add($this->getActionIcons($bp, $node)); + } + + $li->add($leftAlign); + return $li; } diff --git a/library/Businessprocess/ServiceNode.php b/library/Businessprocess/ServiceNode.php index 6160bce..fc308f6 100644 --- a/library/Businessprocess/ServiceNode.php +++ b/library/Businessprocess/ServiceNode.php @@ -19,7 +19,12 @@ class ServiceNode extends MonitoredNode public function __construct($object) { - $this->name = $object->hostname . ';' . $object->service; + if (isset($object->statesOverride)) { + $this->name = $object->hostname . ';' . $object->service . ':' . $object->statesOverride; + $this->setStatesOverride($object->statesOverride); + } else { + $this->name = $object->hostname . ';' . $object->service; + } $this->hostname = $object->hostname; $this->service = $object->service; if (isset($object->state)) { @@ -67,6 +72,11 @@ class ServiceNode extends MonitoredNode { return $this->getHostAlias() . ': ' . $this->alias; } + + public function getShortName() + { + return $this->hostname . ';' . $this->service; + } public function getUrl() { diff --git a/library/Businessprocess/State/MonitoringState.php b/library/Businessprocess/State/MonitoringState.php index d317528..beea0af 100644 --- a/library/Businessprocess/State/MonitoringState.php +++ b/library/Businessprocess/State/MonitoringState.php @@ -122,24 +122,27 @@ class MonitoringState $key .= ';Hoststatus'; } - // We fetch more states than we need, so skip unknown ones - if (! $config->hasNode($key)) { - return; - } + $nodesNames = $config->getMatchingNodeNams($key); + foreach ($nodesNames as $nodeName) { + // We fetch more states than we need, so skip unknown ones + if (! $config->hasNode($nodeName)) { + return; + } - $node = $config->getNode($key); + $node = $config->getNode($nodeName); - if ($row->state !== null) { - $node->setState($row->state)->setMissing(false); - } - if ($row->last_state_change !== null) { - $node->setLastStateChange($row->last_state_change); - } - if ((int) $row->in_downtime === 1) { - $node->setDowntime(true); - } - if ((int) $row->ack === 1) { - $node->setAck(true); + if ($row->state !== null) { + $node->setState($row->state)->setMissing(false); + } + if ($row->last_state_change !== null) { + $node->setLastStateChange($row->last_state_change); + } + if ((int) $row->in_downtime === 1) { + $node->setDowntime(true); + } + if ((int) $row->ack === 1) { + $node->setAck(true); + } } $node->setAlias($row->display_name); diff --git a/library/Businessprocess/Storage/LegacyConfigParser.php b/library/Businessprocess/Storage/LegacyConfigParser.php index d4325cf..fcf382c 100644 --- a/library/Businessprocess/Storage/LegacyConfigParser.php +++ b/library/Businessprocess/Storage/LegacyConfigParser.php @@ -335,11 +335,17 @@ class LegacyConfigParser if ($bp->hasNode($val)) { $node->addChild($bp->getNode($val)); } else { - list($host, $service) = preg_split('~;~', $val, 2); - if ($service === 'Hoststatus') { - $node->addChild($bp->createHost($host)); + if (strpos($val, ':') !== false) { + list($host, $service, $statesOverride) = preg_split('~[;:]~', $val, 3); + $node->addChild($bp->createService($host, $service, $statesOverride)); } else { - $node->addChild($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)); + } } } } elseif ($val[0] === '@') { diff --git a/library/Businessprocess/Web/Form/Validator/NoDuplicateChildrenValidator.php b/library/Businessprocess/Web/Form/Validator/NoDuplicateChildrenValidator.php index 9676de0..ecdd345 100644 --- a/library/Businessprocess/Web/Form/Validator/NoDuplicateChildrenValidator.php +++ b/library/Businessprocess/Web/Form/Validator/NoDuplicateChildrenValidator.php @@ -43,7 +43,7 @@ class NoDuplicateChildrenValidator extends Zend_Validate_Abstract } elseif ($this->form instanceof EditNodeForm && $this->form->getNode()->getName() === $value) { $found = false; } else { - $found = $this->parent->hasChild($value); + $found = $this->parent->hasMatchingChild($value); } if (! $found) { diff --git a/public/css/module.less b/public/css/module.less index 40d71f7..2da19a2 100644 --- a/public/css/module.less +++ b/public/css/module.less @@ -214,12 +214,12 @@ ul.bp { margin-right: .5em; } - > a.action-link { - margin-left: auto; // Let the first action link move everything to the right + a.action-link { + margin-left: 20px; + } - & + a.action-link { - margin-left: 0; // But really only the first one - } + > .left-side { + margin-left: auto; // Left alligned Icons } } @@ -260,13 +260,18 @@ ul.bp { } li.process > div > .state-ball, - li:not(.process) > .state-ball { + li:not(.process) > .state-ball, + div.left-side > .state-ball { border: .15em solid white; &.size-s { width: 7em/6em; height: 7em/6em; line-height: 7em/6em; + + &.last { + margin-right: 10px; + } } } } @@ -416,6 +421,14 @@ td > a > .badges { font-size: 0.5em; } + .state-ball { + position: absolute; + right: 0px; + bottom: 0px; + margin: 5px 5px 5px 5px; + border: 1px solid white; + } + > a { display: block; text-decoration: none; @@ -949,6 +962,39 @@ form dt label { } } +fieldset.collapsed { + dd, dt, ul, div { + display: none; + } + } + +fieldset { + margin-top: 15px; + padding: 0 0 1.5em 0; + border: none; + + legend { + margin: 0em 0 0.5em 0; + font-size: 1em; + box-shadow: 0 3px 4px -4px rgba(0,0,0,0.2); + font-weight: bold; + width: 100%; + padding-left: 0.5em; + line-height: 2em; + user-select: none; + + &:hover { + border-color: @text-color; + } + } + dd, dt, ul, div { + display: none; + } + + margin-bottom: 0.2em; + padding-bottom: 0; +} + form fieldset { min-width: 36em; }