diff options
Diffstat (limited to 'application/forms')
-rw-r--r-- | application/forms/AddNodeForm.php | 579 | ||||
-rw-r--r-- | application/forms/BpConfigForm.php | 222 | ||||
-rw-r--r-- | application/forms/BpUploadForm.php | 202 | ||||
-rw-r--r-- | application/forms/DeleteNodeForm.php | 165 | ||||
-rw-r--r-- | application/forms/EditNodeForm.php | 460 | ||||
-rw-r--r-- | application/forms/MoveNodeForm.php | 185 | ||||
-rw-r--r-- | application/forms/ProcessForm.php | 215 | ||||
-rw-r--r-- | application/forms/SimulationForm.php | 136 |
8 files changed, 2164 insertions, 0 deletions
diff --git a/application/forms/AddNodeForm.php b/application/forms/AddNodeForm.php new file mode 100644 index 0000000..43afa4c --- /dev/null +++ b/application/forms/AddNodeForm.php @@ -0,0 +1,579 @@ +<?php + +namespace Icinga\Module\Businessprocess\Forms; + +use Icinga\Module\Businessprocess\BpNode; +use Icinga\Module\Businessprocess\BpConfig; +use Icinga\Module\Businessprocess\Common\EnumList; +use Icinga\Module\Businessprocess\ImportedNode; +use Icinga\Module\Businessprocess\Modification\ProcessChanges; +use Icinga\Module\Businessprocess\Storage\Storage; +use Icinga\Module\Businessprocess\Web\Form\QuickForm; +use Icinga\Module\Businessprocess\Web\Form\Validator\NoDuplicateChildrenValidator; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; +use Icinga\Web\Session\SessionNamespace; +use ipl\Sql\Connection as IcingaDbConnection; + +class AddNodeForm extends QuickForm +{ + use EnumList; + + /** @var MonitoringBackend|IcingaDbConnection*/ + protected $backend; + + /** @var Storage */ + protected $storage; + + /** @var BpConfig */ + protected $bp; + + /** @var BpNode */ + protected $parent; + + protected $objectList = array(); + + protected $processList = array(); + + /** @var SessionNamespace */ + protected $session; + + public function setup() + { + $view = $this->getView(); + if ($this->hasParentNode()) { + $this->addHtml( + '<h2>' . $view->escape( + sprintf($this->translate('Add a node to %s'), $this->parent->getAlias()) + ) . '</h2>' + ); + } else { + $this->addHtml( + '<h2>' . $this->translate('Add a new root node') . '</h2>' + ); + } + + $type = $this->selectNodeType(); + switch ($type) { + case 'host': + $this->selectHost(); + break; + case 'service': + $this->selectService(); + break; + case 'process': + $this->selectProcess(); + break; + case 'new-process': + $this->addNewProcess(); + break; + case 'hosts_from_filter': + $this->selectHostsFromFilter(); + break; + case 'services_from_filter': + $this->selectServicesFromFilter(); + break; + case null: + $this->setSubmitLabel($this->translate('Next')); + return; + } + } + + protected function addNewProcess() + { + $this->addElement('text', 'name', array( + 'label' => $this->translate('ID'), + 'required' => true, + 'description' => $this->translate( + 'This is the unique identifier of this process' + ), + 'validators' => [ + ['Callback', true, [ + 'callback' => function ($value) { + if ($this->hasParentNode()) { + return ! $this->parent->hasChild($value); + } + + return ! $this->bp->hasRootNode($value); + }, + 'messages' => [ + 'callbackValue' => $this->translate('%value% is already defined in this process') + ] + ]] + ] + )); + + $this->addElement('text', 'alias', array( + 'label' => $this->translate('Display Name'), + 'description' => $this->translate( + 'Usually this name will be shown for this node. Equals ID' + . ' if not given' + ), + )); + + $this->addElement('select', 'operator', array( + 'label' => $this->translate('Operator'), + 'required' => true, + 'multiOptions' => array( + '&' => $this->translate('AND'), + '|' => $this->translate('OR'), + '!' => $this->translate('NOT'), + '%' => $this->translate('DEGRADED'), + '1' => $this->translate('MIN 1'), + '2' => $this->translate('MIN 2'), + '3' => $this->translate('MIN 3'), + '4' => $this->translate('MIN 4'), + '5' => $this->translate('MIN 5'), + '6' => $this->translate('MIN 6'), + '7' => $this->translate('MIN 7'), + '8' => $this->translate('MIN 8'), + '9' => $this->translate('MIN 9'), + ) + )); + + $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' : "$display", + 'multiOptions' => array( + "$display" => $this->translate('Toplevel Process'), + '0' => $this->translate('Subprocess only'), + ) + )); + + $this->addElement('text', 'infoUrl', array( + 'label' => $this->translate('Info URL'), + 'description' => $this->translate( + 'URL pointing to more information about this node' + ) + )); + } + + /** + * @return string|null + */ + protected function selectNodeType() + { + $types = array(); + if ($this->hasParentNode()) { + $types['host'] = $this->translate('Host'); + $types['service'] = $this->translate('Service'); + $types['hosts_from_filter'] = $this->translate('Hosts from filter'); + $types['services_from_filter'] = $this->translate('Services from filter'); + } elseif (! $this->hasProcesses()) { + $this->addElement('hidden', 'node_type', array( + 'ignore' => true, + 'decorators' => array('ViewHelper'), + 'value' => 'new-process' + )); + + return 'new-process'; + } + + if ($this->hasProcesses() || ($this->hasParentNode() && $this->hasMoreConfigs())) { + $types['process'] = $this->translate('Existing Process'); + } + + $types['new-process'] = $this->translate('New Process Node'); + + $this->addElement('select', 'node_type', array( + 'label' => $this->translate('Node type'), + 'required' => true, + 'description' => $this->translate( + 'The node type you want to add' + ), + 'ignore' => true, + 'class' => 'autosubmit', + 'multiOptions' => $this->optionalEnum($types) + )); + + return $this->getSentValue('node_type'); + } + + protected function selectHost() + { + $this->addElement('multiselect', 'children', [ + 'label' => $this->translate('Hosts'), + 'required' => true, + 'size' => 8, + 'style' => 'width: 25em', + 'multiOptions' => $this->enumHostList(), + 'description' => $this->translate( + 'Hosts that should be part of this business process node' + ), + 'validators' => [[new NoDuplicateChildrenValidator($this, $this->bp, $this->parent), true]] + ]); + + $this->addHostOverrideCheckbox(); + if ($this->getSentValue('host_override') === '1') { + $this->addHostOverrideElement(); + } + } + + protected function selectService() + { + $this->addHostElement(); + if ($host = $this->getSentValue('host')) { + $this->addServicesElement($host); + $this->addServiceOverrideCheckbox(); + + if ($this->getSentValue('service_override') === '1') { + $this->addServiceOverrideElement(); + } + } else { + $this->setSubmitLabel($this->translate('Next')); + } + } + + protected function addHostElement() + { + $this->addElement('select', 'host', array( + 'label' => $this->translate('Host'), + 'required' => true, + 'ignore' => true, + 'class' => 'autosubmit', + 'multiOptions' => $this->optionalEnum($this->enumHostForServiceList()), + )); + } + + protected function addServicesElement($host) + { + $this->addElement('multiselect', 'children', [ + 'label' => $this->translate('Services'), + 'required' => true, + 'size' => 8, + 'style' => 'width: 25em', + 'multiOptions' => $this->enumServiceList($host), + 'description' => $this->translate( + 'Services that should be part of this business process node' + ), + 'validators' => [[new NoDuplicateChildrenValidator($this, $this->bp, $this->parent), true]] + ]); + } + + protected function addFilteredHostsElement($filter) + { + $this->addElement('submit', 'refresh', [ + 'label' => $this->translate('Refresh'), + 'class' => 'refresh-filter' + ]); + $this->addElement('multiselect', 'children', [ + 'label' => $this->translate('Hosts'), + 'required' => true, + 'size' => 8, + 'style' => 'width: 25em', + 'multiOptions' => $this->enumHostListByFilter($filter), + 'description' => $this->translate( + 'Hosts that should be part of this business process node' + ), + 'validators' => [[new NoDuplicateChildrenValidator($this, $this->bp, $this->parent), true]] + ]); + } + + protected function addFilteredServicesElement($filter) + { + $this->addElement('submit', 'refresh', [ + 'label' => $this->translate('Refresh'), + 'class' => 'refresh-filter' + ]); + $this->addElement('multiselect', 'children', [ + 'label' => $this->translate('Services'), + 'required' => true, + 'size' => 8, + 'style' => 'width: 25em', + 'multiOptions' => $this->enumServiceListByFilter($filter), + 'description' => $this->translate( + 'Services that should be part of this business process node' + ), + 'validators' => [[new NoDuplicateChildrenValidator($this, $this->bp, $this->parent), true]] + ]); + } + + protected function addFilterElement() + { + $this->addElement('text', 'filter', array( + 'label' => $this->translate('Filter'), + 'required' => true, + 'ignore' => true + )); + } + + protected function addFileElement() + { + $this->addElement('select', 'file', [ + 'label' => $this->translate('File'), + 'required' => true, + 'ignore' => true, + 'value' => $this->bp->getName(), + 'class' => 'autosubmit', + 'multiOptions' => $this->optionalEnum($this->enumConfigs()), + 'description' => $this->translate( + 'Choose a different configuration file to import its processes' + ) + ]); + } + + protected function addHostOverrideCheckbox() + { + $this->addElement('checkbox', 'host_override', [ + 'ignore' => true, + 'class' => 'autosubmit', + 'label' => $this->translate('Override Host State'), + 'description' => $this->translate('Enable host state overrides') + ]); + } + + protected function addHostOverrideElement() + { + $this->addElement('stateOverrides', 'stateOverrides', [ + 'required' => true, + 'label' => $this->translate('State Overrides'), + 'states' => $this->enumHostStateList() + ]); + } + + 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 addServiceOverrideElement() + { + $this->addElement('stateOverrides', 'stateOverrides', [ + 'required' => true, + 'label' => $this->translate('State Overrides'), + 'states' => $this->enumServiceStateList() + ]); + } + + protected function selectHostsFromFilter() + { + $this->addFilterElement(); + if ($filter = $this->getSentValue('filter')) { + $this->addFilteredHostsElement($filter); + } else { + $this->setSubmitLabel($this->translate('Next')); + } + } + + protected function selectServicesFromFilter() + { + $this->addFilterElement(); + if ($filter = $this->getSentValue('filter')) { + $this->addFilteredServicesElement($filter); + } else { + $this->setSubmitLabel($this->translate('Next')); + } + } + + protected function selectProcess() + { + if ($this->hasParentNode()) { + $this->addFileElement(); + } + + if (($file = $this->getSentValue('file')) || !$this->hasParentNode()) { + $this->addElement('multiselect', 'children', [ + 'label' => $this->translate('Process nodes'), + 'required' => true, + 'size' => 8, + 'style' => 'width: 25em', + 'multiOptions' => $this->enumProcesses($file), + 'description' => $this->translate( + 'Other processes that should be part of this business process node' + ), + 'validators' => [[new NoDuplicateChildrenValidator($this, $this->bp, $this->parent), true]] + ]); + } else { + $this->setSubmitLabel($this->translate('Next')); + } + } + + /** + * @param MonitoringBackend|IcingaDbConnection $backend + * @return $this + */ + public function setBackend($backend) + { + $this->backend = $backend; + return $this; + } + + /** + * @param Storage $storage + * @return $this + */ + public function setStorage(Storage $storage) + { + $this->storage = $storage; + return $this; + } + + /** + * @param BpConfig $process + * @return $this + */ + public function setProcess(BpConfig $process) + { + $this->bp = $process; + $this->setBackend($process->getBackend()); + return $this; + } + + /** + * @param BpNode|null $node + * @return $this + */ + public function setParentNode(BpNode $node = null) + { + $this->parent = $node; + return $this; + } + + /** + * @return bool + */ + public function hasParentNode() + { + return $this->parent !== null; + } + + /** + * @param SessionNamespace $session + * @return $this + */ + public function setSession(SessionNamespace $session) + { + $this->session = $session; + return $this; + } + + protected function hasProcesses() + { + return count($this->enumProcesses()) > 0; + } + + /** + * @param string $file + * @return array + */ + protected function enumProcesses($file = null) + { + $list = array(); + + $parents = array(); + + $differentFile = $file !== null && $file !== $this->bp->getName(); + + if (! $differentFile && $this->hasParentNode()) { + $this->collectAllParents($this->parent, $parents); + $parents[$this->parent->getName()] = $this->parent; + } + + $bp = $this->bp; + if ($differentFile) { + $bp = $this->storage->loadProcess($file); + } + + foreach ($bp->getNodes() as $node) { + if (! $node instanceof ImportedNode && $node instanceof BpNode && ! isset($parents[$node->getName()])) { + $name = $node->getName(); + if ($differentFile) { + $name = '@' . $file . ':' . $name; + } + + $list[$name] = $node->getName(); // display name? + } + } + + if (! $this->bp->getMetadata()->isManuallyOrdered()) { + natcasesort($list); + } + return $list; + } + + protected function hasMoreConfigs() + { + $configs = $this->enumConfigs(); + return !empty($configs); + } + + protected function enumConfigs() + { + return $this->storage->listProcesses(); + } + + /** + * Collect the given node's parents recursively into the given array by their names + * + * @param BpNode $node + * @param BpNode[] $parents + */ + protected function collectAllParents(BpNode $node, array &$parents) + { + foreach ($node->getParents() as $parent) { + $parents[$parent->getName()] = $parent; + $this->collectAllParents($parent, $parents); + } + } + + public function onSuccess() + { + $changes = ProcessChanges::construct($this->bp, $this->session); + switch ($this->getValue('node_type')) { + case 'host': + case 'service': + $stateOverrides = $this->getValue('stateOverrides'); + if (! empty($stateOverrides)) { + $childOverrides = []; + foreach ($this->getValue('children') as $service) { + $childOverrides[$service] = $stateOverrides; + } + + $changes->modifyNode($this->parent, [ + 'stateOverrides' => array_merge($this->parent->getStateOverrides(), $childOverrides) + ]); + } + + // Fallthrough + case 'process': + case 'hosts_from_filter': + case 'services_from_filter': + if ($this->hasParentNode()) { + $changes->addChildrenToNode($this->getValue('children'), $this->parent); + } else { + foreach ($this->getValue('children') as $nodeName) { + $changes->copyNode($nodeName); + } + } + + break; + case 'new-process': + $properties = $this->getValues(); + unset($properties['name']); + if ($this->hasParentNode()) { + $properties['parentName'] = $this->parent->getName(); + } + $changes->createNode($this->getValue('name'), $properties); + break; + } + + // Trigger session destruction to make sure it get's stored. + // TODO: figure out why this is necessary, might be an unclean shutdown on redirect + unset($changes); + + parent::onSuccess(); + } +} diff --git a/application/forms/BpConfigForm.php b/application/forms/BpConfigForm.php new file mode 100644 index 0000000..fc19160 --- /dev/null +++ b/application/forms/BpConfigForm.php @@ -0,0 +1,222 @@ +<?php + +namespace Icinga\Module\Businessprocess\Forms; + +use Icinga\Authentication\Auth; +use Icinga\Module\Businessprocess\BpConfig; +use Icinga\Module\Businessprocess\Web\Form\BpConfigBaseForm; + +class BpConfigForm extends BpConfigBaseForm +{ + protected $deleteButtonName; + + public function setup() + { + $this->addElement('text', 'name', array( + 'label' => $this->translate('ID'), + 'required' => true, + 'validators' => array( + array( + 'validator' => 'StringLength', + 'options' => array( + 'min' => 2, + 'max' => 40 + ) + ), + array( + 'validator' => 'Regex', + 'options' => array( + 'pattern' => '/^[a-zA-Z0-9](?:[a-zA-Z0-9 ._-]*)?[a-zA-Z0-9_]$/' + ) + ) + ), + 'description' => $this->translate( + 'This is the unique identifier of this process' + ), + )); + + $this->addElement('text', 'Title', array( + 'label' => $this->translate('Display Name'), + 'description' => $this->translate( + 'Usually this name will be shown for this process. Equals ID' + . ' if not given' + ), + )); + + $this->addElement('textarea', 'Description', array( + 'label' => $this->translate('Description'), + 'description' => $this->translate( + 'A slightly more detailed description for this process, about 100-150 characters long' + ), + 'rows' => 4, + )); + + if (! empty($this->listAvailableBackends())) { + $this->addElement('select', 'Backend', array( + 'label' => $this->translate('Backend'), + 'description' => $this->translate( + 'Icinga Web Monitoring Backend where current object states for' + . ' this process should be retrieved from' + ), + 'multiOptions' => array( + null => $this->translate('Use the configured default backend'), + ) + $this->listAvailableBackends() + )); + } + + $this->addElement('select', 'Statetype', array( + 'label' => $this->translate('State Type'), + 'required' => true, + 'description' => $this->translate( + 'Whether this process should be based on Icinga hard or soft states' + ), + 'multiOptions' => array( + 'soft' => $this->translate('Use SOFT states'), + 'hard' => $this->translate('Use HARD states'), + ) + )); + + $this->addElement('select', 'AddToMenu', array( + 'label' => $this->translate('Add to menu'), + 'required' => true, + 'description' => $this->translate( + 'Whether this process should be linked in the main Icinga Web 2 menu' + ), + 'multiOptions' => array( + 'yes' => $this->translate('Yes'), + 'no' => $this->translate('No'), + ) + )); + + $this->addElement('text', 'AllowedUsers', array( + 'label' => $this->translate('Allowed Users'), + 'description' => $this->translate( + 'Allowed Users (comma-separated)' + ), + )); + + $this->addElement('text', 'AllowedGroups', array( + 'label' => $this->translate('Allowed Groups'), + 'description' => $this->translate( + 'Allowed Groups (comma-separated)' + ), + )); + + $this->addElement('text', 'AllowedRoles', array( + 'label' => $this->translate('Allowed Roles'), + 'description' => $this->translate( + 'Allowed Roles (comma-separated)' + ), + )); + + if ($this->config === null) { + $this->setSubmitLabel( + $this->translate('Add') + ); + } else { + $config = $this->config; + + $meta = $config->getMetadata(); + foreach ($meta->getProperties() as $k => $v) { + if ($el = $this->getElement($k)) { + $el->setValue($v); + } + } + $this->getElement('name') + ->setValue($config->getName()) + ->setAttrib('readonly', true); + + $this->setSubmitLabel( + $this->translate('Store') + ); + + $label = $this->translate('Delete'); + $el = $this->createElement('submit', $label, array( + 'data-base-target' => '_main' + ))->setLabel($label)->setDecorators(array('ViewHelper')); + $this->deleteButtonName = $el->getName(); + $this->addElement($el); + } + } + + protected function onSetup() + { + $this->getElement($this->getSubmitLabel())->setAttrib('data-base-target', '_main'); + } + + protected function onRequest() + { + $name = $this->getValue('name'); + + if ($this->shouldBeDeleted()) { + $this->config->clearAppliedChanges(); + $this->storage->deleteProcess($name); + $this->setSuccessUrl('businessprocess'); + $this->redirectOnSuccess(sprintf('Process %s has been deleted', $name)); + } + } + + public function onSuccess() + { + $name = $this->getValue('name'); + + if ($this->config === null) { + if ($this->storage->hasProcess($name)) { + $this->addError(sprintf( + $this->translate('A process named "%s" already exists'), + $name + )); + + return; + } + + // New config + $config = new BpConfig(); + $config->setName($name); + + if (! $this->prepareMetadata($config)) { + return; + } + + $this->setSuccessUrl( + $this->getSuccessUrl()->setParams( + array('config' => $name, 'unlocked' => true) + ) + ); + $this->setSuccessMessage(sprintf('Process %s has been created', $name)); + } else { + $config = $this->config; + $this->setSuccessMessage(sprintf('Process %s has been stored', $name)); + } + $meta = $config->getMetadata(); + foreach ($this->getValues() as $key => $value) { + if (! in_array($key, ['Title', 'Description', 'Backend'], true) + && ($value === null || $value === '')) { + continue; + } + + if ($meta->hasKey($key)) { + $meta->set($key, $value); + } + } + + $this->storage->storeProcess($config); + $config->clearAppliedChanges(); + parent::onSuccess(); + } + + public function hasDeleteButton() + { + return $this->deleteButtonName !== null; + } + + public function shouldBeDeleted() + { + if (! $this->hasDeleteButton()) { + return false; + } + + $name = $this->deleteButtonName; + return $this->getSentValue($name) === $this->getElement($name)->getLabel(); + } +} diff --git a/application/forms/BpUploadForm.php b/application/forms/BpUploadForm.php new file mode 100644 index 0000000..5d97860 --- /dev/null +++ b/application/forms/BpUploadForm.php @@ -0,0 +1,202 @@ +<?php + +namespace Icinga\Module\Businessprocess\Forms; + +use Exception; +use Icinga\Module\Businessprocess\BpConfig; +use Icinga\Module\Businessprocess\Storage\LegacyConfigParser; +use Icinga\Module\Businessprocess\Web\Form\BpConfigBaseForm; +use Icinga\Web\Notification; + +class BpUploadForm extends BpConfigBaseForm +{ + protected $backend; + + protected $node; + + protected $objectList = array(); + + protected $processList = array(); + + protected $deleteButtonName; + + private $sourceCode; + + /** @var BpConfig */ + private $uploadedConfig; + + public function setup() + { + $this->showUpload(); + if ($this->hasSource()) { + $this->showDetails(); + } + } + + protected function showDetails() + { + $this->addElement('text', 'name', array( + 'label' => $this->translate('Name'), + 'required' => true, + 'description' => $this->translate( + 'This is the unique identifier of this process' + ), + 'validators' => array( + array( + 'validator' => 'StringLength', + 'options' => array( + 'min' => 2, + 'max' => 40 + ) + ), + array( + 'validator' => 'Regex', + 'options' => array( + 'pattern' => '/^[a-zA-Z0-9](?:[a-zA-Z0-9 ._-]*)?[a-zA-Z0-9_]$/' + ) + ) + ), + )); + + $this->addElement('textarea', 'source', array( + 'label' => $this->translate('Source'), + 'description' => $this->translate( + 'Business process source code' + ), + 'value' => $this->sourceCode, + 'class' => 'preformatted smaller', + 'rows' => 7, + )); + + $this->getUploadedConfig(); + + $this->setSubmitLabel( + $this->translate('Store') + ); + } + + public function getUploadedConfig() + { + if ($this->uploadedConfig === null) { + $this->uploadedConfig = $this->parseSubmittedSourceCode(); + } + + return $this->uploadedConfig; + } + + protected function parseSubmittedSourceCode() + { + $code = $this->getSentValue('source'); + $name = $this->getSentValue('name', '<new config>'); + if (empty($code)) { + $code = $this->sourceCode; + } + + try { + $config = LegacyConfigParser::parseString($name, $code); + + if ($config->hasErrors()) { + foreach ($config->getErrors() as $error) { + $this->addError($error); + } + } + } catch (Exception $e) { + $this->addError($e->getMessage()); + return null; + } + + return $config; + } + + protected function hasSource() + { + if ($this->hasBeenSent() && $source = $this->getSentValue('source')) { + $this->sourceCode = $source; + } else { + $this->processUploadedSource(); + } + + if (empty($this->sourceCode)) { + return false; + } else { + $this->removeElement('uploaded_file'); + return true; + } + } + + protected function showUpload() + { + $this->setAttrib('enctype', 'multipart/form-data'); + + $this->addElement('file', 'uploaded_file', array( + 'label' => $this->translate('File'), + 'destination' => $this->getTempDir(), + 'required' => true, + )); + + /** @var \Zend_Form_Element_File $el */ + $el = $this->getElement('uploaded_file'); + $el->setValueDisabled(true); + + $this->setSubmitLabel( + $this->translate('Next') + ); + } + + protected function getTempDir() + { + return sys_get_temp_dir(); + } + + protected function processUploadedSource() + { + /** @var \Zend_Form_Element_File $el */ + $el = $this->getElement('uploaded_file'); + + if ($el && $this->hasBeenSent()) { + $tmpdir = $this->getTempDir(); + $tmpfile = tempnam($tmpdir, 'bpupload_'); + + // TODO: race condition, try to do this without unlinking here + unlink($tmpfile); + + $el->addFilter('Rename', $tmpfile); + if ($el->receive()) { + $this->sourceCode = file_get_contents($tmpfile); + unlink($tmpfile); + } else { + foreach ($el->file->getMessages() as $error) { + $this->addError($error); + } + } + } + + return $this; + } + + public function onSuccess() + { + $config = $this->getUploadedConfig(); + $name = $config->getName(); + + if ($this->storage->hasProcess($name)) { + $this->addError(sprintf( + $this->translate('A process named "%s" already exists'), + $name + )); + + return; + } + + if (! $this->prepareMetadata($config)) { + return; + } + + $this->storage->storeProcess($config); + Notification::success(sprintf('Process %s has been stored', $name)); + + $this->getSuccessUrl()->setParam('config', $name); + + parent::onSuccess(); + } +} diff --git a/application/forms/DeleteNodeForm.php b/application/forms/DeleteNodeForm.php new file mode 100644 index 0000000..dada9d3 --- /dev/null +++ b/application/forms/DeleteNodeForm.php @@ -0,0 +1,165 @@ +<?php + +namespace Icinga\Module\Businessprocess\Forms; + +use Icinga\Module\Businessprocess\BpNode; +use Icinga\Module\Businessprocess\BpConfig; +use Icinga\Module\Businessprocess\Modification\ProcessChanges; +use Icinga\Module\Businessprocess\Node; +use Icinga\Module\Businessprocess\Web\Form\QuickForm; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; +use Icinga\Web\Session\SessionNamespace; +use ipl\Sql\Connection as IcingaDbConnection; + +class DeleteNodeForm extends QuickForm +{ + /** @var MonitoringBackend|IcingaDbConnection */ + protected $backend; + + /** @var BpConfig */ + protected $bp; + + /** @var Node */ + protected $node; + + /** @var BpNode */ + protected $parentNode; + + /** @var SessionNamespace */ + protected $session; + + public function setup() + { + $node = $this->node; + $view = $this->getView(); + $this->addHtml( + '<h2>' . $view->escape( + sprintf($this->translate('Delete "%s"'), $node->getAlias()) + ) . '</h2>' + ); + + $biLink = $view->qlink( + $node->getAlias(), + 'businessprocess/node/impact', + array('name' => $node->getName()), + array('data-base-target' => '_next') + ); + $this->addHtml( + '<p>' . sprintf( + $view->escape( + $this->translate('Unsure? Show business impact of "%s"') + ), + $biLink + ) . '</p>' + ); + + if ($this->parentNode) { + $yesMsg = sprintf( + $this->translate('Delete from %s'), + $this->parentNode->getAlias() + ); + } else { + $yesMsg = sprintf( + $this->translate('Delete root node "%s"'), + $this->node->getAlias() + ); + } + + $this->addElement('select', 'confirm', array( + 'label' => $this->translate('Are you sure?'), + 'required' => true, + 'description' => $this->translate( + 'Do you really want to delete this node?' + ), + 'multiOptions' => $this->optionalEnum(array( + 'no' => $this->translate('No'), + 'yes' => $yesMsg, + 'all' => sprintf($this->translate('Delete all occurrences of %s'), $node->getAlias()), + )) + )); + } + + /** + * @param MonitoringBackend|IcingaDbConnection $backend + * @return $this + */ + public function setBackend($backend) + { + $this->backend = $backend; + return $this; + } + + /** + * @param BpConfig $process + * @return $this + */ + public function setProcess(BpConfig $process) + { + $this->bp = $process; + $this->setBackend($process->getBackend()); + 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() + { + $changes = ProcessChanges::construct($this->bp, $this->session); + + $confirm = $this->getValue('confirm'); + switch ($confirm) { + case 'yes': + $changes->deleteNode($this->node, $this->parentNode === null ? null : $this->parentNode->getName()); + break; + case 'all': + $changes->deleteNode($this->node); + break; + case 'no': + $this->setSuccessMessage($this->translate('Well, maybe next time')); + } + + switch ($confirm) { + case 'yes': + case 'all': + if ($this->successUrl === null) { + $this->successUrl = clone $this->getRequest()->getUrl(); + } + + $this->successUrl->getParams()->remove(array('action', 'deletenode')); + } + + // Trigger session desctruction to make sure it get's stored. + // TODO: figure out why this is necessary, might be an unclean shutdown on redirect + unset($changes); + + parent::onSuccess(); + } +} diff --git a/application/forms/EditNodeForm.php b/application/forms/EditNodeForm.php new file mode 100644 index 0000000..eceb065 --- /dev/null +++ b/application/forms/EditNodeForm.php @@ -0,0 +1,460 @@ +<?php + +namespace Icinga\Module\Businessprocess\Forms; + +use Icinga\Module\Businessprocess\BpNode; +use Icinga\Module\Businessprocess\BpConfig; +use Icinga\Module\Businessprocess\Common\EnumList; +use Icinga\Module\Businessprocess\Modification\ProcessChanges; +use Icinga\Module\Businessprocess\Node; +use Icinga\Module\Businessprocess\Web\Form\QuickForm; +use Icinga\Module\Businessprocess\Web\Form\Validator\NoDuplicateChildrenValidator; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; +use Icinga\Web\Session\SessionNamespace; +use ipl\Sql\Connection as IcingaDbConnection; + +class EditNodeForm extends QuickForm +{ + use EnumList; + + /** @var MonitoringBackend|IcingaDbConnection */ + protected $backend; + + /** @var BpConfig */ + protected $bp; + + /** @var Node */ + protected $node; + + /** @var BpNode */ + protected $parent; + + protected $objectList = array(); + + protected $processList = array(); + + protected $service; + + protected $host; + + /** @var SessionNamespace */ + protected $session; + + public function setup() + { + $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); + } + + $view = $this->getView(); + $this->addHtml( + '<h2>' . $view->escape( + sprintf($this->translate('Modify "%s"'), $this->getNode()->getAlias()) + ) . '</h2>' + ); + + $monitoredNodeType = null; + if ($this->isService()) { + $monitoredNodeType = 'service'; + } else { + $monitoredNodeType = 'host'; + } + + $type = $this->selectNodeType($monitoredNodeType); + switch ($type) { + case 'host': + $this->selectHost(); + break; + case 'service': + $this->selectService(); + break; + case 'process': + $this->selectProcess(); + break; + case 'new-process': + $this->addNewProcess(); + break; + case null: + $this->setSubmitLabel($this->translate('Next')); + return; + } + } + + protected function isService() + { + if (strpos($this->getNode()->getName(), ';Hoststatus')) { + return false; + } + return true; + } + + protected function addNewProcess() + { + $this->addElement('text', 'name', array( + 'label' => $this->translate('ID'), + 'required' => true, + 'disabled' => true, + 'description' => $this->translate( + 'This is the unique identifier of this process' + ), + )); + + $this->addElement('text', 'alias', array( + 'label' => $this->translate('Display Name'), + 'description' => $this->translate( + 'Usually this name will be shown for this node. Equals ID' + . ' if not given' + ), + )); + + $this->addElement('select', 'operator', array( + 'label' => $this->translate('Operator'), + 'required' => true, + 'multiOptions' => array( + '&' => $this->translate('AND'), + '|' => $this->translate('OR'), + '!' => $this->translate('NOT'), + '%' => $this->translate('DEGRADED'), + '1' => $this->translate('MIN 1'), + '2' => $this->translate('MIN 2'), + '3' => $this->translate('MIN 3'), + '4' => $this->translate('MIN 4'), + '5' => $this->translate('MIN 5'), + '6' => $this->translate('MIN 6'), + '7' => $this->translate('MIN 7'), + '8' => $this->translate('MIN 8'), + '9' => $this->translate('MIN 9'), + ) + )); + + $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' => $display, + 'multiOptions' => array( + "$display" => $this->translate('Toplevel Process'), + '0' => $this->translate('Subprocess only'), + ) + )); + + $this->addElement('text', 'infoUrl', array( + 'label' => $this->translate('Info URL'), + 'description' => $this->translate( + 'URL pointing to more information about this node' + ) + )); + } + + /** + * @return string|null + */ + protected function selectNodeType($monitoredNodeType = null) + { + if ($this->hasParentNode()) { + $this->addElement('hidden', 'node_type', [ + 'disabled' => true, + 'decorators' => ['ViewHelper'], + 'value' => $monitoredNodeType + ]); + + return $monitoredNodeType; + } elseif (! $this->hasProcesses()) { + $this->addElement('hidden', 'node_type', array( + 'ignore' => true, + 'decorators' => array('ViewHelper'), + 'value' => 'new-process' + )); + + return 'new-process'; + } + } + + protected function selectHost() + { + $this->addElement('select', 'children', array( + 'required' => true, + 'value' => $this->getNode()->getName(), + 'multiOptions' => $this->enumHostList(), + 'label' => $this->translate('Host'), + 'description' => $this->translate('The host for this business process node'), + 'validators' => [[new NoDuplicateChildrenValidator($this, $this->bp, $this->parent), true]] + )); + + $this->addHostOverrideCheckbox(); + $hostOverrideSent = $this->getSentValue('host_override'); + if ($hostOverrideSent === '1' + || ($hostOverrideSent === null && $this->getElement('host_override')->isChecked()) + ) { + $this->addHostOverrideElement(); + } + } + + protected function selectService() + { + $this->addHostElement(); + + if ($this->getSentValue('hosts') === null) { + $this->addServicesElement($this->host); + $this->addServiceOverrideCheckbox(); + if ($this->getElement('service_override')->isChecked() || $this->getSentValue('service_override') === '1') { + $this->addServiceOverrideElement(); + } + } elseif ($host = $this->getSentValue('hosts')) { + $this->addServicesElement($host); + $this->addServiceOverrideCheckbox(); + if ($this->getSentValue('service_override') === '1') { + $this->addServiceOverrideElement(); + } + } else { + $this->setSubmitLabel($this->translate('Next')); + } + } + + protected function addHostElement() + { + $this->addElement('select', 'hosts', array( + 'label' => $this->translate('Host'), + 'required' => true, + 'ignore' => true, + 'class' => 'autosubmit', + 'multiOptions' => $this->optionalEnum($this->enumHostForServiceList()), + )); + + $this->getElement('hosts')->setValue($this->host); + } + + protected function addHostOverrideCheckbox() + { + $this->addElement('checkbox', 'host_override', [ + 'ignore' => true, + 'class' => 'autosubmit', + 'value' => ! empty($this->parent->getStateOverrides($this->node->getName())), + 'label' => $this->translate('Override Host State'), + 'description' => $this->translate('Enable host state overrides') + ]); + } + + protected function addHostOverrideElement() + { + $this->addElement('stateOverrides', 'stateOverrides', [ + 'required' => true, + 'states' => $this->enumHostStateList(), + 'value' => $this->parent->getStateOverrides($this->node->getName()), + 'label' => $this->translate('State Overrides') + ]); + } + + protected function addServicesElement($host) + { + $this->addElement('select', 'children', array( + 'required' => true, + 'value' => $this->getNode()->getName(), + '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]] + )); + } + + protected function addServiceOverrideCheckbox() + { + $this->addElement('checkbox', 'service_override', [ + 'ignore' => true, + 'class' => 'autosubmit', + 'value' => ! empty($this->parent->getStateOverrides($this->node->getName())), + 'label' => $this->translate('Override Service State'), + 'description' => $this->translate('Enable service state overrides') + ]); + } + + protected function addServiceOverrideElement() + { + $this->addElement('stateOverrides', 'stateOverrides', [ + 'required' => true, + 'states' => $this->enumServiceStateList(), + 'value' => $this->parent->getStateOverrides($this->node->getName()), + 'label' => $this->translate('State Overrides') + ]); + } + + protected function selectProcess() + { + $this->addElement('multiselect', 'children', array( + 'label' => $this->translate('Process nodes'), + 'required' => true, + 'size' => 8, + 'style' => 'width: 25em', + 'multiOptions' => $this->enumProcesses(), + 'description' => $this->translate( + 'Other processes that should be part of this business process node' + ) + )); + } + + /** + * @param MonitoringBackend|IcingaDbConnection $backend + * @return $this + */ + public function setBackend($backend) + { + $this->backend = $backend; + return $this; + } + + /** + * @param BpConfig $process + * @return $this + */ + public function setProcess(BpConfig $process) + { + $this->bp = $process; + $this->setBackend($process->getBackend()); + return $this; + } + + /** + * @param BpNode|null $node + * @return $this + */ + public function setParentNode(BpNode $node = null) + { + $this->parent = $node; + return $this; + } + + /** + * @return bool + */ + public function hasParentNode() + { + return $this->parent !== null; + } + + /** + * @param SessionNamespace $session + * @return $this + */ + public function setSession(SessionNamespace $session) + { + $this->session = $session; + return $this; + } + + protected function hasProcesses() + { + return count($this->enumProcesses()) > 0; + } + + protected function enumProcesses() + { + $list = array(); + + $parents = array(); + + if ($this->hasParentNode()) { + $this->collectAllParents($this->parent, $parents); + $parents[$this->parent->getName()] = $this->parent; + } + + foreach ($this->bp->getNodes() as $node) { + if ($node instanceof BpNode && ! isset($parents[$node->getName()])) { + $list[$node->getName()] = $node->getName(); // display name? + } + } + + if (! $this->bp->getMetadata()->isManuallyOrdered()) { + natcasesort($list); + } + return $list; + } + + /** + * Collect the given node's parents recursively into the given array by their names + * + * @param BpNode $node + * @param BpNode[] $parents + */ + protected function collectAllParents(BpNode $node, array &$parents) + { + foreach ($node->getParents() as $parent) { + $parents[$parent->getName()] = $parent; + $this->collectAllParents($parent, $parents); + } + } + + /** + * @param Node $node + * @return $this + */ + public function setNode(Node $node) + { + $this->node = $node; + return $this; + } + + public function getNode() + { + return $this->node; + } + + public function onSuccess() + { + $changes = ProcessChanges::construct($this->bp, $this->session); + + $changes->deleteNode($this->node, $this->parent->getName()); + + switch ($this->getValue('node_type')) { + case 'host': + case 'service': + $stateOverrides = $this->getValue('stateOverrides') ?: []; + if (! empty($stateOverrides)) { + $stateOverrides = array_merge( + $this->parent->getStateOverrides(), + [$this->getValue('children') => $stateOverrides] + ); + } else { + $stateOverrides = $this->parent->getStateOverrides(); + unset($stateOverrides[$this->getValue('children')]); + } + + $changes->modifyNode($this->parent, ['stateOverrides' => $stateOverrides]); + // Fallthrough + case 'process': + $changes->addChildrenToNode($this->getValue('children'), $this->parent); + break; + case 'new-process': + $properties = $this->getValues(); + unset($properties['name']); + if ($this->hasParentNode()) { + $properties['parentName'] = $this->parent->getName(); + } + $changes->createNode($this->getValue('name'), $properties); + break; + } + + // Trigger session destruction to make sure it get's stored. + // TODO: figure out why this is necessary, might be an unclean shutdown on redirect + unset($changes); + + parent::onSuccess(); + } + + public function isValid($data) + { + // Don't allow to override disabled elements. This is probably too harsh + // but also wouldn't be necessary if this would be a Icinga\Web\Form... + foreach ($this->getElements() as $element) { + /** @var \Zend_Form_Element $element */ + if ($element->getAttrib('disabled')) { + $data[$element->getName()] = $element->getValue(); + } + } + + return parent::isValid($data); + } +} diff --git a/application/forms/MoveNodeForm.php b/application/forms/MoveNodeForm.php new file mode 100644 index 0000000..8e77f87 --- /dev/null +++ b/application/forms/MoveNodeForm.php @@ -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 + } +} diff --git a/application/forms/ProcessForm.php b/application/forms/ProcessForm.php new file mode 100644 index 0000000..be1abbf --- /dev/null +++ b/application/forms/ProcessForm.php @@ -0,0 +1,215 @@ +<?php + +namespace Icinga\Module\Businessprocess\Forms; + +use Icinga\Module\Businessprocess\BpNode; +use Icinga\Module\Businessprocess\BpConfig; +use Icinga\Module\Businessprocess\Modification\ProcessChanges; +use Icinga\Module\Businessprocess\Web\Form\QuickForm; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; +use Icinga\Web\Notification; +use Icinga\Web\Session\SessionNamespace; +use ipl\Sql\Connection as IcingaDbConnection; + +class ProcessForm extends QuickForm +{ + /** @var MonitoringBackend|IcingaDbConnection */ + protected $backend; + + /** @var BpConfig */ + protected $bp; + + /** @var BpNode */ + protected $node; + + protected $objectList = array(); + + protected $processList = array(); + + /** @var SessionNamespace */ + protected $session; + + public function setup() + { + if ($this->node === null) { + $this->addElement('text', 'name', array( + 'label' => $this->translate('ID'), + 'required' => true, + 'description' => $this->translate( + 'This is the unique identifier of this process' + ), + )); + } else { + $this->addHtml( + '<h2>' . $this->getView()->escape( + sprintf($this->translate('Modify "%s"'), $this->node->getAlias()) + ) . '</h2>' + ); + } + + $this->addElement('text', 'alias', array( + 'label' => $this->translate('Display Name'), + 'description' => $this->translate( + 'Usually this name will be shown for this node. Equals ID' + . ' if not given' + ), + )); + + $this->addElement('select', 'operator', array( + 'label' => $this->translate('Operator'), + 'required' => true, + 'multiOptions' => array( + '&' => $this->translate('AND'), + '|' => $this->translate('OR'), + '!' => $this->translate('NOT'), + '%' => $this->translate('DEGRADED'), + '1' => $this->translate('MIN 1'), + '2' => $this->translate('MIN 2'), + '3' => $this->translate('MIN 3'), + '4' => $this->translate('MIN 4'), + '5' => $this->translate('MIN 5'), + '6' => $this->translate('MIN 6'), + '7' => $this->translate('MIN 7'), + '8' => $this->translate('MIN 8'), + '9' => $this->translate('MIN 9'), + ) + )); + + 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' + ), + 'multiOptions' => array( + "$display" => $this->translate('Toplevel Process'), + '0' => $this->translate('Subprocess only'), + ) + )); + + $this->addElement('text', 'url', array( + 'label' => $this->translate('Info URL'), + 'description' => $this->translate( + 'URL pointing to more information about this node' + ) + )); + + if ($node = $this->node) { + if ($node->hasAlias()) { + $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()); + } + } + } + + /** + * @param MonitoringBackend|IcingaDbConnection $backend + * @return $this + */ + public function setBackend($backend) + { + $this->backend = $backend; + return $this; + } + + /** + * @param BpConfig $process + * @return $this + */ + public function setProcess(BpConfig $process) + { + $this->bp = $process; + $this->setBackend($process->getBackend()); + return $this; + } + + /** + * @param BpNode $node + * @return $this + */ + public function setNode(BpNode $node) + { + $this->node = $node; + return $this; + } + + /** + * @param SessionNamespace $session + * @return $this + */ + public function setSession(SessionNamespace $session) + { + $this->session = $session; + return $this; + } + + public function onSuccess() + { + $changes = ProcessChanges::construct($this->bp, $this->session); + + $modifications = array(); + $alias = $this->getValue('alias'); + $operator = $this->getValue('operator'); + $display = $this->getValue('display'); + $url = $this->getValue('url'); + if (empty($url)) { + $url = null; + } + if (empty($alias)) { + $alias = null; + } + // TODO: rename + + if ($node = $this->node) { + if ($display !== $node->getDisplay()) { + $modifications['display'] = $display; + } + if ($operator !== $node->getOperator()) { + $modifications['operator'] = $operator; + } + if ($url !== $node->getInfoUrl()) { + $modifications['infoUrl'] = $url; + } + if ($alias !== $node->getAlias()) { + $modifications['alias'] = $alias; + } + } else { + $modifications = array( + 'display' => $display, + 'operator' => $operator, + 'infoUrl' => $url, + 'alias' => $alias, + ); + } + + if (! empty($modifications)) { + if ($this->node === null) { + $changes->createNode($this->getValue('name'), $modifications); + } else { + $changes->modifyNode($this->node, $modifications); + } + + Notification::success( + sprintf( + 'Process %s has been modified', + $this->bp->getName() + ) + ); + } + + // Trigger session destruction to make sure it get's stored. + // TODO: figure out why this is necessary, might be an unclean shutdown on redirect + unset($changes); + + parent::onSuccess(); + } +} diff --git a/application/forms/SimulationForm.php b/application/forms/SimulationForm.php new file mode 100644 index 0000000..263976b --- /dev/null +++ b/application/forms/SimulationForm.php @@ -0,0 +1,136 @@ +<?php + +namespace Icinga\Module\Businessprocess\Forms; + +use Icinga\Module\Businessprocess\MonitoredNode; +use Icinga\Module\Businessprocess\Simulation; +use Icinga\Module\Businessprocess\Web\Form\QuickForm; + +class SimulationForm extends QuickForm +{ + /** @var MonitoredNode */ + protected $node; + + /** @var MonitoredNode */ + protected $simulatedNode; + + /** @var Simulation */ + protected $simulation; + + public function setup() + { + $states = $this->enumStateNames(); + + // TODO: Fetch state from object + if ($this->simulatedNode) { + $simulatedState = $this->simulatedNode->getState(); + $states[$simulatedState] = sprintf( + '%s (%s)', + $this->node->getStateName($simulatedState), + $this->translate('Current simulation') + ); + $node = $this->simulatedNode; + $hasSimulation = true; + } else { + $hasSimulation = false; + $node = $this->node; + } + + $view = $this->getView(); + if ($hasSimulation) { + $title = $this->translate('Modify simulation for %s'); + } else { + $title = $this->translate('Add simulation for %s'); + } + $this->addHtml( + '<h2>' + . $view->escape(sprintf($title, $node->getAlias())) + . '</h2>' + ); + + $this->addElement('select', 'state', array( + 'label' => $this->translate('State'), + 'multiOptions' => $states, + 'class' => 'autosubmit', + 'value' => $this->simulatedNode ? $node->getState() : null, + )); + + $sentState = $this->getSentValue('state'); + if (in_array($sentState, array('0', '99'))) { + return; + } + + if ($hasSimulation || ($sentState !== null && ctype_digit($sentState))) { + $this->addElement('checkbox', 'acknowledged', array( + 'label' => $this->translate('Acknowledged'), + 'value' => $node->isAcknowledged(), + )); + + $this->addElement('checkbox', 'in_downtime', array( + 'label' => $this->translate('In downtime'), + 'value' => $node->isInDowntime(), + )); + } + + $this->setSubmitLabel($this->translate('Apply')); + } + + public function setNode($node) + { + $this->node = $node; + return $this; + } + + public function setSimulation(Simulation $simulation) + { + $this->simulation = $simulation; + + $name = $this->node->getName(); + if ($simulation->hasNode($name)) { + $this->simulatedNode = clone($this->node); + $s = $simulation->getNode($name); + $this->simulatedNode->setState($s->state) + ->setAck($s->acknowledged) + ->setDowntime($s->in_downtime) + ->setMissing(false); + } + + return $this; + } + + public function onSuccess() + { + $nodeName = $this->node->getName(); + $state = $this->getValue('state'); + + if ($state !== null && ctype_digit($state)) { + $this->notifySuccess($this->translate('Simulation has been set')); + $this->simulation->set($nodeName, (object) array( + 'state' => $this->getValue('state'), + 'acknowledged' => $this->getValue('acknowledged'), + 'in_downtime' => $this->getValue('in_downtime'), + )); + } else { + if ($this->simulation->remove($nodeName)) { + $this->notifySuccess($this->translate('Simulation has been removed')); + } + } + + parent::onSuccess(); + } + + /** + * @return array + */ + protected function enumStateNames() + { + $states = array( + null => sprintf( + $this->translate('Use current state (%s)'), + $this->translate($this->node->getStateName()) + ) + ) + $this->node->enumStateNames(); + + return $states; + } +} |