summaryrefslogtreecommitdiffstats
path: root/application/forms
diff options
context:
space:
mode:
Diffstat (limited to 'application/forms')
-rw-r--r--application/forms/AddNodeForm.php579
-rw-r--r--application/forms/BpConfigForm.php222
-rw-r--r--application/forms/BpUploadForm.php202
-rw-r--r--application/forms/DeleteNodeForm.php165
-rw-r--r--application/forms/EditNodeForm.php460
-rw-r--r--application/forms/MoveNodeForm.php185
-rw-r--r--application/forms/ProcessForm.php215
-rw-r--r--application/forms/SimulationForm.php136
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;
+ }
+}