From 0fdee96debfb1915b23a90bc57452cd5b8c2222c Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Aug 2023 10:11:41 +0200 Subject: [PATCH 01/14] IDO: Add custom hoststatus and servicestatus queries For autocompletion with custom variable support, it is required to be able to filter for custom variable values if no customvariable name is known. But since the template how custom variables are joined is on a private property, this many classes are required to circumvent/adjust that. --- .../Query/CustomVarJoinTemplateOverride.php | 84 +++++++++++++++++++ .../Backend/Ido/Query/HostStatusQuery.php | 8 ++ .../Backend/Ido/Query/ServiceStatusQuery.php | 8 ++ .../Monitoring/DataView/HostStatus.php | 16 ++++ .../Monitoring/DataView/ServiceStatus.php | 16 ++++ 5 files changed, 132 insertions(+) create mode 100644 library/Businessprocess/Monitoring/Backend/Ido/Query/CustomVarJoinTemplateOverride.php create mode 100644 library/Businessprocess/Monitoring/Backend/Ido/Query/HostStatusQuery.php create mode 100644 library/Businessprocess/Monitoring/Backend/Ido/Query/ServiceStatusQuery.php create mode 100644 library/Businessprocess/Monitoring/DataView/HostStatus.php create mode 100644 library/Businessprocess/Monitoring/DataView/ServiceStatus.php diff --git a/library/Businessprocess/Monitoring/Backend/Ido/Query/CustomVarJoinTemplateOverride.php b/library/Businessprocess/Monitoring/Backend/Ido/Query/CustomVarJoinTemplateOverride.php new file mode 100644 index 0000000..385ca59 --- /dev/null +++ b/library/Businessprocess/Monitoring/Backend/Ido/Query/CustomVarJoinTemplateOverride.php @@ -0,0 +1,84 @@ +customvarNameToTypeName($customvar); + $alias = ($type === 'host' ? 'hcv_' : 'scv_') . preg_replace('~[^a-zA-Z0-9_]~', '_', $name); + + // We're replacing any problematic char with an underscore, which will lead to duplicates, this avoids them + $from = $this->select->getPart(Zend_Db_Select::FROM); + for ($i = 2; array_key_exists($alias, $from); $i++) { + $alias = $alias . '_' . $i; + } + + $this->customVars[strtolower($customvar)] = $alias; + + if ($type === 'host') { + if ($this instanceof ServicecommentQuery + || $this instanceof ServicedowntimeQuery + || $this instanceof ServicecommenthistoryQuery + || $this instanceof ServicedowntimestarthistoryQuery + || $this instanceof ServiceflappingstarthistoryQuery + || $this instanceof ServicegroupQuery + || $this instanceof ServicenotificationQuery + || $this instanceof ServicestatehistoryQuery + || $this instanceof \Icinga\Module\Monitoring\Backend\Ido\Query\ServicestatusQuery + ) { + $this->requireVirtualTable('services'); + $leftcol = 's.host_object_id'; + } else { + $leftcol = 'ho.object_id'; + if (! $this->hasJoinedTable('ho')) { + $this->requireVirtualTable('hosts'); + } + } + } else { // $type === 'service' + $leftcol = 'so.object_id'; + if (! $this->hasJoinedTable('so')) { + $this->requireVirtualTable('services'); + } + } + + $mapped = $this->getMappedField($leftcol); + if ($mapped !== null) { + $this->requireColumn($leftcol); + $leftcol = $mapped; + } + + $joinOn = sprintf( + $this->customVarsJoinTemplate, + $leftcol, + $alias, + $this->db->quote($name) + ); + + $this->select->joinLeft( + array($alias => $this->prefix . 'customvariablestatus'), + $joinOn, + array() + ); + + return $this; + } +} diff --git a/library/Businessprocess/Monitoring/Backend/Ido/Query/HostStatusQuery.php b/library/Businessprocess/Monitoring/Backend/Ido/Query/HostStatusQuery.php new file mode 100644 index 0000000..e6ea238 --- /dev/null +++ b/library/Businessprocess/Monitoring/Backend/Ido/Query/HostStatusQuery.php @@ -0,0 +1,8 @@ +query = new HostStatusQuery($connection->getResource(), $columns); + } +} diff --git a/library/Businessprocess/Monitoring/DataView/ServiceStatus.php b/library/Businessprocess/Monitoring/DataView/ServiceStatus.php new file mode 100644 index 0000000..f3a9c3c --- /dev/null +++ b/library/Businessprocess/Monitoring/DataView/ServiceStatus.php @@ -0,0 +1,16 @@ +query = new ServiceStatusQuery($connection->getResource(), $columns); + } +} From cb83f800b979169ba3faab020ac5bd74deeeb9de Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Aug 2023 10:26:50 +0200 Subject: [PATCH 02/14] Drop class `AddNodeForm` --- application/controllers/ProcessController.php | 10 +- application/forms/AddNodeForm.php | 513 ------------------ 2 files changed, 1 insertion(+), 522 deletions(-) delete mode 100644 application/forms/AddNodeForm.php diff --git a/application/controllers/ProcessController.php b/application/controllers/ProcessController.php index 0cfced2..4a6495e 100644 --- a/application/controllers/ProcessController.php +++ b/application/controllers/ProcessController.php @@ -268,15 +268,7 @@ class ProcessController extends Controller $canEdit = $bp->getMetadata()->canModify(); - if ($action === 'add' && $canEdit) { - $form = $this->loadForm('AddNode') - ->setSuccessUrl(Url::fromRequest()->without('action')) - ->setStorage($this->storage()) - ->setProcess($bp) - ->setParentNode($node) - ->setSession($this->session()) - ->handleRequest(); - } elseif ($action === 'cleanup' && $canEdit) { + if ($action === 'cleanup' && $canEdit) { $form = $this->loadForm('CleanupNode') ->setSuccessUrl(Url::fromRequest()->without('action')) ->setProcess($bp) diff --git a/application/forms/AddNodeForm.php b/application/forms/AddNodeForm.php deleted file mode 100644 index 4759d99..0000000 --- a/application/forms/AddNodeForm.php +++ /dev/null @@ -1,513 +0,0 @@ -getView(); - if ($this->hasParentNode()) { - $this->addHtml( - '

' . $view->escape( - sprintf($this->translate('Add a node to %s'), $this->parent->getAlias()) - ) . '

' - ); - } else { - $this->addHtml( - '

' . $this->translate('Add a new root node') . '

' - ); - } - - $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' => Node::getOperators() - )); - - $display = 1; - if ($this->bp->getMetadata()->isManuallyOrdered() && ! $this->bp->isEmpty()) { - $rootNodes = self::applyManualSorting($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, - '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, - '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, - '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, - '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, - '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 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; - } - - 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) { - try { - $bp = $this->storage->loadProcess($file); - } catch (Exception $e) { - $this->addError('Cannot add invalid config file'); - - return $list; - } - } - - 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? - } - } - - 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 (! $properties['alias']) { - unset($properties['alias']); - } - if ($this->hasParentNode()) { - $properties['parentName'] = $this->parent->getName(); - } - $changes->createNode(BpConfig::escapeName($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(); - } -} From b84405180c502826e5e3c0bdbd587ca40da41cf9 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Aug 2023 10:29:05 +0200 Subject: [PATCH 03/14] Add new endpoint for node suggestions --- .../controllers/SuggestionsController.php | 372 ++++++++++++++++++ 1 file changed, 372 insertions(+) create mode 100644 application/controllers/SuggestionsController.php diff --git a/application/controllers/SuggestionsController.php b/application/controllers/SuggestionsController.php new file mode 100644 index 0000000..9fa0331 --- /dev/null +++ b/application/controllers/SuggestionsController.php @@ -0,0 +1,372 @@ +params->has('config')) { + $forConfig = $this->loadModifiedBpConfig(); + + $parentName = $this->params->get('node'); + if ($parentName) { + $forParent = $forConfig->getBpNode($parentName); + + $collectParents = function ($node) use ($ignoreList, &$collectParents) { + foreach ($node->getParents() as $parent) { + $ignoreList[$parent->getName()] = true; + + if ($parent->hasParents()) { + $collectParents($parent); + } + } + }; + + $ignoreList[$parentName] = true; + if ($forParent->hasParents()) { + $collectParents($forParent); + } + + foreach ($forParent->getChildNames() as $name) { + $ignoreList[$name] = true; + } + } + } + + $suggestions = new TermSuggestions((function () use ($forConfig, $forParent, $ignoreList, &$suggestions) { + foreach ($this->storage()->listProcessNames() as $config) { + $differentConfig = false; + if ($forConfig === null || $config !== $forConfig->getName()) { + if ($forConfig !== null && $forParent === null) { + continue; + } + + try { + $bp = $this->storage()->loadProcess($config); + } catch (Exception $_) { + continue; + } + + $differentConfig = true; + } else { + $bp = $forConfig; + } + + foreach ($bp->getBpNodes() as $bpNode) { + /** @var BpNode $bpNode */ + if ($bpNode instanceof ImportedNode) { + continue; + } + + $search = $bpNode->getName(); + if ($differentConfig) { + $search = "@$config:$search"; + } + + if (in_array($search, $suggestions->getExcludeTerms(), true) + || isset($ignoreList[$search]) + || ($forParent + ? $forParent->hasChild($search) + : ($forConfig && $forConfig->hasRootNode($search)) + ) + ) { + continue; + } + + if ($suggestions->matchSearch($bpNode->getName()) + || (! $bpNode->hasAlias() || $suggestions->matchSearch($bpNode->getAlias())) + || $bpNode->getName() === $suggestions->getOriginalSearchValue() + || $bpNode->getAlias() === $suggestions->getOriginalSearchValue() + ) { + yield [ + 'search' => $search, + 'label' => $bpNode->getAlias() ?? $bpNode->getName(), + 'config' => $config + ]; + } + } + } + })()); + $suggestions->setGroupingCallback(function (array $data) { + return $this->storage()->loadMetadata($data['config'])->getTitle(); + }); + + $this->getDocument()->addHtml($suggestions->forRequest($this->getServerRequest())); + } + + public function icingadbHostAction() + { + $excludes = Filter::none(); + $forConfig = null; + if ($this->params->has('config')) { + $forConfig = $this->loadModifiedBpConfig(); + + if ($this->params->has('node')) { + $nodeName = $this->params->get('node'); + $node = $forConfig->getBpNode($nodeName); + + foreach ($node->getChildren() as $child) { + if ($child instanceof HostNode) { + $excludes->add(Filter::equal('host.name', $child->getHostname())); + } + } + } + } + + $suggestions = new TermSuggestions((function () use ($forConfig, $excludes, &$suggestions) { + foreach ($suggestions->getExcludeTerms() as $excludeTerm) { + [$hostName, $_] = BpConfig::splitNodeName($excludeTerm); + $excludes->add(Filter::equal('host.name', $hostName)); + } + + $hosts = Host::on($forConfig->getBackend()) + ->columns(['host.name', 'host.display_name']) + ->limit(50); + IcingaDbObject::applyIcingaDbRestrictions($hosts); + $hosts->filter(Filter::all( + $excludes, + Filter::any( + Filter::like('host.name', $suggestions->getSearchTerm()), + Filter::equal('host.name', $suggestions->getOriginalSearchValue()), + Filter::like('host.display_name', $suggestions->getSearchTerm()), + Filter::equal('host.display_name', $suggestions->getOriginalSearchValue()), + Filter::like('host.address', $suggestions->getSearchTerm()), + Filter::equal('host.address', $suggestions->getOriginalSearchValue()), + Filter::like('host.address6', $suggestions->getSearchTerm()), + Filter::equal('host.address6', $suggestions->getOriginalSearchValue()), + Filter::like('host.customvar_flat.flatvalue', $suggestions->getSearchTerm()), + Filter::equal('host.customvar_flat.flatvalue', $suggestions->getOriginalSearchValue()), + Filter::like('hostgroup.name', $suggestions->getSearchTerm()), + Filter::equal('hostgroup.name', $suggestions->getOriginalSearchValue()) + ) + )); + foreach ($hosts as $host) { + yield [ + 'search' => BpConfig::joinNodeName($host->name, 'Hoststatus'), + 'label' => $host->display_name, + 'class' => 'host' + ]; + } + })()); + + $this->getDocument()->addHtml($suggestions->forRequest($this->getServerRequest())); + } + + public function icingadbServiceAction() + { + $excludes = Filter::none(); + $forConfig = null; + if ($this->params->has('config')) { + $forConfig = $this->loadModifiedBpConfig(); + + if ($this->params->has('node')) { + $nodeName = $this->params->get('node'); + $node = $forConfig->getBpNode($nodeName); + + foreach ($node->getChildren() as $child) { + if ($child instanceof ServiceNode) { + $excludes->add(Filter::all( + Filter::equal('host.name', $child->getHostname()), + Filter::equal('service.name', $child->getServiceDescription()) + )); + } + } + } + } + + $suggestions = new TermSuggestions((function () use ($forConfig, $excludes, &$suggestions) { + foreach ($suggestions->getExcludeTerms() as $excludeTerm) { + [$hostName, $serviceName] = BpConfig::splitNodeName($excludeTerm); + if ($serviceName !== null && $serviceName !== 'Hoststatus') { + $excludes->add(Filter::all( + Filter::equal('host.name', $hostName), + Filter::equal('service.name', $serviceName) + )); + } + } + + $services = Service::on($forConfig->getBackend()) + ->columns(['host.name', 'host.display_name', 'service.name', 'service.display_name']) + ->limit(50); + IcingaDbObject::applyIcingaDbRestrictions($services); + $services->filter(Filter::all( + $excludes, + Filter::any( + Filter::like('host.name', $suggestions->getSearchTerm()), + Filter::equal('host.name', $suggestions->getOriginalSearchValue()), + Filter::like('host.display_name', $suggestions->getSearchTerm()), + Filter::equal('host.display_name', $suggestions->getOriginalSearchValue()), + Filter::like('service.name', $suggestions->getSearchTerm()), + Filter::equal('service.name', $suggestions->getOriginalSearchValue()), + Filter::like('service.display_name', $suggestions->getSearchTerm()), + Filter::equal('service.display_name', $suggestions->getOriginalSearchValue()), + Filter::like('service.customvar_flat.flatvalue', $suggestions->getSearchTerm()), + Filter::equal('service.customvar_flat.flatvalue', $suggestions->getOriginalSearchValue()), + Filter::like('servicegroup.name', $suggestions->getSearchTerm()), + Filter::equal('servicegroup.name', $suggestions->getOriginalSearchValue()) + ) + )); + foreach ($services as $service) { + yield [ + 'class' => 'service', + 'search' => BpConfig::joinNodeName($service->host->name, $service->name), + 'label' => sprintf( + $this->translate('%s on %s', ' on '), + $service->display_name, + $service->host->display_name + ) + ]; + } + })()); + + $this->getDocument()->addHtml($suggestions->forRequest($this->getServerRequest())); + } + + public function monitoringHostAction() + { + $excludes = LegacyFilter::matchAny(); + $forConfig = null; + if ($this->params->has('config')) { + $forConfig = $this->loadModifiedBpConfig(); + + if ($this->params->has('node')) { + $nodeName = $this->params->get('node'); + $node = $forConfig->getBpNode($nodeName); + + foreach ($node->getChildren() as $child) { + if ($child instanceof HostNode) { + $excludes->addFilter(LegacyFilter::where('host_name', $child->getHostname())); + } + } + } + } + + $suggestions = new TermSuggestions((function () use ($forConfig, $excludes, &$suggestions) { + foreach ($suggestions->getExcludeTerms() as $excludeTerm) { + [$hostName, $_] = BpConfig::splitNodeName($excludeTerm); + $excludes->addFilter(LegacyFilter::where('host_name', $hostName)); + } + + $hosts = (new HostStatus($forConfig->getBackend()->select(), ['host_name', 'host_display_name'])) + ->limit(50) + ->applyFilter(MonitoringRestrictions::getRestriction('monitoring/filter/objects')) + ->applyFilter(LegacyFilter::matchAny( + LegacyFilter::where('host_name', $suggestions->getSearchTerm()), + LegacyFilter::where('host_display_name', $suggestions->getSearchTerm()), + LegacyFilter::where('host_address', $suggestions->getSearchTerm()), + LegacyFilter::where('host_address6', $suggestions->getSearchTerm()), + LegacyFilter::where('_host_%', $suggestions->getSearchTerm()), + // This also forces a group by on the query, needed anyway due to the custom var filter + // above, which may return multiple rows because of the wildcard in the name filter. + LegacyFilter::where('hostgroup_name', $suggestions->getSearchTerm()), + LegacyFilter::where('hostgroup_alias', $suggestions->getSearchTerm()) + )); + if (! $excludes->isEmpty()) { + $hosts->applyFilter(LegacyFilter::not($excludes)); + } + + foreach ($hosts as $row) { + yield [ + 'search' => BpConfig::joinNodeName($row->host_name, 'Hoststatus'), + 'label' => $row->host_display_name, + 'class' => 'host' + ]; + } + })()); + + $this->getDocument()->addHtml($suggestions->forRequest($this->getServerRequest())); + } + + public function monitoringServiceAction() + { + $excludes = LegacyFilter::matchAny(); + $forConfig = null; + if ($this->params->has('config')) { + $forConfig = $this->loadModifiedBpConfig(); + + if ($this->params->has('node')) { + $nodeName = $this->params->get('node'); + $node = $forConfig->getBpNode($nodeName); + + foreach ($node->getChildren() as $child) { + if ($child instanceof ServiceNode) { + $excludes->addFilter(LegacyFilter::matchAll( + LegacyFilter::where('host_name', $child->getHostname()), + LegacyFilter::where('service_description', $child->getServiceDescription()) + )); + } + } + } + } + + $suggestions = new TermSuggestions((function () use ($forConfig, $excludes, &$suggestions) { + foreach ($suggestions->getExcludeTerms() as $excludeTerm) { + [$hostName, $serviceName] = BpConfig::splitNodeName($excludeTerm); + if ($serviceName !== null && $serviceName !== 'Hoststatus') { + $excludes->addFilter(LegacyFilter::matchAll( + LegacyFilter::where('host_name', $hostName), + LegacyFilter::where('service_description', $serviceName) + )); + } + } + + $services = (new ServiceStatus($forConfig->getBackend()->select(), [ + 'host_name', + 'host_display_name', + 'service_description', + 'service_display_name' + ])) + ->limit(50) + ->applyFilter(MonitoringRestrictions::getRestriction('monitoring/filter/objects')) + ->applyFilter(LegacyFilter::matchAny( + LegacyFilter::where('host_name', $suggestions->getSearchTerm()), + LegacyFilter::where('host_display_name', $suggestions->getSearchTerm()), + LegacyFilter::where('service_description', $suggestions->getSearchTerm()), + LegacyFilter::where('service_display_name', $suggestions->getSearchTerm()), + LegacyFilter::where('_service_%', $suggestions->getSearchTerm()), + // This also forces a group by on the query, needed anyway due to the custom var filter + // above, which may return multiple rows because of the wildcard in the name filter. + LegacyFilter::where('servicegroup_name', $suggestions->getSearchTerm()), + LegacyFilter::where('servicegroup_alias', $suggestions->getSearchTerm()) + )); + if (! $excludes->isEmpty()) { + $services->applyFilter(LegacyFilter::not($excludes)); + } + + foreach ($services as $row) { + yield [ + 'class' => 'service', + 'search' => BpConfig::joinNodeName($row->host_name, $row->service_description), + 'label' => sprintf( + $this->translate('%s on %s', ' on '), + $row->service_display_name, + $row->host_display_name + ) + ]; + } + })()); + + $this->getDocument()->addHtml($suggestions->forRequest($this->getServerRequest())); + } +} From 46cce05c0a66e44c0b915c9982d1df562efe0510 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Aug 2023 10:29:37 +0200 Subject: [PATCH 04/14] Add ipl-form compatible implementation of the state override element --- .../Web/Form/Element/IplStateOverrides.php | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 library/Businessprocess/Web/Form/Element/IplStateOverrides.php diff --git a/library/Businessprocess/Web/Form/Element/IplStateOverrides.php b/library/Businessprocess/Web/Form/Element/IplStateOverrides.php new file mode 100644 index 0000000..5b9ea16 --- /dev/null +++ b/library/Businessprocess/Web/Form/Element/IplStateOverrides.php @@ -0,0 +1,75 @@ +options = $options; + + return $this; + } + + /** + * Get the options to show + * + * @return array + */ + public function getOptions(): array + { + return $this->options; + } + + public function getValues() + { + $cleanedValue = parent::getValues(); + + if (! empty($cleanedValue)) { + foreach ($cleanedValue as $from => $to) { + if ((int) $from === (int) $to) { + unset($cleanedValue[$from]); + } + } + } + + return $cleanedValue; + } + + protected function assemble() + { + $states = $this->getOptions(); + foreach ($states as $state => $label) { + if ($state === 0) { + continue; + } + + $this->addElement('select', $state, [ + 'label' => $label, + 'value' => $state, + 'options' => [$state => $this->translate('Keep actual state')] + $states + ]); + } + } + + protected function registerAttributeCallbacks(Attributes $attributes) + { + parent::registerAttributeCallbacks($attributes); + + $this->getAttributes() + ->registerAttributeCallback('options', null, [$this, 'setOptions']); + } +} From 6dd8a51ce56c799b62d421634e8e0c9a0f517dd5 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Aug 2023 14:03:02 +0200 Subject: [PATCH 05/14] Introduce new form validator for host/service terms --- .../Validator/HostServiceTermValidator.php | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 library/Businessprocess/Web/Form/Validator/HostServiceTermValidator.php diff --git a/library/Businessprocess/Web/Form/Validator/HostServiceTermValidator.php b/library/Businessprocess/Web/Form/Validator/HostServiceTermValidator.php new file mode 100644 index 0000000..b141206 --- /dev/null +++ b/library/Businessprocess/Web/Form/Validator/HostServiceTermValidator.php @@ -0,0 +1,90 @@ +parent = $parent; + + return $this; + } + + public function isValid($terms) + { + if ($this->parent === null) { + throw new LogicException('Missing parent process. Cannot validate terms.'); + } + + if (! is_array($terms)) { + $terms = [$terms]; + } + + $testConfig = new BpConfig(); + + foreach ($terms as $term) { + /** @var Term $term */ + [$hostName, $serviceName] = BpConfig::splitNodeName($term->getSearchValue()); + if ($serviceName !== null && $serviceName !== 'Hoststatus') { + $node = $testConfig->createService($hostName, $serviceName); + } else { + $node = $testConfig->createHost($hostName); + if ($serviceName === null) { + $term->setSearchValue(BpConfig::joinNodeName($hostName, 'Hoststatus')); + } + } + + if ($this->parent->hasChild($term->getSearchValue())) { + $term->setMessage($this->translate('Already defined in this process')); + } else { + $testConfig->getNode('__unbound__') + ->addChild($node); + } + } + + if ($this->parent->getBpConfig()->getBackend() instanceof MonitoringBackend) { + MonitoringState::apply($testConfig); + } else { + IcingaDbState::apply($testConfig); + } + + foreach ($terms as $term) { + /** @var Term $term */ + $node = $testConfig->getNode($term->getSearchValue()); + if ($node->isMissing()) { + if ($node instanceof ServiceNode) { + $term->setMessage($this->translate('Service not found')); + } else { + $term->setMessage($this->translate('Host not found')); + } + } else { + $term->setLabel($node->getAlias()); + $term->setClass($node->getObjectClassName()); + } + } + } +} From da4edda2b31d292bfb7ee92b211855ae6ef65503 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Aug 2023 10:31:10 +0200 Subject: [PATCH 06/14] Re-introduce class `AddNodeForm` Rewritten from scratch. Now with ipl-form compatibility. --- application/forms/AddNodeForm.php | 410 ++++++++++++++++++++++++++++++ 1 file changed, 410 insertions(+) create mode 100644 application/forms/AddNodeForm.php diff --git a/application/forms/AddNodeForm.php b/application/forms/AddNodeForm.php new file mode 100644 index 0000000..68299b7 --- /dev/null +++ b/application/forms/AddNodeForm.php @@ -0,0 +1,410 @@ +storage = $storage; + + return $this; + } + + /** + * Set the affected configuration + * + * @param BpConfig $bp + * + * @return $this + */ + public function setProcess(BpConfig $bp): self + { + $this->bp = $bp; + + return $this; + } + + /** + * Set the affected sub-process + * + * @param ?BpNode $node + * + * @return $this + */ + public function setParentNode(BpNode $node = null): self + { + $this->parent = $node; + + return $this; + } + + /** + * Set the user's session + * + * @param SessionNamespace $session + * + * @return $this + */ + public function setSession(SessionNamespace $session): self + { + $this->session = $session; + + return $this; + } + + protected function assemble() + { + if ($this->parent !== null) { + $title = sprintf($this->translate('Add a node to %s'), $this->parent->getAlias()); + $nodeTypes = [ + 'host' => $this->translate('Host'), + 'service' => $this->translate('Service'), + 'process' => $this->translate('Existing Process'), + 'new-process' => $this->translate('New Process') + ]; + } else { + $title = $this->translate('Add a new root node'); + if (! $this->bp->isEmpty()) { + $nodeTypes = [ + 'process' => $this->translate('Existing Process'), + 'new-process' => $this->translate('New Process') + ]; + } else { + $nodeTypes = []; + } + } + + $this->addHtml(new HtmlElement('h2', null, Text::create($title))); + + if (! empty($nodeTypes)) { + $this->addElement('select', 'node_type', [ + 'label' => $this->translate('Node type'), + 'options' => array_merge( + ['' => ' - ' . $this->translate('Please choose') . ' - '], + $nodeTypes + ), + 'disabledOptions' => [''], + 'class' => 'autosubmit', + 'required' => true, + 'ignore' => true + ]); + + $nodeType = $this->getPopulatedValue('node_type'); + } else { + $nodeType = 'new-process'; + } + + if ($nodeType === 'new-process') { + $this->assembleNewProcessElements(); + } elseif ($nodeType === 'process') { + $this->assembleExistingProcessElements(); + } elseif ($nodeType === 'host') { + $this->assembleHostElements(); + } elseif ($nodeType === 'service') { + $this->assembleServiceElements(); + } + + $this->addElement('submit', 'submit', [ + 'label' => $this->translate('Add Process') + ]); + } + + protected function assembleNewProcessElements(): void + { + $this->addElement('text', 'name', [ + 'required' => true, + 'ignore' => true, + 'label' => $this->translate('ID'), + 'description' => $this->translate('This is the unique identifier of this process'), + 'validators' => [ + 'callback' => function ($value, $validator) { + if ($this->parent !== null ? $this->parent->hasChild($value) : $this->bp->hasRootNode($value)) { + $validator->addMessage( + sprintf($this->translate('%s is already defined in this process'), $value) + ); + + return false; + } + + return true; + } + ] + ]); + + $this->addElement('text', 'alias', [ + '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', [ + 'required' => true, + 'label' => $this->translate('Operator'), + 'multiOptions' => Node::getOperators() + ]); + + $display = 1; + if (! $this->bp->isEmpty() && $this->bp->getMetadata()->isManuallyOrdered()) { + $rootNodes = self::applyManualSorting($this->bp->getRootNodes()); + $display = end($rootNodes)->getDisplay() + 1; + } + $this->addElement('select', 'display', [ + 'required' => true, + 'label' => $this->translate('Visualization'), + 'description' => $this->translate('Where to show this process'), + 'value' => $this->parent !== null ? '0' : "$display", + 'multiOptions' => [ + "$display" => $this->translate('Toplevel Process'), + '0' => $this->translate('Subprocess only'), + ] + ]); + + $this->addElement('text', 'infoUrl', [ + 'label' => $this->translate('Info URL'), + 'description' => $this->translate('URL pointing to more information about this node') + ]); + } + + protected function assembleExistingProcessElements(): void + { + $termValidator = function (array $terms) { + foreach ($terms as $term) { + /** @var TermInput\ValidatedTerm $term */ + $nodeName = $term->getSearchValue(); + if ($nodeName[0] === '@') { + if ($this->parent === null) { + $term->setMessage($this->translate('Imported nodes cannot be used as root nodes')); + } elseif (strpos($nodeName, ':') === false) { + $term->setMessage($this->translate('Missing node name')); + } else { + [$config, $nodeName] = Str::trimSplit(substr($nodeName, 1), ':', 2); + if (! $this->storage->hasProcess($config)) { + $term->setMessage($this->translate('Config does not exist or access has been denied')); + } else { + try { + $bp = $this->storage->loadProcess($config); + } catch (Exception $e) { + $term->setMessage( + sprintf($this->translate('Cannot load config: %s'), $e->getMessage()) + ); + } + + if (isset($bp)) { + if (! $bp->hasNode($nodeName)) { + $term->setMessage($this->translate('No node with this name found in config')); + } else { + $term->setLabel($bp->getNode($nodeName)->getAlias()); + } + } + } + } + } elseif (! $this->bp->hasNode($nodeName)) { + $term->setMessage($this->translate('No node with this name found in config')); + } else { + $term->setLabel($this->bp->getNode($nodeName)->getAlias()); + } + + if ($this->parent !== null && $this->parent->hasChild($term->getSearchValue())) { + $term->setMessage($this->translate('Already defined in this process')); + } + + if ($this->parent !== null && $term->getSearchValue() === $this->parent->getName()) { + $term->setMessage($this->translate('Results in a parent/child loop')); + } + } + }; + + $this->addElement( + (new TermInput('children')) + ->setRequired() + ->setVerticalTermDirection() + ->setLabel($this->translate('Process Nodes')) + ->setSuggestionUrl(Url::fromPath('businessprocess/suggestions/process', [ + 'node' => isset($this->parent) ? $this->parent->getName() : null, + 'config' => $this->bp->getName(), + 'showCompact' => true, + '_disableLayout' => true + ])) + ->on(TermInput::ON_ENRICH, $termValidator) + ->on(TermInput::ON_ADD, $termValidator) + ->on(TermInput::ON_PASTE, $termValidator) + ->on(TermInput::ON_SAVE, $termValidator) + ); + } + + protected function assembleHostElements(): void + { + if ($this->bp->getBackend() instanceof MonitoringBackend) { + $suggestionsPath = 'businessprocess/suggestions/monitoring-host'; + } else { + $suggestionsPath = 'businessprocess/suggestions/icingadb-host'; + } + + $this->addElement($this->createChildrenElementForObjects( + $this->translate('Hosts'), + $suggestionsPath + )); + + $this->addElement('checkbox', 'host_override', [ + 'ignore' => true, + 'class' => 'autosubmit', + 'label' => $this->translate('Override Host State') + ]); + if ($this->getPopulatedValue('host_override') === 'y') { + $this->addElement(new IplStateOverrides('stateOverrides', [ + 'label' => $this->translate('State Overrides'), + 'options' => [ + 0 => $this->translate('UP'), + 1 => $this->translate('DOWN'), + 99 => $this->translate('PENDING') + ] + ])); + } + } + + protected function assembleServiceElements(): void + { + if ($this->bp->getBackend() instanceof MonitoringBackend) { + $suggestionsPath = 'businessprocess/suggestions/monitoring-service'; + } else { + $suggestionsPath = 'businessprocess/suggestions/icingadb-service'; + } + + $this->addElement($this->createChildrenElementForObjects( + $this->translate('Services'), + $suggestionsPath + )); + + $this->addElement('checkbox', 'service_override', [ + 'ignore' => true, + 'class' => 'autosubmit', + 'label' => $this->translate('Override Service State') + ]); + if ($this->getPopulatedValue('service_override') === 'y') { + $this->addElement(new IplStateOverrides('stateOverrides', [ + 'label' => $this->translate('State Overrides'), + 'options' => [ + 0 => $this->translate('OK'), + 1 => $this->translate('WARNING'), + 2 => $this->translate('CRITICAL'), + 3 => $this->translate('UNKNOWN'), + 99 => $this->translate('PENDING'), + ] + ])); + } + } + + protected function createChildrenElementForObjects(string $label, string $suggestionsPath): TermInput + { + $termValidator = function (array $terms) { + (new HostServiceTermValidator()) + ->setParent($this->parent) + ->isValid($terms); + }; + + return (new TermInput('children')) + ->setRequired() + ->setLabel($label) + ->setVerticalTermDirection() + ->setSuggestionUrl(Url::fromPath($suggestionsPath, [ + 'node' => isset($this->parent) ? $this->parent->getName() : null, + 'config' => $this->bp->getName(), + 'showCompact' => true, + '_disableLayout' => true + ])) + ->on(TermInput::ON_ENRICH, $termValidator) + ->on(TermInput::ON_ADD, $termValidator) + ->on(TermInput::ON_PASTE, $termValidator) + ->on(TermInput::ON_SAVE, $termValidator); + } + + protected function onSuccess() + { + $changes = ProcessChanges::construct($this->bp, $this->session); + + $nodeType = $this->getValue('node_type'); + if (! $nodeType || $nodeType === 'new-process') { + $properties = $this->getValues(); + if (! $properties['alias']) { + unset($properties['alias']); + } + + if ($this->parent !== null) { + $properties['parentName'] = $this->parent->getName(); + } + + $changes->createNode(BpConfig::escapeName($this->getValue('name')), $properties); + } else { + $children = array_unique(array_map(function ($term) { + return $term->getSearchValue(); + }, $this->getElement('children')->getTerms())); + + if ($nodeType === 'host' || $nodeType === 'service') { + $stateOverrides = $this->getValue('stateOverrides'); + if (! empty($stateOverrides)) { + $childOverrides = []; + foreach ($children as $nodeName) { + $childOverrides[$nodeName] = $stateOverrides; + } + + $changes->modifyNode($this->parent, [ + 'stateOverrides' => array_merge($this->parent->getStateOverrides(), $childOverrides) + ]); + } + } + + if ($this->parent !== null) { + $changes->addChildrenToNode($children, $this->parent); + } else { + foreach ($children as $nodeName) { + $changes->copyNode($nodeName); + } + } + } + + unset($changes); + } +} From 4aac6580892475ec5ad894e19a208ffb64285b07 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Aug 2023 10:32:03 +0200 Subject: [PATCH 07/14] process/show: Utilize the new `AddNodeForm` --- application/controllers/ProcessController.php | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/application/controllers/ProcessController.php b/application/controllers/ProcessController.php index 4a6495e..f61fb89 100644 --- a/application/controllers/ProcessController.php +++ b/application/controllers/ProcessController.php @@ -6,6 +6,7 @@ use Icinga\Application\Modules\Module; use Icinga\Date\DateFormatter; use Icinga\Module\Businessprocess\BpConfig; use Icinga\Module\Businessprocess\BpNode; +use Icinga\Module\Businessprocess\Forms\AddNodeForm; use Icinga\Module\Businessprocess\Node; use Icinga\Module\Businessprocess\ProvidedHook\Icingadb\IcingadbSupport; use Icinga\Module\Businessprocess\Renderer\Breadcrumb; @@ -268,7 +269,27 @@ class ProcessController extends Controller $canEdit = $bp->getMetadata()->canModify(); - if ($action === 'cleanup' && $canEdit) { + if ($action === 'add' && $canEdit) { + $form = (new AddNodeForm()) + ->setProcess($bp) + ->setParentNode($node) + ->setStorage($this->storage()) + ->setSession($this->session()) + ->on(AddNodeForm::ON_SUCCESS, function () { + $this->redirectNow(Url::fromRequest()->without('action')); + }) + ->handleRequest($this->getServerRequest()); + + if ($form->hasElement('children')) { + foreach ($form->getElement('children')->prepareMultipartUpdate($this->getServerRequest()) as $update) { + if (! is_array($update)) { + $update = [$update]; + } + + $this->addPart(...$update); + } + } + } elseif ($action === 'cleanup' && $canEdit) { $form = $this->loadForm('CleanupNode') ->setSuccessUrl(Url::fromRequest()->without('action')) ->setProcess($bp) @@ -341,8 +362,11 @@ class ProcessController extends Controller return; } - if ($this->params->get('action')) { - $this->setAutorefreshInterval(45); + if ($this->params->has('action')) { + if ($this->params->get('action') !== 'add') { + // The new add form uses the term input, which doesn't support value persistence across refreshes + $this->setAutorefreshInterval(45); + } } else { $this->setAutorefreshInterval(10); } From 1b837c72a3b310778703b1ff77319d8e216a8b13 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Aug 2023 17:54:17 +0200 Subject: [PATCH 08/14] Drop class `EditNodeForm` --- application/controllers/ProcessController.php | 8 - application/forms/EditNodeForm.php | 400 ------------------ 2 files changed, 408 deletions(-) delete mode 100644 application/forms/EditNodeForm.php diff --git a/application/controllers/ProcessController.php b/application/controllers/ProcessController.php index f61fb89..f9dcf61 100644 --- a/application/controllers/ProcessController.php +++ b/application/controllers/ProcessController.php @@ -295,14 +295,6 @@ class ProcessController extends Controller ->setProcess($bp) ->setSession($this->session()) ->handleRequest(); - } elseif ($action === 'editmonitored' && $canEdit) { - $form = $this->loadForm('EditNode') - ->setSuccessUrl(Url::fromRequest()->without('action')) - ->setProcess($bp) - ->setNode($bp->getNode($this->params->get('editmonitorednode'))) - ->setParentNode($node) - ->setSession($this->session()) - ->handleRequest(); } elseif ($action === 'delete' && $canEdit) { $form = $this->loadForm('DeleteNode') ->setSuccessUrl(Url::fromRequest()->without('action')) diff --git a/application/forms/EditNodeForm.php b/application/forms/EditNodeForm.php deleted file mode 100644 index a4cd919..0000000 --- a/application/forms/EditNodeForm.php +++ /dev/null @@ -1,400 +0,0 @@ -host, $suffix] = BpConfig::splitNodeName($this->getNode()->getName()); - if ($suffix !== 'Hoststatus') { - $this->service = $suffix; - } - - $view = $this->getView(); - $nodeName = $this->getNode()->getAlias() ?? $this->getNode()->getName(); - $this->addHtml( - '

' . $view->escape( - sprintf($this->translate('Modify "%s"'), $nodeName) - ) . '

' - ); - - $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' => Node::getOperators() - )); - - $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, - 'multiOptions' => $this->enumProcesses(), - 'description' => $this->translate( - 'Other processes that should be part of this business process node' - ) - )); - } - - /** - * @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; - } - - 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? - } - } - - 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); - } -} From 9c84764157e23d7a8967357d7a9bd22daea9f420 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Aug 2023 17:56:36 +0200 Subject: [PATCH 09/14] BpNode: Re-index childnames upon node removal --- library/Businessprocess/BpNode.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/Businessprocess/BpNode.php b/library/Businessprocess/BpNode.php index 419f836..4b67622 100644 --- a/library/Businessprocess/BpNode.php +++ b/library/Businessprocess/BpNode.php @@ -173,6 +173,8 @@ class BpNode extends Node if (! empty($this->children)) { unset($this->children[$name]); } + + $this->childNames = array_values($this->childNames); } return $this; From d9c6debfa9b84aae524184ae83dbb985504d7d7f Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Aug 2023 14:01:54 +0200 Subject: [PATCH 10/14] ServiceNode: Change alias to ` on ` --- library/Businessprocess/ServiceNode.php | 9 ++++++++- test/php/library/Businessprocess/ServiceNodeTest.php | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/library/Businessprocess/ServiceNode.php b/library/Businessprocess/ServiceNode.php index cba6acf..c80b984 100644 --- a/library/Businessprocess/ServiceNode.php +++ b/library/Businessprocess/ServiceNode.php @@ -3,9 +3,12 @@ namespace Icinga\Module\Businessprocess; use Icinga\Module\Businessprocess\Web\Url; +use ipl\I18n\Translation; class ServiceNode extends MonitoredNode { + use Translation; + protected $hostname; /** @var string Alias of the host */ @@ -69,7 +72,11 @@ class ServiceNode extends MonitoredNode return null; } - return $this->getHostAlias() . ': ' . $this->alias; + return sprintf( + $this->translate('%s on %s', ' on '), + $this->alias, + $this->getHostAlias() + ); } public function getUrl() diff --git a/test/php/library/Businessprocess/ServiceNodeTest.php b/test/php/library/Businessprocess/ServiceNodeTest.php index d56529d..62c1605 100644 --- a/test/php/library/Businessprocess/ServiceNodeTest.php +++ b/test/php/library/Businessprocess/ServiceNodeTest.php @@ -27,7 +27,7 @@ class ServiceNodeTest extends BaseTestCase public function testReturnsCorrectAlias() { $this->assertEquals( - 'localhost: ping <> pong', + 'ping <> pong on localhost', $this->pingOnLocalhost()->getAlias() ); } @@ -36,7 +36,7 @@ class ServiceNodeTest extends BaseTestCase { $this->assertEquals( '' - . 'localhost: ping <> pong', + . 'ping <> pong on localhost', $this->pingOnLocalhost()->getLink()->render() ); } From 890df4be7c3a79c9fd2745b72484c985b88612df Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Aug 2023 17:56:13 +0200 Subject: [PATCH 11/14] Re-introduce class `EditNodeForm` Rewritten from scratch. Now with ipl-form compatibility. --- application/controllers/ProcessController.php | 11 + application/forms/EditNodeForm.php | 315 ++++++++++++++++++ 2 files changed, 326 insertions(+) create mode 100644 application/forms/EditNodeForm.php diff --git a/application/controllers/ProcessController.php b/application/controllers/ProcessController.php index f9dcf61..06e5bb8 100644 --- a/application/controllers/ProcessController.php +++ b/application/controllers/ProcessController.php @@ -7,6 +7,7 @@ use Icinga\Date\DateFormatter; use Icinga\Module\Businessprocess\BpConfig; use Icinga\Module\Businessprocess\BpNode; use Icinga\Module\Businessprocess\Forms\AddNodeForm; +use Icinga\Module\Businessprocess\Forms\EditNodeForm; use Icinga\Module\Businessprocess\Node; use Icinga\Module\Businessprocess\ProvidedHook\Icingadb\IcingadbSupport; use Icinga\Module\Businessprocess\Renderer\Breadcrumb; @@ -295,6 +296,16 @@ class ProcessController extends Controller ->setProcess($bp) ->setSession($this->session()) ->handleRequest(); + } elseif ($action === 'editmonitored' && $canEdit) { + $form = (new EditNodeForm()) + ->setProcess($bp) + ->setNode($bp->getNode($this->params->get('editmonitorednode'))) + ->setParentNode($node) + ->setSession($this->session()) + ->on(EditNodeForm::ON_SUCCESS, function () { + $this->redirectNow(Url::fromRequest()->without(['action', 'editmonitorednode'])); + }) + ->handleRequest($this->getServerRequest()); } elseif ($action === 'delete' && $canEdit) { $form = $this->loadForm('DeleteNode') ->setSuccessUrl(Url::fromRequest()->without('action')) diff --git a/application/forms/EditNodeForm.php b/application/forms/EditNodeForm.php new file mode 100644 index 0000000..bd1592b --- /dev/null +++ b/application/forms/EditNodeForm.php @@ -0,0 +1,315 @@ +bp = $bp; + + return $this; + } + + /** + * Set the affected node + * + * @param Node $node + * + * @return $this + */ + public function setNode(Node $node): self + { + $this->node = $node; + + $this->populate([ + 'node-search' => $node->getName(), + 'node-label' => $node->getAlias() + ]); + + return $this; + } + + /** + * Set the affected sub-process + * + * @param ?BpNode $node + * + * @return $this + */ + public function setParentNode(BpNode $node = null): self + { + $this->parent = $node; + + if ($this->node !== null) { + $stateOverrides = $this->parent->getStateOverrides($this->node->getName()); + if (! empty($stateOverrides)) { + $this->populate([ + 'overrideStates' => 'y', + 'stateOverrides' => $stateOverrides + ]); + } + } + + return $this; + } + + /** + * Set the user's session + * + * @param SessionNamespace $session + * + * @return $this + */ + public function setSession(SessionNamespace $session): self + { + $this->session = $session; + + return $this; + } + + /** + * Identify and return the node the user has chosen + * + * @return Node + */ + protected function identifyChosenNode(): Node + { + $userInput = $this->getPopulatedValue('node'); + $nodeName = $this->getPopulatedValue('node-search'); + $nodeLabel = $this->getPopulatedValue('node-label'); + + if ($nodeName && $userInput === $nodeLabel) { + // User accepted a suggestion and didn't change it manually + $node = $this->bp->getNode($nodeName); + } elseif ($userInput && (! $nodeLabel || $userInput !== $nodeLabel)) { + // User didn't choose a suggestion or changed it manually + $node = $this->bp->getNode(BpConfig::joinNodeName($userInput, 'Hoststatus')); + } else { + // If the search and user input are both empty, it can only be the initial value + $node = $this->node; + } + + return $node; + } + + protected function assemble() + { + $this->addHtml(new HtmlElement('h2', null, FormattedString::create( + $this->translate('Modify "%s"'), + $this->node->getAlias() ?? $this->node->getName() + ))); + + if ($this->node instanceof ServiceNode) { + $this->assembleServiceElements(); + } else { + $this->assembleHostElements(); + } + + $this->addElement('submit', 'btn_submit', [ + 'label' => $this->translate('Save Changes') + ]); + } + + protected function assembleServiceElements(): void + { + if ($this->bp->getBackend() instanceof MonitoringBackend) { + $suggestionsPath = 'businessprocess/suggestions/monitoring-service'; + } else { + $suggestionsPath = 'businessprocess/suggestions/icingadb-service'; + } + + $node = $this->identifyChosenNode(); + + $this->addHtml($this->createSearchInput( + $this->translate('Service'), + $node->getAlias() ?? $node->getName(), + $suggestionsPath + )); + + $this->addElement('checkbox', 'overrideStates', [ + 'ignore' => true, + 'class' => 'autosubmit', + 'label' => $this->translate('Override Service State') + ]); + if ($this->getPopulatedValue('overrideStates') === 'y') { + $this->addElement(new IplStateOverrides('stateOverrides', [ + 'label' => $this->translate('State Overrides'), + 'options' => [ + 0 => $this->translate('OK'), + 1 => $this->translate('WARNING'), + 2 => $this->translate('CRITICAL'), + 3 => $this->translate('UNKNOWN'), + 99 => $this->translate('PENDING'), + ] + ])); + } + } + + protected function assembleHostElements(): void + { + if ($this->bp->getBackend() instanceof MonitoringBackend) { + $suggestionsPath = 'businessprocess/suggestions/monitoring-host'; + } else { + $suggestionsPath = 'businessprocess/suggestions/icingadb-host'; + } + + $node = $this->identifyChosenNode(); + + $this->addHtml($this->createSearchInput( + $this->translate('Host'), + $node->getAlias() ?? $node->getName(), + $suggestionsPath + )); + + $this->addElement('checkbox', 'overrideStates', [ + 'ignore' => true, + 'class' => 'autosubmit', + 'label' => $this->translate('Override Host State') + ]); + if ($this->getPopulatedValue('overrideStates') === 'y') { + $this->addElement(new IplStateOverrides('stateOverrides', [ + 'label' => $this->translate('State Overrides'), + 'options' => [ + 0 => $this->translate('UP'), + 1 => $this->translate('DOWN'), + 99 => $this->translate('PENDING') + ] + ])); + } + } + + protected function createSearchInput(string $label, string $value, string $suggestionsPath): ValidHtml + { + $userInput = $this->createElement('text', 'node', [ + 'ignore' => true, + 'required' => true, + 'autocomplete' => 'off', + 'label' => $label, + 'value' => $value, + 'data-enrichment-type' => 'completion', + 'data-term-suggestions' => '#node-suggestions', + 'data-suggest-url' => Url::fromPath($suggestionsPath, [ + 'node' => isset($this->parent) ? $this->parent->getName() : null, + 'config' => $this->bp->getName(), + 'showCompact' => true, + '_disableLayout' => true + ]), + 'validators' => ['callback' => function ($_, $validator) { + $newName = $this->identifyChosenNode()->getName(); + if ($newName === $this->node->getName()) { + return true; + } + + $term = new ValidatedTerm($newName); + + (new HostServiceTermValidator()) + ->setParent($this->parent) + ->isValid($term); + + if (! $term->isValid()) { + $validator->addMessage($term->getMessage()); + return false; + } + + return true; + }] + ]); + + $fieldset = new HtmlElement('fieldset'); + + $searchInput = $this->createElement('hidden', 'node-search', ['ignore' => true]); + $this->registerElement($searchInput); + $fieldset->addHtml($searchInput); + + $labelInput = $this->createElement('hidden', 'node-label', ['ignore' => true]); + $this->registerElement($labelInput); + $fieldset->addHtml($labelInput); + + $this->registerElement($userInput); + $this->decorate($userInput); + + $fieldset->addHtml( + $userInput, + new HtmlElement('div', Attributes::create([ + 'id' => 'node-suggestions', + 'class' => 'search-suggestions' + ])) + ); + + return $fieldset; + } + + protected function onSuccess() + { + $changes = ProcessChanges::construct($this->bp, $this->session); + + $children = $this->parent->getChildNames(); + $previousPos = array_search($this->node->getName(), $children, true); + $node = $this->identifyChosenNode(); + $nodeName = $node->getName(); + + $changes->deleteNode($this->node, $this->parent->getName()); + $changes->addChildrenToNode([$nodeName], $this->parent); + + $stateOverrides = $this->getValue('stateOverrides'); + if (! empty($stateOverrides)) { + $changes->modifyNode($this->parent, [ + 'stateOverrides' => array_merge($this->parent->getStateOverrides(), [ + $nodeName => $stateOverrides + ]) + ]); + } + + if ($this->bp->getMetadata()->isManuallyOrdered() && ($newPos = count($children) - 1) > $previousPos) { + $changes->moveNode( + $node, + $newPos, + $previousPos, + $this->parent->getName(), + $this->parent->getName() + ); + } + + unset($changes); + } +} From 0ea04f898fcfa12f8ce7179eb920ed5748a6c57d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Aug 2023 14:06:53 +0200 Subject: [PATCH 12/14] Drop obsolete class `NoDuplicateChildrenValidator` --- .../NoDuplicateChildrenValidator.php | 57 ------------------- 1 file changed, 57 deletions(-) delete mode 100644 library/Businessprocess/Web/Form/Validator/NoDuplicateChildrenValidator.php diff --git a/library/Businessprocess/Web/Form/Validator/NoDuplicateChildrenValidator.php b/library/Businessprocess/Web/Form/Validator/NoDuplicateChildrenValidator.php deleted file mode 100644 index 9676de0..0000000 --- a/library/Businessprocess/Web/Form/Validator/NoDuplicateChildrenValidator.php +++ /dev/null @@ -1,57 +0,0 @@ -form = $form; - $this->bp = $bp; - $this->parent = $parent; - - $this->_messageVariables['label'] = 'label'; - $this->_messageTemplates = [ - self::CHILD_FOUND => mt('businessprocess', '%label% is already defined in this process') - ]; - } - - public function isValid($value) - { - if ($this->parent === null) { - $found = $this->bp->hasRootNode($value); - } elseif ($this->form instanceof EditNodeForm && $this->form->getNode()->getName() === $value) { - $found = false; - } else { - $found = $this->parent->hasChild($value); - } - - if (! $found) { - return true; - } - - $this->label = $this->form->getElement('children')->getMultiOptions()[$value]; - $this->_error(self::CHILD_FOUND); - return false; - } -} From 30d952ae0a12f70f5a9fcf47a76b1bc8732103e2 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Aug 2023 14:08:17 +0200 Subject: [PATCH 13/14] Drop obsolete form element class `StateOverrides` --- .../Web/Form/Element/StateOverrides.php | 55 ------------------- public/css/module.less | 23 -------- 2 files changed, 78 deletions(-) delete mode 100644 library/Businessprocess/Web/Form/Element/StateOverrides.php diff --git a/library/Businessprocess/Web/Form/Element/StateOverrides.php b/library/Businessprocess/Web/Form/Element/StateOverrides.php deleted file mode 100644 index c2216c0..0000000 --- a/library/Businessprocess/Web/Form/Element/StateOverrides.php +++ /dev/null @@ -1,55 +0,0 @@ -states = $states; - - return $this; - } - - /** - * Get the overridable states - * - * @return array - */ - public function getStates() - { - return $this->states; - } - - public function init() - { - $this->setIsArray(true); - } - - public function setValue($value) - { - $cleanedValue = []; - - if (! empty($value)) { - foreach ($value as $from => $to) { - if ((int) $from !== (int) $to) { - $cleanedValue[$from] = $to; - } - } - } - - return parent::setValue($cleanedValue); - } -} diff --git a/public/css/module.less b/public/css/module.less index 500a137..f048863 100644 --- a/public/css/module.less +++ b/public/css/module.less @@ -869,29 +869,6 @@ form.bp-form { } } - #stateOverrides-element { - display: inline-table; - table-layout: fixed; - border-spacing: .5em; - padding: 0; - - label { - display: table-row; - - span, select { - display: table-cell; - } - - span { - width: 10em; - } - - select { - width: 26em; - } - } - } - fieldset { min-width: 36em; } From b8d5f0de2be2467861cecb0eea07f6edca61085e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Aug 2023 14:11:55 +0200 Subject: [PATCH 14/14] Drop unused trait `EnumList` --- library/Businessprocess/Common/EnumList.php | 170 -------------------- 1 file changed, 170 deletions(-) delete mode 100644 library/Businessprocess/Common/EnumList.php diff --git a/library/Businessprocess/Common/EnumList.php b/library/Businessprocess/Common/EnumList.php deleted file mode 100644 index 3419505..0000000 --- a/library/Businessprocess/Common/EnumList.php +++ /dev/null @@ -1,170 +0,0 @@ -useIcingaDbBackend()) { - $names = (new IcingaDbObject())->yieldHostnames(); - } else { - $names = $this->backend - ->select() - ->from('hostStatus', ['hostname' => 'host_name']) - ->applyFilter(MonitoringRestrictions::getRestriction('monitoring/filter/objects')) - ->order('host_name') - ->getQuery() - ->fetchColumn(); - } - - // fetchPairs doesn't seem to work when using the same column with - // different aliases twice - $res = array(); - foreach ($names as $name) { - $res[$name] = $name; - } - - return $res; - } - - protected function enumHostList() - { - if ($this->useIcingaDbBackend()) { - $names = (new IcingaDbObject())->yieldHostnames(); - } else { - $names = $this->backend - ->select() - ->from('hostStatus', ['hostname' => 'host_name']) - ->applyFilter(MonitoringRestrictions::getRestriction('monitoring/filter/objects')) - ->order('host_name') - ->getQuery() - ->fetchColumn(); - } - - // fetchPairs doesn't seem to work when using the same column with - // different aliases twice - $res = array(); - foreach ($names as $name) { - $res[BpConfig::joinNodeName($name, 'Hoststatus')] = $name; - } - - return $res; - } - - protected function enumServiceList($host) - { - if ($this->useIcingaDbBackend()) { - $names = (new IcingaDbObject())->yieldServicenames($host); - } else { - $names = $this->backend - ->select() - ->from('serviceStatus', ['service' => 'service_description']) - ->where('host_name', $host) - ->applyFilter(MonitoringRestrictions::getRestriction('monitoring/filter/objects')) - ->order('service_description') - ->getQuery() - ->fetchColumn(); - } - - $services = array(); - foreach ($names as $name) { - $services[BpConfig::joinNodeName($host, $name)] = $name; - } - - return $services; - } - - protected function enumHostListByFilter($filter) - { - if ($this->useIcingaDbBackend()) { - $names = (new IcingaDbObject())->yieldHostnames($filter); - } else { - $names = $this->backend - ->select() - ->from('hostStatus', ['hostname' => 'host_name']) - ->applyFilter(Filter::fromQueryString($filter)) - ->applyFilter(MonitoringRestrictions::getRestriction('monitoring/filter/objects')) - ->order('host_name') - ->getQuery() - ->fetchColumn(); - } - - // fetchPairs doesn't seem to work when using the same column with - // different aliases twice - $res = array(); - foreach ($names as $name) { - $res[BpConfig::joinNodeName($name, 'Hoststatus')] = $name; - } - - return $res; - } - - protected function enumServiceListByFilter($filter) - { - $services = array(); - - if ($this->useIcingaDbBackend()) { - $objects = (new IcingaDbObject())->fetchServices($filter); - foreach ($objects as $object) { - $services[BpConfig::joinNodeName($object->host->name, $object->name)] - = $object->host->name . ':' . $object->name; - } - } else { - $objects = $this->backend - ->select() - ->from('serviceStatus', ['host' => 'host_name', 'service' => 'service_description']) - ->applyFilter(Filter::fromQueryString($filter)) - ->applyFilter(MonitoringRestrictions::getRestriction('monitoring/filter/objects')) - ->order('service_description') - ->getQuery() - ->fetchAll(); - foreach ($objects as $object) { - $services[BpConfig::joinNodeName($object->host, $object->service)] - = $object->host . ':' . $object->service; - } - } - - return $services; - } - - protected function enumHostStateList() - { - $hostStateList = [ - 0 => $this->translate('UP'), - 1 => $this->translate('DOWN'), - 99 => $this->translate('PENDING') - ]; - - return $hostStateList; - } - - protected function enumServiceStateList() - { - $serviceStateList = [ - 0 => $this->translate('OK'), - 1 => $this->translate('WARNING'), - 2 => $this->translate('CRITICAL'), - 3 => $this->translate('UNKNOWN'), - 99 => $this->translate('PENDING'), - ]; - - return $serviceStateList; - } - - protected function useIcingaDbBackend() - { - if (Module::exists('icingadb')) { - return ! $this->bp->hasBackendName() && IcingadbSupport::useIcingaDbAsBackend(); - } - - return false; - } -}