Version 2.5.0
-----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEjeMGOyH1H9MofRhTOqKMoN1x5FcFAmUnn6sACgkQOqKMoN1x 5FcqhQ/9Hcz52MHnnp6OBHOnUxEwToLS2yYqA0zFCqLYjwWzLWTg8B+YSd5DPHd+ dK1QZs/8bNc8q4ebLzwOOx0V+QgpRhkO90ixtmJkGvQg5SBBQjWCJy5YIXm3qGh7 Ce5hsWv2EGTgZMW9FsULcsierE7j4iWa5AqZ9b2Nz7vpSoUkTojOJRLEKGm9a6+g /EyePSAo0vgwkLX2lK3gR5Q6MFRKV/PY4sX+Bx+YQElz2yJK3A3joUEpYXjCQ7Gl c0SQ3XCCfxmxmVVSfOCEd2X45WAGWSoWmF7ZfB6dfkT9QmJZ5tAHqilxdU8tsEF+ RnS+Jtl1DG/dN52xkDPUWfBzAqvQzupXZOAZ4MwUSAFEJazrAWMWddM1tdcXYKo5 1ts1EXAE9OLHMbnnlK7KrxaZJfsyKktYt0WGLIE9KGaAnxqLwtaWz1QyoMMeHZBV RgaWVfixkjPZIajVRIhEnI9dWcXk7s1AeHgEoQpvFpIeBwdzBLKNAUjyTIBKd+Qk Wkbzq808UR4UyWASqlto5ce/ovYI7QvXB6nCLCBDlHvbh7YLQBqmshHEq2PMIuoS rD0xxYYRD32QJSGRI6svqOmLruogx5/BRP+Zq+3akhUaj6Gqke6v94QR8RsAFeHt z91B6wSjVtwzk5EaP6g+U4aUR57psHdpekG68KDYZuasnxTteGo= =ktL6 -----END PGP SIGNATURE----- Merge tag 'v2.5.0' into feature/dashboard Version 2.5.0
2
.github/workflows/L10n-update.yml
vendored
|
|
@ -3,7 +3,7 @@ name: L10n Update
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
|
||||
jobs:
|
||||
trigger-update:
|
||||
|
|
|
|||
67
.github/workflows/php.yml
vendored
|
|
@ -3,11 +3,11 @@ name: PHP Tests
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- release/*
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
|
|
@ -17,12 +17,12 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: ['7.2', '7.3', '7.4', '8.0', '8.1']
|
||||
php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
|
||||
os: ['ubuntu-latest']
|
||||
|
||||
steps:
|
||||
- name: Checkout code base
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
|
|
@ -31,12 +31,65 @@ jobs:
|
|||
tools: phpcs
|
||||
|
||||
- name: Setup dependencies
|
||||
run: composer require -n --no-progress overtrue/phplint
|
||||
run: |
|
||||
composer require -n --no-progress overtrue/phplint
|
||||
git clone --depth 1 https://github.com/Icinga/icingaweb2.git vendor/icingaweb2
|
||||
git clone --depth 1 https://github.com/Icinga/icingadb-web.git vendor/icingadb-web
|
||||
git clone --depth 1 https://github.com/Icinga/icingaweb2-module-director.git vendor/icingaweb2-module-director
|
||||
git clone --depth 1 -b snapshot/nightly https://github.com/Icinga/icinga-php-library.git vendor/icinga-php-library
|
||||
git clone --depth 1 -b snapshot/nightly https://github.com/Icinga/icinga-php-thirdparty.git vendor/icinga-php-thirdparty
|
||||
|
||||
- name: PHP Lint
|
||||
if: success() || matrix.allow_failure
|
||||
if: ${{ ! cancelled() }}
|
||||
run: ./vendor/bin/phplint -n --exclude={^vendor/.*} -- .
|
||||
|
||||
- name: PHP CodeSniffer
|
||||
if: success() || matrix.allow_failure
|
||||
if: ${{ ! cancelled() }}
|
||||
run: phpcs
|
||||
|
||||
- name: PHPStan
|
||||
if: ${{ ! cancelled() }}
|
||||
uses: php-actions/phpstan@v3
|
||||
|
||||
test:
|
||||
name: Unit tests with php ${{ matrix.php }} on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
env:
|
||||
phpunit-version: 8.5
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
|
||||
os: ['ubuntu-latest']
|
||||
|
||||
steps:
|
||||
- name: Checkout code base
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: phpunit:${{ matrix.phpunit-version || env.phpunit-version }}
|
||||
|
||||
- name: Setup Icinga Web
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/Icinga/icingaweb2.git _icingaweb2
|
||||
ln -s `pwd` _icingaweb2/modules/businessprocess
|
||||
|
||||
- name: Setup Libraries
|
||||
run: |
|
||||
mkdir _libraries
|
||||
git clone --depth 1 -b snapshot/nightly https://github.com/Icinga/icinga-php-library.git _libraries/ipl
|
||||
git clone --depth 1 -b snapshot/nightly https://github.com/Icinga/icinga-php-thirdparty.git _libraries/vendor
|
||||
|
||||
- name: Setup dependencies
|
||||
run: composer require -d _icingaweb2 -n --no-progress mockery/mockery
|
||||
|
||||
- name: PHPUnit
|
||||
env:
|
||||
ICINGAWEB_LIBDIR: _libraries
|
||||
ICINGAWEB_CONFIGDIR: test/config
|
||||
run: phpunit --verbose --bootstrap _icingaweb2/test/php/bootstrap.php
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
stages:
|
||||
- Coding Standards
|
||||
- Unit-Tests
|
||||
- Build Packages
|
||||
|
||||
variables:
|
||||
BASE_VERSION: "2.0.0"
|
||||
VERSION_SUFFIX: "-b${CI_BUILD_ID}-${CI_BUILD_REF_SLUG}"
|
||||
|
||||
PSR2 CS Test:
|
||||
stage: Coding Standards
|
||||
tags:
|
||||
- xenial
|
||||
script:
|
||||
- phpcs --report-width=auto --report-full --report-gitblame --report-summary -p --standard=PSR2 --extensions=php --encoding=utf-8 -w -s library/Businessprocess/ application/ configuration.php run.php test
|
||||
|
||||
Ubuntu Xenial:
|
||||
stage: Unit-Tests
|
||||
tags:
|
||||
- xenial
|
||||
- businessprocess
|
||||
script:
|
||||
- phpunit --testdox --coverage-html=coverage || phpunit --verbose
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
name: code-coverage
|
||||
paths:
|
||||
- coverage/*
|
||||
|
||||
Debian Jessie:
|
||||
stage: Unit-Tests
|
||||
tags:
|
||||
- jessie
|
||||
- businessprocess
|
||||
script:
|
||||
- phpunit --testdox || phpunit --verbose
|
||||
|
||||
CentOS 6:
|
||||
stage: Unit-Tests
|
||||
tags:
|
||||
- centos6
|
||||
- businessprocess
|
||||
script:
|
||||
- phpunit --testdox || phpunit --verbose
|
||||
|
||||
CentOS 7:
|
||||
stage: Unit-Tests
|
||||
tags:
|
||||
- centos7
|
||||
- businessprocess
|
||||
script:
|
||||
- phpunit --testdox || phpunit --verbose
|
||||
|
||||
Xenial Packages:
|
||||
stage: Build Packages
|
||||
tags:
|
||||
- xenial
|
||||
- businessprocess
|
||||
script:
|
||||
- cp -a packaging/debian debian
|
||||
- dch --no-conf -U -M --empty -v "${BASE_VERSION}${VERSION_SUFFIX}-${CI_BUILD_REF:0:7}" "Automated build triggered by ${GITLAB_USER_ID} <${GITLAB_USER_EMAIL}>"
|
||||
- cp LICENSE debian/copyright
|
||||
- dpkg-buildpackage -us -uc
|
||||
- mkdir build
|
||||
- mv ../icingaweb2-module-businessprocess*.deb build/
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
paths:
|
||||
- build/*
|
||||
|
||||
Jessie Packages:
|
||||
stage: Build Packages
|
||||
tags:
|
||||
- jessie
|
||||
- businessprocess
|
||||
script:
|
||||
- cp -a packaging/debian debian
|
||||
- dch --no-conf -U -M --empty -v "${BASE_VERSION}${VERSION_SUFFIX}-${CI_BUILD_REF:0:7}" "Automated build triggered by ${GITLAB_USER_ID} <${GITLAB_USER_EMAIL}>"
|
||||
- cp LICENSE debian/copyright
|
||||
- dpkg-buildpackage -us -uc
|
||||
- mkdir build
|
||||
- mv ../icingaweb2-module-businessprocess*.deb build/
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
paths:
|
||||
- build/*
|
||||
31
.travis.yml
|
|
@ -1,31 +0,0 @@
|
|||
language: php
|
||||
php:
|
||||
- '5.4'
|
||||
- '5.5'
|
||||
- '5.6'
|
||||
- '7.0'
|
||||
- '7.1'
|
||||
- '7.2'
|
||||
- nightly
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- php: '5.4'
|
||||
- php: '5.5'
|
||||
- php: nightly
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- vendor
|
||||
|
||||
env:
|
||||
- ICINGAWEB_VERSION=2.6.2
|
||||
- IPL_VERSION=0.1.1
|
||||
|
||||
before_script:
|
||||
- ./test/setup_vendor.sh
|
||||
|
||||
script:
|
||||
- php vendor/phpcs.phar
|
||||
- php vendor/phpunit.phar --testdox || php vendor/phpunit.phar --verbose
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
# Icinga Business Process Modeling
|
||||
|
||||
[](https://php.net/)
|
||||

|
||||
[](https://github.com/Icinga/icingaweb2-module-businessprocess)
|
||||
[](https://github.com/Icinga/icingaweb2-module-businessprocess/actions/workflows/php.yml)
|
||||
[](https://github.com/Icinga/icingaweb2-module-businessprocess/releases/latest)
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
106
application/clicommands/CleanupCommand.php
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Businessprocess\Clicommands;
|
||||
|
||||
use Exception;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Application\Modules\Module;
|
||||
use Icinga\Cli\Command;
|
||||
use Icinga\Module\Businessprocess\Modification\NodeRemoveAction;
|
||||
use Icinga\Module\Businessprocess\ProvidedHook\Icingadb\IcingadbSupport;
|
||||
use Icinga\Module\Businessprocess\State\IcingaDbState;
|
||||
use Icinga\Module\Businessprocess\State\MonitoringState;
|
||||
use Icinga\Module\Businessprocess\Storage\LegacyStorage;
|
||||
|
||||
class CleanupCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var LegacyStorage
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
protected $defaultActionName = 'cleanup';
|
||||
|
||||
public function init()
|
||||
{
|
||||
$this->storage = LegacyStorage::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup all missing monitoring nodes from the specified config name
|
||||
* If no config name is specified, the missing nodes are cleaned from all available configs.
|
||||
* Invalid config files and file names are ignored
|
||||
*
|
||||
* USAGE
|
||||
*
|
||||
* icingacli businessprocess cleanup [<config-name>]
|
||||
*
|
||||
* OPTIONS
|
||||
*
|
||||
* <config-name>
|
||||
*/
|
||||
public function cleanupAction(): void
|
||||
{
|
||||
$configNames = (array) $this->params->shift() ?: $this->storage->listAllProcessNames();
|
||||
$foundMissingNode = false;
|
||||
foreach ($configNames as $configName) {
|
||||
if (! $this->storage->hasProcess($configName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$bp = $this->storage->loadProcess($configName);
|
||||
} catch (Exception $e) {
|
||||
Logger::error(
|
||||
'Failed to scan the %s.conf file for missing nodes. Faulty config found.',
|
||||
$configName
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Module::exists('icingadb')
|
||||
&& (! $bp->hasBackendName() && IcingadbSupport::useIcingaDbAsBackend())
|
||||
) {
|
||||
IcingaDbState::apply($bp);
|
||||
} else {
|
||||
MonitoringState::apply($bp);
|
||||
}
|
||||
|
||||
$removedNodes = [];
|
||||
foreach (array_keys($bp->getMissingChildren()) as $missingNode) {
|
||||
$node = $bp->getNode($missingNode);
|
||||
$remove = new NodeRemoveAction($node);
|
||||
|
||||
try {
|
||||
if ($remove->appliesTo($bp)) {
|
||||
$remove->applyTo($bp);
|
||||
$removedNodes[] = $node->getName();
|
||||
$this->storage->storeProcess($bp);
|
||||
$bp->clearAppliedChanges();
|
||||
|
||||
$foundMissingNode = true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Logger::error(sprintf('(%s.conf) %s', $configName, $e->getMessage()));
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($removedNodes)) {
|
||||
echo sprintf(
|
||||
'Removed following %d missing node(s) from %s.conf successfully:',
|
||||
count($removedNodes),
|
||||
$configName
|
||||
);
|
||||
|
||||
echo "\n" . implode("\n", $removedNodes) . "\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (! $foundMissingNode) {
|
||||
echo "No missing node found.\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -110,8 +110,8 @@ class ProcessCommand extends Command
|
|||
exit(1);
|
||||
}
|
||||
|
||||
$name = $this->params->get('config');
|
||||
try {
|
||||
$name = $this->params->get('config');
|
||||
if ($name === null) {
|
||||
$name = $this->getFirstProcessName();
|
||||
}
|
||||
|
|
@ -132,8 +132,8 @@ class ProcessCommand extends Command
|
|||
}
|
||||
}
|
||||
|
||||
/** @var BpNode $node */
|
||||
try {
|
||||
/** @var BpNode $node */
|
||||
$node = $bp->getNode($nodeName);
|
||||
if (Module::exists('icingadb')
|
||||
&& (! $bp->hasBackendName() && IcingadbSupport::useIcingaDbAsBackend())
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use Icinga\Module\Businessprocess\IcingaDbObject;
|
|||
use Icinga\Module\Businessprocess\ProvidedHook\Icingadb\IcingadbSupport;
|
||||
use Icinga\Module\Icingadb\Model\Host;
|
||||
use Icinga\Module\Monitoring\Controller;
|
||||
use Icinga\Module\Monitoring\DataView\DataView;
|
||||
use Icinga\Web\Url;
|
||||
use ipl\Stdlib\Filter;
|
||||
|
||||
|
|
@ -54,7 +55,8 @@ class HostController extends Controller
|
|||
->from('hoststatus', array('host_name'))
|
||||
->where('host_name', $hostName);
|
||||
|
||||
if ($this->applyRestriction('monitoring/filter/objects', $query)->fetchRow() !== false) {
|
||||
$this->applyRestriction('monitoring/filter/objects', $query);
|
||||
if ($query->fetchRow() !== false) {
|
||||
$this->redirectNow(Url::fromPath('monitoring/host/show')->setParams($this->params));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Icinga\Module\Businessprocess\Controllers;
|
||||
|
||||
use Exception;
|
||||
use Icinga\Application\Modules\Module;
|
||||
use Icinga\Module\Businessprocess\ProvidedHook\Icingadb\IcingadbSupport;
|
||||
use Icinga\Module\Businessprocess\Renderer\Breadcrumb;
|
||||
|
|
@ -11,6 +12,8 @@ use Icinga\Module\Businessprocess\State\IcingaDbState;
|
|||
use Icinga\Module\Businessprocess\State\MonitoringState;
|
||||
use Icinga\Module\Businessprocess\Web\Controller;
|
||||
use Icinga\Module\Businessprocess\Web\Url;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Web\Widget\Link;
|
||||
|
||||
class NodeController extends Controller
|
||||
{
|
||||
|
|
@ -24,9 +27,16 @@ class NodeController extends Controller
|
|||
$name = $this->params->get('name');
|
||||
$this->addTitle($this->translate('Business Impact (%s)'), $name);
|
||||
|
||||
$brokenFiles = [];
|
||||
$simulation = Simulation::fromSession($this->session());
|
||||
foreach ($this->storage()->listProcessNames() as $configName) {
|
||||
$config = $this->storage()->loadProcess($configName);
|
||||
try {
|
||||
$config = $this->storage()->loadProcess($configName);
|
||||
} catch (Exception $e) {
|
||||
$meta = $this->storage()->loadMetadata($configName);
|
||||
$brokenFiles[$meta->get('Title')] = $configName;
|
||||
continue;
|
||||
}
|
||||
|
||||
$parents = [];
|
||||
if ($config->hasNode($name)) {
|
||||
|
|
@ -108,5 +118,31 @@ class NodeController extends Controller
|
|||
if ($content->isEmpty()) {
|
||||
$content->add($this->translate('No impact detected. Is this node part of a business process?'));
|
||||
}
|
||||
|
||||
if (! empty($brokenFiles)) {
|
||||
$elem = Html::tag(
|
||||
'ul',
|
||||
['class' => 'broken-files'],
|
||||
tp(
|
||||
'The following business process has an invalid config file and therefore cannot be read:',
|
||||
'The following business processes have invalid config files and therefore cannot be read:',
|
||||
count($brokenFiles)
|
||||
)
|
||||
);
|
||||
|
||||
foreach ($brokenFiles as $bpName => $fileName) {
|
||||
$elem->addHtml(
|
||||
Html::tag(
|
||||
'li',
|
||||
new Link(
|
||||
sprintf('%s (%s.conf)', $bpName, $fileName),
|
||||
\ipl\Web\Url::fromPath('businessprocess/process/show', ['config' => $fileName])
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$content->addHtml($elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ 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\Forms\EditNodeForm;
|
||||
use Icinga\Module\Businessprocess\Node;
|
||||
use Icinga\Module\Businessprocess\ProvidedHook\Icingadb\IcingadbSupport;
|
||||
use Icinga\Module\Businessprocess\Renderer\Breadcrumb;
|
||||
|
|
@ -26,8 +28,16 @@ use Icinga\Web\Notification;
|
|||
use Icinga\Web\Url;
|
||||
use Icinga\Web\Widget\Tabextension\DashboardAction;
|
||||
use Icinga\Web\Widget\Tabextension\OutputFormat;
|
||||
use ipl\Html\Form;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Html\HtmlElement;
|
||||
use ipl\Html\HtmlString;
|
||||
use ipl\Html\TemplateString;
|
||||
use ipl\Html\Text;
|
||||
use ipl\Web\Control\SortControl;
|
||||
use ipl\Web\FormElement\TermInput;
|
||||
use ipl\Web\Widget\Link;
|
||||
use ipl\Web\Widget\Icon;
|
||||
|
||||
class ProcessController extends Controller
|
||||
{
|
||||
|
|
@ -114,15 +124,7 @@ class ProcessController extends Controller
|
|||
|
||||
$this->tabs()->extend(new OutputFormat());
|
||||
|
||||
$missing = $bp->getMissingChildren();
|
||||
if (! empty($missing)) {
|
||||
if (($count = count($missing)) > 10) {
|
||||
$missing = array_slice($missing, 0, 10);
|
||||
$missing[] = '...';
|
||||
}
|
||||
$bp->addError('There are %d missing nodes: %s', $count, implode(', ', $missing));
|
||||
}
|
||||
$this->content()->add($this->showHints($bp));
|
||||
$this->content()->add($this->showHints($bp, $renderer));
|
||||
$this->content()->add($this->showWarnings($bp));
|
||||
$this->content()->add($this->showErrors($bp));
|
||||
$this->content()->add($renderer);
|
||||
|
|
@ -130,6 +132,50 @@ class ProcessController extends Controller
|
|||
$this->setDynamicAutorefresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sort control and apply its sort specification to the given renderer
|
||||
*
|
||||
* @param Renderer $renderer
|
||||
* @param BpConfig $config
|
||||
*
|
||||
* @return SortControl
|
||||
*/
|
||||
protected function createBpSortControl(Renderer $renderer, BpConfig $config): SortControl
|
||||
{
|
||||
$defaultSort = $this->session()->get('sort.default', $renderer->getDefaultSort());
|
||||
$options = [
|
||||
'display_name asc' => $this->translate('Name'),
|
||||
'state desc' => $this->translate('State')
|
||||
];
|
||||
if ($config->getMetadata()->isManuallyOrdered()) {
|
||||
$options['manual asc'] = $this->translate('Manual');
|
||||
} elseif ($defaultSort === 'manual desc') {
|
||||
$defaultSort = $renderer->getDefaultSort();
|
||||
}
|
||||
|
||||
$sortControl = SortControl::create($options)
|
||||
->setDefault($defaultSort)
|
||||
->setMethod('POST')
|
||||
->setAttribute('name', 'bp-sort-control')
|
||||
->on(Form::ON_SUCCESS, function (SortControl $sortControl) use ($renderer) {
|
||||
$sort = $sortControl->getSort();
|
||||
if ($sort === $renderer->getDefaultSort()) {
|
||||
$this->session()->delete('sort.default');
|
||||
$url = Url::fromRequest()->without($sortControl->getSortParam());
|
||||
} else {
|
||||
$this->session()->set('sort.default', $sort);
|
||||
$url = Url::fromRequest()->with($sortControl->getSortParam(), $sort);
|
||||
}
|
||||
|
||||
$this->redirectNow($url);
|
||||
})->handleRequest($this->getServerRequest());
|
||||
|
||||
$renderer->setSort($sortControl->getSort());
|
||||
$this->params->shift($sortControl->getSortParam());
|
||||
|
||||
return $sortControl;
|
||||
}
|
||||
|
||||
protected function prepareControls($bp, $renderer)
|
||||
{
|
||||
$controls = $this->controls();
|
||||
|
|
@ -140,10 +186,9 @@ class ProcessController extends Controller
|
|||
'a',
|
||||
[
|
||||
'href' => $this->url()->without('showFullscreen')->without('view'),
|
||||
'title' => $this->translate('Leave full screen and switch back to normal mode'),
|
||||
'style' => 'float: right'
|
||||
'title' => $this->translate('Leave full screen and switch back to normal mode')
|
||||
],
|
||||
Html::tag('i', ['class' => 'icon icon-resize-small'])
|
||||
new Icon('down-left-and-up-right-to-center')
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -155,9 +200,11 @@ class ProcessController extends Controller
|
|||
$controls->add(Breadcrumb::create(clone $renderer));
|
||||
if (! $this->showFullscreen && ! $this->view->compact) {
|
||||
$controls->add(
|
||||
new RenderedProcessActionBar($bp, $renderer, $this->Auth(), $this->url())
|
||||
new RenderedProcessActionBar($bp, $renderer, $this->url())
|
||||
);
|
||||
}
|
||||
|
||||
$controls->addHtml($this->createBpSortControl($renderer, $bp));
|
||||
}
|
||||
|
||||
protected function getNode(BpConfig $bp)
|
||||
|
|
@ -225,21 +272,43 @@ class ProcessController extends Controller
|
|||
$canEdit = $bp->getMetadata()->canModify();
|
||||
|
||||
if ($action === 'add' && $canEdit) {
|
||||
$form = $this->loadForm('AddNode')
|
||||
->setSuccessUrl(Url::fromRequest()->without('action'))
|
||||
->setStorage($this->storage())
|
||||
$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')) {
|
||||
/** @var TermInput $childrenElement */
|
||||
$childrenElement = $form->getElement('children');
|
||||
foreach ($childrenElement->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)
|
||||
->setSession($this->session())
|
||||
->handleRequest();
|
||||
} elseif ($action === 'editmonitored' && $canEdit) {
|
||||
$form = $this->loadForm('EditNode')
|
||||
->setSuccessUrl(Url::fromRequest()->without('action'))
|
||||
$form = (new EditNodeForm())
|
||||
->setProcess($bp)
|
||||
->setNode($bp->getNode($this->params->get('editmonitorednode')))
|
||||
->setParentNode($node)
|
||||
->setSession($this->session())
|
||||
->handleRequest();
|
||||
->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'))
|
||||
|
|
@ -262,7 +331,21 @@ class ProcessController extends Controller
|
|||
->setSimulation(Simulation::fromSession($this->session()))
|
||||
->handleRequest();
|
||||
} elseif ($action === 'move') {
|
||||
$successUrl = $this->url()->without(['action', 'movenode']);
|
||||
if ($this->params->get('mode') === 'tree') {
|
||||
// If the user moves a node from a subtree, the `node` param exists
|
||||
$successUrl->getParams()->remove('node');
|
||||
}
|
||||
|
||||
if ($this->session()->get('sort.default')) {
|
||||
// If there's a default sort specification in the session, it can only be `display_name desc`,
|
||||
// as otherwise the user wouldn't be able to trigger this action. So it's safe to just define
|
||||
// descending manual order now.
|
||||
$successUrl->getParams()->add(SortControl::DEFAULT_SORT_PARAM, 'manual desc');
|
||||
}
|
||||
|
||||
$form = $this->loadForm('MoveNode')
|
||||
->setSuccessUrl($successUrl)
|
||||
->setProcess($bp)
|
||||
->setParentNode($node)
|
||||
->setSession($this->session())
|
||||
|
|
@ -285,8 +368,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);
|
||||
}
|
||||
|
|
@ -320,12 +406,14 @@ class ProcessController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
protected function showHints(BpConfig $bp)
|
||||
protected function showHints(BpConfig $bp, Renderer $renderer)
|
||||
{
|
||||
$ul = Html::tag('ul', ['class' => 'error']);
|
||||
$this->prepareMissingNodeLinks($ul);
|
||||
foreach ($bp->getErrors() as $error) {
|
||||
$ul->add(Html::tag('li')->setContent($error));
|
||||
$ul->addHtml(Html::tag('li', $error));
|
||||
}
|
||||
|
||||
if ($bp->hasChanges()) {
|
||||
$li = Html::tag('li')->setSeparator(' ');
|
||||
$li->add(sprintf(
|
||||
|
|
@ -359,6 +447,20 @@ class ProcessController extends Controller
|
|||
$ul->add($li);
|
||||
}
|
||||
|
||||
if (! $renderer->isLocked() && $renderer->appliesCustomSorting()) {
|
||||
$ul->addHtml(Html::tag('li', null, [
|
||||
Text::create($this->translate('Drag&Drop disabled. Custom sort order applied.')),
|
||||
(new Form())
|
||||
->setAttribute('class', 'inline')
|
||||
->addElement('submitButton', SortControl::DEFAULT_SORT_PARAM, [
|
||||
'label' => $this->translate('Reset to default'),
|
||||
'value' => $renderer->getDefaultSort(),
|
||||
'class' => 'link-button'
|
||||
])
|
||||
->addElement('hidden', 'uid', ['value' => 'bp-sort-control'])
|
||||
])->setSeparator(' '));
|
||||
}
|
||||
|
||||
if (! $ul->isEmpty()) {
|
||||
return $ul;
|
||||
} else {
|
||||
|
|
@ -366,6 +468,66 @@ class ProcessController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
protected function prepareMissingNodeLinks(HtmlElement $ul): void
|
||||
{
|
||||
$missing = array_keys($this->bp->getMissingChildren());
|
||||
if (! empty($missing)) {
|
||||
$missingLinkedNodes = null;
|
||||
foreach ($this->bp->getImportedNodes() as $process) {
|
||||
if ($process->hasMissingChildren()) {
|
||||
$missingLinkedNodes = array_keys($process->getMissingChildren());
|
||||
$link = Url::fromPath('businessprocess/process/show')
|
||||
->addParams(['config' => $process->getConfigName()]);
|
||||
|
||||
$ul->addHtml(Html::tag(
|
||||
'li',
|
||||
[
|
||||
TemplateString::create(
|
||||
tp(
|
||||
'Linked node %s has one missing child node: {{#link}}Show{{/link}}',
|
||||
'Linked node %s has %d missing child nodes: {{#link}}Show{{/link}}',
|
||||
count($missingLinkedNodes)
|
||||
),
|
||||
$process->getAlias(),
|
||||
count($missingLinkedNodes),
|
||||
['link' => new Link(null, (string) $link)]
|
||||
)
|
||||
]
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($missingLinkedNodes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$count = count($missing);
|
||||
if ($count > 10) {
|
||||
$missing = array_slice($missing, 0, 10);
|
||||
$missing[] = '...';
|
||||
}
|
||||
|
||||
$link = Url::fromPath('businessprocess/process/show')
|
||||
->addParams(['config' => $this->bp->getName(), 'action' => 'cleanup']);
|
||||
|
||||
$ul->addHtml(Html::tag(
|
||||
'li',
|
||||
[
|
||||
TemplateString::create(
|
||||
tp(
|
||||
'{{#link}}Cleanup{{/link}} one missing node: %2$s',
|
||||
'{{#link}}Cleanup{{/link}} %d missing nodes: %s',
|
||||
count($missing)
|
||||
),
|
||||
['link' => new Link(null, (string) $link)],
|
||||
$count,
|
||||
implode(', ', $missing)
|
||||
)
|
||||
]
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the source code for a process
|
||||
*/
|
||||
|
|
@ -447,7 +609,7 @@ class ProcessController extends Controller
|
|||
->setParams($this->getRequest()->getUrl()->getParams());
|
||||
$this->content()->add(
|
||||
$this->loadForm('bpConfig')
|
||||
->setProcessConfig($bp)
|
||||
->setProcess($bp)
|
||||
->setStorage($this->storage())
|
||||
->setSuccessUrl($url)
|
||||
->handleRequest()
|
||||
|
|
@ -464,10 +626,12 @@ class ProcessController extends Controller
|
|||
'a',
|
||||
[
|
||||
'href' => Url::fromPath('businessprocess/process/source', $params),
|
||||
'class' => 'icon-doc-text',
|
||||
'title' => $this->translate('Show source code')
|
||||
],
|
||||
$this->translate('Source')
|
||||
[
|
||||
new Icon('file-lines'),
|
||||
$this->translate('Source'),
|
||||
]
|
||||
));
|
||||
} else {
|
||||
$params = array(
|
||||
|
|
@ -479,10 +643,12 @@ class ProcessController extends Controller
|
|||
'a',
|
||||
[
|
||||
'href' => Url::fromPath('businessprocess/process/source', $params),
|
||||
'class' => 'icon-flapping',
|
||||
'title' => $this->translate('Highlight changes')
|
||||
],
|
||||
$this->translate('Diff')
|
||||
[
|
||||
new Icon('shuffle'),
|
||||
$this->translate('Diff')
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -490,11 +656,13 @@ class ProcessController extends Controller
|
|||
'a',
|
||||
[
|
||||
'href' => Url::fromPath('businessprocess/process/download', ['config' => $config->getName()]),
|
||||
'class' => 'icon-download',
|
||||
'target' => '_blank',
|
||||
'title' => $this->translate('Download process configuration')
|
||||
],
|
||||
$this->translate('Download')
|
||||
[
|
||||
new Icon('download'),
|
||||
$this->translate('Download')
|
||||
]
|
||||
));
|
||||
|
||||
return $actionBar;
|
||||
|
|
@ -584,7 +752,7 @@ class ProcessController extends Controller
|
|||
if (isset($node['since'])) {
|
||||
$data[] = DateFormatter::formatDateTime($node['since']);
|
||||
}
|
||||
|
||||
|
||||
if (isset($node['in_downtime'])) {
|
||||
$data[] = $node['in_downtime'];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use Icinga\Module\Businessprocess\IcingaDbObject;
|
|||
use Icinga\Module\Businessprocess\ProvidedHook\Icingadb\IcingadbSupport;
|
||||
use Icinga\Module\Icingadb\Model\Service;
|
||||
use Icinga\Module\Monitoring\Controller;
|
||||
use Icinga\Module\Monitoring\DataView\DataView;
|
||||
use Icinga\Web\Url;
|
||||
use ipl\Stdlib\Filter;
|
||||
|
||||
|
|
@ -61,7 +62,8 @@ class ServiceController extends Controller
|
|||
->where('host_name', $hostName)
|
||||
->where('service_description', $serviceName);
|
||||
|
||||
if ($this->applyRestriction('monitoring/filter/objects', $query)->fetchRow() !== false) {
|
||||
$this->applyRestriction('monitoring/filter/objects', $query);
|
||||
if ($query->fetchRow() !== false) {
|
||||
$this->redirectNow(Url::fromPath('monitoring/service/show')->setParams($this->params));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
372
application/controllers/SuggestionsController.php
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Businessprocess\Controllers;
|
||||
|
||||
use Exception;
|
||||
use Icinga\Data\Filter\Filter as LegacyFilter;
|
||||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Module\Businessprocess\BpNode;
|
||||
use Icinga\Module\Businessprocess\HostNode;
|
||||
use Icinga\Module\Businessprocess\IcingaDbObject;
|
||||
use Icinga\Module\Businessprocess\ImportedNode;
|
||||
use Icinga\Module\Businessprocess\Monitoring\DataView\HostStatus;
|
||||
use Icinga\Module\Businessprocess\Monitoring\DataView\ServiceStatus;
|
||||
use Icinga\Module\Businessprocess\MonitoringRestrictions;
|
||||
use Icinga\Module\Businessprocess\ServiceNode;
|
||||
use Icinga\Module\Businessprocess\Web\Controller;
|
||||
use Icinga\Module\Icingadb\Model\Host;
|
||||
use Icinga\Module\Icingadb\Model\Service;
|
||||
use ipl\Stdlib\Filter;
|
||||
use ipl\Web\FormElement\TermInput\TermSuggestions;
|
||||
|
||||
class SuggestionsController extends Controller
|
||||
{
|
||||
public function processAction()
|
||||
{
|
||||
$ignoreList = [];
|
||||
$forConfig = null;
|
||||
$forParent = null;
|
||||
if ($this->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', '<service> on <host>'),
|
||||
$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', '<service> on <host>'),
|
||||
$row->service_display_name,
|
||||
$row->host_display_name
|
||||
)
|
||||
];
|
||||
}
|
||||
})());
|
||||
|
||||
$this->getDocument()->addHtml($suggestions->forRequest($this->getServerRequest()));
|
||||
}
|
||||
}
|
||||
|
|
@ -2,578 +2,411 @@
|
|||
|
||||
namespace Icinga\Module\Businessprocess\Forms;
|
||||
|
||||
use Icinga\Module\Businessprocess\BpNode;
|
||||
use Exception;
|
||||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Module\Businessprocess\Common\EnumList;
|
||||
use Icinga\Module\Businessprocess\ImportedNode;
|
||||
use Icinga\Module\Businessprocess\BpNode;
|
||||
use Icinga\Module\Businessprocess\Common\Sort;
|
||||
use Icinga\Module\Businessprocess\Modification\ProcessChanges;
|
||||
use Icinga\Module\Businessprocess\Node;
|
||||
use Icinga\Module\Businessprocess\Storage\Storage;
|
||||
use Icinga\Module\Businessprocess\Web\Form\QuickForm;
|
||||
use Icinga\Module\Businessprocess\Web\Form\Validator\NoDuplicateChildrenValidator;
|
||||
use Icinga\Module\Businessprocess\Web\Form\Element\IplStateOverrides;
|
||||
use Icinga\Module\Businessprocess\Web\Form\Validator\HostServiceTermValidator;
|
||||
use Icinga\Module\Monitoring\Backend\MonitoringBackend;
|
||||
use Icinga\Web\Session\SessionNamespace;
|
||||
use ipl\Sql\Connection as IcingaDbConnection;
|
||||
use ipl\Html\HtmlElement;
|
||||
use ipl\Html\Text;
|
||||
use ipl\I18n\Translation;
|
||||
use ipl\Stdlib\Str;
|
||||
use ipl\Web\Compat\CompatForm;
|
||||
use ipl\Web\FormElement\TermInput;
|
||||
use ipl\Web\Url;
|
||||
|
||||
class AddNodeForm extends QuickForm
|
||||
class AddNodeForm extends CompatForm
|
||||
{
|
||||
use EnumList;
|
||||
|
||||
/** @var MonitoringBackend|IcingaDbConnection*/
|
||||
protected $backend;
|
||||
use Sort;
|
||||
use Translation;
|
||||
|
||||
/** @var Storage */
|
||||
protected $storage;
|
||||
|
||||
/** @var BpConfig */
|
||||
/** @var ?BpConfig */
|
||||
protected $bp;
|
||||
|
||||
/** @var BpNode */
|
||||
/** @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the storage to use
|
||||
*
|
||||
* @param Storage $storage
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setStorage(Storage $storage)
|
||||
public function setStorage(Storage $storage): self
|
||||
{
|
||||
$this->storage = $storage;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BpConfig $process
|
||||
* Set the affected configuration
|
||||
*
|
||||
* @param BpConfig $bp
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setProcess(BpConfig $process)
|
||||
public function setProcess(BpConfig $bp): self
|
||||
{
|
||||
$this->bp = $process;
|
||||
$this->setBackend($process->getBackend());
|
||||
$this->bp = $bp;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BpNode|null $node
|
||||
* Set the affected sub-process
|
||||
*
|
||||
* @param ?BpNode $node
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setParentNode(BpNode $node = null)
|
||||
public function setParentNode(BpNode $node = null): self
|
||||
{
|
||||
$this->parent = $node;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasParentNode()
|
||||
{
|
||||
return $this->parent !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user's session
|
||||
*
|
||||
* @param SessionNamespace $session
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSession(SessionNamespace $session)
|
||||
public function setSession(SessionNamespace $session): self
|
||||
{
|
||||
$this->session = $session;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function hasProcesses()
|
||||
protected function assemble()
|
||||
{
|
||||
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->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 = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (! $this->bp->getMetadata()->isManuallyOrdered()) {
|
||||
natcasesort($list);
|
||||
$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';
|
||||
}
|
||||
return $list;
|
||||
|
||||
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 hasMoreConfigs()
|
||||
protected function assembleNewProcessElements(): void
|
||||
{
|
||||
$configs = $this->enumConfigs();
|
||||
return !empty($configs);
|
||||
$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 enumConfigs()
|
||||
protected function assembleExistingProcessElements(): void
|
||||
{
|
||||
return $this->storage->listProcesses();
|
||||
$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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
protected function assembleHostElements(): void
|
||||
{
|
||||
foreach ($node->getParents() as $parent) {
|
||||
$parents[$parent->getName()] = $parent;
|
||||
$this->collectAllParents($parent, $parents);
|
||||
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')
|
||||
]
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
public function onSuccess()
|
||||
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);
|
||||
switch ($this->getValue('node_type')) {
|
||||
case 'host':
|
||||
case 'service':
|
||||
|
||||
$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 {
|
||||
/** @var TermInput $term */
|
||||
$term = $this->getElement('children');
|
||||
$children = array_unique(array_map(function ($term) {
|
||||
return $term->getSearchValue();
|
||||
}, $term->getTerms()));
|
||||
|
||||
if ($nodeType === 'host' || $nodeType === 'service') {
|
||||
$stateOverrides = $this->getValue('stateOverrides');
|
||||
if (! empty($stateOverrides)) {
|
||||
$childOverrides = [];
|
||||
foreach ($this->getValue('children') as $service) {
|
||||
$childOverrides[$service] = $stateOverrides;
|
||||
foreach ($children as $nodeName) {
|
||||
$childOverrides[$nodeName] = $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);
|
||||
}
|
||||
if ($this->parent !== null) {
|
||||
$changes->addChildrenToNode($children, $this->parent);
|
||||
} else {
|
||||
foreach ($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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,12 +23,19 @@ class BpConfigForm extends BpConfigBaseForm
|
|||
'max' => 40
|
||||
)
|
||||
),
|
||||
array(
|
||||
[
|
||||
'validator' => 'Regex',
|
||||
'options' => array(
|
||||
'pattern' => '/^[a-zA-Z0-9](?:[a-zA-Z0-9 ._-]*)?[a-zA-Z0-9_]$/'
|
||||
)
|
||||
)
|
||||
'options' => [
|
||||
'pattern' => '/^[a-zA-Z0-9](?:[\w\h._-]*)?\w$/',
|
||||
'messages' => [
|
||||
'regexNotMatch' => $this->translate(
|
||||
'Id must only consist of alphanumeric characters.'
|
||||
. ' Underscore at the beginning and space, dot and hyphen at the beginning'
|
||||
. ' and end are not allowed.'
|
||||
)
|
||||
]
|
||||
]
|
||||
]
|
||||
),
|
||||
'description' => $this->translate(
|
||||
'This is the unique identifier of this process'
|
||||
|
|
@ -109,12 +116,12 @@ class BpConfigForm extends BpConfigBaseForm
|
|||
),
|
||||
));
|
||||
|
||||
if ($this->config === null) {
|
||||
if ($this->bp === null) {
|
||||
$this->setSubmitLabel(
|
||||
$this->translate('Add')
|
||||
);
|
||||
} else {
|
||||
$config = $this->config;
|
||||
$config = $this->bp;
|
||||
|
||||
$meta = $config->getMetadata();
|
||||
foreach ($meta->getProperties() as $k => $v) {
|
||||
|
|
@ -149,13 +156,13 @@ class BpConfigForm extends BpConfigBaseForm
|
|||
$name = $this->getValue('name');
|
||||
|
||||
if ($this->shouldBeDeleted()) {
|
||||
if ($this->config->isReferenced()) {
|
||||
if ($this->bp->isReferenced()) {
|
||||
$this->addError(sprintf(
|
||||
$this->translate('Process "%s" cannot be deleted as it has been referenced in other processes'),
|
||||
$name
|
||||
));
|
||||
} else {
|
||||
$this->config->clearAppliedChanges();
|
||||
$this->bp->clearAppliedChanges();
|
||||
$this->storage->deleteProcess($name);
|
||||
$this->setSuccessUrl('businessprocess');
|
||||
$this->redirectOnSuccess(sprintf('Process %s has been deleted', $name));
|
||||
|
|
@ -167,7 +174,7 @@ class BpConfigForm extends BpConfigBaseForm
|
|||
{
|
||||
$name = $this->getValue('name');
|
||||
|
||||
if ($this->config === null) {
|
||||
if ($this->bp === null) {
|
||||
if ($this->storage->hasProcess($name)) {
|
||||
$this->addError(sprintf(
|
||||
$this->translate('A process named "%s" already exists'),
|
||||
|
|
@ -192,7 +199,7 @@ class BpConfigForm extends BpConfigBaseForm
|
|||
);
|
||||
$this->setSuccessMessage(sprintf('Process %s has been created', $name));
|
||||
} else {
|
||||
$config = $this->config;
|
||||
$config = $this->bp;
|
||||
$this->setSuccessMessage(sprintf('Process %s has been stored', $name));
|
||||
}
|
||||
$meta = $config->getMetadata();
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ use Icinga\Web\Notification;
|
|||
|
||||
class BpUploadForm extends BpConfigBaseForm
|
||||
{
|
||||
protected $backend;
|
||||
|
||||
protected $node;
|
||||
|
||||
protected $objectList = array();
|
||||
|
|
@ -49,12 +47,19 @@ class BpUploadForm extends BpConfigBaseForm
|
|||
'max' => 40
|
||||
)
|
||||
),
|
||||
array(
|
||||
[
|
||||
'validator' => 'Regex',
|
||||
'options' => array(
|
||||
'pattern' => '/^[a-zA-Z0-9](?:[a-zA-Z0-9 ._-]*)?[a-zA-Z0-9_]$/'
|
||||
)
|
||||
)
|
||||
'options' => [
|
||||
'pattern' => '/^[a-zA-Z0-9](?:[\w\h._-]*)?\w$/',
|
||||
'messages' => [
|
||||
'regexNotMatch' => $this->translate(
|
||||
'Id must only consist of alphanumeric characters.'
|
||||
. ' Underscore at the beginning and space, dot and hyphen at the beginning'
|
||||
. ' and end are not allowed.'
|
||||
)
|
||||
]
|
||||
]
|
||||
]
|
||||
),
|
||||
));
|
||||
|
||||
|
|
@ -150,7 +155,7 @@ class BpUploadForm extends BpConfigBaseForm
|
|||
|
||||
protected function processUploadedSource()
|
||||
{
|
||||
/** @var \Zend_Form_Element_File $el */
|
||||
/** @var ?\Zend_Form_Element_File $el */
|
||||
$el = $this->getElement('uploaded_file');
|
||||
|
||||
if ($el && $this->hasBeenSent()) {
|
||||
|
|
|
|||
61
application/forms/CleanupNodeForm.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Businessprocess\Forms;
|
||||
|
||||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Module\Businessprocess\Modification\ProcessChanges;
|
||||
use Icinga\Module\Businessprocess\Web\Form\BpConfigBaseForm;
|
||||
use Icinga\Module\Monitoring\Backend\MonitoringBackend;
|
||||
use Icinga\Web\Session\SessionNamespace;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Sql\Connection as IcingaDbConnection;
|
||||
|
||||
class CleanupNodeForm extends BpConfigBaseForm
|
||||
{
|
||||
/** @var MonitoringBackend|IcingaDbConnection */
|
||||
protected $backend;
|
||||
|
||||
/** @var BpConfig */
|
||||
protected $bp;
|
||||
|
||||
/** @var SessionNamespace */
|
||||
protected $session;
|
||||
|
||||
public function setup()
|
||||
{
|
||||
$this->addHtml(Html::tag('h2', $this->translate('Cleanup missing nodes')));
|
||||
|
||||
$this->addElement('checkbox', 'cleanup_all', [
|
||||
'class' => 'autosubmit',
|
||||
'label' => $this->translate('Cleanup all missing nodes'),
|
||||
'description' => $this->translate('Remove all missing nodes from config')
|
||||
]);
|
||||
|
||||
if ($this->getSentValue('cleanup_all') !== '1') {
|
||||
$this->addElement('multiselect', 'nodes', [
|
||||
'label' => $this->translate('Select nodes to cleanup'),
|
||||
'required' => true,
|
||||
'size' => 8,
|
||||
'multiOptions' => $this->bp->getMissingChildren()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function onSuccess()
|
||||
{
|
||||
$changes = ProcessChanges::construct($this->bp, $this->session);
|
||||
|
||||
$nodesToCleanup = $this->getValue('cleanup_all') === '1'
|
||||
? array_keys($this->bp->getMissingChildren())
|
||||
: $this->getValue('nodes');
|
||||
|
||||
foreach ($nodesToCleanup as $nodeName) {
|
||||
$node = $this->bp->getNode($nodeName);
|
||||
$changes->deleteNode($node);
|
||||
}
|
||||
|
||||
unset($changes);
|
||||
|
||||
parent::onSuccess();
|
||||
}
|
||||
}
|
||||
|
|
@ -3,43 +3,34 @@
|
|||
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;
|
||||
use Icinga\Module\Businessprocess\Web\Form\BpConfigBaseForm;
|
||||
use Icinga\Web\View;
|
||||
|
||||
class DeleteNodeForm extends QuickForm
|
||||
class DeleteNodeForm extends BpConfigBaseForm
|
||||
{
|
||||
/** @var MonitoringBackend|IcingaDbConnection */
|
||||
protected $backend;
|
||||
|
||||
/** @var BpConfig */
|
||||
protected $bp;
|
||||
|
||||
/** @var Node */
|
||||
protected $node;
|
||||
|
||||
/** @var BpNode */
|
||||
/** @var ?BpNode */
|
||||
protected $parentNode;
|
||||
|
||||
/** @var SessionNamespace */
|
||||
protected $session;
|
||||
|
||||
public function setup()
|
||||
{
|
||||
$node = $this->node;
|
||||
$nodeName = $node->getAlias() ?? $node->getName();
|
||||
|
||||
/** @var View $view */
|
||||
$view = $this->getView();
|
||||
$this->addHtml(
|
||||
'<h2>' . $view->escape(
|
||||
sprintf($this->translate('Delete "%s"'), $node->getAlias())
|
||||
sprintf($this->translate('Delete "%s"'), $nodeName)
|
||||
) . '</h2>'
|
||||
);
|
||||
|
||||
$biLink = $view->qlink(
|
||||
$node->getAlias(),
|
||||
$nodeName,
|
||||
'businessprocess/node/impact',
|
||||
array('name' => $node->getName()),
|
||||
array('data-base-target' => '_next')
|
||||
|
|
@ -61,7 +52,7 @@ class DeleteNodeForm extends QuickForm
|
|||
} else {
|
||||
$yesMsg = sprintf(
|
||||
$this->translate('Delete root node "%s"'),
|
||||
$this->node->getAlias()
|
||||
$nodeName
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -74,32 +65,11 @@ class DeleteNodeForm extends QuickForm
|
|||
'multiOptions' => $this->optionalEnum(array(
|
||||
'no' => $this->translate('No'),
|
||||
'yes' => $yesMsg,
|
||||
'all' => sprintf($this->translate('Delete all occurrences of %s'), $node->getAlias()),
|
||||
'all' => sprintf($this->translate('Delete all occurrences of %s'), $nodeName),
|
||||
))
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
|
|
@ -120,16 +90,6 @@ class DeleteNodeForm extends QuickForm
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -2,459 +2,314 @@
|
|||
|
||||
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\BpNode;
|
||||
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\Businessprocess\ServiceNode;
|
||||
use Icinga\Module\Businessprocess\Web\Form\Element\IplStateOverrides;
|
||||
use Icinga\Module\Businessprocess\Web\Form\Validator\HostServiceTermValidator;
|
||||
use Icinga\Module\Monitoring\Backend\MonitoringBackend;
|
||||
use Icinga\Web\Session\SessionNamespace;
|
||||
use ipl\Sql\Connection as IcingaDbConnection;
|
||||
use ipl\Html\Attributes;
|
||||
use ipl\Html\FormattedString;
|
||||
use ipl\Html\HtmlElement;
|
||||
use ipl\Html\ValidHtml;
|
||||
use ipl\I18n\Translation;
|
||||
use ipl\Web\Compat\CompatForm;
|
||||
use ipl\Web\FormElement\TermInput\ValidatedTerm;
|
||||
use ipl\Web\Url;
|
||||
|
||||
class EditNodeForm extends QuickForm
|
||||
class EditNodeForm extends CompatForm
|
||||
{
|
||||
use EnumList;
|
||||
use Translation;
|
||||
|
||||
/** @var MonitoringBackend|IcingaDbConnection */
|
||||
protected $backend;
|
||||
|
||||
/** @var BpConfig */
|
||||
/** @var ?BpConfig */
|
||||
protected $bp;
|
||||
|
||||
/** @var Node */
|
||||
/** @var ?Node */
|
||||
protected $node;
|
||||
|
||||
/** @var BpNode */
|
||||
/** @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
|
||||
* Set the affected configuration
|
||||
*
|
||||
* @param BpNode $node
|
||||
* @param BpNode[] $parents
|
||||
* @param BpConfig $bp
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function collectAllParents(BpNode $node, array &$parents)
|
||||
public function setProcess(BpConfig $bp): self
|
||||
{
|
||||
foreach ($node->getParents() as $parent) {
|
||||
$parents[$parent->getName()] = $parent;
|
||||
$this->collectAllParents($parent, $parents);
|
||||
}
|
||||
$this->bp = $bp;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the affected node
|
||||
*
|
||||
* @param Node $node
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setNode(Node $node)
|
||||
public function setNode(Node $node): self
|
||||
{
|
||||
$this->node = $node;
|
||||
|
||||
$this->populate([
|
||||
'node-search' => $node->getName(),
|
||||
'node-label' => $node->getAlias()
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNode()
|
||||
/**
|
||||
* Set the affected sub-process
|
||||
*
|
||||
* @param ?BpNode $node
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setParentNode(BpNode $node = null): self
|
||||
{
|
||||
return $this->node;
|
||||
$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;
|
||||
}
|
||||
|
||||
public function onSuccess()
|
||||
/**
|
||||
* 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);
|
||||
|
||||
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;
|
||||
$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()
|
||||
);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,18 +3,19 @@
|
|||
namespace Icinga\Module\Businessprocess\Forms;
|
||||
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Application\Web;
|
||||
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\BpConfigBaseForm;
|
||||
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
|
||||
class MoveNodeForm extends BpConfigBaseForm
|
||||
{
|
||||
/** @var BpConfig */
|
||||
protected $bp;
|
||||
|
|
@ -95,16 +96,6 @@ class MoveNodeForm extends QuickForm
|
|||
$this->setSubmitLabel('movenode');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BpConfig $process
|
||||
* @return $this
|
||||
*/
|
||||
public function setProcess(BpConfig $process)
|
||||
{
|
||||
$this->bp = $process;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node
|
||||
* @return $this
|
||||
|
|
@ -125,16 +116,6 @@ class MoveNodeForm extends QuickForm
|
|||
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'))) {
|
||||
|
|
@ -156,7 +137,9 @@ class MoveNodeForm extends QuickForm
|
|||
);
|
||||
} catch (ModificationError $e) {
|
||||
$this->notifyError($e->getMessage());
|
||||
Icinga::app()->getResponse()
|
||||
/** @var Web $app */
|
||||
$app = Icinga::app();
|
||||
$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)
|
||||
|
|
@ -171,7 +154,11 @@ class MoveNodeForm extends QuickForm
|
|||
$this->notifySuccess($this->getSuccessMessage($this->translate('Node order updated')));
|
||||
|
||||
$response = $this->getRequest()->getResponse()
|
||||
->setHeader('X-Icinga-Container', 'ignore');
|
||||
->setHeader('X-Icinga-Container', 'ignore')
|
||||
->setHeader('X-Icinga-Extra-Updates', implode(';', [
|
||||
$this->getRequest()->getHeader('X-Icinga-Container'),
|
||||
$this->getSuccessUrl()->getAbsoluteUrl()
|
||||
]));
|
||||
|
||||
Session::getSession()->write();
|
||||
$response->sendResponse();
|
||||
|
|
|
|||
|
|
@ -3,50 +3,38 @@
|
|||
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\Module\Businessprocess\Node;
|
||||
use Icinga\Module\Businessprocess\Web\Form\BpConfigBaseForm;
|
||||
use Icinga\Web\Notification;
|
||||
use Icinga\Web\Session\SessionNamespace;
|
||||
use ipl\Sql\Connection as IcingaDbConnection;
|
||||
use Icinga\Web\View;
|
||||
|
||||
class ProcessForm extends QuickForm
|
||||
class ProcessForm extends BpConfigBaseForm
|
||||
{
|
||||
/** @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 {
|
||||
if ($this->node !== null) {
|
||||
/** @var View $view */
|
||||
$view = $this->getView();
|
||||
|
||||
$this->addHtml(
|
||||
'<h2>' . $this->getView()->escape(
|
||||
'<h2>' . $view->escape(
|
||||
sprintf($this->translate('Modify "%s"'), $this->node->getAlias())
|
||||
) . '</h2>'
|
||||
);
|
||||
}
|
||||
|
||||
$this->addElement('text', 'name', [
|
||||
'label' => $this->translate('ID'),
|
||||
'value' => (string) $this->node,
|
||||
'required' => true,
|
||||
'readonly' => $this->node ? true : null,
|
||||
'description' => $this->translate('This is the unique identifier of this process')
|
||||
]);
|
||||
|
||||
$this->addElement('text', 'alias', array(
|
||||
'label' => $this->translate('Display Name'),
|
||||
'description' => $this->translate(
|
||||
|
|
@ -58,21 +46,7 @@ class ProcessForm extends QuickForm
|
|||
$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'),
|
||||
)
|
||||
'multiOptions' => Node::getOperators()
|
||||
));
|
||||
|
||||
if ($this->node !== null) {
|
||||
|
|
@ -111,27 +85,6 @@ class ProcessForm extends QuickForm
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
|
|
@ -142,16 +95,6 @@ class ProcessForm extends QuickForm
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -4,14 +4,15 @@ namespace Icinga\Module\Businessprocess\Forms;
|
|||
|
||||
use Icinga\Module\Businessprocess\MonitoredNode;
|
||||
use Icinga\Module\Businessprocess\Simulation;
|
||||
use Icinga\Module\Businessprocess\Web\Form\QuickForm;
|
||||
use Icinga\Module\Businessprocess\Web\Form\BpConfigBaseForm;
|
||||
use Icinga\Web\View;
|
||||
|
||||
class SimulationForm extends QuickForm
|
||||
class SimulationForm extends BpConfigBaseForm
|
||||
{
|
||||
/** @var MonitoredNode */
|
||||
protected $node;
|
||||
|
||||
/** @var MonitoredNode */
|
||||
/** @var ?MonitoredNode */
|
||||
protected $simulatedNode;
|
||||
|
||||
/** @var Simulation */
|
||||
|
|
@ -36,6 +37,7 @@ class SimulationForm extends QuickForm
|
|||
$node = $this->node;
|
||||
}
|
||||
|
||||
/** @var View $view */
|
||||
$view = $this->getView();
|
||||
if ($hasSimulation) {
|
||||
$title = $this->translate('Modify simulation for %s');
|
||||
|
|
@ -44,7 +46,7 @@ class SimulationForm extends QuickForm
|
|||
}
|
||||
$this->addHtml(
|
||||
'<h2>'
|
||||
. $view->escape(sprintf($title, $node->getAlias()))
|
||||
. $view->escape(sprintf($title, $node->getAlias() ?? $node->getName()))
|
||||
. '</h2>'
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
<?php
|
||||
|
||||
// Avoid complaints about missing namespace and invalid class name
|
||||
// @codingStandardsIgnoreStart
|
||||
class Zend_View_Helper_FormStateOverrides extends Zend_View_Helper_FormElement
|
||||
{
|
||||
// @codingStandardsIgnoreEnd
|
||||
|
||||
public function formStateOverrides($name, $value = null, $attribs = null)
|
||||
{
|
||||
$states = $attribs['states'];
|
||||
unset($attribs['states']);
|
||||
$attribs['multiple'] = '';
|
||||
|
||||
$html = '';
|
||||
foreach ($states as $state => $label) {
|
||||
if ($state === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$chosen = $state;
|
||||
if (isset($value[$state])) {
|
||||
$chosen = $value[$state];
|
||||
}
|
||||
|
||||
$options = [$state => t('Keep actual state')] + $states;
|
||||
|
||||
$html .= '<label><span>' . $this->view->escape($label) . '</span>';
|
||||
$html .= $this->view->formSelect(
|
||||
sprintf('%s[%d]', substr($name, 0, -2), $state),
|
||||
$chosen,
|
||||
$attribs,
|
||||
$options
|
||||
);
|
||||
$html .= '</label>';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
# Icinga Business Process Modelling
|
||||
# Icinga Business Process Modeling
|
||||
|
||||
If you want to visualize and monitor hierarchical business processes based on
|
||||
any or all objects monitored by Icinga, the Icinga Web 2 business process
|
||||
module is the way to go.
|
||||
objects monitored by Icinga, Icinga Business Process Modeling is the solution.
|
||||
|
||||
[](doc/16-Add-To-Dashboard.md)
|
||||
[](16-Add-To-Dashboard.md)
|
||||
|
||||
Want to create custom process-based dashboards? Trigger notifications at
|
||||
process or sub-process level? Provide a quick top-level view for thousands of
|
||||
|
|
|
|||
|
|
@ -1,20 +1,24 @@
|
|||
# Installation
|
||||
<!-- {% if index %} -->
|
||||
# Installing Icinga Business Process Modeling
|
||||
|
||||
## Requirements
|
||||
The recommended way to install Icinga Business Process Modeling is to use prebuilt packages for
|
||||
all supported platforms from our official release repository.
|
||||
Please note that [Icinga Web](https://icinga.com/docs/icinga-web) is required to run Icinga
|
||||
Business Process Modeling and if it is not already set up, it is best to do this first.
|
||||
|
||||
* PHP (>= 7.2)
|
||||
* Icinga Web 2 (>= 2.9)
|
||||
* Icinga Web 2 libraries:
|
||||
* [Icinga PHP Library (ipl)](https://github.com/Icinga/icinga-php-library) (>= 0.8)
|
||||
* [Icinga PHP Thirdparty](https://github.com/Icinga/icinga-php-thirdparty) (>= 0.11)
|
||||
* Icinga Web 2 modules:
|
||||
* The `monitoring` or `icingadb` module needs to be configured and enabled.
|
||||
The following steps will guide you through installing and setting up Icinga Business Process Modeling.
|
||||
<!-- {% else %} -->
|
||||
<!-- {% if not icingaDocs %} -->
|
||||
|
||||
## Install Icinga Business Process Modeling
|
||||
## Installing the Package
|
||||
|
||||
Install it [like any other module](https://icinga.com/docs/icinga-web-2/latest/doc/08-Modules/#installation).
|
||||
Use `businessprocess` as name.
|
||||
If the [repository](https://packages.icinga.com) is not configured yet, please add it first.
|
||||
Then use your distribution's package manager to install the `icinga-businessprocess` package
|
||||
or install [from source](02-Installation.md.d/From-Source.md).
|
||||
<!-- {% endif %} --><!-- {# end if not icingaDocs #} -->
|
||||
|
||||
## Create your first Business Process definition
|
||||
## Configuring Icinga Business Process Modeling
|
||||
|
||||
That's it, *Business Process* is now ready for use. Please read more on [how to get started](03-Getting-Started.md).
|
||||
That's it, Icinga Business Process Modeling is now ready to use.
|
||||
Please read more on [how to get started](03-Getting-Started.md).
|
||||
<!-- {% endif %} --><!-- {# end else if index #} -->
|
||||
|
|
|
|||
15
doc/02-Installation.md.d/From-Source.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# Installing Icinga Business Process Modeling from Source
|
||||
|
||||
Please see the Icinga Web documentation on
|
||||
[how to install modules](https://icinga.com/docs/icinga-web/latest/doc/08-Modules/#installation) from source.
|
||||
Make sure you use `businessprocess` as the module name. The following requirements must also be met.
|
||||
|
||||
## Requirements
|
||||
|
||||
* PHP (≥7.2)
|
||||
* [Icinga Web](https://github.com/Icinga/icingaweb2) (≥2.9)
|
||||
* [Icinga DB Web](https://github.com/Icinga/icingadb-web) (≥1.0)
|
||||
* [Icinga PHP Library (ipl)](https://github.com/Icinga/icinga-php-library) (≥0.13.0)
|
||||
* [Icinga PHP Thirdparty](https://github.com/Icinga/icinga-php-thirdparty) (≥0.12.0)
|
||||
|
||||
<!-- {% include "02-Installation.md" %} -->
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
<a id="Getting-Started"></a>Getting Started
|
||||
===========================================
|
||||
# Getting Started
|
||||
|
||||
Once you enable the *Business Process* module, it will pop up in your menu.
|
||||
When you click on it, it will show you a new Dashboard:
|
||||
Once you enable Icinga Business Process Modeling, it will pop up in your menu.
|
||||
If you click on it, it will show you a new Dashboard:
|
||||
|
||||

|
||||
|
||||
A new Business Process configuration
|
||||
-------------------------------------------
|
||||
## A new Business Process configuration
|
||||
|
||||
From here we choose to create a new *Business Process configuration*:
|
||||
|
||||
|
|
@ -65,8 +63,7 @@ first five configurations a user is allowed to see will be shown there:
|
|||
That's all for now, click `Add` to store your new (still empty) Business Process
|
||||
configuration.
|
||||
|
||||
Empty configuration
|
||||
===================
|
||||
## Empty configuration
|
||||
|
||||
You are redirected to your newly created Business Process configuration:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<a id="Create-your-first-process-node"></a>Create your first Business Process Node
|
||||
==================================================================================
|
||||
# Create your first Business Process Node
|
||||
|
||||
A *Business Process Node* consists of a *name*, *title*, an *operator* and one or
|
||||
more child nodes. It can be a Root Node, child node of other Business Process
|
||||
|
|
@ -7,8 +6,7 @@ Nodes - or both.
|
|||
|
||||

|
||||
|
||||
Configuring our first node
|
||||
--------------------------
|
||||
## Configuring our first node
|
||||
|
||||
To create our first *Business Process Node* we click the *Add* button. This
|
||||
leads to the related configuration form:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<a id="Importing-Processes"></a>Importing Processes
|
||||
===================================================
|
||||
# Importing Processes
|
||||
|
||||
To avoid redundancy and make complex *Business Process Configurations* easier
|
||||
to maintain it is possible to import processes from other configurations.
|
||||
|
|
@ -9,8 +8,7 @@ import processes into the root level.
|
|||
|
||||

|
||||
|
||||
Importing a Process
|
||||
-------------------
|
||||
## Importing a Process
|
||||
|
||||
Once the related configuration form is open, choose `Existing Process` and wait
|
||||
for the form to refresh.
|
||||
|
|
@ -37,8 +35,7 @@ to save your changes!
|
|||
|
||||

|
||||
|
||||
Navigation with Imported Processes
|
||||
----------------------------------
|
||||
## Navigation with Imported Processes
|
||||
|
||||
### Seamless Breadcrumbs
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<a id="Customize-Node-Order"></a>Customize Node Order
|
||||
=====================================================
|
||||
# Customize Node Order
|
||||
|
||||
By default all nodes are ordered alphabetically while viewing them in the UI.
|
||||
Though, it is also possible to order nodes entirely manually.
|
||||
|
|
@ -9,8 +8,7 @@ Though, it is also possible to order nodes entirely manually.
|
|||
> Once manual order is applied (no matter where) alphabetical order is
|
||||
> disabled for the entire configuration.
|
||||
|
||||
Reorder by Drag'n'Drop
|
||||
----------------------
|
||||
## Reorder by Drag'n'Drop
|
||||
|
||||
Make sure to unlock the configuration first to be able to reorder nodes.
|
||||
|
||||
|
|
@ -34,8 +32,7 @@ The tree view also has an advantage the tile view has not. It is possible to
|
|||
move nodes within the entire hierarchy. But remember to unfold processes first,
|
||||
if you want to move a node into them.
|
||||
|
||||
File Format Extensions
|
||||
----------------------
|
||||
## File Format Extensions
|
||||
|
||||
The configuration file format has slightly been changed to accommodate the new
|
||||
manual order. Though, previous configurations are perfectly upwards compatible.
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
# Operators <a id="operators">
|
||||
# Operators
|
||||
|
||||
Every Business Process requires an Operator. This operator defines its behaviour and specifies how its very own state is
|
||||
going to be calculated.
|
||||
|
||||
## AND <a id="and-operator">
|
||||
## AND
|
||||
|
||||
The `AND` operator selects the **WORST** state of its child nodes:
|
||||
|
||||

|
||||
|
||||
## OR <a id="or-operator">
|
||||
## OR
|
||||
|
||||
The `OR` operator selects the **BEST** state of its child nodes:
|
||||
|
||||
|
|
@ -17,7 +17,17 @@ The `OR` operator selects the **BEST** state of its child nodes:
|
|||
|
||||

|
||||
|
||||
## DEGRADED <a id="deg-operator">
|
||||
## XOR
|
||||
|
||||
The `XOR` operator shows OK if only one of n children is OK at the same time. In all other cases the parent node is CRITICAL.
|
||||
Useful for a service on n servers, only one of which may be running. If both were running,
|
||||
race conditions and duplication of data could occur.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## DEGRADED
|
||||
|
||||
The `DEGRADED` operator behaves like an `AND`, but if the resulting
|
||||
state is **CRITICAL** it transforms it into a **WARNING**.
|
||||
|
|
@ -26,7 +36,7 @@ analysis of the statuses.
|
|||
|
||||

|
||||
|
||||
## MIN n <a id="min-operator">
|
||||
## MIN n
|
||||
|
||||
The `MIN` operator selects the **WORST** state out of the **BEST n** child node states:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<a id="Web-Components-Breadcrumb"></a>Web Components: Breadcrumb
|
||||
================================================================
|
||||
# Web Components: Breadcrumb
|
||||
|
||||
All Business Process renderers show a **breadcrumb** component to always give
|
||||
you a quick indication of your current location.
|
||||
|
|
@ -24,8 +23,7 @@ column view to make it obvious that you moved to another context. It is also
|
|||
perfectly legal to open any of the available links in a new browser tab or
|
||||
window.
|
||||
|
||||
Available actions below the Breadcrumb
|
||||
--------------------------------------
|
||||
## Available actions below the Breadcrumb
|
||||
|
||||
### Choose a renderer
|
||||
|
||||
|
|
@ -60,8 +58,7 @@ settings for the your currently loaded *Business Process Configuration*:
|
|||
But there is more. When unlocked, all nodes provide links allowing you to modify or
|
||||
to delete them. Host/Service Nodes now allow you to simulate a specific state.
|
||||
|
||||
Other main actions
|
||||
------------------
|
||||
## Other main actions
|
||||
|
||||
### Add content to your Dashboard
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<a id="Web-Components-Tile-Renderer"></a>Web Components: Tile Renderer
|
||||
======================================================================
|
||||
# Web Components: Tile Renderer
|
||||
|
||||
The default Business Process *Renderer* is the *Tile Renderer*. It always shows
|
||||
one level of your tree, enriched with badges giving some hint on lower level
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<a id="Web-Components-Tree-Renderer"></a>Web Components: Tree Renderer
|
||||
======================================================================
|
||||
# Web Components: Tree Renderer
|
||||
|
||||
The main advantage of the *Tree Renderer* is that it is able to show all nodes
|
||||
of Business Process trees at once. This works fine even for huge trees with lots
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<a id="Add-To-Dashboard"></a>Show Processes on a Dashboard
|
||||
==========================================================
|
||||
# Show Processes on a Dashboard
|
||||
|
||||
When being in *Locked mode*, you can add any Business Process at top or sub level
|
||||
to any Icinga Web 2 Dashboard. The related link can be found in the Tab bar:
|
||||
|
|
@ -13,8 +12,7 @@ want to create a dedicated Dashboard as shown in this example:
|
|||

|
||||
|
||||
|
||||
Want more?
|
||||
----------
|
||||
## Want more?
|
||||
|
||||
Head on and add multiple Business Processes to your Dashboard to show all of
|
||||
them at once:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<a id="Store-Config"></a>Store your Configuration
|
||||
=================================================
|
||||
# Store your Configuration
|
||||
|
||||
Changes to your *Business Process Configuration* are added to a stack and will
|
||||
not be stored immediately. In case there are pending unstored changes, this will
|
||||
|
|
@ -13,8 +12,7 @@ you created your [very first configuration](03-Getting-Started.md):
|
|||
|
||||

|
||||
|
||||
Config Diff
|
||||
-----------
|
||||
## Config Diff
|
||||
|
||||
If unsure what changes you're going to store, you can still check the *Config Diff*
|
||||
before finally storing to disk:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<a id="Upload-Config"></a>Upload a Configuration File
|
||||
=====================================================
|
||||
# Upload a Configuration File
|
||||
|
||||
You can upload a formerly downloaded or even a manually created file directly
|
||||
through the web frontend. Given sufficient permissions, the Dashboard provides
|
||||
|
|
@ -7,14 +6,13 @@ a related link:
|
|||
|
||||

|
||||
|
||||
Chose a file
|
||||
------------
|
||||
## Chose a file
|
||||
|
||||
This can be any file:
|
||||
|
||||

|
||||
|
||||
It should be valid of course, but don't worry - the *Business Process* module
|
||||
It should be valid of course, but don't worry - Icinga Business Process Modeling
|
||||
protects you from syntax errors:
|
||||
|
||||

|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
<a id="Permission System"></a>Permission System
|
||||
=================================================
|
||||
# Permission System
|
||||
|
||||
The permission system of the module is based on permissions and restrictions.
|
||||
|
||||
Permissions
|
||||
-----------
|
||||
## Permissions
|
||||
|
||||
The module has five levels of permissions:
|
||||
|
||||
|
|
@ -14,8 +12,7 @@ The module has five levels of permissions:
|
|||
* Permission to view all business processes regardless restrictions. (`businessprocess/showall`)
|
||||
* Full permissions. (`businessprocess/*`)
|
||||
|
||||
Restrictions
|
||||
-----------
|
||||
## Restrictions
|
||||
|
||||
There are two ways to configure restrictions: prefix-based and access controls
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
Project History
|
||||
===============
|
||||
# Project History
|
||||
|
||||
The Business Process module is based on the ideas of the Nagios(tm) [Business
|
||||
Icinga Business Process Modeling is based on the ideas of the Nagios(tm) [Business
|
||||
Process AddOn](http://bp-addon.monitoringexchange.org/) written by Bernd
|
||||
Strößenreuther. We always loved its simplicity, and while it looks pretty
|
||||
oldschool right now there are still many shops happily using it in production.
|
||||
|
||||

|
||||
|
||||
Compatibility
|
||||
-------------
|
||||
## Compatibility
|
||||
|
||||
We fully support the BPaddon configuration language and will continue to do so.
|
||||
It's also perfectly valid to run both products in parallel based on the very same
|
||||
|
|
@ -33,8 +31,7 @@ backends like SQL databases or the Icinga 2 DSL.
|
|||
|
||||
This would make it easier to distribute configuration in large environments.
|
||||
|
||||
Improvements
|
||||
------------
|
||||
## Improvements
|
||||
|
||||
Major focus has been put on execution speed. So while the Web integration shows
|
||||
much more details at once and is able to display huge unfolded trees, it should
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 30 KiB |
BIN
doc/screenshot/09_operators/0906_xor-operator.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
doc/screenshot/09_operators/0907_xor-operator-not-ok.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
|
|
@ -130,6 +130,9 @@ class BpConfig
|
|||
/** @var ProcessChanges */
|
||||
protected $appliedChanges;
|
||||
|
||||
/** @var bool Whether the config is faulty */
|
||||
protected $isFaulty = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
|
@ -223,7 +226,7 @@ class BpConfig
|
|||
/**
|
||||
* Whether changes have been applied to this configuration
|
||||
*
|
||||
* @return int
|
||||
* @return bool
|
||||
*/
|
||||
public function hasChanges()
|
||||
{
|
||||
|
|
@ -432,33 +435,12 @@ class BpConfig
|
|||
*/
|
||||
public function getRootNodes()
|
||||
{
|
||||
if ($this->getMetadata()->isManuallyOrdered()) {
|
||||
uasort($this->root_nodes, function (BpNode $a, BpNode $b) {
|
||||
$a = $a->getDisplay();
|
||||
$b = $b->getDisplay();
|
||||
return $a > $b ? 1 : ($a < $b ? -1 : 0);
|
||||
});
|
||||
} else {
|
||||
ksort($this->root_nodes, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
}
|
||||
|
||||
return $this->root_nodes;
|
||||
}
|
||||
|
||||
public function listRootNodes()
|
||||
{
|
||||
$names = array_keys($this->root_nodes);
|
||||
if ($this->getMetadata()->isManuallyOrdered()) {
|
||||
uasort($names, function ($a, $b) {
|
||||
$a = $this->root_nodes[$a]->getDisplay();
|
||||
$b = $this->root_nodes[$b]->getDisplay();
|
||||
return $a > $b ? 1 : ($a < $b ? -1 : 0);
|
||||
});
|
||||
} else {
|
||||
natcasesort($names);
|
||||
}
|
||||
|
||||
return $names;
|
||||
return array_keys($this->root_nodes);
|
||||
}
|
||||
|
||||
public function getNodes()
|
||||
|
|
@ -492,7 +474,7 @@ class BpConfig
|
|||
)
|
||||
);
|
||||
$node->setBpConfig($this);
|
||||
$this->nodes[$host . ';' . $service] = $node;
|
||||
$this->nodes[$node->getName()] = $node;
|
||||
$this->hosts[$host] = true;
|
||||
return $node;
|
||||
}
|
||||
|
|
@ -501,7 +483,7 @@ class BpConfig
|
|||
{
|
||||
$node = new HostNode((object) array('hostname' => $host));
|
||||
$node->setBpConfig($this);
|
||||
$this->nodes[$host . ';Hoststatus'] = $node;
|
||||
$this->nodes[$node->getName()] = $node;
|
||||
$this->hosts[$host] = true;
|
||||
return $node;
|
||||
}
|
||||
|
|
@ -598,7 +580,13 @@ class BpConfig
|
|||
public function getImportedConfig($name)
|
||||
{
|
||||
if (! isset($this->importedConfigs[$name])) {
|
||||
$import = $this->storage()->loadProcess($name);
|
||||
try {
|
||||
$import = $this->storage()->loadProcess($name);
|
||||
} catch (Exception $e) {
|
||||
$import = (new static())
|
||||
->setName($name)
|
||||
->setFaulty();
|
||||
}
|
||||
|
||||
if ($this->usesSoftStates()) {
|
||||
$import->useSoftStates();
|
||||
|
|
@ -643,7 +631,7 @@ class BpConfig
|
|||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return Node
|
||||
* @return MonitoredNode|BpNode
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getNode($name)
|
||||
|
|
@ -663,15 +651,13 @@ class BpConfig
|
|||
|
||||
// Fallback: if it is a service, create an empty one:
|
||||
$this->warn(sprintf('The node "%s" doesn\'t exist', $name));
|
||||
$pos = strpos($name, ';');
|
||||
if ($pos !== false) {
|
||||
$host = substr($name, 0, $pos);
|
||||
$service = substr($name, $pos + 1);
|
||||
// TODO: deactivated, this scares me, test it
|
||||
if ($service === 'Hoststatus') {
|
||||
return $this->createHost($host);
|
||||
|
||||
[$name, $suffix] = self::splitNodeName($name);
|
||||
if ($suffix !== null) {
|
||||
if ($suffix === 'Hoststatus') {
|
||||
return $this->createHost($name);
|
||||
} else {
|
||||
return $this->createService($host, $service);
|
||||
return $this->createService($name, $suffix);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -710,9 +696,17 @@ class BpConfig
|
|||
{
|
||||
if ($this->hasBpNode($name)) {
|
||||
return $this->nodes[$name];
|
||||
} else {
|
||||
throw new NotFoundError('Trying to access a missing business process node "%s"', $name);
|
||||
}
|
||||
|
||||
$msg = $this->isFaulty()
|
||||
? sprintf(
|
||||
t('Trying to import node "%s" from faulty config file "%s.conf"'),
|
||||
self::unescapeName($name),
|
||||
$this->getName()
|
||||
)
|
||||
: sprintf(t('Trying to access a missing business process node "%s"'), $name);
|
||||
|
||||
throw new NotFoundError($msg);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -826,16 +820,6 @@ class BpConfig
|
|||
$nodes[$name] = $name === $alias ? $name : sprintf('%s (%s)', $alias, $node);
|
||||
}
|
||||
|
||||
if ($this->getMetadata()->isManuallyOrdered()) {
|
||||
uasort($nodes, function ($a, $b) {
|
||||
$a = $this->nodes[$a]->getDisplay();
|
||||
$b = $this->nodes[$b]->getDisplay();
|
||||
return $a > $b ? 1 : ($a < $b ? -1 : 0);
|
||||
});
|
||||
} else {
|
||||
natcasesort($nodes);
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
|
|
@ -945,7 +929,10 @@ class BpConfig
|
|||
throw new IcingaException($msg);
|
||||
}
|
||||
|
||||
$this->errors[] = $msg;
|
||||
if (! in_array($msg, $this->errors)) {
|
||||
$this->errors[] = $msg;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -1046,4 +1033,85 @@ class BpConfig
|
|||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape the given node name
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function escapeName(string $name): string
|
||||
{
|
||||
return preg_replace('/((?<!\\\\);)/', '\\\\$1', $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unescape the given node name
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function unescapeName(string $name): string
|
||||
{
|
||||
return str_replace('\\;', ';', $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join the given two name parts together
|
||||
*
|
||||
* The used separator is the semicolon. If a semicolon exists in either part, it's escaped.
|
||||
*
|
||||
* @param string $name
|
||||
* @param ?string $suffix
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function joinNodeName(string $name, ?string $suffix = null): string
|
||||
{
|
||||
return self::escapeName($name) . ($suffix ? ";$suffix" : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the given node name into two parts
|
||||
*
|
||||
* The first part is always a string, with any semicolons unescaped.
|
||||
* The second part may be null or a string otherwise.
|
||||
*
|
||||
* @param string $nodeName
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function splitNodeName(string $nodeName): array
|
||||
{
|
||||
$parts = preg_split('/(?<!\\\\);/', $nodeName, 2);
|
||||
$parts[0] = self::unescapeName($parts[0]);
|
||||
|
||||
return array_pad($parts, 2, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the config is faulty
|
||||
*
|
||||
* @param bool $isFaulty
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setFaulty(bool $isFaulty = true): self
|
||||
{
|
||||
$this->isFaulty = $isFaulty;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether the config is faulty
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFaulty(): bool
|
||||
{
|
||||
return $this->isFaulty;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,20 +5,23 @@ namespace Icinga\Module\Businessprocess;
|
|||
use Icinga\Exception\ConfigurationError;
|
||||
use Icinga\Exception\NotFoundError;
|
||||
use Icinga\Module\Businessprocess\Exception\NestingError;
|
||||
use ipl\Web\Widget\Icon;
|
||||
|
||||
class BpNode extends Node
|
||||
{
|
||||
const OP_AND = '&';
|
||||
const OP_OR = '|';
|
||||
const OP_XOR = '^';
|
||||
const OP_NOT = '!';
|
||||
const OP_DEGRADED = '%';
|
||||
|
||||
protected $operator = '&';
|
||||
|
||||
protected $url;
|
||||
protected $info_command;
|
||||
|
||||
protected $display = 0;
|
||||
|
||||
/** @var Node[] */
|
||||
/** @var ?Node[] */
|
||||
protected $children;
|
||||
|
||||
/** @var array */
|
||||
|
|
@ -54,7 +57,8 @@ class BpNode extends Node
|
|||
|
||||
public function __construct($object)
|
||||
{
|
||||
$this->name = $object->name;
|
||||
$this->name = BpConfig::escapeName($object->name);
|
||||
$this->alias = BpConfig::unescapeName($object->name);
|
||||
$this->operator = $object->operator;
|
||||
$this->childNames = $object->child_names;
|
||||
}
|
||||
|
|
@ -133,7 +137,6 @@ class BpNode extends Node
|
|||
|
||||
$this->children[$name] = $node;
|
||||
$this->childNames[] = $name;
|
||||
$this->reorderChildren();
|
||||
$node->addParent($this);
|
||||
return $this;
|
||||
}
|
||||
|
|
@ -170,6 +173,8 @@ class BpNode extends Node
|
|||
if (! empty($this->children)) {
|
||||
unset($this->children[$name]);
|
||||
}
|
||||
|
||||
$this->childNames = array_values($this->childNames);
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
|
@ -272,11 +277,11 @@ class BpNode extends Node
|
|||
|
||||
foreach ($this->getChildren() as $child) {
|
||||
if ($child->isMissing()) {
|
||||
$missing[$child->getName()] = $child;
|
||||
$missing[$child->getAlias() ?? $child->getName()] = $child;
|
||||
}
|
||||
|
||||
foreach ($child->getMissingChildren() as $m) {
|
||||
$missing[$m->getName()] = $m;
|
||||
$missing[$m->getAlias() ?? $m->getName()] = $m;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -303,6 +308,7 @@ class BpNode extends Node
|
|||
switch ($operator) {
|
||||
case self::OP_AND:
|
||||
case self::OP_OR:
|
||||
case self::OP_XOR:
|
||||
case self::OP_NOT:
|
||||
case self::OP_DEGRADED:
|
||||
return;
|
||||
|
|
@ -334,21 +340,6 @@ class BpNode extends Node
|
|||
return $this->url;
|
||||
}
|
||||
|
||||
public function setInfoCommand($cmd)
|
||||
{
|
||||
$this->info_command = $cmd;
|
||||
}
|
||||
|
||||
public function hasInfoCommand()
|
||||
{
|
||||
return $this->info_command !== null;
|
||||
}
|
||||
|
||||
public function getInfoCommand()
|
||||
{
|
||||
return $this->info_command;
|
||||
}
|
||||
|
||||
public function setStateOverrides(array $overrides, $name = null)
|
||||
{
|
||||
if ($name === null) {
|
||||
|
|
@ -476,6 +467,21 @@ class BpNode extends Node
|
|||
case self::OP_OR:
|
||||
$sort_state = min($sort_states);
|
||||
break;
|
||||
case self::OP_XOR:
|
||||
$actualGood = 0;
|
||||
foreach ($sort_states as $s) {
|
||||
if ($this->sortStateTostate($s) === self::ICINGA_OK) {
|
||||
$actualGood++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($actualGood === 1) {
|
||||
$this->state = self::ICINGA_OK;
|
||||
} else {
|
||||
$this->state = self::ICINGA_CRITICAL;
|
||||
}
|
||||
|
||||
return $this;
|
||||
case self::OP_DEGRADED:
|
||||
$maxState = max($sort_states);
|
||||
$flags = $maxState & 0xf;
|
||||
|
|
@ -546,7 +552,6 @@ class BpNode extends Node
|
|||
{
|
||||
$this->childNames = $names;
|
||||
$this->children = null;
|
||||
$this->reorderChildren();
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -565,7 +570,6 @@ class BpNode extends Node
|
|||
{
|
||||
if ($this->children === null) {
|
||||
$this->children = [];
|
||||
$this->reorderChildren();
|
||||
foreach ($this->getChildNames() as $name) {
|
||||
$this->children[$name] = $this->getBpConfig()->getNode($name);
|
||||
$this->children[$name]->addParent($this);
|
||||
|
|
@ -575,29 +579,6 @@ class BpNode extends Node
|
|||
return $this->children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder this node's children, in case manual order is not applied
|
||||
*/
|
||||
protected function reorderChildren()
|
||||
{
|
||||
if ($this->getBpConfig()->getMetadata()->isManuallyOrdered()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$childNames = $this->getChildNames();
|
||||
natcasesort($childNames);
|
||||
$this->childNames = array_values($childNames);
|
||||
|
||||
if (! empty($this->children)) {
|
||||
$children = [];
|
||||
foreach ($this->childNames as $name) {
|
||||
$children[$name] = $this->children[$name];
|
||||
}
|
||||
|
||||
$this->children = $children;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return BpNode[]
|
||||
*/
|
||||
|
|
@ -642,16 +623,14 @@ class BpNode extends Node
|
|||
switch ($this->getOperator()) {
|
||||
case self::OP_AND:
|
||||
return 'AND';
|
||||
break;
|
||||
case self::OP_OR:
|
||||
return 'OR';
|
||||
break;
|
||||
case self::OP_XOR:
|
||||
return 'XOR';
|
||||
case self::OP_NOT:
|
||||
return 'NOT';
|
||||
break;
|
||||
case self::OP_DEGRADED:
|
||||
return 'DEG';
|
||||
break;
|
||||
default:
|
||||
// MIN
|
||||
$this->assertNumericOperator();
|
||||
|
|
@ -659,7 +638,7 @@ class BpNode extends Node
|
|||
}
|
||||
}
|
||||
|
||||
public function getIcon()
|
||||
public function getIcon(): Icon
|
||||
{
|
||||
$this->icon = $this->hasParents() ? 'cubes' : 'sitemap';
|
||||
return parent::getIcon();
|
||||
|
|
|
|||
|
|
@ -1,169 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Businessprocess\Common;
|
||||
|
||||
use Icinga\Application\Modules\Module;
|
||||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Module\Businessprocess\IcingaDbObject;
|
||||
use Icinga\Module\Businessprocess\MonitoringRestrictions;
|
||||
use Icinga\Module\Businessprocess\ProvidedHook\Icingadb\IcingadbSupport;
|
||||
|
||||
trait EnumList
|
||||
{
|
||||
protected function enumHostForServiceList()
|
||||
{
|
||||
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[$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();
|
||||
$suffix = ';Hoststatus';
|
||||
foreach ($names as $name) {
|
||||
$res[$name . $suffix] = $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[$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();
|
||||
$suffix = ';Hoststatus';
|
||||
foreach ($names as $name) {
|
||||
$res[$name . $suffix] = $name;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function enumServiceListByFilter($filter)
|
||||
{
|
||||
$services = array();
|
||||
|
||||
if ($this->useIcingaDbBackend()) {
|
||||
$objects = (new IcingaDbObject())->fetchServices($filter);
|
||||
foreach ($objects as $object) {
|
||||
$services[$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[$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;
|
||||
}
|
||||
}
|
||||
158
library/Businessprocess/Common/Sort.php
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
<?php
|
||||
// Icinga Business Process Modelling | (c) 2023 Icinga GmbH | GPLv2
|
||||
|
||||
namespace Icinga\Module\Businessprocess\Common;
|
||||
|
||||
use Icinga\Module\Businessprocess\BpNode;
|
||||
use Icinga\Module\Businessprocess\Node;
|
||||
use InvalidArgumentException;
|
||||
use ipl\Stdlib\Str;
|
||||
|
||||
trait Sort
|
||||
{
|
||||
/** @var ?string Current sort specification */
|
||||
protected $sort;
|
||||
|
||||
/** @var ?callable Actual sorting function */
|
||||
protected $sortFn;
|
||||
|
||||
/**
|
||||
* Get the sort specification
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function getSort(): ?string
|
||||
{
|
||||
return $this->sort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sort specification
|
||||
*
|
||||
* @param ?string $sort
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws InvalidArgumentException When sorting according to the specified specification is not possible
|
||||
*/
|
||||
public function setSort(?string $sort): self
|
||||
{
|
||||
if (empty($sort)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
list($sortBy, $direction) = Str::symmetricSplit($sort, ' ', 2, 'asc');
|
||||
|
||||
switch ($sortBy) {
|
||||
case 'manual':
|
||||
if ($direction === 'asc') {
|
||||
$this->sortFn = function (array &$nodes) {
|
||||
$firstNode = reset($nodes);
|
||||
if ($firstNode instanceof BpNode && $firstNode->getDisplay() > 0) {
|
||||
$nodes = self::applyManualSorting($nodes);
|
||||
}
|
||||
|
||||
// Child nodes don't need to be ordered in this case, their implicit order is significant
|
||||
};
|
||||
} else {
|
||||
$this->sortFn = function (array &$nodes) {
|
||||
$firstNode = reset($nodes);
|
||||
if ($firstNode instanceof BpNode && $firstNode->getDisplay() > 0) {
|
||||
uasort($nodes, function (BpNode $a, BpNode $b) {
|
||||
return $b->getDisplay() <=> $a->getDisplay();
|
||||
});
|
||||
} else {
|
||||
$nodes = array_reverse($nodes);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
case 'display_name':
|
||||
if ($direction === 'asc') {
|
||||
$this->sortFn = function (array &$nodes) {
|
||||
uasort($nodes, function (Node $a, Node $b) {
|
||||
return strnatcasecmp(
|
||||
$a->getAlias() ?? $a->getName(),
|
||||
$b->getAlias() ?? $b->getName()
|
||||
);
|
||||
});
|
||||
};
|
||||
} else {
|
||||
$this->sortFn = function (array &$nodes) {
|
||||
uasort($nodes, function (Node $a, Node $b) {
|
||||
return strnatcasecmp(
|
||||
$b->getAlias() ?? $b->getName(),
|
||||
$a->getAlias() ?? $a->getName()
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
case 'state':
|
||||
if ($direction === 'asc') {
|
||||
$this->sortFn = function (array &$nodes) {
|
||||
uasort($nodes, function (Node $a, Node $b) {
|
||||
return $a->getSortingState() <=> $b->getSortingState();
|
||||
});
|
||||
};
|
||||
} else {
|
||||
$this->sortFn = function (array &$nodes) {
|
||||
uasort($nodes, function (Node $a, Node $b) {
|
||||
return $b->getSortingState() <=> $a->getSortingState();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
"Can't sort by %s. It's only possible to sort by manual order, display_name or state",
|
||||
$sortBy
|
||||
));
|
||||
}
|
||||
|
||||
$this->sort = $sort;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the given nodes as specified by {@see setSort()}
|
||||
*
|
||||
* If {@see setSort()} has not been called yet, the default sort specification is used
|
||||
*
|
||||
* @param array $nodes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function sort(array $nodes): array
|
||||
{
|
||||
if (empty($nodes)) {
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
if ($this->sortFn !== null) {
|
||||
call_user_func_array($this->sortFn, [&$nodes]);
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply manual sort order on the given process nodes
|
||||
*
|
||||
* @param array $bpNodes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function applyManualSorting(array $bpNodes): array
|
||||
{
|
||||
uasort($bpNodes, function (BpNode $a, BpNode $b) {
|
||||
return $a->getDisplay() <=> $b->getDisplay();
|
||||
});
|
||||
|
||||
return $bpNodes;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Businessprocess;
|
||||
|
||||
use Icinga\Web\Request;
|
||||
use Icinga\Web\Form as WebForm;
|
||||
|
||||
class Form extends WebForm
|
||||
{
|
||||
public function __construct($options = null)
|
||||
{
|
||||
parent::__construct($options);
|
||||
$this->setup();
|
||||
}
|
||||
|
||||
public function addHidden($name, $value = null)
|
||||
{
|
||||
$this->addElement('hidden', $name);
|
||||
$this->getElement($name)->setDecorators(array('ViewHelper'));
|
||||
if ($value !== null) {
|
||||
$this->setDefault($name, $value);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function handleRequest(Request $request = null)
|
||||
{
|
||||
parent::handleRequest();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public static function construct()
|
||||
{
|
||||
return new static;
|
||||
}
|
||||
}
|
||||
|
|
@ -31,11 +31,11 @@ class HostNode extends MonitoredNode
|
|||
|
||||
protected $className = 'host';
|
||||
|
||||
protected $icon = 'host';
|
||||
protected $icon = 'laptop';
|
||||
|
||||
public function __construct($object)
|
||||
{
|
||||
$this->name = $object->hostname . ';Hoststatus';
|
||||
$this->name = BpConfig::joinNodeName($object->hostname, 'Hoststatus');
|
||||
$this->hostname = $object->hostname;
|
||||
if (isset($object->state)) {
|
||||
$this->setState($object->state);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class ImportedNode extends BpNode
|
|||
{
|
||||
$this->parentBp = $parentBp;
|
||||
$this->configName = $object->configName;
|
||||
$this->nodeName = $object->node;
|
||||
$this->nodeName = BpConfig::escapeName($object->node);
|
||||
|
||||
parent::__construct((object) [
|
||||
'name' => '@' . $this->configName . ':' . $this->nodeName,
|
||||
|
|
@ -69,11 +69,7 @@ class ImportedNode extends BpNode
|
|||
|
||||
public function getAlias()
|
||||
{
|
||||
if ($this->alias === null) {
|
||||
$this->alias = $this->importedNode()->getAlias();
|
||||
}
|
||||
|
||||
return $this->alias;
|
||||
return $this->importedNode()->getAlias();
|
||||
}
|
||||
|
||||
public function getOperator()
|
||||
|
|
@ -94,6 +90,15 @@ class ImportedNode extends BpNode
|
|||
return $this->childNames;
|
||||
}
|
||||
|
||||
public function isMissing()
|
||||
{
|
||||
if ($this->missing === null && $this->getBpConfig()->isFaulty()) {
|
||||
$this->missing = true;
|
||||
}
|
||||
|
||||
return parent::isMissing();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BpNode
|
||||
*/
|
||||
|
|
@ -125,10 +130,9 @@ class ImportedNode extends BpNode
|
|||
));
|
||||
$node->setBpConfig($this->getBpConfig());
|
||||
$node->setState(2);
|
||||
$node->setMissing(false)
|
||||
$node->setMissing()
|
||||
->setDowntime(false)
|
||||
->setAck(false)
|
||||
->setAlias($e->getMessage());
|
||||
->setAck(false);
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,8 +111,8 @@ abstract class NodeAction
|
|||
public static function create($actionName, $nodeName)
|
||||
{
|
||||
$className = __NAMESPACE__ . '\\Node' . ucfirst($actionName) . 'Action';
|
||||
$object = new $className($nodeName);
|
||||
return $object;
|
||||
|
||||
return new $className($nodeName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -144,7 +144,7 @@ abstract class NodeAction
|
|||
*/
|
||||
public static function unSerialize($string)
|
||||
{
|
||||
$object = json_decode($string, JSON_FORCE_OBJECT);
|
||||
$object = json_decode($string, true);
|
||||
$action = self::create($object['actionName'], $object['nodeName']);
|
||||
|
||||
foreach ($object['properties'] as $key => $val) {
|
||||
|
|
|
|||
|
|
@ -33,13 +33,12 @@ class NodeAddChildrenAction extends NodeAction
|
|||
|
||||
foreach ($this->children as $name) {
|
||||
if (! $config->hasNode($name) || $config->getNode($name)->getBpConfig()->getName() !== $config->getName()) {
|
||||
if (strpos($name, ';') !== false) {
|
||||
list($host, $service) = preg_split('/;/', $name, 2);
|
||||
|
||||
if ($service === 'Hoststatus') {
|
||||
$config->createHost($host);
|
||||
[$prefix, $suffix] = BpConfig::splitNodeName($name);
|
||||
if ($suffix !== null) {
|
||||
if ($suffix === 'Hoststatus') {
|
||||
$config->createHost($prefix);
|
||||
} else {
|
||||
$config->createService($host, $service);
|
||||
$config->createService($prefix, $suffix);
|
||||
}
|
||||
} elseif ($name[0] === '@' && strpos($name, ':') !== false) {
|
||||
list($configName, $nodeName) = preg_split('~:\s*~', substr($name, 1), 2);
|
||||
|
|
|
|||
|
|
@ -3,9 +3,12 @@
|
|||
namespace Icinga\Module\Businessprocess\Modification;
|
||||
|
||||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Module\Businessprocess\Common\Sort;
|
||||
|
||||
class NodeApplyManualOrderAction extends NodeAction
|
||||
{
|
||||
use Sort;
|
||||
|
||||
public function appliesTo(BpConfig $config)
|
||||
{
|
||||
return $config->getMetadata()->get('ManualOrder') !== 'yes';
|
||||
|
|
@ -20,7 +23,10 @@ class NodeApplyManualOrderAction extends NodeAction
|
|||
}
|
||||
|
||||
if ($node->hasChildren()) {
|
||||
$node->setChildNames($node->getChildNames());
|
||||
$node->setChildNames(array_keys(
|
||||
$this->setSort('display_name asc')
|
||||
->sort($node->getChildren())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,12 @@
|
|||
namespace Icinga\Module\Businessprocess\Modification;
|
||||
|
||||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Module\Businessprocess\Common\Sort;
|
||||
|
||||
class NodeCopyAction extends NodeAction
|
||||
{
|
||||
use Sort;
|
||||
|
||||
/**
|
||||
* @param BpConfig $config
|
||||
* @return bool
|
||||
|
|
@ -31,9 +34,15 @@ class NodeCopyAction extends NodeAction
|
|||
public function applyTo(BpConfig $config)
|
||||
{
|
||||
$name = $this->getNodeName();
|
||||
$rootNodes = $config->getRootNodes();
|
||||
|
||||
$display = 1;
|
||||
if ($config->getMetadata()->isManuallyOrdered()) {
|
||||
$rootNodes = self::applyManualSorting($config->getRootNodes());
|
||||
$display = end($rootNodes)->getDisplay() + 1;
|
||||
}
|
||||
|
||||
$config->addRootNode($name)
|
||||
->getBpNode($name)
|
||||
->setDisplay(end($rootNodes)->getDisplay() + 1);
|
||||
->setDisplay($display);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,12 @@ namespace Icinga\Module\Businessprocess\Modification;
|
|||
|
||||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Module\Businessprocess\BpNode;
|
||||
use Icinga\Module\Businessprocess\Common\Sort;
|
||||
|
||||
class NodeMoveAction extends NodeAction
|
||||
{
|
||||
use Sort;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
|
@ -87,16 +90,28 @@ class NodeMoveAction extends NodeAction
|
|||
|
||||
$nodes = $parent->getChildNames();
|
||||
if (! isset($nodes[$this->from]) || $nodes[$this->from] !== $name) {
|
||||
$this->error('Node "%s" not found at position %d', $name, $this->from);
|
||||
$reversedNodes = array_reverse($nodes); // The user may have reversed the sort direction
|
||||
if (! isset($reversedNodes[$this->from]) || $reversedNodes[$this->from] !== $name) {
|
||||
$this->error('Node "%s" not found at position %d', $name, $this->from);
|
||||
} else {
|
||||
$this->from = array_search($reversedNodes[$this->from], $nodes, true);
|
||||
$this->to = array_search($reversedNodes[$this->to], $nodes, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (! $config->hasRootNode($name)) {
|
||||
$this->error('Toplevel process "%s" not found', $name);
|
||||
}
|
||||
|
||||
$nodes = $config->listRootNodes();
|
||||
$nodes = array_keys(self::applyManualSorting($config->getRootNodes()));
|
||||
if (! isset($nodes[$this->from]) || $nodes[$this->from] !== $name) {
|
||||
$this->error('Toplevel process "%s" not found at position %d', $name, $this->from);
|
||||
$reversedNodes = array_reverse($nodes); // The user may have reversed the sort direction
|
||||
if (! isset($reversedNodes[$this->from]) || $reversedNodes[$this->from] !== $name) {
|
||||
$this->error('Toplevel process "%s" not found at position %d', $name, $this->from);
|
||||
} else {
|
||||
$this->from = array_search($reversedNodes[$this->from], $nodes, true);
|
||||
$this->to = array_search($reversedNodes[$this->to], $nodes, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -144,7 +159,7 @@ class NodeMoveAction extends NodeAction
|
|||
if ($this->parent !== null) {
|
||||
$nodes = $config->getBpNode($this->parent)->getChildren();
|
||||
} else {
|
||||
$nodes = $config->getRootNodes();
|
||||
$nodes = self::applyManualSorting($config->getRootNodes());
|
||||
}
|
||||
|
||||
$node = $nodes[$name];
|
||||
|
|
@ -162,7 +177,7 @@ class NodeMoveAction extends NodeAction
|
|||
if ($this->newParent !== null) {
|
||||
$newNodes = $config->getBpNode($this->newParent)->getChildren();
|
||||
} else {
|
||||
$newNodes = $config->getRootNodes();
|
||||
$newNodes = self::applyManualSorting($config->getRootNodes());
|
||||
}
|
||||
|
||||
$newNodes = array_merge(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
namespace Icinga\Module\Businessprocess\Modification;
|
||||
|
||||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Module\Businessprocess\BpNode;
|
||||
use Icinga\Module\Businessprocess\Node;
|
||||
|
||||
/**
|
||||
* NodeRemoveAction
|
||||
|
|
@ -64,6 +66,12 @@ class NodeRemoveAction extends NodeAction
|
|||
{
|
||||
$name = $this->getNodeName();
|
||||
$parentName = $this->getParentName();
|
||||
$node = $config->getNode($name);
|
||||
|
||||
/** @var ?BpNode $parentBpNode */
|
||||
$parentBpNode = $parentName ? $config->getNode($parentName) : null;
|
||||
$this->updateStateOverrides($node, $parentBpNode);
|
||||
|
||||
if ($parentName === null) {
|
||||
if (! $config->hasBpNode($name)) {
|
||||
$config->removeNode($name);
|
||||
|
|
@ -82,7 +90,6 @@ class NodeRemoveAction extends NodeAction
|
|||
}
|
||||
}
|
||||
} else {
|
||||
$node = $config->getNode($name);
|
||||
$parent = $config->getBpNode($parentName);
|
||||
$parent->removeChild($name);
|
||||
$node->removeParent($parentName);
|
||||
|
|
@ -91,4 +98,28 @@ class NodeRemoveAction extends NodeAction
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state overrides
|
||||
*
|
||||
* @param Node $node
|
||||
* @param BpNode|null $nodeParent
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function updateStateOverrides(Node $node, ?BpNode $nodeParent): void
|
||||
{
|
||||
$parents = [];
|
||||
if ($nodeParent !== null) {
|
||||
$parents = [$nodeParent];
|
||||
} else {
|
||||
$parents = $node->getParents();
|
||||
}
|
||||
|
||||
foreach ($parents as $parent) {
|
||||
$parentStateOverrides = $parent->getStateOverrides();
|
||||
unset($parentStateOverrides[$node->getName()]);
|
||||
$parent->setStateOverrides($parentStateOverrides);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,6 @@ class ProcessChanges
|
|||
|
||||
/**
|
||||
* @param $nodeName
|
||||
* @param Node|null $parent
|
||||
* @return $this
|
||||
*/
|
||||
public function copyNode($nodeName)
|
||||
|
|
@ -211,7 +210,7 @@ class ProcessChanges
|
|||
/**
|
||||
* Number of stacked changes
|
||||
*
|
||||
* @return bool
|
||||
* @return int
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ abstract class MonitoredNode extends Node
|
|||
public function getLink()
|
||||
{
|
||||
if ($this->isMissing()) {
|
||||
return Html::tag('a', ['href' => '#'], $this->getAlias());
|
||||
return Html::tag('a', ['href' => '#'], $this->getAlias() ?? $this->getName());
|
||||
} else {
|
||||
return Html::tag('a', ['href' => $this->getUrl()], $this->getAlias());
|
||||
return Html::tag('a', ['href' => $this->getUrl()], $this->getAlias() ?? $this->getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Businessprocess\Monitoring\Backend\Ido\Query;
|
||||
|
||||
use Icinga\Module\Monitoring\Backend\Ido\Query\ServicecommenthistoryQuery;
|
||||
use Icinga\Module\Monitoring\Backend\Ido\Query\ServicecommentQuery;
|
||||
use Icinga\Module\Monitoring\Backend\Ido\Query\ServicedowntimeQuery;
|
||||
use Icinga\Module\Monitoring\Backend\Ido\Query\ServicedowntimestarthistoryQuery;
|
||||
use Icinga\Module\Monitoring\Backend\Ido\Query\ServiceflappingstarthistoryQuery;
|
||||
use Icinga\Module\Monitoring\Backend\Ido\Query\ServicegroupQuery;
|
||||
use Icinga\Module\Monitoring\Backend\Ido\Query\ServicenotificationQuery;
|
||||
use Icinga\Module\Monitoring\Backend\Ido\Query\ServicestatehistoryQuery;
|
||||
use Zend_Db_Select;
|
||||
|
||||
trait CustomVarJoinTemplateOverride
|
||||
{
|
||||
private $customVarsJoinTemplate = '%1$s = %2$s.object_id AND %2$s.varname LIKE %3$s';
|
||||
|
||||
/**
|
||||
* This is a 1:1 copy of {@see IdoQuery::joinCustomvar()} to be able to
|
||||
* adjust {@see IdoQuery::$customVarsJoinTemplate} as it's private
|
||||
*/
|
||||
protected function joinCustomvar($customvar)
|
||||
{
|
||||
// TODO: This is not generic enough yet
|
||||
list($type, $name) = $this->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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Businessprocess\Monitoring\Backend\Ido\Query;
|
||||
|
||||
class HostStatusQuery extends \Icinga\Module\Monitoring\Backend\Ido\Query\HoststatusQuery
|
||||
{
|
||||
use CustomVarJoinTemplateOverride;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Businessprocess\Monitoring\Backend\Ido\Query;
|
||||
|
||||
class ServiceStatusQuery extends \Icinga\Module\Monitoring\Backend\Ido\Query\ServicestatusQuery
|
||||
{
|
||||
use CustomVarJoinTemplateOverride;
|
||||
}
|
||||
16
library/Businessprocess/Monitoring/DataView/HostStatus.php
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Businessprocess\Monitoring\DataView;
|
||||
|
||||
use Icinga\Data\ConnectionInterface;
|
||||
use Icinga\Module\Businessprocess\Monitoring\Backend\Ido\Query\HostStatusQuery;
|
||||
|
||||
class HostStatus extends \Icinga\Module\Monitoring\DataView\Hoststatus
|
||||
{
|
||||
public function __construct(ConnectionInterface $connection, array $columns = null)
|
||||
{
|
||||
parent::__construct($connection, $columns);
|
||||
|
||||
$this->query = new HostStatusQuery($connection->getResource(), $columns);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Businessprocess\Monitoring\DataView;
|
||||
|
||||
use Icinga\Data\ConnectionInterface;
|
||||
use Icinga\Module\Businessprocess\Monitoring\Backend\Ido\Query\ServiceStatusQuery;
|
||||
|
||||
class ServiceStatus extends \Icinga\Module\Monitoring\DataView\Servicestatus
|
||||
{
|
||||
public function __construct(ConnectionInterface $connection, array $columns = null)
|
||||
{
|
||||
parent::__construct($connection, $columns);
|
||||
|
||||
$this->query = new ServiceStatusQuery($connection->getResource(), $columns);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ namespace Icinga\Module\Businessprocess;
|
|||
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Web\Widget\Icon;
|
||||
|
||||
abstract class Node
|
||||
{
|
||||
|
|
@ -46,7 +47,7 @@ abstract class Node
|
|||
self::NODE_EMPTY => 0
|
||||
);
|
||||
|
||||
/** @var string Alias of the node */
|
||||
/** @var ?string Alias of the node */
|
||||
protected $alias;
|
||||
|
||||
/**
|
||||
|
|
@ -73,7 +74,7 @@ abstract class Node
|
|||
/**
|
||||
* Node state
|
||||
*
|
||||
* @var int
|
||||
* @var ?int
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
|
|
@ -97,7 +98,7 @@ abstract class Node
|
|||
/**
|
||||
* This node's icon
|
||||
*
|
||||
* @var string
|
||||
* @var ?string
|
||||
*/
|
||||
protected $icon;
|
||||
|
||||
|
|
@ -345,7 +346,7 @@ abstract class Node
|
|||
/**
|
||||
* Get the alias of the node
|
||||
*
|
||||
* @return string
|
||||
* @return ?string
|
||||
*/
|
||||
public function getAlias()
|
||||
{
|
||||
|
|
@ -442,7 +443,7 @@ abstract class Node
|
|||
throw new ProgrammingError(
|
||||
'Got invalid state for node %s: %s',
|
||||
$this->getName(),
|
||||
var_export($state, 1) . var_export($this->stateToSortStateMap, 1)
|
||||
var_export($state, true) . var_export($this->stateToSortStateMap, true)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -463,14 +464,12 @@ abstract class Node
|
|||
|
||||
public function getLink()
|
||||
{
|
||||
return Html::tag('a', ['href' => '#', 'class' => 'toggle'], Html::tag('i', [
|
||||
'class' => 'icon icon-down-dir'
|
||||
]));
|
||||
return Html::tag('a', ['href' => '#', 'class' => 'toggle'], new Icon('caret-down'));
|
||||
}
|
||||
|
||||
public function getIcon()
|
||||
public function getIcon(): Icon
|
||||
{
|
||||
return Html::tag('i', ['class' => 'icon icon-' . ($this->icon ?: 'attention-circled')]);
|
||||
return new Icon($this->icon ?? 'circle-exclamation');
|
||||
}
|
||||
|
||||
public function operatorHtml()
|
||||
|
|
@ -483,6 +482,31 @@ abstract class Node
|
|||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Node operators
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getOperators(): array
|
||||
{
|
||||
return [
|
||||
'&' => t('AND'),
|
||||
'|' => t('OR'),
|
||||
'^' => t('XOR'),
|
||||
'!' => t('NOT'),
|
||||
'%' => t('DEGRADED'),
|
||||
'1' => t('MIN 1'),
|
||||
'2' => t('MIN 2'),
|
||||
'3' => t('MIN 3'),
|
||||
'4' => t('MIN 4'),
|
||||
'5' => t('MIN 5'),
|
||||
'6' => t('MIN 6'),
|
||||
'7' => t('MIN 7'),
|
||||
'8' => t('MIN 8'),
|
||||
'9' => t('MIN 9'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getIdentifier()
|
||||
{
|
||||
return '@' . $this->getBpConfig()->getName() . ':' . $this->getName();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Icinga\Module\Businessprocess\ProvidedHook\Icingadb;
|
||||
|
||||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Module\Icingadb\Hook\HostActionsHook;
|
||||
use Icinga\Module\Icingadb\Model\Host;
|
||||
use ipl\Web\Widget\Link;
|
||||
|
|
@ -15,7 +16,7 @@ class HostActions extends HostActionsHook
|
|||
new Link(
|
||||
$label,
|
||||
'businessprocess/node/impact?name='
|
||||
. rawurlencode($host->name . ';Hoststatus')
|
||||
. rawurlencode(BpConfig::joinNodeName($host->name, 'Hoststatus'))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Icinga\Module\Businessprocess\ProvidedHook\Icingadb;
|
||||
|
||||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Module\Icingadb\Hook\ServiceActionsHook;
|
||||
use Icinga\Module\Icingadb\Model\Service;
|
||||
use ipl\Web\Widget\Link;
|
||||
|
|
@ -16,9 +17,7 @@ class ServiceActions extends ServiceActionsHook
|
|||
$label,
|
||||
sprintf(
|
||||
'businessprocess/node/impact?name=%s',
|
||||
rawurlencode(
|
||||
sprintf('%s;%s', $service->host->name, $service->name)
|
||||
)
|
||||
rawurlencode(BpConfig::joinNodeName($service->host->name, $service->name))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use ipl\Html\ValidHtml;
|
|||
|
||||
class ServiceDetailExtension extends ServiceDetailExtensionHook
|
||||
{
|
||||
/** @var LegacyStorage */
|
||||
/** @var ?LegacyStorage */
|
||||
private $storage;
|
||||
|
||||
/** @var string */
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use Icinga\Module\Monitoring\Object\Service;
|
|||
|
||||
class DetailviewExtension extends DetailviewExtensionHook
|
||||
{
|
||||
/** @var LegacyStorage */
|
||||
/** @var ?LegacyStorage */
|
||||
private $storage;
|
||||
|
||||
/** @var string */
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Icinga\Module\Businessprocess\ProvidedHook\Monitoring;
|
||||
|
||||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Module\Monitoring\Hook\HostActionsHook;
|
||||
use Icinga\Module\Monitoring\Object\Host;
|
||||
|
||||
|
|
@ -12,7 +13,7 @@ class HostActions extends HostActionsHook
|
|||
$label = mt('businessprocess', 'Business Impact');
|
||||
return array(
|
||||
$label => 'businessprocess/node/impact?name='
|
||||
. rawurlencode($host->getName() . ';Hoststatus')
|
||||
. rawurlencode(BpConfig::joinNodeName($host->getName(), 'Hoststatus'))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace Icinga\Module\Businessprocess\ProvidedHook\Monitoring;
|
|||
|
||||
use Exception;
|
||||
use Icinga\Application\Config;
|
||||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Module\Monitoring\Hook\ServiceActionsHook;
|
||||
use Icinga\Module\Monitoring\Object\Service;
|
||||
use Icinga\Web\Url;
|
||||
|
|
@ -16,9 +17,7 @@ class ServiceActions extends ServiceActionsHook
|
|||
return array(
|
||||
$label => sprintf(
|
||||
'businessprocess/node/impact?name=%s',
|
||||
rawurlencode(
|
||||
sprintf('%s;%s', $service->getHost()->getName(), $service->getName())
|
||||
)
|
||||
rawurlencode(BpConfig::joinNodeName($service->getHost()->getName(), $service->getName()))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use Icinga\Module\Businessprocess\Renderer\TileRenderer\NodeTile;
|
|||
use Icinga\Module\Businessprocess\Web\Url;
|
||||
use ipl\Html\BaseHtmlElement;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Web\Widget\Icon;
|
||||
|
||||
class Breadcrumb extends BaseHtmlElement
|
||||
{
|
||||
|
|
@ -37,7 +38,7 @@ class Breadcrumb extends BaseHtmlElement
|
|||
'href' => Url::fromPath('businessprocess'),
|
||||
'title' => mt('businessprocess', 'Show Overview')
|
||||
],
|
||||
Html::tag('i', ['class' => 'icon icon-home'])
|
||||
new Icon('house')
|
||||
)
|
||||
));
|
||||
$breadcrumb->add(Html::tag('li')->add(
|
||||
|
|
@ -47,6 +48,7 @@ class Breadcrumb extends BaseHtmlElement
|
|||
|
||||
$parts = array();
|
||||
while ($nodeName = array_pop($path)) {
|
||||
/** @var BpNode $node */
|
||||
$node = $bp->getNode($nodeName);
|
||||
$renderer->setParentNode($node);
|
||||
array_unshift(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace Icinga\Module\Businessprocess\Renderer;
|
|||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\Module\Businessprocess\BpNode;
|
||||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Module\Businessprocess\Common\Sort;
|
||||
use Icinga\Module\Businessprocess\ImportedNode;
|
||||
use Icinga\Module\Businessprocess\MonitoredNode;
|
||||
use Icinga\Module\Businessprocess\Node;
|
||||
|
|
@ -12,10 +13,13 @@ use Icinga\Module\Businessprocess\Web\Url;
|
|||
use ipl\Html\BaseHtmlElement;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Html\HtmlDocument;
|
||||
use ipl\Stdlib\Str;
|
||||
use ipl\Web\Widget\StateBadge;
|
||||
|
||||
abstract class Renderer extends HtmlDocument
|
||||
{
|
||||
use Sort;
|
||||
|
||||
/** @var BpConfig */
|
||||
protected $config;
|
||||
|
||||
|
|
@ -120,6 +124,37 @@ abstract class Renderer extends HtmlDocument
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default sort specification
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultSort(): string
|
||||
{
|
||||
if ($this->config->getMetadata()->isManuallyOrdered()) {
|
||||
return 'manual asc';
|
||||
}
|
||||
|
||||
return 'display_name asc';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether a custom sort order is applied
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function appliesCustomSorting(): bool
|
||||
{
|
||||
if (empty($this->getSort())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($sortBy, $_) = Str::symmetricSplit($this->getSort(), ' ', 2);
|
||||
list($defaultSortBy, $_) = Str::symmetricSplit($this->getDefaultSort(), ' ', 2);
|
||||
|
||||
return $sortBy !== $defaultSortBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
|
|
@ -134,12 +169,10 @@ abstract class Renderer extends HtmlDocument
|
|||
|
||||
/**
|
||||
* @param $summary
|
||||
* @return BaseHtmlElement
|
||||
* @return ?BaseHtmlElement
|
||||
*/
|
||||
public function renderStateBadges($summary, $totalChildren)
|
||||
{
|
||||
$elements = [];
|
||||
|
||||
$itemCount = Html::tag(
|
||||
'span',
|
||||
[
|
||||
|
|
@ -150,7 +183,7 @@ abstract class Renderer extends HtmlDocument
|
|||
sprintf(mtp('businessprocess', '%u Child', '%u Children', $totalChildren), $totalChildren)
|
||||
);
|
||||
|
||||
$elements[] = array_filter([
|
||||
$elements = array_filter([
|
||||
$this->createBadgeGroup($summary, 'CRITICAL'),
|
||||
$this->createBadgeGroup($summary, 'UNKNOWN'),
|
||||
$this->createBadgeGroup($summary, 'WARNING'),
|
||||
|
|
@ -265,7 +298,7 @@ abstract class Renderer extends HtmlDocument
|
|||
*/
|
||||
public function getId(Node $node, $path)
|
||||
{
|
||||
return md5((empty($path) ? '' : implode(';', $path)) . $node->getName());
|
||||
return 'businessprocess-' . md5((empty($path) ? '' : implode(';', $path)) . $node->getName());
|
||||
}
|
||||
|
||||
public function setPath(array $path)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ class TileRenderer extends Renderer
|
|||
[
|
||||
'class' => ['sortable', 'tiles', $this->howMany()],
|
||||
'data-base-target' => '_self',
|
||||
'data-sortable-disabled' => $this->isLocked() ? 'true' : 'false',
|
||||
'data-sortable-disabled' => $this->isLocked() || $this->appliesCustomSorting()
|
||||
? 'true'
|
||||
: 'false',
|
||||
'data-sortable-data-id-attr' => 'id',
|
||||
'data-sortable-direction' => 'horizontal', // Otherwise movement is buggy on small lists
|
||||
'data-csrf-token' => CsrfToken::generate()
|
||||
|
|
@ -43,10 +45,8 @@ class TileRenderer extends Renderer
|
|||
->getAbsoluteUrl());
|
||||
}
|
||||
|
||||
$nodes = $this->getChildNodes();
|
||||
|
||||
$path = $this->getCurrentPath();
|
||||
foreach ($nodes as $name => $node) {
|
||||
foreach ($this->sort($this->getChildNodes()) as $name => $node) {
|
||||
$this->add(new NodeTile($this, $node, $path));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,15 +4,15 @@ namespace Icinga\Module\Businessprocess\Renderer\TileRenderer;
|
|||
|
||||
use Icinga\Date\DateFormatter;
|
||||
use Icinga\Module\Businessprocess\BpNode;
|
||||
use Icinga\Module\Businessprocess\HostNode;
|
||||
use Icinga\Module\Businessprocess\ImportedNode;
|
||||
use Icinga\Module\Businessprocess\MonitoredNode;
|
||||
use Icinga\Module\Businessprocess\Node;
|
||||
use Icinga\Module\Businessprocess\Renderer\Renderer;
|
||||
use Icinga\Module\Businessprocess\ServiceNode;
|
||||
use Icinga\Web\Url;
|
||||
use ipl\Html\BaseHtmlElement;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Web\Widget\Icon;
|
||||
use ipl\Web\Widget\Link;
|
||||
use ipl\Web\Widget\StateBall;
|
||||
|
||||
class NodeTile extends BaseHtmlElement
|
||||
|
|
@ -35,9 +35,8 @@ class NodeTile extends BaseHtmlElement
|
|||
/**
|
||||
* NodeTile constructor.
|
||||
* @param Renderer $renderer
|
||||
* @param $name
|
||||
* @param Node $node
|
||||
* @param null $path
|
||||
* @param ?array $path
|
||||
*/
|
||||
public function __construct(Renderer $renderer, Node $node, $path = null)
|
||||
{
|
||||
|
|
@ -103,14 +102,7 @@ class NodeTile extends BaseHtmlElement
|
|||
|
||||
$this->add($link);
|
||||
} else {
|
||||
$this->add(Html::tag(
|
||||
'a',
|
||||
Html::tag(
|
||||
'span',
|
||||
['style' => 'font-size: 75%'],
|
||||
sprintf('Trying to access a missing business process node "%s"', $node->getNodeName())
|
||||
)
|
||||
));
|
||||
$this->add(new Link($node->getAlias(), $this->getMainNodeUrl($node)->getAbsoluteUrl()));
|
||||
}
|
||||
|
||||
if ($this->renderer->rendersSubNode()
|
||||
|
|
@ -180,7 +172,11 @@ class NodeTile extends BaseHtmlElement
|
|||
$node = $this->node;
|
||||
$url = $this->getMainNodeUrl($node);
|
||||
if ($node instanceof MonitoredNode) {
|
||||
$link = Html::tag('a', ['href' => $url, 'data-base-target' => '_next'], $node->getAlias());
|
||||
$link = Html::tag(
|
||||
'a',
|
||||
['href' => $url, 'data-base-target' => '_next'],
|
||||
$node->getAlias() ?? $node->getName()
|
||||
);
|
||||
} else {
|
||||
$link = Html::tag('a', ['href' => $url], $node->getAlias());
|
||||
}
|
||||
|
|
@ -200,28 +196,31 @@ class NodeTile extends BaseHtmlElement
|
|||
'href' => $url->with('mode', 'tile'),
|
||||
'title' => mt('businessprocess', 'Show tiles for this subtree')
|
||||
],
|
||||
Html::tag('i', ['class' => 'icon icon-dashboard'])
|
||||
new Icon('grip')
|
||||
))->add(Html::tag(
|
||||
'a',
|
||||
[
|
||||
'href' => $url->with('mode', 'tree'),
|
||||
'title' => mt('businessprocess', 'Show this subtree as a tree')
|
||||
],
|
||||
Html::tag('i', ['class' => 'icon icon-sitemap'])
|
||||
new Icon('sitemap')
|
||||
));
|
||||
if ($node instanceof ImportedNode) {
|
||||
if ($node->getBpConfig()->hasNode($node->getName())) {
|
||||
$bpConfig = $node->getBpConfig();
|
||||
if ($bpConfig->isFaulty() || $bpConfig->hasNode($node->getName())) {
|
||||
$this->actions()->add(Html::tag(
|
||||
'a',
|
||||
[
|
||||
'data-base-target' => '_next',
|
||||
'href' => $this->renderer->getSourceUrl($node)->getAbsoluteUrl(),
|
||||
'href' => $bpConfig->isFaulty()
|
||||
? $this->renderer->getBaseUrl()->setParam('config', $bpConfig->getName())
|
||||
: $this->renderer->getSourceUrl($node)->getAbsoluteUrl(),
|
||||
'title' => mt(
|
||||
'businessprocess',
|
||||
'Show this process as part of its original configuration'
|
||||
)
|
||||
],
|
||||
Html::tag('i', ['class' => 'icon icon-forward'])
|
||||
new Icon('share')
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -236,7 +235,7 @@ class NodeTile extends BaseHtmlElement
|
|||
'class' => 'node-info',
|
||||
'title' => sprintf('%s: %s', mt('businessprocess', 'More information'), $url)
|
||||
],
|
||||
Html::tag('i', ['class' => 'icon icon-info-circled'])
|
||||
new Icon('info')
|
||||
);
|
||||
if (preg_match('#^http(?:s)?://#', $url)) {
|
||||
$link->addAttributes(['target' => '_blank']);
|
||||
|
|
@ -244,20 +243,17 @@ class NodeTile extends BaseHtmlElement
|
|||
$this->actions()->add($link);
|
||||
}
|
||||
} else {
|
||||
// $url = $this->makeMonitoredNodeUrl($node);
|
||||
if ($node instanceof ServiceNode) {
|
||||
$this->actions()->add(Html::tag(
|
||||
'a',
|
||||
['href' => $node->getUrl(), 'data-base-target' => '_next'],
|
||||
Html::tag('i', ['class' => 'icon icon-service'])
|
||||
));
|
||||
} elseif ($node instanceof HostNode) {
|
||||
$this->actions()->add(Html::tag(
|
||||
'a',
|
||||
['href' => $node->getUrl(), 'data-base-target' => '_next'],
|
||||
Html::tag('i', ['class' => 'icon icon-host'])
|
||||
));
|
||||
}
|
||||
$this->actions()->add(Html::tag(
|
||||
'a',
|
||||
['href' => $node->getUrl(), 'data-base-target' => '_next'],
|
||||
$node->getIcon()
|
||||
));
|
||||
}
|
||||
|
||||
if ($node->isAcknowledged()) {
|
||||
$this->actions()->add(new Icon('check', ['class' => 'handled-icon']));
|
||||
} elseif ($node->isInDowntime()) {
|
||||
$this->actions()->add(new Icon('plug', ['class' => 'handled-icon']));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -291,7 +287,7 @@ class NodeTile extends BaseHtmlElement
|
|||
'Show the business impact of this node by simulating a specific state'
|
||||
)
|
||||
],
|
||||
Html::tag('i', ['class' => 'icon icon-magic'])
|
||||
new Icon('wand-magic-sparkles')
|
||||
));
|
||||
|
||||
$this->actions()->add(Html::tag(
|
||||
|
|
@ -302,7 +298,7 @@ class NodeTile extends BaseHtmlElement
|
|||
->with('editmonitorednode', $this->node->getName()),
|
||||
'title' => mt('businessprocess', 'Modify this monitored node')
|
||||
],
|
||||
Html::tag('i', ['class' => 'icon icon-edit'])
|
||||
new Icon('edit')
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -319,7 +315,7 @@ class NodeTile extends BaseHtmlElement
|
|||
->with('editnode', $this->node->getName()),
|
||||
'title' => mt('businessprocess', 'Modify this business process node')
|
||||
],
|
||||
Html::tag('i', ['class' => 'icon icon-edit'])
|
||||
new Icon('edit')
|
||||
));
|
||||
|
||||
$addUrl = $baseUrl->with([
|
||||
|
|
@ -333,7 +329,7 @@ class NodeTile extends BaseHtmlElement
|
|||
'href' => $addUrl,
|
||||
'title' => mt('businessprocess', 'Add a new sub-node to this business process')
|
||||
],
|
||||
Html::tag('i', ['class' => 'icon icon-plus'])
|
||||
new Icon('plus')
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -350,7 +346,7 @@ class NodeTile extends BaseHtmlElement
|
|||
'href' => $baseUrl->with($params),
|
||||
'title' => mt('businessprocess', 'Delete this node')
|
||||
],
|
||||
Html::tag('i', ['class' => 'icon icon-cancel'])
|
||||
new Icon('xmark')
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,19 +2,24 @@
|
|||
|
||||
namespace Icinga\Module\Businessprocess\Renderer;
|
||||
|
||||
use Icinga\Application\Version;
|
||||
use Icinga\Date\DateFormatter;
|
||||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Module\Businessprocess\BpNode;
|
||||
use Icinga\Module\Businessprocess\ImportedNode;
|
||||
use Icinga\Module\Businessprocess\Node;
|
||||
use Icinga\Module\Businessprocess\Web\Form\CsrfToken;
|
||||
use Icinga\Module\Icingadb\Model\State;
|
||||
use ipl\Html\Attributes;
|
||||
use ipl\Html\BaseHtmlElement;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Html\HtmlElement;
|
||||
use ipl\Web\Widget\Icon;
|
||||
use ipl\Web\Widget\StateBall;
|
||||
|
||||
class TreeRenderer extends Renderer
|
||||
{
|
||||
const NEW_COLLAPSIBLE_IMPLEMENTATION_SINCE = '2.11.2';
|
||||
|
||||
public function assemble()
|
||||
{
|
||||
$bp = $this->config;
|
||||
|
|
@ -24,7 +29,9 @@ class TreeRenderer extends Renderer
|
|||
[
|
||||
'id' => $htmlId,
|
||||
'class' => ['bp', 'sortable', $this->wantsRootNodes() ? '' : 'process'],
|
||||
'data-sortable-disabled' => $this->isLocked() ? 'true' : 'false',
|
||||
'data-sortable-disabled' => $this->isLocked() || $this->appliesCustomSorting()
|
||||
? 'true'
|
||||
: 'false',
|
||||
'data-sortable-data-id-attr' => 'id',
|
||||
'data-sortable-direction' => 'vertical',
|
||||
'data-sortable-group' => json_encode([
|
||||
|
|
@ -32,7 +39,6 @@ class TreeRenderer extends Renderer
|
|||
'put' => 'function:rowPutAllowed'
|
||||
]),
|
||||
'data-sortable-invert-swap' => 'true',
|
||||
'data-is-root-config' => $this->wantsRootNodes() ? 'true' : 'false',
|
||||
'data-csrf-token' => CsrfToken::generate()
|
||||
],
|
||||
$this->renderBp($bp)
|
||||
|
|
@ -42,6 +48,10 @@ class TreeRenderer extends Renderer
|
|||
'data-action-url',
|
||||
$this->getUrl()->with(['config' => $bp->getName()])->getAbsoluteUrl()
|
||||
);
|
||||
|
||||
if (version_compare(Version::VERSION, self::NEW_COLLAPSIBLE_IMPLEMENTATION_SINCE, '<')) {
|
||||
$tree->getAttributes()->add('data-is-root-config', true);
|
||||
}
|
||||
} else {
|
||||
$nodeName = $this->parent instanceof ImportedNode
|
||||
? $this->parent->getNodeName()
|
||||
|
|
@ -61,18 +71,18 @@ class TreeRenderer extends Renderer
|
|||
|
||||
/**
|
||||
* @param BpConfig $bp
|
||||
* @return string
|
||||
* @return array
|
||||
*/
|
||||
public function renderBp(BpConfig $bp)
|
||||
{
|
||||
$html = array();
|
||||
$html = [];
|
||||
if ($this->wantsRootNodes()) {
|
||||
$nodes = $bp->getChildren();
|
||||
$nodes = $bp->getRootNodes();
|
||||
} else {
|
||||
$nodes = $this->parent->getChildren();
|
||||
}
|
||||
|
||||
foreach ($nodes as $name => $node) {
|
||||
foreach ($this->sort($nodes) as $name => $node) {
|
||||
if ($node instanceof BpNode) {
|
||||
$html[] = $this->renderNode($bp, $node);
|
||||
} else {
|
||||
|
|
@ -110,7 +120,7 @@ class TreeRenderer extends Renderer
|
|||
{
|
||||
$icons = [];
|
||||
if (empty($path) && $node instanceof BpNode) {
|
||||
$icons[] = Html::tag('i', ['class' => 'icon icon-sitemap']);
|
||||
$icons[] = new Icon('sitemap');
|
||||
} else {
|
||||
$icons[] = $node->getIcon();
|
||||
}
|
||||
|
|
@ -125,12 +135,13 @@ class TreeRenderer extends Renderer
|
|||
DateFormatter::timeSince($node->getLastStateChange())
|
||||
)
|
||||
]);
|
||||
if ($node->isInDowntime()) {
|
||||
$icons[] = Html::tag('i', ['class' => 'icon icon-plug']);
|
||||
}
|
||||
|
||||
if ($node->isAcknowledged()) {
|
||||
$icons[] = Html::tag('i', ['class' => 'icon icon-ok']);
|
||||
$icons[] = new Icon('check');
|
||||
} elseif ($node->isInDowntime()) {
|
||||
$icons[] = new Icon('plug');
|
||||
}
|
||||
|
||||
return $icons;
|
||||
}
|
||||
|
||||
|
|
@ -146,7 +157,8 @@ class TreeRenderer extends Renderer
|
|||
)
|
||||
])
|
||||
);
|
||||
$overriddenState->add(Html::tag('i', ['class' => 'icon icon-right-small']));
|
||||
|
||||
$overriddenState->add(new Icon('arrow-right'));
|
||||
$overriddenState->add(
|
||||
(new StateBall(strtolower($node->getStateName($fakeState)), StateBall::SIZE_MEDIUM))
|
||||
->addAttributes([
|
||||
|
|
@ -192,36 +204,48 @@ class TreeRenderer extends Renderer
|
|||
$attributes->add('class', 'node');
|
||||
}
|
||||
|
||||
$div = Html::tag('div');
|
||||
$li->add($div);
|
||||
$details = new HtmlElement('details', Attributes::create(['open' => true]));
|
||||
$summary = new HtmlElement('summary');
|
||||
if (version_compare(Version::VERSION, self::NEW_COLLAPSIBLE_IMPLEMENTATION_SINCE, '>=')) {
|
||||
$details->getAttributes()->add('class', 'collapsible');
|
||||
$summary->getAttributes()->add('class', 'collapsible-control'); // Helps JS, improves performance a bit
|
||||
}
|
||||
|
||||
$div->add($node->getLink());
|
||||
$div->add($this->getNodeIcons($node, $path));
|
||||
$summary->addHtml(
|
||||
new Icon('caret-down', ['class' => 'collapse-icon']),
|
||||
new Icon('caret-right', ['class' => 'expand-icon'])
|
||||
);
|
||||
|
||||
$div->add(Html::tag('span', null, $node->getAlias()));
|
||||
$summary->add($this->getNodeIcons($node, $path));
|
||||
|
||||
$summary->add(Html::tag('span', null, $node->getAlias()));
|
||||
|
||||
if ($node instanceof BpNode) {
|
||||
$div->add(Html::tag('span', ['class' => 'op'], $node->operatorHtml()));
|
||||
$summary->add(Html::tag('span', ['class' => 'op'], $node->operatorHtml()));
|
||||
}
|
||||
|
||||
if ($node instanceof BpNode && $node->hasInfoUrl()) {
|
||||
$div->add($this->createInfoAction($node));
|
||||
$summary->add($this->createInfoAction($node));
|
||||
}
|
||||
|
||||
$differentConfig = $node->getBpConfig()->getName() !== $this->getBusinessProcess()->getName();
|
||||
if (! $this->isLocked() && !$differentConfig) {
|
||||
$div->add($this->getActionIcons($bp, $node));
|
||||
$summary->add($this->getActionIcons($bp, $node));
|
||||
} elseif ($differentConfig) {
|
||||
$div->add($this->actionIcon(
|
||||
'forward',
|
||||
$this->getSourceUrl($node)->addParams(['mode' => 'tree'])->getAbsoluteUrl(),
|
||||
$summary->add($this->actionIcon(
|
||||
'share',
|
||||
$node->getBpConfig()->isFaulty()
|
||||
? $this->getBaseUrl()->setParam('config', $node->getBpConfig()->getName())
|
||||
: $this->getSourceUrl($node)->addParams(['mode' => 'tree'])->getAbsoluteUrl(),
|
||||
mt('businessprocess', 'Show this process as part of its original configuration')
|
||||
)->addAttributes(['data-base-target' => '_next']));
|
||||
}
|
||||
|
||||
$ul = Html::tag('ul', [
|
||||
'class' => ['bp', 'sortable'],
|
||||
'data-sortable-disabled' => ($this->isLocked() || $differentConfig) ? 'true' : 'false',
|
||||
'data-sortable-disabled' => ($this->isLocked() || $differentConfig || $this->appliesCustomSorting())
|
||||
? 'true'
|
||||
: 'false',
|
||||
'data-sortable-invert-swap' => 'true',
|
||||
'data-sortable-data-id-attr' => 'id',
|
||||
'data-sortable-draggable' => '.movable',
|
||||
|
|
@ -240,10 +264,9 @@ class TreeRenderer extends Renderer
|
|||
])
|
||||
->getAbsoluteUrl()
|
||||
]);
|
||||
$li->add($ul);
|
||||
|
||||
$path[] = $differentConfig ? $node->getIdentifier() : $node->getName();
|
||||
foreach ($node->getChildren() as $name => $child) {
|
||||
foreach ($this->sort($node->getChildren()) as $name => $child) {
|
||||
if ($child instanceof BpNode) {
|
||||
$ul->add($this->renderNode($bp, $child, $path));
|
||||
} else {
|
||||
|
|
@ -251,6 +274,10 @@ class TreeRenderer extends Renderer
|
|||
}
|
||||
}
|
||||
|
||||
$details->addHtml($summary);
|
||||
$details->addHtml($ul);
|
||||
$li->addHtml($details);
|
||||
|
||||
return $li;
|
||||
}
|
||||
|
||||
|
|
@ -307,7 +334,7 @@ class TreeRenderer extends Renderer
|
|||
protected function createSimulationAction(BpConfig $bp, Node $node)
|
||||
{
|
||||
return $this->actionIcon(
|
||||
'magic',
|
||||
'wand-magic-sparkles',
|
||||
$this->getUrl()->with(array(
|
||||
//'config' => $bp->getName(),
|
||||
'action' => 'simulation',
|
||||
|
|
@ -321,7 +348,7 @@ class TreeRenderer extends Renderer
|
|||
{
|
||||
$url = $node->getInfoUrl();
|
||||
return $this->actionIcon(
|
||||
'help',
|
||||
'question',
|
||||
$url,
|
||||
sprintf('%s: %s', mt('businessprocess', 'More information'), $url)
|
||||
)->addAttributes(['target' => '_blank']);
|
||||
|
|
@ -336,7 +363,7 @@ class TreeRenderer extends Renderer
|
|||
'title' => $title,
|
||||
'class' => 'action-link'
|
||||
],
|
||||
Html::tag('i', ['class' => 'icon icon-' . $icon])
|
||||
new Icon($icon)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
@ -15,11 +18,11 @@ class ServiceNode extends MonitoredNode
|
|||
|
||||
protected $className = 'service';
|
||||
|
||||
protected $icon = 'service';
|
||||
protected $icon = 'gear';
|
||||
|
||||
public function __construct($object)
|
||||
{
|
||||
$this->name = $object->hostname . ';' . $object->service;
|
||||
$this->name = BpConfig::joinNodeName($object->hostname, $object->service);
|
||||
$this->hostname = $object->hostname;
|
||||
$this->service = $object->service;
|
||||
if (isset($object->state)) {
|
||||
|
|
@ -65,7 +68,15 @@ class ServiceNode extends MonitoredNode
|
|||
|
||||
public function getAlias()
|
||||
{
|
||||
return $this->getHostAlias() . ': ' . $this->alias;
|
||||
if ($this->getHostAlias() === null || $this->alias === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
$this->translate('%s on %s', '<service> on <host>'),
|
||||
$this->alias,
|
||||
$this->getHostAlias()
|
||||
);
|
||||
}
|
||||
|
||||
public function getUrl()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use Icinga\Application\Benchmark;
|
|||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Module\Businessprocess\IcingaDbObject;
|
||||
use Icinga\Module\Businessprocess\ServiceNode;
|
||||
use Icinga\Module\Icingadb\Common\IcingaRedis;
|
||||
use Icinga\Module\Icingadb\Model\Host;
|
||||
use Icinga\Module\Icingadb\Model\Service;
|
||||
use ipl\Sql\Connection as IcingaDbConnection;
|
||||
|
|
@ -52,43 +53,97 @@ class IcingaDbState
|
|||
{
|
||||
$config = $this->config;
|
||||
|
||||
$involvedHostNames = $config->listInvolvedHostNames();
|
||||
if (empty($involvedHostNames)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
Benchmark::measure(sprintf(
|
||||
'Retrieving states for business process %s using Icinga DB backend',
|
||||
$config->getName()
|
||||
));
|
||||
|
||||
$hosts = $config->listInvolvedHostNames();
|
||||
if (empty($hosts)) {
|
||||
return $this;
|
||||
}
|
||||
$hosts = Host::on($this->backend)->columns([
|
||||
'id' => 'host.id',
|
||||
'name' => 'host.name',
|
||||
'display_name' => 'host.display_name',
|
||||
'hard_state' => 'host.state.hard_state',
|
||||
'soft_state' => 'host.state.soft_state',
|
||||
'last_state_change' => 'host.state.last_state_change',
|
||||
'in_downtime' => 'host.state.in_downtime',
|
||||
'is_acknowledged' => 'host.state.is_acknowledged'
|
||||
])->filter(Filter::equal('host.name', $involvedHostNames));
|
||||
|
||||
$queryHost = Host::on($this->backend)->with('state');
|
||||
$queryHost->filter(Filter::equal('host.name', $hosts));
|
||||
$services = Service::on($this->backend)->columns([
|
||||
'id' => 'service.id',
|
||||
'name' => 'service.name',
|
||||
'display_name' => 'service.display_name',
|
||||
'host_name' => 'host.name',
|
||||
'host_display_name' => 'host.display_name',
|
||||
'hard_state' => 'service.state.hard_state',
|
||||
'soft_state' => 'service.state.soft_state',
|
||||
'last_state_change' => 'service.state.last_state_change',
|
||||
'in_downtime' => 'service.state.in_downtime',
|
||||
'is_acknowledged' => 'service.state.is_acknowledged'
|
||||
])->filter(Filter::equal('host.name', $involvedHostNames));
|
||||
|
||||
$hostObject = $queryHost->getModel()->getTableName();
|
||||
|
||||
Benchmark::measure('Retrieved states for ' . $queryHost->count() . ' hosts in ' . $config->getName());
|
||||
|
||||
$queryService = Service::on($this->backend)
|
||||
->with('state')
|
||||
->with('host')
|
||||
->with('host.state');
|
||||
|
||||
$queryService->filter(Filter::equal('host.name', $hosts));
|
||||
|
||||
Benchmark::measure('Retrieved states for ' . $queryService->count() . ' services in ' . $config->getName());
|
||||
|
||||
$configs = $config->listInvolvedConfigs();
|
||||
|
||||
$serviceObject = $queryService->getModel()->getTableName();
|
||||
|
||||
foreach ($configs as $cfg) {
|
||||
foreach ($queryService as $row) {
|
||||
$this->handleDbRow($row, $cfg, $serviceObject);
|
||||
// All of this is ipl-sql now, for performance reasons
|
||||
foreach ($config->listInvolvedConfigs() as $cfg) {
|
||||
$serviceIds = [];
|
||||
$serviceResults = [];
|
||||
foreach ($this->backend->yieldAll($services->assembleSelect()) as $row) {
|
||||
$row->hex_id = bin2hex(is_resource($row->id) ? stream_get_contents($row->id) : $row->id);
|
||||
$serviceIds[] = $row->hex_id;
|
||||
$serviceResults[] = $row;
|
||||
}
|
||||
foreach ($queryHost as $row) {
|
||||
$this->handleDbRow($row, $cfg, $hostObject);
|
||||
|
||||
$redisServiceResults = iterator_to_array(IcingaRedis::fetchServiceState($serviceIds, [
|
||||
'hard_state',
|
||||
'soft_state',
|
||||
'last_state_change',
|
||||
'in_downtime',
|
||||
'is_acknowledged'
|
||||
]));
|
||||
foreach ($serviceResults as $row) {
|
||||
if (isset($redisServiceResults[$row->hex_id])) {
|
||||
$row = (object) array_merge(
|
||||
(array) $row,
|
||||
$redisServiceResults[$row->hex_id]
|
||||
);
|
||||
}
|
||||
|
||||
$this->handleDbRow($row, $cfg, 'service');
|
||||
}
|
||||
|
||||
Benchmark::measure('Retrieved states for ' . count($serviceIds) . ' services in ' . $config->getName());
|
||||
|
||||
$hostIds = [];
|
||||
$hostResults = [];
|
||||
foreach ($this->backend->yieldAll($hosts->assembleSelect()) as $row) {
|
||||
$row->hex_id = bin2hex(is_resource($row->id) ? stream_get_contents($row->id) : $row->id);
|
||||
$hostIds[] = $row->hex_id;
|
||||
$hostResults[] = $row;
|
||||
}
|
||||
|
||||
$redisHostResults = iterator_to_array(IcingaRedis::fetchHostState($hostIds, [
|
||||
'hard_state',
|
||||
'soft_state',
|
||||
'last_state_change',
|
||||
'in_downtime',
|
||||
'is_acknowledged'
|
||||
]));
|
||||
foreach ($hostResults as $row) {
|
||||
if (isset($redisHostResults[$row->hex_id])) {
|
||||
$row = (object) array_merge(
|
||||
(array) $row,
|
||||
$redisHostResults[$row->hex_id]
|
||||
);
|
||||
}
|
||||
|
||||
$this->handleDbRow($row, $cfg, 'host');
|
||||
}
|
||||
|
||||
Benchmark::measure('Retrieved states for ' . count($hostIds) . ' hosts in ' . $config->getName());
|
||||
}
|
||||
|
||||
Benchmark::measure('Got states for business process ' . $config->getName());
|
||||
|
|
@ -96,12 +151,12 @@ class IcingaDbState
|
|||
return $this;
|
||||
}
|
||||
|
||||
protected function handleDbRow($row, BpConfig $config, $objectName)
|
||||
protected function handleDbRow($row, BpConfig $config, $type)
|
||||
{
|
||||
if ($objectName === 'service') {
|
||||
$key = $row->host->name . ';' . $row->name;
|
||||
if ($type === 'service') {
|
||||
$key = BpConfig::joinNodeName($row->host_name, $row->name);
|
||||
} else {
|
||||
$key = $row->name . ';Hoststatus';
|
||||
$key = BpConfig::joinNodeName($row->name, 'Hoststatus');
|
||||
}
|
||||
|
||||
// We fetch more states than we need, so skip unknown ones
|
||||
|
|
@ -112,29 +167,25 @@ class IcingaDbState
|
|||
$node = $config->getNode($key);
|
||||
|
||||
if ($this->config->usesHardStates()) {
|
||||
if ($row->state->hard_state !== null) {
|
||||
$node->setState($row->state->hard_state)->setMissing(false);
|
||||
if ($row->hard_state !== null) {
|
||||
$node->setState($row->hard_state)->setMissing(false);
|
||||
}
|
||||
} else {
|
||||
if ($row->state->soft_state !== null) {
|
||||
$node->setState($row->state->soft_state)->setMissing(false);
|
||||
if ($row->soft_state !== null) {
|
||||
$node->setState($row->soft_state)->setMissing(false);
|
||||
}
|
||||
}
|
||||
|
||||
if ($row->state->last_state_change !== null) {
|
||||
$node->setLastStateChange($row->state->last_state_change/1000);
|
||||
}
|
||||
if ($row->state->in_downtime) {
|
||||
$node->setDowntime(true);
|
||||
}
|
||||
if ($row->state->is_acknowledged) {
|
||||
$node->setAck(true);
|
||||
if ($row->last_state_change !== null) {
|
||||
$node->setLastStateChange($row->last_state_change / 1000.0);
|
||||
}
|
||||
|
||||
$node->setDowntime($row->in_downtime === 'y');
|
||||
$node->setAck($row->is_acknowledged === 'y');
|
||||
$node->setAlias($row->display_name);
|
||||
|
||||
if ($node instanceof ServiceNode) {
|
||||
$node->setHostAlias($row->host->display_name);
|
||||
$node->setHostAlias($row->host_display_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,12 +115,12 @@ class MonitoringState
|
|||
|
||||
protected function handleDbRow($row, BpConfig $config)
|
||||
{
|
||||
$key = $row->hostname;
|
||||
if (property_exists($row, 'service')) {
|
||||
$key .= ';' . $row->service;
|
||||
} else {
|
||||
$key .= ';Hoststatus';
|
||||
}
|
||||
$key = BpConfig::joinNodeName(
|
||||
$row->hostname,
|
||||
property_exists($row, 'service')
|
||||
? $row->service
|
||||
: 'Hoststatus'
|
||||
);
|
||||
|
||||
// We fetch more states than we need, so skip unknown ones
|
||||
if (! $config->hasNode($key)) {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use Icinga\Module\Businessprocess\Metadata;
|
|||
|
||||
class LegacyConfigParser
|
||||
{
|
||||
/** @var string */
|
||||
/** @var ?string */
|
||||
protected static $prevKey;
|
||||
|
||||
/** @var int */
|
||||
|
|
@ -196,6 +196,10 @@ class LegacyConfigParser
|
|||
*/
|
||||
protected static function parseHeaderLine($line, Metadata $metadata)
|
||||
{
|
||||
if (empty($line)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (preg_match('/^\s*#\s+(.+?)\s*:\s*(.+)$/', trim($line), $m)) {
|
||||
if ($metadata->hasKey($m[1])) {
|
||||
static::$prevKey = $m[1];
|
||||
|
|
@ -226,33 +230,16 @@ class LegacyConfigParser
|
|||
*/
|
||||
protected function parseDisplay(&$line, BpConfig $bp)
|
||||
{
|
||||
list($display, $name, $desc) = preg_split('~\s*;\s*~', substr($line, 8), 3);
|
||||
list($display, $name, $desc) = preg_split('~\s*(?<!\\\\);\s*~', substr($line, 8), 3);
|
||||
$bp->getBpNode($name)->setAlias($desc)->setDisplay($display);
|
||||
if ($display > 0) {
|
||||
$bp->addRootNode($name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $line
|
||||
* @param BpConfig $bp
|
||||
*/
|
||||
protected function parseExternalInfo(&$line, BpConfig $bp)
|
||||
{
|
||||
list($name, $script) = preg_split('~\s*;\s*~', substr($line, 14), 2);
|
||||
$bp->getBpNode($name)->setInfoCommand($script);
|
||||
}
|
||||
|
||||
protected function parseExtraInfo(&$line, BpConfig $bp)
|
||||
{
|
||||
// TODO: Not yet
|
||||
// list($name, $script) = preg_split('~\s*;\s*~', substr($line, 14), 2);
|
||||
// $this->getNode($name)->setExtraInfo($script);
|
||||
}
|
||||
|
||||
protected function parseInfoUrl(&$line, BpConfig $bp)
|
||||
{
|
||||
list($name, $url) = preg_split('~\s*;\s*~', substr($line, 9), 2);
|
||||
list($name, $url) = preg_split('~\s*(?<!\\\\);\s*~', substr($line, 9), 2);
|
||||
$bp->getBpNode($name)->setInfoUrl($url);
|
||||
}
|
||||
|
||||
|
|
@ -260,6 +247,7 @@ class LegacyConfigParser
|
|||
{
|
||||
// state_overrides <bp-node>!<child>|n-n[,n-n]!<child>|n-n[,n-n]
|
||||
$segments = preg_split('~\s*!\s*~', substr($line, 16));
|
||||
/** @var BpNode $node */
|
||||
$node = $bp->getNode(array_shift($segments));
|
||||
foreach ($segments as $overrideDef) {
|
||||
list($childName, $overrides) = preg_split('~\s*\|\s*~', $overrideDef, 2);
|
||||
|
|
@ -284,10 +272,7 @@ class LegacyConfigParser
|
|||
|
||||
switch ($type) {
|
||||
case 'external_info':
|
||||
$this->parseExternalInfo($line, $bp);
|
||||
break;
|
||||
case 'extra_info':
|
||||
$this->parseExtraInfo($line, $bp);
|
||||
break;
|
||||
case 'info_url':
|
||||
$this->parseInfoUrl($line, $bp);
|
||||
|
|
@ -340,12 +325,8 @@ class LegacyConfigParser
|
|||
|
||||
list($name, $value) = preg_split('~\s*=\s*~', $line, 2);
|
||||
|
||||
if (strpos($name, ';') !== false) {
|
||||
$this->parseError('No semicolon allowed in varname');
|
||||
}
|
||||
|
||||
$op = '&';
|
||||
if (preg_match_all('~(?<!\\\\)([\|\+&\!\%])~', $value, $m)) {
|
||||
if (preg_match_all('~(?<!\\\\)([\|\+&\!\%\^])~', $value, $m)) {
|
||||
$op = implode('', $m[1]);
|
||||
for ($i = 1; $i < strlen($op); $i++) {
|
||||
if ($op[$i] !== $op[$i - 1]) {
|
||||
|
|
@ -374,16 +355,16 @@ class LegacyConfigParser
|
|||
|
||||
$cmps = preg_split('~\s*(?<!\\\\)\\' . $op . '\s*~', $value, -1, PREG_SPLIT_NO_EMPTY);
|
||||
foreach ($cmps as $val) {
|
||||
$val = preg_replace('~(\\\\([\|\+&\!\%]))~', '$2', $val);
|
||||
if (strpos($val, ';') !== false) {
|
||||
$val = preg_replace('~(\\\\([\|\+&\!\%\^]))~', '$2', $val);
|
||||
if (preg_match('~(?<!\\\\);~', $val)) {
|
||||
if ($bp->hasNode($val)) {
|
||||
$node->addChild($bp->getNode($val));
|
||||
} else {
|
||||
list($host, $service) = preg_split('~;~', $val, 2);
|
||||
list($host, $service) = preg_split('~(?<!\\\\);~', $val, 2);
|
||||
if ($service === 'Hoststatus') {
|
||||
$node->addChild($bp->createHost($host));
|
||||
$node->addChild($bp->createHost(str_replace('\\;', ';', $host)));
|
||||
} else {
|
||||
$node->addChild($bp->createService($host, $service));
|
||||
$node->addChild($bp->createService(str_replace('\\;', ';', $host), $service));
|
||||
}
|
||||
}
|
||||
} elseif ($val[0] === '@') {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ class LegacyConfigRenderer
|
|||
/** @var array */
|
||||
protected $renderedNodes;
|
||||
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* LecagyConfigRenderer constructor
|
||||
*
|
||||
|
|
@ -197,7 +199,7 @@ class LegacyConfigRenderer
|
|||
$op = static::renderOperator($node);
|
||||
$children = $node->getChildNames();
|
||||
$str = implode(' ' . $op . ' ', array_map(function ($val) {
|
||||
return preg_replace('~([\|\+&\!\%])~', '\\\\$1', $val);
|
||||
return preg_replace('~([\|\+&\!\%\^])~', '\\\\$1', $val);
|
||||
}, $children));
|
||||
|
||||
if ((count($children) < 2) && $op !== '&') {
|
||||
|
|
|
|||
|
|
@ -73,7 +73,6 @@ class LegacyStorage extends Storage
|
|||
$files[$name] = $meta->getExtendedTitle();
|
||||
}
|
||||
|
||||
natcasesort($files);
|
||||
return $files;
|
||||
}
|
||||
|
||||
|
|
@ -93,7 +92,6 @@ class LegacyStorage extends Storage
|
|||
$files[$name] = $name;
|
||||
}
|
||||
|
||||
natcasesort($files);
|
||||
return $files;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,21 +7,21 @@ use Icinga\Application\ApplicationBootstrap;
|
|||
use Icinga\Application\Icinga;
|
||||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Module\Businessprocess\Storage\LegacyStorage;
|
||||
use Icinga\Module\Businessprocess\Web\FakeRequest;
|
||||
use PHPUnit_Framework_TestCase;
|
||||
|
||||
abstract class BaseTestCase extends PHPUnit_Framework_TestCase
|
||||
abstract class BaseTestCase extends \Icinga\Test\BaseTestCase
|
||||
{
|
||||
/** @var ApplicationBootstrap */
|
||||
private static $app;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setUp()
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->app();
|
||||
FakeRequest::setConfiguredBaseUrl('/icingaweb2/');
|
||||
parent::setUp();
|
||||
|
||||
$this->getRequestMock()->shouldReceive('getBaseUrl')->andReturn('/icingaweb2/');
|
||||
|
||||
$this->app()
|
||||
->getModuleManager()
|
||||
->loadModule('businessprocess');
|
||||
}
|
||||
|
||||
protected function emptyConfigSection()
|
||||
|
|
@ -49,7 +49,7 @@ abstract class BaseTestCase extends PHPUnit_Framework_TestCase
|
|||
}
|
||||
|
||||
/**
|
||||
* @param null $subDir
|
||||
* @param ?string $subDir
|
||||
* @return string
|
||||
*/
|
||||
protected function getTestsBaseDir($subDir = null)
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@
|
|||
namespace Icinga\Module\Businessprocess\Web\Component;
|
||||
|
||||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Web\Url;
|
||||
use ipl\Html\BaseHtmlElement;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Html\Text;
|
||||
use ipl\Web\Url;
|
||||
use ipl\Web\Widget\Icon;
|
||||
use ipl\Web\Widget\Link;
|
||||
|
||||
class BpDashboardTile extends BaseHtmlElement
|
||||
{
|
||||
|
|
@ -16,14 +18,10 @@ class BpDashboardTile extends BaseHtmlElement
|
|||
|
||||
public function __construct(BpConfig $bp, $title, $description, $icon, $url, $urlParams = null, $attributes = null)
|
||||
{
|
||||
if (! isset($attributes['href'])) {
|
||||
$attributes['href'] = Url::fromPath($url, $urlParams ?: []);
|
||||
}
|
||||
|
||||
$this->add(Html::tag(
|
||||
'div',
|
||||
['class' => 'bp-link', 'data-base-target' => '_main'],
|
||||
Html::tag('a', $attributes, Html::tag('i', ['class' => 'icon icon-' . $icon]))
|
||||
(new Link(new Icon($icon), Url::fromPath($url, $urlParams ?: []), $attributes))
|
||||
->add(Html::tag('span', ['class' => 'header'], $title))
|
||||
->add($description)
|
||||
));
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
namespace Icinga\Module\Businessprocess\Web\Component;
|
||||
|
||||
use Exception;
|
||||
use Icinga\Application\Modules\Module;
|
||||
use Icinga\Authentication\Auth;
|
||||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Module\Businessprocess\ProvidedHook\Icingadb\IcingadbSupport;
|
||||
use Icinga\Module\Businessprocess\State\IcingaDbState;
|
||||
use Icinga\Module\Businessprocess\State\MonitoringState;
|
||||
|
|
@ -87,13 +89,25 @@ class Dashboard extends BaseHtmlElement
|
|||
foreach ($processes as $name) {
|
||||
$meta = $storage->loadMetadata($name);
|
||||
$title = $meta->get('Title');
|
||||
if ($title) {
|
||||
$title = sprintf('%s (%s)', $title, $name);
|
||||
} else {
|
||||
|
||||
if ($title === null) {
|
||||
$title = $name;
|
||||
}
|
||||
|
||||
$bp = $storage->loadProcess($name);
|
||||
try {
|
||||
$bp = $storage->loadProcess($name);
|
||||
} catch (Exception $e) {
|
||||
$this->add(new BpDashboardTile(
|
||||
new BpConfig(),
|
||||
$title,
|
||||
sprintf(t('File %s has faulty config'), $name . '.conf'),
|
||||
'file-circle-xmark',
|
||||
'businessprocess/process/show',
|
||||
['config' => $name]
|
||||
));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Module::exists('icingadb') &&
|
||||
(! $bp->hasBackendName() && IcingadbSupport::useIcingaDbAsBackend())
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace Icinga\Module\Businessprocess\Web\Component;
|
|||
use Icinga\Web\Url;
|
||||
use ipl\Html\BaseHtmlElement;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Web\Widget\Icon;
|
||||
|
||||
class DashboardAction extends BaseHtmlElement
|
||||
{
|
||||
|
|
@ -19,7 +20,7 @@ class DashboardAction extends BaseHtmlElement
|
|||
}
|
||||
|
||||
$this->add(Html::tag('a', $attributes)
|
||||
->add(Html::tag('i', ['class' => 'icon icon-' . $icon]))
|
||||
->add(new Icon($icon))
|
||||
->add(Html::tag('span', ['class' => 'header'], $title))
|
||||
->add($description));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,11 @@ use Icinga\Module\Businessprocess\Renderer\Renderer;
|
|||
use Icinga\Module\Businessprocess\Renderer\TreeRenderer;
|
||||
use Icinga\Web\Url;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Web\Widget\Icon;
|
||||
|
||||
class RenderedProcessActionBar extends ActionBar
|
||||
{
|
||||
public function __construct(BpConfig $config, Renderer $renderer, Auth $auth, Url $url)
|
||||
public function __construct(BpConfig $config, Renderer $renderer, Url $url)
|
||||
{
|
||||
$meta = $config->getMetadata();
|
||||
|
||||
|
|
@ -34,8 +35,8 @@ class RenderedProcessActionBar extends ActionBar
|
|||
}
|
||||
|
||||
$link->add([
|
||||
Html::tag('i', ['class' => 'icon icon-dashboard' . ($renderer instanceof TreeRenderer ? '' : ' active')]),
|
||||
Html::tag('i', ['class' => 'icon icon-sitemap' . ($renderer instanceof TreeRenderer ? ' active' : '')])
|
||||
new Icon('grip', ['class' => $renderer instanceof TreeRenderer ? null : 'active']),
|
||||
new Icon('sitemap', ['class' => $renderer instanceof TreeRenderer ? 'active' : null])
|
||||
]);
|
||||
|
||||
$this->add(
|
||||
|
|
@ -50,9 +51,11 @@ class RenderedProcessActionBar extends ActionBar
|
|||
'data-base-target' => '_main',
|
||||
'href' => $url->with('showFullscreen', true),
|
||||
'title' => mt('businessprocess', 'Switch to fullscreen mode'),
|
||||
'class' => 'icon-resize-full-alt'
|
||||
],
|
||||
mt('businessprocess', 'Fullscreen')
|
||||
[
|
||||
new Icon('maximize'),
|
||||
mt('businessprocess', 'Fullscreen')
|
||||
]
|
||||
));
|
||||
|
||||
$hasChanges = $config->hasSimulations() || $config->hasBeenChanged();
|
||||
|
|
@ -66,8 +69,7 @@ class RenderedProcessActionBar extends ActionBar
|
|||
'Imported processes can only be changed in their original configuration'
|
||||
)
|
||||
]);
|
||||
$span->add(Html::tag('i', ['class' => 'icon icon-lock']))
|
||||
->add(mt('businessprocess', 'Editing Locked'));
|
||||
$span->add([new Icon('lock'), mt('businessprocess', 'Editing Locked')]);
|
||||
$this->add($span);
|
||||
} else {
|
||||
$this->add(Html::tag(
|
||||
|
|
@ -75,9 +77,11 @@ class RenderedProcessActionBar extends ActionBar
|
|||
[
|
||||
'href' => $url->with('unlocked', true),
|
||||
'title' => mt('businessprocess', 'Click to unlock editing for this process'),
|
||||
'class' => 'icon-lock'
|
||||
],
|
||||
mt('businessprocess', 'Unlock Editing')
|
||||
[
|
||||
new Icon('lock'),
|
||||
mt('businessprocess', 'Unlock Editing')
|
||||
]
|
||||
));
|
||||
}
|
||||
} elseif (! $hasChanges) {
|
||||
|
|
@ -86,9 +90,11 @@ class RenderedProcessActionBar extends ActionBar
|
|||
[
|
||||
'href' => $url->without('unlocked')->without('action'),
|
||||
'title' => mt('businessprocess', 'Click to lock editing for this process'),
|
||||
'class' => 'icon-lock-open'
|
||||
],
|
||||
mt('businessprocess', 'Lock Editing')
|
||||
[
|
||||
new Icon('lock-open'),
|
||||
mt('businessprocess', 'Lock Editing')
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -100,9 +106,11 @@ class RenderedProcessActionBar extends ActionBar
|
|||
'data-base-target' => '_next',
|
||||
'href' => Url::fromPath('businessprocess/process/config', $this->currentProcessParams($url)),
|
||||
'title' => mt('businessprocess', 'Modify this process'),
|
||||
'class' => 'icon-wrench'
|
||||
],
|
||||
mt('businessprocess', 'Config')
|
||||
[
|
||||
new Icon('wrench'),
|
||||
mt('businessprocess', 'Config')
|
||||
]
|
||||
));
|
||||
} else {
|
||||
$this->add(Html::tag(
|
||||
|
|
@ -113,9 +121,11 @@ class RenderedProcessActionBar extends ActionBar
|
|||
'editnode' => $url->getParam('node')
|
||||
])->getAbsoluteUrl(),
|
||||
'title' => mt('businessprocess', 'Modify this process'),
|
||||
'class' => 'icon-wrench'
|
||||
],
|
||||
mt('businessprocess', 'Config')
|
||||
[
|
||||
new Icon('wrench'),
|
||||
mt('businessprocess', 'Config')
|
||||
]
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -126,9 +136,12 @@ class RenderedProcessActionBar extends ActionBar
|
|||
[
|
||||
'href' => $url->with('action', 'add'),
|
||||
'title' => mt('businessprocess', 'Add a new business process node'),
|
||||
'class' => 'icon-plus button-link'
|
||||
'class' => 'button-link'
|
||||
],
|
||||
mt('businessprocess', 'Add Node')
|
||||
[
|
||||
new Icon('plus'),
|
||||
mt('businessprocess', 'Add Node')
|
||||
]
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ use Icinga\Module\Businessprocess\Web\Component\Controls;
|
|||
use Icinga\Module\Businessprocess\Web\Component\Content;
|
||||
use Icinga\Module\Businessprocess\Web\Component\Tabs;
|
||||
use Icinga\Module\Businessprocess\Web\Form\FormLoader;
|
||||
use Icinga\Web\Controller as ModuleController;
|
||||
use Icinga\Web\Notification;
|
||||
use Icinga\Web\View;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Web\Compat\CompatController;
|
||||
|
||||
class Controller extends ModuleController
|
||||
class Controller extends CompatController
|
||||
{
|
||||
/** @var View */
|
||||
public $view;
|
||||
|
|
@ -176,14 +176,6 @@ class Controller extends ModuleController
|
|||
return $this;
|
||||
}
|
||||
|
||||
protected function setTitle($title)
|
||||
{
|
||||
$args = func_get_args();
|
||||
array_shift($args);
|
||||
$this->view->title = vsprintf($title, $args);
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function addTitle($title)
|
||||
{
|
||||
$args = func_get_args();
|
||||
|
|
@ -221,6 +213,7 @@ class Controller extends ModuleController
|
|||
protected function loadBpConfig()
|
||||
{
|
||||
$name = $this->params->get('config');
|
||||
/** @var LegacyStorage $storage */
|
||||
$storage = $this->storage();
|
||||
|
||||
if (! $storage->hasProcess($name)) {
|
||||
|
|
@ -259,7 +252,7 @@ class Controller extends ModuleController
|
|||
}
|
||||
|
||||
/**
|
||||
* @return LegacyStorage|Storage
|
||||
* @return LegacyStorage
|
||||
*/
|
||||
protected function storage()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,16 +5,25 @@ namespace Icinga\Module\Businessprocess\Web\Form;
|
|||
use Icinga\Application\Config;
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Authentication\Auth;
|
||||
use Icinga\Module\Businessprocess\Storage\LegacyStorage;
|
||||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Module\Businessprocess\Storage\Storage;
|
||||
use Icinga\Module\Monitoring\Backend\MonitoringBackend;
|
||||
use Icinga\Web\Session\SessionNamespace;
|
||||
use ipl\Sql\Connection as IcingaDbConnection;
|
||||
|
||||
abstract class BpConfigBaseForm extends QuickForm
|
||||
{
|
||||
/** @var LegacyStorage */
|
||||
/** @var Storage */
|
||||
protected $storage;
|
||||
|
||||
/** @var BpConfig */
|
||||
protected $config;
|
||||
protected $bp;
|
||||
|
||||
/** @var MonitoringBackend|IcingaDbConnection*/
|
||||
protected $backend;
|
||||
|
||||
/** @var SessionNamespace */
|
||||
protected $session;
|
||||
|
||||
protected function listAvailableBackends()
|
||||
{
|
||||
|
|
@ -28,15 +37,60 @@ abstract class BpConfigBaseForm extends QuickForm
|
|||
return $keys;
|
||||
}
|
||||
|
||||
public function setStorage(LegacyStorage $storage)
|
||||
/**
|
||||
* Set the storage to use
|
||||
*
|
||||
* @param Storage $storage
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setStorage(Storage $storage): self
|
||||
{
|
||||
$this->storage = $storage;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setProcessConfig(BpConfig $config)
|
||||
/**
|
||||
* Set the config to use
|
||||
*
|
||||
* @param BpConfig $config
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setProcess(BpConfig $config): self
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->bp = $config;
|
||||
$this->setBackend($config->getBackend());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the backend to use
|
||||
*
|
||||
* @param MonitoringBackend|IcingaDbConnection $backend
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setBackend($backend): self
|
||||
{
|
||||
$this->backend = $backend;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the session namespace to use
|
||||
*
|
||||
* @param SessionNamespace $session
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSession(SessionNamespace $session): self
|
||||
{
|
||||
$this->session = $session;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -69,4 +123,13 @@ abstract class BpConfigBaseForm extends QuickForm
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function setPreferredDecorators()
|
||||
{
|
||||
parent::setPreferredDecorators();
|
||||
|
||||
$this->setAttrib('class', $this->getAttrib('class') . ' bp-form');
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Businessprocess\Web\Form\Element;
|
||||
|
||||
use ipl\Html\Attributes;
|
||||
use ipl\Html\FormElement\FieldsetElement;
|
||||
|
||||
class IplStateOverrides extends FieldsetElement
|
||||
{
|
||||
/** @var array */
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* Set the options show
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setOptions(array $options): self
|
||||
{
|
||||
$this->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']);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Businessprocess\Web\Form\Element;
|
||||
|
||||
class StateOverrides extends FormElement
|
||||
{
|
||||
public $helper = 'formStateOverrides';
|
||||
|
||||
/** @var array The overridable states */
|
||||
protected $states;
|
||||
|
||||
/**
|
||||
* Set the overridable states
|
||||
*
|
||||
* @param array $states
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setStates(array $states)
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,8 @@ class FormLoader
|
|||
$basedir = $module->getFormDir();
|
||||
$ns = '\\Icinga\\Module\\' . ucfirst($module->getName()) . '\\Forms\\';
|
||||
}
|
||||
|
||||
$file = null;
|
||||
if (preg_match('~^[a-z0-9/]+$~i', $name)) {
|
||||
$parts = preg_split('~/~', $name);
|
||||
$class = ucfirst(array_pop($parts)) . 'Form';
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ abstract class QuickBaseForm extends Zend_Form implements ValidHtml
|
|||
* The Icinga module this form belongs to. Usually only set if the
|
||||
* form is initialized through the FormLoader
|
||||
*
|
||||
* @var Module
|
||||
* @var ?Module
|
||||
*/
|
||||
protected $icingaModule;
|
||||
|
||||
|
|
@ -123,7 +123,6 @@ abstract class QuickBaseForm extends Zend_Form implements ValidHtml
|
|||
}
|
||||
|
||||
if (array_key_exists('icingaModule', $options)) {
|
||||
/** @var Module icingaModule */
|
||||
$this->icingaModule = $options['icingaModule'];
|
||||
$this->icingaModuleName = $this->icingaModule->getName();
|
||||
unset($options['icingaModule']);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace Icinga\Module\Businessprocess\Web\Form;
|
||||
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Application\Web;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\Web\Notification;
|
||||
use Icinga\Web\Request;
|
||||
|
|
@ -45,7 +46,7 @@ abstract class QuickForm extends QuickBaseForm
|
|||
protected $request;
|
||||
|
||||
/**
|
||||
* @var Url
|
||||
* @var ?Url
|
||||
*/
|
||||
protected $successUrl;
|
||||
|
||||
|
|
@ -251,6 +252,10 @@ abstract class QuickForm extends QuickBaseForm
|
|||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $action string|Url
|
||||
* @return $this
|
||||
*/
|
||||
public function setAction($action)
|
||||
{
|
||||
if ($action instanceof Url) {
|
||||
|
|
@ -428,14 +433,18 @@ abstract class QuickForm extends QuickBaseForm
|
|||
|
||||
protected function redirectAndExit($url)
|
||||
{
|
||||
/** @var Web $app */
|
||||
$app = Icinga::app();
|
||||
/** @var Response $response */
|
||||
$response = Icinga::app()->getFrontController()->getResponse();
|
||||
$response = $app->getFrontController()->getResponse();
|
||||
$response->redirectAndExit($url);
|
||||
}
|
||||
|
||||
protected function setHttpResponseCode($code)
|
||||
{
|
||||
Icinga::app()->getFrontController()->getResponse()->setHttpResponseCode($code);
|
||||
/** @var Web $app */
|
||||
$app = Icinga::app();
|
||||
$app->getFrontController()->getResponse()->setHttpResponseCode($code);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -461,8 +470,10 @@ abstract class QuickForm extends QuickBaseForm
|
|||
public function getRequest()
|
||||
{
|
||||
if ($this->request === null) {
|
||||
/** @var Web $app */
|
||||
$app = Icinga::app();
|
||||
/** @var Request $request */
|
||||
$request = Icinga::app()->getFrontController()->getRequest();
|
||||
$request = $app->getFrontController()->getRequest();
|
||||
$this->setRequest($request);
|
||||
}
|
||||
return $this->request;
|
||||
|
|
@ -471,14 +482,15 @@ abstract class QuickForm extends QuickBaseForm
|
|||
public function hasBeenSent()
|
||||
{
|
||||
if ($this->hasBeenSent === null) {
|
||||
|
||||
/** @var Request $req */
|
||||
if ($this->request === null) {
|
||||
$req = Icinga::app()->getFrontController()->getRequest();
|
||||
/** @var Web $app */
|
||||
$app = Icinga::app();
|
||||
$req = $app->getFrontController()->getRequest();
|
||||
} else {
|
||||
$req = $this->request;
|
||||
}
|
||||
|
||||
/** @var Request $req */
|
||||
if ($req->isPost()) {
|
||||
$post = $req->getPost();
|
||||
$this->hasBeenSent = array_key_exists(self::ID, $post) &&
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Businessprocess\Web\Form\Validator;
|
||||
|
||||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Module\Businessprocess\BpNode;
|
||||
use Icinga\Module\Businessprocess\ServiceNode;
|
||||
use Icinga\Module\Businessprocess\State\IcingaDbState;
|
||||
use Icinga\Module\Businessprocess\State\MonitoringState;
|
||||
use Icinga\Module\Monitoring\Backend\MonitoringBackend;
|
||||
use ipl\I18n\Translation;
|
||||
use ipl\Validator\BaseValidator;
|
||||
use ipl\Web\FormElement\TermInput\Term;
|
||||
use LogicException;
|
||||
|
||||
class HostServiceTermValidator extends BaseValidator
|
||||
{
|
||||
use Translation;
|
||||
|
||||
/** @var ?BpNode */
|
||||
protected $parent;
|
||||
|
||||
/**
|
||||
* Set the affected process
|
||||
*
|
||||
* @param BpNode $parent
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setParent(BpNode $parent): self
|
||||
{
|
||||
$this->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];
|
||||
}
|
||||
|
||||
$isValid = true;
|
||||
$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'));
|
||||
$isValid = false;
|
||||
} 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'));
|
||||
}
|
||||
|
||||
$isValid = false;
|
||||
} else {
|
||||
$term->setLabel($node->getAlias());
|
||||
$term->setClass($node->getObjectClassName());
|
||||
}
|
||||
}
|
||||
|
||||
return $isValid;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Businessprocess\Web\Form\Validator;
|
||||
|
||||
use Icinga\Module\Businessprocess\BpConfig;
|
||||
use Icinga\Module\Businessprocess\BpNode;
|
||||
use Icinga\Module\Businessprocess\Forms\EditNodeForm;
|
||||
use Icinga\Module\Businessprocess\Web\Form\QuickForm;
|
||||
use Zend_Validate_Abstract;
|
||||
|
||||
class NoDuplicateChildrenValidator extends Zend_Validate_Abstract
|
||||
{
|
||||
const CHILD_FOUND = 'childFound';
|
||||
|
||||
/** @var QuickForm */
|
||||
protected $form;
|
||||
|
||||
/** @var BpConfig */
|
||||
protected $bp;
|
||||
|
||||
/** @var BpNode */
|
||||
protected $parent;
|
||||
|
||||
/** @var string */
|
||||
protected $label;
|
||||
|
||||
public function __construct(QuickForm $form, BpConfig $bp, BpNode $parent = null)
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
|
|
@ -19,12 +19,10 @@ class ProcessProblemsBadge extends BadgeNavigationItemRenderer
|
|||
|
||||
public function getCount()
|
||||
{
|
||||
$count = 0;
|
||||
if ($this->count === null) {
|
||||
$storage = LegacyStorage::getInstance();
|
||||
$count = 0;
|
||||
$bp = $storage->loadProcess($this->getBpConfigName());
|
||||
|
||||
|
||||
foreach ($bp->getRootNodes() as $rootNode) {
|
||||
if (! $rootNode->isEmpty() &&
|
||||
$rootNode->getState() !== $rootNode::ICINGA_PENDING
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
namespace Icinga\Module\Businessprocess\Web;
|
||||
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Application\Web;
|
||||
use Icinga\Web\Request;
|
||||
use Icinga\Web\Url as WebUrl;
|
||||
|
||||
/**
|
||||
|
|
@ -14,13 +16,17 @@ use Icinga\Web\Url as WebUrl;
|
|||
*/
|
||||
class Url extends WebUrl
|
||||
{
|
||||
/**
|
||||
* @return FakeRequest|Request
|
||||
*/
|
||||
protected static function getRequest()
|
||||
{
|
||||
$app = Icinga::app();
|
||||
if ($app->isCli()) {
|
||||
return new FakeRequest();
|
||||
} else {
|
||||
return $app->getRequest();
|
||||
}
|
||||
|
||||
/** @var Web $app */
|
||||
return $app->getRequest();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
Name: Businessprocess
|
||||
Version: 2.4.0
|
||||
Version: 2.5.0
|
||||
Requires:
|
||||
Libraries: icinga-php-library (>=0.8.0), icinga-php-thirdparty (>=0.11.0)
|
||||
Modules: monitoring (>=2.9.0), icingadb (>=1.0.0)
|
||||
Libraries: icinga-php-library (>=0.13.0), icinga-php-thirdparty (>=0.12.0)
|
||||
Modules: monitoring (>=2.9.0), icingadb (>=1.1.0)
|
||||
Description: A Business Process viewer and modeler
|
||||
Provides a web-based process modeler for Icinga. It integrates as a module
|
||||
into Icinga Web 2 and provides a plugin check command for Icinga. Tile and tree
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
Building Debian packages
|
||||
========================
|
||||
|
||||
This is work in progress, please expect build instructions to change any time
|
||||
soon. Currently, to build custom Debian or Ubuntu packages, please proceed as
|
||||
follows:
|
||||
|
||||
```sh
|
||||
apt-get install --no-install-recommends \
|
||||
debhelper devscripts build-essential fakeroot libparse-debcontrol-perl
|
||||
# Eventually adjust debian/changelog
|
||||
cp -a packaging/debian debian
|
||||
dpkg-buildpackage -us -uc
|
||||
rm -rf debian
|
||||
```
|
||||
|
||||
Please move to your parent directory (`cd ..`) to find your new Debian packages.
|
||||
|
||||