Compare commits

..

18 Commits

Author SHA1 Message Date
Johannes Meyer
1c1f5e306e Prepare version 1.8.2 2024-02-07 15:12:09 +01:00
Ravi Kumar Kempapura Srinivasa
e49cccd13c Remove gipfl/format package dependency 2024-02-07 14:53:47 +01:00
Thomas Gelf
087ce05ebe Prepare v1.8.1 2021-07-13 10:18:55 +02:00
Thomas Gelf
babf42c764 DependencyChecker: new implemenation
This is now also able to give help for web 2.9.x

fixes #2354
fixes #2350
2021-07-13 10:01:39 +02:00
Thomas Gelf
05dfef5ccc ActionController: check view->compact
fixes #2141
2021-07-13 01:02:23 +02:00
Thomas Gelf
19df9eef8c OverriddenVarsResolver: deal with root templates
fixes #2333
2021-07-13 00:50:07 +02:00
Thomas Gelf
2527541326 IcingaServiceSetServiceTable: show "deactivated"
...in RO users overview table

fixes #2344
2021-07-13 00:45:17 +02:00
Thomas Gelf
cda5f56038 ObjectApplyMatches: fetch allied host groups
fixes #2313
2021-07-13 00:29:44 +02:00
Thomas Gelf
9de28e7c79 ObjectPreview: fix inline Service Template links...
...for Service Sets

fixes #2334
2021-07-13 00:24:10 +02:00
Thomas Gelf
62a684b928 IcingaServiceForm: show Override button also in...
...case all fields belong to categories

fixes #2303
2021-07-13 00:16:35 +02:00
Eric Lippmann
98b07b6dbe Use Icinga 2's generate-ticket API 2021-07-13 00:04:18 +02:00
Thomas Gelf
367fb811b1 IcingaConfig: store config file relations in a...
...transaction: all or nothing

fixes #2351
2021-07-12 23:44:33 +02:00
Sebastian Gumprich
d87057e43d IcingaObject: alias scheduled_downtime
Fixes an error when trying to create scheduled_downtime via api

fixes #1879
2021-07-12 23:28:24 +02:00
Thomas Gelf
3a2f019e18 ImportRunBasedPurgeStrategy: fixed combined keys
fixes #2339
2021-07-12 23:17:46 +02:00
Thomas Gelf
575c8fad4e DirectorObjectForm: render inherited scalars only
fixes #2288
2021-07-12 23:17:13 +02:00
Thomas Gelf
b80fdc9c37 IcingaObject/Imports: better error message wording
fixes #2224
2021-07-12 23:16:55 +02:00
Thomas Gelf
032b043e3a IcingaHostForm: strike only HTML elements
fixes #2253
2020-12-16 06:00:57 +01:00
Thomas Gelf
ea2c4fdaf7 doc/changelog: mention Icinga for Windows 2020-12-16 06:00:43 +01:00
512 changed files with 8299 additions and 23633 deletions

View File

@ -1,7 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"

View File

@ -12,7 +12,7 @@ jobs:
steps:
- name: Repository dispatch
uses: peter-evans/repository-dispatch@v3
uses: peter-evans/repository-dispatch@v1
with:
token: ${{ secrets.ICINGABOT_TOKEN }}
repository: Icinga/L10n

View File

@ -1,174 +0,0 @@
name: PHP Tests
on:
push:
branches:
- master
- release/*
pull_request:
branches:
- master
jobs:
lint:
name: Static analysis for php ${{ matrix.php }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
os: ['ubuntu-latest']
steps:
- name: Checkout code base
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: phpcs
- name: Setup dependencies
run: composer require -n --no-progress overtrue/phplint phpunit/phpunit
- name: PHP Lint
if: ${{ ! cancelled() }}
run: ./vendor/bin/phplint -n --exclude={^vendor/.*} -- .
- name: PHP CodeSniffer
if: ${{ ! cancelled() }}
run: phpcs
test:
name: Unit tests with php ${{ matrix.php }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
env:
phpunit-version: 9.5
strategy:
fail-fast: false
matrix:
php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
os: ['ubuntu-latest']
include:
- php: '7.2'
phpunit-version: 8.5
services:
mysql:
image: mariadb
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: director_test
MYSQL_USER: director_test
MYSQL_PASSWORD: director_test
options: >-
--health-cmd "mariadb -s -uroot -proot -e'SHOW DATABASES;' 2> /dev/null | grep director_test > test"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 3306/tcp
pgsql:
image: postgres
env:
POSTGRES_USER: director_test
POSTGRES_PASSWORD: director_test
POSTGRES_DB: director_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432/tcp
steps:
- name: Checkout code base
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: phpunit:${{ matrix.phpunit-version || env.phpunit-version }}
extensions: mysql, pgsql
- name: Setup Icinga Web
run: |
git clone --depth 1 https://github.com/Icinga/icingaweb2.git _icingaweb2
ln -s `pwd` _icingaweb2/modules/director
- name: Setup Libraries
run: |
composer require --working-dir=_icingaweb2 -n --no-progress mockery/mockery
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 Incubator
run: |
git clone --depth 1 https://github.com/Icinga/icingaweb2-module-incubator _icingaweb2/modules/incubator
mkdir -p test/config/enabledModules
cd _icingaweb2/modules/incubator
ln -s `pwd` ../../../test/config/enabledModules/incubator
composer require --no-update \
"gipfl/calendar": "dev-master as 99.x-dev" \
"gipfl/cli": "dev-master as 99.x-dev" \
"gipfl/curl": "dev-master as 99.x-dev" \
"gipfl/data-type": "dev-master as 99.x-dev" \
"gipfl/db-migration": "dev-master as 99.x-dev" \
"gipfl/diff": "dev-master as 99.x-dev" \
"gipfl/format": "dev-master as 99.x-dev" \
"gipfl/icinga-bundles": "dev-master as 99.x-dev" \
"gipfl/icinga-cli-daemon": "dev-master as 99.x-dev" \
"gipfl/icingaweb2": "dev-master as 99.x-dev" \
"gipfl/influxdb": "dev-master as 99.x-dev" \
"gipfl/json": "dev-master as 99.x-dev" \
"gipfl/linux-health": "dev-master as 99.x-dev" \
"gipfl/log": "dev-master as 99.x-dev" \
"gipfl/process": "dev-master as 99.x-dev" \
"gipfl/protocol-jsonrpc": "dev-master as 99.x-dev" \
"gipfl/protocol-netstring": "dev-master as 99.x-dev" \
"gipfl/react-utils": "dev-master as 99.x-dev" \
"gipfl/simple-daemon": "dev-master as 99.x-dev" \
"gipfl/socket": "dev-master as 99.x-dev" \
"gipfl/stream": "dev-master as 99.x-dev" \
"gipfl/systemd": "dev-master as 99.x-dev" \
"gipfl/translation": "dev-master as 99.x-dev" \
"gipfl/web": "dev-master as 99.x-dev" \
"gipfl/zfdb": "dev-master as 99.x-dev" \
"gipfl/zfdbstore": "dev-master as 99.x-dev"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"
bin/make-release.sh snapshot
- name: PHPUnit with MySQL
if: ${{ ! cancelled() }}
env:
ICINGAWEB_LIBDIR: _libraries
ICINGAWEB_CONFIGDIR: test/config
DIRECTOR_TESTDB_RES: Director MySQL TestDB
DIRECTOR_TESTDB: director_test
DIRECTOR_TESTDB_HOST: 127.0.0.1
DIRECTOR_TESTDB_PORT: ${{ job.services.mysql.ports['3306'] }}
DIRECTOR_TESTDB_USER: director_test
DIRECTOR_TESTDB_PASSWORD: director_test
run: phpunit --verbose --bootstrap _icingaweb2/test/php/bootstrap.php
- name: PHPUnit with PostgreSQL
if: ${{ ! cancelled() }}
env:
ICINGAWEB_LIBDIR: _libraries
ICINGAWEB_CONFIGDIR: test/config
DIRECTOR_TESTDB_RES: Director PostgreSQL TestDB
DIRECTOR_TESTDB: director_test
DIRECTOR_TESTDB_HOST: 127.0.0.1
DIRECTOR_TESTDB_PORT: ${{ job.services.pgsql.ports['5432'] }}
DIRECTOR_TESTDB_USER: director_test
DIRECTOR_TESTDB_PASSWORD: director_test
run: phpunit --verbose --bootstrap _icingaweb2/test/php/bootstrap.php

View File

@ -1,16 +0,0 @@
name: PHPStan
on:
pull_request:
jobs:
phpstan:
uses: icinga/github-actions/.github/workflows/phpstan.yml@main
with:
dependencies: |
{
"/icingaweb2" : "https://github.com/Icinga/icingaweb2.git",
"/usr/share/icingaweb2-modules/icingadb" : "https://github.com/Icinga/icingadb-web.git",
"/usr/share/icingaweb2-modules/cube" : "https://github.com/Icinga/icingaweb2-module-cube.git",
"/usr/share/icingaweb2-modules/incubator" : "-b stable/0.22.0 https://github.com/Icinga/icingaweb2-module-incubator"
}

42
.travis.yml Normal file
View File

@ -0,0 +1,42 @@
language: php
php:
- '5.6'
- '7.0'
- '7.1'
- '7.2'
- '7.3'
- '7.4snapshot'
- nightly
services:
- mysql
- postgresql
#cache:
# directories:
# - vendor
matrix:
fast_finish: true
include:
- env: CHECK=phpcs
php: nightly # Note: will be allowed failure
- env: CHECK=phpcs
php: '7.0'
- env: CHECK=phpcs
php: '5.6'
allow_failures:
- php: nightly
env:
- CHECK=phpunit DB=mysql DIRECTOR_TESTDB_RES="Director MySQL TestDB" DIRECTOR_TESTDB="director_test"
- CHECK=phpunit DB=pgsql DIRECTOR_TESTDB_RES="Director PostgreSQL TestDB" DIRECTOR_TESTDB="director_test"
DIRECTOR_TESTDB_USER="director_test"
before_script:
- ./test/setup_vendor.sh
- '[ "$CHECK" != phpunit ] || ./test/travis-prepare.sh'
script:
- '[ "$CHECK" != phpcs ] || php vendor/phpcs.phar'
- '[ "$CHECK" != phpunit ] || php vendor/phpunit.phar --testdox || php vendor/phpunit.phar --verbose'

View File

@ -42,6 +42,9 @@ or code.
* Make sure your code conforms to the [PSR-2: Coding Style Guide](http://www.php-fig.org/psr/psr-2/)
* [Unit-Tests](doc/93-Testing.md) would be great
* Send a [Pull Request](https://github.com/Icinga/icingaweb2-module-director/pulls)
(it will automatically be tested on Travis-CI)
* We try hard to keep our master always green: [![Build Status](https://travis-ci.org/Icinga/icingaweb2-module-director.svg?branch=master)](https://travis-ci.org/Icinga/icingaweb2-module-director)
Addons
------

View File

@ -4,10 +4,8 @@ namespace Icinga\Module\Director\Clicommands;
use Icinga\Date\DateFormatter;
use Icinga\Module\Director\Cli\Command;
use Icinga\Module\Director\Core\Json;
use Icinga\Module\Director\DirectorObject\Automation\Basket;
use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshot;
use Icinga\Module\Director\DirectorObject\ObjectPurgeHelper;
/**
* Export Director Config Objects
@ -81,43 +79,14 @@ class BasketCommand extends Command
* icingacli director basket restore < basket-dump.json
*
* OPTIONS
* --purge <ObjectType>[,<ObjectType] Purge objects of the
* Given types. WARNING: this removes ALL objects that are
* not shipped with the given basket
* --force Purge refuses to purge Objects in case there are
* no Objects of a given ObjectType in the provided basket
* unless forced to do so
*/
public function restoreAction()
{
if ($purge = $this->params->get('purge')) {
$purge = explode(',', $purge);
ObjectPurgeHelper::assertObjectTypesAreEligibleForPurge($purge);
}
$json = file_get_contents('php://stdin');
BasketSnapshot::restoreJson($json, $this->db());
if ($purge) {
$this->purgeObjectTypes(Json::decode($json), $purge, $this->params->get('force'));
}
echo "Objects from Basket Snapshot have been restored\n";
}
protected function purgeObjectTypes($objects, array $types, $force = false)
{
$helper = new ObjectPurgeHelper($this->db());
if ($force) {
$helper->force();
}
foreach ($types as $type) {
list($className, $typeFilter) = BasketSnapshot::getClassAndObjectTypeForType($type);
$helper->purge(
isset($objects->$type) ? (array) $objects->$type : [],
$className,
$typeFilter
);
}
}
/**
*/
protected function requireBasket()

View File

@ -5,8 +5,6 @@ namespace Icinga\Module\Director\Clicommands;
use Icinga\Application\Benchmark;
use Icinga\Module\Director\Cli\Command;
use Icinga\Module\Director\Core\Json;
use Icinga\Module\Director\Deployment\ConditionalDeployment;
use Icinga\Module\Director\Deployment\DeploymentGracePeriod;
use Icinga\Module\Director\Deployment\DeploymentStatus;
use Icinga\Module\Director\IcingaConfig\IcingaConfig;
use Icinga\Module\Director\Import\SyncUtils;
@ -88,23 +86,12 @@ class ConfigCommand extends Command
/**
* Deploy the current configuration
*
* USAGE
*
* icingacli director config deploy [--checksum <checksum>] [--force] [--wait <seconds>]
* [--grace-period <seconds>]
*
* OPTIONS
*
* --checksum <checksum> Optionally deploy a specific configuration
* --force Force a deployment, even when the configuration
* hasn't changed
* --wait <seconds> Optionally wait until Icinga completed it's
* restart
* --grace-period <seconds> Do not deploy if a deployment took place
* less than <seconds> ago
* Does nothing if config didn't change unless you provide
* the --force parameter
*/
public function deployAction()
{
$api = $this->api();
$db = $this->db();
$checksum = $this->params->get('checksum');
@ -115,31 +102,24 @@ class ConfigCommand extends Command
$checksum = $config->getHexChecksum();
}
$deployer = new ConditionalDeployment($db, $this->api());
$deployer->force((bool) $this->params->get('force'));
if ($graceTime = $this->params->get('grace-period')) {
$deployer->setGracePeriod(new DeploymentGracePeriod((int) $graceTime, $db));
$api->wipeInactiveStages($db);
$current = $api->getActiveChecksum($db);
if ($current === $checksum) {
if ($this->params->get('force')) {
fwrite(STDERR, "WARNING: force overrides Grace period\n");
echo "Config matches active stage, deploying anyway\n";
} else {
echo "Config matches active stage, nothing to do\n";
return;
}
}
$deployer->refresh();
if ($deployment = $deployer->deploy($config)) {
if ($deployer->hasBeenForced()) {
echo $deployer->getNoDeploymentReason() . ", deploying anyway\n";
}
if ($api->dumpConfig($config, $db)) {
printf("Config '%s' has been deployed\n", $checksum);
} else {
echo $deployer->getNoDeploymentReason() . "\n";
return;
}
if ($timeout = $this->getWaitTime()) {
$deployed = $deployer->waitForStartupAfterDeploy($deployment, $timeout);
if ($deployed !== true) {
$this->fail("Waiting for Icinga restart failed '%s': %s\n", $checksum, $deployed);
}
$this->fail(
sprintf("Failed to deploy config '%s'\n", $checksum)
);
}
}
@ -162,17 +142,4 @@ class ConfigCommand extends Command
echo Json::encode($result, JSON_PRETTY_PRINT) . "\n";
}
}
protected function getWaitTime()
{
if ($timeout = $this->params->get('wait')) {
if (!ctype_digit($timeout)) {
$this->fail("--wait must be the number of seconds to wait'");
}
return (int) $timeout;
}
return null;
}
}

View File

@ -4,7 +4,6 @@ namespace Icinga\Module\Director\Clicommands;
use Icinga\Application\Benchmark;
use Icinga\Module\Director\Cli\Command;
use Icinga\Module\Director\Core\Json;
use Icinga\Module\Director\Hook\ImportSourceHook;
use Icinga\Module\Director\Objects\ImportSource;
@ -91,7 +90,7 @@ class ImportsourceCommand extends Command
$data = $hook->fetchData();
$source->applyModifiers($data);
Benchmark::measure(sprintf('Got %d rows, ready to dump JSON', count($data)));
echo Json::encode($data, JSON_PRETTY_PRINT);
echo json_encode($data, JSON_PRETTY_PRINT);
}
/**

View File

@ -29,9 +29,8 @@ class KickstartCommand extends Command
* require => Exec['Icinga Director DB migration'],
* }
*
* Exit code 0: A kickstart run is required.
* Exit code 1: Kickstart is configured but a run is not required.
* Exit code 2: A kickstart run is not required.
* Exit code 0 means that a kickstart run is required, code 2 that it is
* not.
*/
public function requiredAction()
{

View File

@ -2,12 +2,8 @@
namespace Icinga\Module\Director\Clicommands;
use Icinga\Cli\Params;
use Icinga\Module\Director\Cli\ObjectCommand;
use Icinga\Module\Director\DirectorObject\Lookup\ServiceFinder;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Resolver\OverrideHelper;
use InvalidArgumentException;
/**
* Manage Icinga Services
@ -17,53 +13,6 @@ use InvalidArgumentException;
*/
class ServiceCommand extends ObjectCommand
{
public function setAction()
{
if (($host = $this->params->get('host')) && $this->params->shift('allow-overrides')) {
if ($this->setServiceProperties($host)) {
return;
}
}
parent::setAction();
}
protected function setServiceProperties($hostname)
{
$serviceName = $this->getName();
$host = IcingaHost::load($hostname, $this->db());
$service = ServiceFinder::find($host, $serviceName);
if ($service->requiresOverrides()) {
self::checkForOverrideSafety($this->params);
$properties = $this->remainingParams();
unset($properties['host']);
OverrideHelper::applyOverriddenVars($host, $serviceName, $properties);
$this->persistChanges($host, 'Host', $hostname . " (Overrides for $serviceName)", 'modified');
return true;
}
return false;
}
protected static function checkForOverrideSafety(Params $params)
{
if ($params->shift('replace')) {
throw new InvalidArgumentException('--replace is not available for Variable Overrides');
}
$appends = self::stripPrefixedProperties($params, 'append-');
$remove = self::stripPrefixedProperties($params, 'remove-');
OverrideHelper::assertVarsForOverrides($appends);
OverrideHelper::assertVarsForOverrides($remove);
if (!empty($appends)) {
throw new InvalidArgumentException('--append- is not available for Variable Overrides');
}
if (!empty($remove)) {
throw new InvalidArgumentException('--remove- is not available for Variable Overrides');
}
// Alternative, untested:
// $this->appendToArrayProperties($object, $appends);
// $this->removeProperties($object, $remove);
}
protected function load($name)
{

View File

@ -3,7 +3,6 @@
namespace Icinga\Module\Director\Clicommands;
use Icinga\Module\Director\Cli\Command;
use Icinga\Module\Director\Objects\DirectorActivityLog;
use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Objects\SyncRule;
use RuntimeException;
@ -99,9 +98,9 @@ class SyncruleCommand extends Command
}
return (object) [
DirectorActivityLog::ACTION_CREATE => $create,
DirectorActivityLog::ACTION_MODIFY => $modify,
DirectorActivityLog::ACTION_DELETE => $delete,
'create' => $create,
'modify' => $modify,
'delete' => $delete,
];
}
@ -167,7 +166,7 @@ class SyncruleCommand extends Command
return 'There are pending changes for this Sync Rule. You should'
. ' trigger a new Sync Run.';
case 'failing':
return 'This Sync Rule failed: ' . $rule->get('last_error_message');
return 'This Sync Rule failed: '. $rule->get('last_error_message');
default:
throw new RuntimeException('Invalid sync state: ' . $rule->get('sync_state'));
}

View File

@ -9,10 +9,12 @@ use gipfl\IcingaWeb2\Link;
use gipfl\Web\Table\NameValueTable;
use gipfl\Web\Widget\Hint;
use Icinga\Date\DateFormatter;
use Icinga\Module\Director\Core\Json;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\DirectorObject\Automation\Basket;
use Icinga\Module\Director\DirectorObject\Automation\BasketDiff;
use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshot;
use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshotFieldResolver;
use Icinga\Module\Director\DirectorObject\Automation\CompareBasketObject;
use Icinga\Module\Director\Forms\AddToBasketForm;
use Icinga\Module\Director\Forms\BasketCreateSnapshotForm;
use Icinga\Module\Director\Forms\BasketForm;
@ -21,7 +23,6 @@ use Icinga\Module\Director\Forms\RestoreBasketForm;
use Icinga\Module\Director\Web\Controller\ActionController;
use ipl\Html\Html;
use Icinga\Module\Director\Web\Table\BasketSnapshotTable;
use Ramsey\Uuid\Uuid;
class BasketController extends ActionController
{
@ -125,26 +126,6 @@ class BasketController extends ActionController
$this->content()->add($form);
}
public function uploadSnapshotAction()
{
$basket = Basket::load($this->params->get('name'), $this->db());
$this->actions()->add(
Link::create(
$this->translate('back'),
'director/basket/snapshots',
['name' => $basket->get('basket_name')],
['class' => 'icon-left-big']
)
);
$this->basketTabs()->activate('snapshots');
$this->addTitle($this->translate('Upload a Configuration Basket Snapshot'));
$form = (new BasketUploadForm())
->setObject($basket)
->setDb($this->db())
->handleRequest();
$this->content()->add($form);
}
/**
* @throws \Icinga\Exception\NotFoundError
*/
@ -165,12 +146,6 @@ class BasketController extends ActionController
$basket->get('basket_name')
));
$this->basketTabs()->activate('snapshots');
$this->actions()->add(Link::create(
$this->translate('Upload'),
'director/basket/upload-snapshot',
['name' => $basket->get('basket_name')],
['class' => 'icon-upload']
));
}
if ($basket !== null) {
$this->content()->add(
@ -269,9 +244,11 @@ class BasketController extends ActionController
$connection = $this->db();
}
$json = $snapshot->getJsonDump();
$this->addSingleTab($this->translate('Snapshot'));
$diff = new BasketDiff($snapshot, $connection);
foreach ($diff->getBasketObjects() as $type => $objects) {
$all = Json::decode($json);
$fieldResolver = new BasketSnapshotFieldResolver($all, $connection);
foreach ($all as $type => $objects) {
if ($type === 'Datafield') {
// TODO: we should now be able to show all fields and link
// to a "diff" for the ones that should be created
@ -295,33 +272,39 @@ class BasketController extends ActionController
$linkParams['target_db'] = $targetDbName;
}
try {
if ($uuid = $object->uuid ?? null) {
$uuid = Uuid::fromString($uuid);
}
if ($diff->hasCurrentInstance($type, $key, $uuid)) {
if ($diff->hasChangedFor($type, $key, $uuid)) {
$link = Link::create(
$this->translate('modified'),
$current = BasketSnapshot::instanceByIdentifier($type, $key, $connection);
if ($current === null) {
$table->addNameValueRow(
$key,
Link::create(
Html::tag('strong', ['style' => 'color: green'], $this->translate('new')),
'director/basket/snapshotobject',
$linkParams,
['class' => 'basket-modified']
);
} else {
$link = Html::tag(
'span',
['class' => 'basket-unchanged'],
$this->translate('unchanged')
);
}
} else {
$link = Link::create(
$this->translate('new'),
'director/basket/snapshotobject',
$linkParams,
['class' => 'basket-new']
$linkParams
)
);
continue;
}
$table->addNameValueRow($key, $link);
$currentExport = $current->export();
$fieldResolver->tweakTargetIds($currentExport);
// Ignore originalId
if (isset($currentExport->originalId)) {
unset($currentExport->originalId);
}
if (isset($object->originalId)) {
unset($object->originalId);
}
$hasChanged = ! CompareBasketObject::equals($currentExport, $object);
$table->addNameValueRow(
$key,
$hasChanged
? Link::create(
Html::tag('strong', ['style' => 'color: orange'], $this->translate('modified')),
'director/basket/snapshotobject',
$linkParams
)
: Html::tag('span', ['style' => 'color: green'], $this->translate('unchanged'))
);
} catch (Exception $e) {
$table->addNameValueRow(
$key,
@ -337,6 +320,7 @@ class BasketController extends ActionController
$this->content()->add(Html::tag('h2', $type));
$this->content()->add($table);
}
$this->content()->add(Html::tag('div', ['style' => 'height: 5em']));
}
/**
@ -383,27 +367,38 @@ class BasketController extends ActionController
*/
]);
$json = $snapshot->getJsonDump();
$this->addSingleTab($this->translate('Snapshot'));
$objects = Json::decode($json);
$targetDbName = $this->params->get('target_db');
if ($targetDbName === null) {
$connection = $this->db();
} else {
$connection = Db::fromResourceName($targetDbName);
}
$diff = new BasketDiff($snapshot, $connection);
$object = $diff->getBasketObject($type, $key);
if ($uuid = $object->uuid ?? null) {
$uuid = Uuid::fromString($uuid);
$fieldResolver = new BasketSnapshotFieldResolver($objects, $connection);
$objectFromBasket = $objects->$type->$key;
unset($objectFromBasket->originalId);
CompareBasketObject::normalize($objectFromBasket);
$objectFromBasket = Json::encode($objectFromBasket, JSON_PRETTY_PRINT);
$current = BasketSnapshot::instanceByIdentifier($type, $key, $connection);
if ($current === null) {
$current = '';
} else {
$exported = $current->export();
$fieldResolver->tweakTargetIds($exported);
unset($exported->originalId);
CompareBasketObject::normalize($exported);
$current = Json::encode($exported, JSON_PRETTY_PRINT);
}
$basketJson = $diff->getBasketString($type, $key);
$currentJson = $diff->getCurrentString($type, $key, $uuid);
if ($currentJson === $basketJson) {
if ($current === $objectFromBasket) {
$this->content()->add([
Hint::ok('Basket equals current object'),
Html::tag('pre', $currentJson)
Html::tag('pre', $current)
]);
} else {
$this->content()->add(new InlineDiff(new PhpDiff($currentJson, $basketJson)));
$this->content()->add(new InlineDiff(new PhpDiff($current, $objectFromBasket)));
}
}

View File

@ -1,138 +0,0 @@
<?php
namespace Icinga\Module\Director\Controllers;
use gipfl\Diff\HtmlRenderer\SideBySideDiff;
use gipfl\Diff\PhpDiff;
use gipfl\IcingaWeb2\Widget\NameValueTable;
use Icinga\Module\Director\Data\Db\DbObjectStore;
use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
use Icinga\Module\Director\Db\Branch\BranchActivity;
use Icinga\Module\Director\Db\Branch\BranchStore;
use Icinga\Module\Director\IcingaConfig\IcingaConfig;
use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Objects\SyncRule;
use Icinga\Module\Director\PlainObjectRenderer;
use Icinga\Module\Director\Web\Controller\ActionController;
use Icinga\Module\Director\Web\Controller\BranchHelper;
use Icinga\Module\Director\Web\Widget\IcingaConfigDiff;
use ipl\Html\Html;
class BranchController extends ActionController
{
use BranchHelper;
public function init()
{
parent::init();
IcingaObject::setDbObjectStore(new DbObjectStore($this->db(), $this->getBranch()));
SyncRule::setDbObjectStore(new DbObjectStore($this->db(), $this->getBranch()));
}
protected function checkDirectorPermissions()
{
}
public function activityAction()
{
$this->assertPermission('director/showconfig');
$ts = $this->params->getRequired('ts');
$activity = BranchActivity::load($ts, $this->db());
$store = new BranchStore($this->db());
$branch = $store->fetchBranchByUuid($activity->getBranchUuid());
if ($branch->isSyncPreview()) {
$this->addSingleTab($this->translate('Sync Preview'));
$this->addTitle($this->translate('Expected Modification'));
} else {
$this->addSingleTab($this->translate('Activity'));
$this->addTitle($this->translate('Branch Activity'));
}
$this->content()->add($this->prepareActivityInfo($activity));
$this->showActivity($activity);
}
protected function prepareActivityInfo(BranchActivity $activity)
{
$table = new NameValueTable();
$table->addNameValuePairs([
$this->translate('Author') => $activity->getAuthor(),
$this->translate('Date') => date('Y-m-d H:i:s', $activity->getTimestamp()),
$this->translate('Action') => $activity->getAction()
. ' ' . preg_replace('/^icinga_/', '', $activity->getObjectTable())
. ' ' . $activity->getObjectName(),
// $this->translate('Actions') => ['Undo form'],
]);
return $table;
}
protected function leftFromActivity(BranchActivity $activity)
{
if ($activity->isActionCreate()) {
return null;
}
$object = DbObjectTypeRegistry::newObject($activity->getObjectTable(), [], $this->db());
$properties = $this->objectTypeFirst($activity->getFormerProperties()->jsonSerialize());
foreach ($properties as $key => $value) {
$object->set($key, $value);
}
return $object;
}
protected function rightFromActivity(BranchActivity $activity)
{
if ($activity->isActionDelete()) {
return null;
}
$object = DbObjectTypeRegistry::newObject($activity->getObjectTable(), [], $this->db());
if (! $activity->isActionCreate()) {
foreach ($activity->getFormerProperties()->jsonSerialize() as $key => $value) {
$object->set($key, $value);
}
}
$properties = $this->objectTypeFirst($activity->getModifiedProperties()->jsonSerialize());
foreach ($properties as $key => $value) {
$object->set($key, $value);
}
return $object;
}
protected function objectTypeFirst($properties)
{
$properties = (array) $properties;
if (isset($properties['object_type'])) {
$type = $properties['object_type'];
unset($properties['object_type']);
$properties = ['object_type' => $type] + $properties;
}
return $properties;
}
protected function showActivity(BranchActivity $activity)
{
$left = $this->leftFromActivity($activity);
$right = $this->rightFromActivity($activity);
if ($left instanceof IcingaObject || $right instanceof IcingaObject) {
$this->content()->add(new IcingaConfigDiff(
$left ? $left->toSingleIcingaConfig() : $this->createEmptyConfig(),
$right ? $right->toSingleIcingaConfig() : $this->createEmptyConfig()
));
} else {
$this->content()->add([
Html::tag('h3', $this->translate('Modification')),
new SideBySideDiff(new PhpDiff(
PlainObjectRenderer::render($left->getProperties()),
PlainObjectRenderer::render($right->getProperties())
))
]);
}
}
protected function createEmptyConfig()
{
return new IcingaConfig($this->db());
}
}

View File

@ -3,8 +3,6 @@
namespace Icinga\Module\Director\Controllers;
use gipfl\Web\Widget\Hint;
use Icinga\Module\Director\Objects\IcingaCommandArgument;
use Icinga\Module\Director\Web\Table\BranchedIcingaCommandArgumentTable;
use ipl\Html\Html;
use Icinga\Module\Director\Forms\IcingaCommandArgumentForm;
use Icinga\Module\Director\Objects\IcingaCommand;
@ -24,14 +22,9 @@ class CommandController extends ObjectController
parent::init();
$o = $this->object;
if ($o && ! $o->isExternal()) {
if ($this->getBranch()->isBranch()) {
$urlParams = ['uuid' => $o->getUniqueId()->toString()];
} else {
$urlParams = ['name' => $o->getObjectName()];
}
$this->tabs()->add('arguments', [
'url' => 'director/command/arguments',
'urlParams' => $urlParams,
'urlParams' => ['name' => $o->getObjectName()],
'label' => 'Arguments'
]);
}
@ -90,33 +83,16 @@ class CommandController extends ObjectController
$o = $this->object;
$this->tabs()->activate('arguments');
$this->addTitle($this->translate('Command arguments: %s'), $o->getObjectName());
$form = (new IcingaCommandArgumentForm())
->setBranch($this->getBranch())
->setCommandObject($o);
if ($argument = $p->shift('argument')) {
$form = IcingaCommandArgumentForm::load()->setCommandObject($o);
if ($id = $p->shift('argument_id')) {
$this->addBackLink('director/command/arguments', [
'name' => $p->get('name')
]);
if ($this->branch->isBranch()) {
$arguments = $o->arguments();
$argument = $arguments->get($argument);
// IcingaCommandArgument::create((array) $arguments->get($argument)->toFullPlainObject());
// $argument->setBeingLoadedFromDb();
} else {
$argument = IcingaCommandArgument::load([
'command_id' => $o->get('id'),
'argument_name' => $argument
], $this->db());
}
$form->setObject($argument);
$form->loadObject($id);
}
$form->handleRequest();
$this->content()->add([$form]);
if ($this->branch->isBranch()) {
(new BranchedIcingaCommandArgumentTable($o, $this->getBranch()))->renderTo($this);
} else {
(new IcingaCommandArgumentTable($o, $this->getBranch()))->renderTo($this);
}
IcingaCommandArgumentTable::create($o)->renderTo($this);
}
protected function hasBasketSupport()

View File

@ -8,17 +8,13 @@ use gipfl\Web\Widget\Hint;
use Icinga\Data\Filter\Filter;
use Icinga\Exception\IcingaException;
use Icinga\Exception\NotFoundError;
use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Db\Branch\Branch;
use Icinga\Module\Director\Deployment\DeploymentStatus;
use Icinga\Module\Director\Forms\DeployConfigForm;
use Icinga\Module\Director\Forms\SettingsForm;
use Icinga\Module\Director\IcingaConfig\IcingaConfig;
use Icinga\Module\Director\Objects\DirectorDeploymentLog;
use Icinga\Module\Director\Settings;
use Icinga\Module\Director\Web\Controller\BranchHelper;
use Icinga\Module\Director\Web\Table\ActivityLogTable;
use Icinga\Module\Director\Web\Table\BranchActivityTable;
use Icinga\Module\Director\Web\Table\ConfigFileDiffTable;
use Icinga\Module\Director\Web\Table\DeploymentLogTable;
use Icinga\Module\Director\Web\Table\GeneratedConfigFileTable;
@ -38,8 +34,6 @@ use gipfl\IcingaWeb2\Url;
class ConfigController extends ActionController
{
use BranchHelper;
protected $isApified = true;
protected function checkDirectorPermissions()
@ -67,15 +61,13 @@ class ConfigController extends ActionController
// No problem, Icinga might be reloading
}
if (! $this->getBranch()->isBranch()) {
// TODO: a form!
$this->actions()->add(Link::create(
$this->translate('Render config'),
'director/config/store',
null,
['class' => 'icon-wrench']
));
}
// TODO: a form!
$this->actions()->add(Link::create(
$this->translate('Render config'),
'director/config/store',
null,
['class' => 'icon-wrench']
));
$this->tabs(new InfraTabs($this->Auth()))->activate('deploymentlog');
$table = new DeploymentLogTable($this->db());
@ -155,7 +147,7 @@ class ConfigController extends ActionController
return;
}
$this->assertPermission('director/audit');
$this->showOptionalBranchActivity();
$this->setAutorefreshInterval(10);
$this->tabs(new InfraTabs($this->Auth()))->activate('activitylog');
$this->addTitle($this->translate('Activity Log'));
@ -187,7 +179,7 @@ class ConfigController extends ActionController
['class' => 'icon-user', 'data-base-target' => '_self']
));
}
if ($this->hasPermission(Permission::DEPLOY) && ! $this->getBranch()->isBranch()) {
if ($this->hasPermission('director/deploy')) {
if ($this->db()->hasDeploymentEndpoint()) {
$this->actions()->add(DeployConfigForm::load()
->setDb($this->db())
@ -284,7 +276,6 @@ class ConfigController extends ActionController
$config,
$this->db(),
$this->api(),
$this->getBranch(),
$deploymentId
));
@ -370,35 +361,33 @@ class ConfigController extends ActionController
$configs = $db->enumDeployedConfigs();
foreach (array($leftSum, $rightSum) as $sum) {
if ($sum && ! array_key_exists($sum, $configs)) {
if (! array_key_exists($sum, $configs)) {
$configs[$sum] = substr($sum, 0, 7);
}
}
$baseUrl = $this->url()->without(['left', 'right']);
$this->content()->add(
Html::tag('form', ['action' => (string) $baseUrl, 'method' => 'GET', 'class' => 'director-form'], [
new HtmlString($this->view->formSelect(
'left',
$leftSum,
['class' => ['autosubmit', 'config-diff']],
[null => $this->translate('- please choose -')] + $configs
)),
Link::create(
Icon::create('flapping'),
$baseUrl,
['left' => $rightSum, 'right' => $leftSum]
),
new HtmlString($this->view->formSelect(
'right',
$rightSum,
['class' => ['autosubmit', 'config-diff']],
[null => $this->translate('- please choose -')] + $configs
)),
])
);
$this->content()->add(Html::tag('form', ['action' => (string) $baseUrl, 'method' => 'GET'], [
new HtmlString($this->view->formSelect(
'left',
$leftSum,
['class' => 'autosubmit', 'style' => 'width: 37%'],
[null => $this->translate('- please choose -')] + $configs
)),
Link::create(
Icon::create('flapping'),
$baseUrl,
['left' => $rightSum, 'right' => $leftSum]
),
new HtmlString($this->view->formSelect(
'right',
$rightSum,
['class' => 'autosubmit', 'style' => 'width: 37%'],
[null => $this->translate('- please choose -')] + $configs
)),
]));
if ($rightSum === null || $leftSum === null || ! strlen($rightSum) || ! strlen($leftSum)) {
if (! strlen($rightSum) || ! strlen($leftSum)) {
return;
}
ConfigFileDiffTable::load($leftSum, $rightSum, $this->db())->renderTo($this);
@ -433,32 +422,6 @@ class ConfigController extends ActionController
)));
}
protected function showOptionalBranchActivity()
{
if ($this->url()->hasParam('idRangeEx')) {
return;
}
$branch = $this->getBranch();
if ($branch->isBranch() && (int) $this->params->get('page', '1') === 1) {
$table = new BranchActivityTable($branch->getUuid(), $this->db());
if (count($table) > 0) {
$this->content()->add(Hint::info(Html::sprintf($this->translate(
'The following modifications are visible in this %s only...'
), Branch::requireHook()->linkToBranch(
$branch,
$this->Auth(),
$this->translate('configuration branch')
))));
$this->content()->add($table);
$this->content()->add(Html::tag('br'));
$this->content()->add(Hint::ok($this->translate(
'...and the modifications below are already in the main branch:'
)));
$this->content()->add(Html::tag('br'));
}
}
}
/**
* @param $checksum
*/
@ -482,7 +445,7 @@ class ConfigController extends ActionController
*/
protected function deploymentFailed($checksum, $error = null)
{
$extra = $error ? ': ' . $error : '';
$extra = $error ? ': ' . $error: '';
if ($this->getRequest()->isApiRequest()) {
$this->sendJsonError($this->getResponse(), 'Config deployment failed' . $extra);
@ -503,8 +466,7 @@ class ConfigController extends ActionController
{
$tabs = $this->tabs();
if (
$this->hasPermission(Permission::DEPLOY)
if ($this->hasPermission('director/deploy')
&& $deploymentId = $this->params->get('deployment_id')
) {
$tabs->add('deployment', [
@ -514,7 +476,7 @@ class ConfigController extends ActionController
]);
}
if ($this->hasPermission(Permission::SHOW_CONFIG)) {
if ($this->hasPermission('director/showconfig')) {
$tabs->add('config', [
'label' => $this->translate('Config'),
'url' => 'director/config/files',

View File

@ -3,7 +3,9 @@
namespace Icinga\Module\Director\Controllers;
use Icinga\Module\Director\Web\Tabs\MainTabs;
use Icinga\Module\Director\Web\Widget\HealthCheckPluginOutput;
use Icinga\Module\Director\Dashboard\Dashboard;
use Icinga\Module\Director\Health;
use Icinga\Module\Director\Web\Controller\ActionController;
use Icinga\Module\Director\Web\Form\DbSelectorForm;
@ -36,7 +38,6 @@ class DashboardController extends ActionController
$mainDashboards = [
'Objects',
'Alerts',
'Branches',
'Automation',
'Deployment',
'Director',

View File

@ -2,18 +2,10 @@
namespace Icinga\Module\Director\Controllers;
use gipfl\Web\Widget\Hint;
use Icinga\Exception\NotFoundError;
use Icinga\Module\Director\Forms\DirectorDatalistEntryForm;
use Icinga\Module\Director\Forms\DirectorDatalistForm;
use Icinga\Module\Director\Forms\IcingaServiceDictionaryMemberForm;
use Icinga\Module\Director\Objects\DirectorDatalist;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Objects\IcingaService;
use Icinga\Module\Director\PlainObjectRenderer;
use Icinga\Module\Director\Web\Controller\ActionController;
use Icinga\Module\Director\Web\Form\IcingaObjectFieldLoader;
use Icinga\Module\Director\Web\Table\CustomvarTable;
use Icinga\Module\Director\Web\Table\DatafieldCategoryTable;
use Icinga\Module\Director\Web\Table\DatafieldTable;
@ -21,9 +13,6 @@ use Icinga\Module\Director\Web\Table\DatalistEntryTable;
use Icinga\Module\Director\Web\Table\DatalistTable;
use Icinga\Module\Director\Web\Tabs\DataTabs;
use gipfl\IcingaWeb2\Link;
use InvalidArgumentException;
use ipl\Html\Html;
use ipl\Html\Table;
class DataController extends ActionController
{
@ -151,218 +140,6 @@ class DataController extends ActionController
$this->content()->add([$form, $table]);
}
public function dictionaryAction()
{
$connection = $this->db();
$this->addSingleTab('Nested Dictionary');
$varName = $this->params->get('varname');
$instance = $this->url()->getParam('instance');
$action = $this->url()->getParam('action');
$object = $this->requireObject();
if ($instance || $action) {
$this->actions()->add(
Link::create($this->translate('Back'), $this->url()->without(['action', 'instance']), null, [
'class' => 'icon-edit'
])
);
} else {
$this->actions()->add(
Link::create($this->translate('Add'), $this->url(), [
'action' => 'add'
], [
'class' => 'icon-edit'
])
);
}
$subjects = $this->prepareSubjectsLabel($object, $varName);
$fieldLoader = new IcingaObjectFieldLoader($object);
$instances = $this->getCurrentInstances($object, $varName);
if (empty($instances)) {
$this->content()->add(Hint::info(sprintf(
$this->translate('No %s have been created yet'),
$subjects
)));
} else {
$this->content()->add($this->prepareInstancesTable($instances));
}
$field = $this->getFieldByName($fieldLoader, $varName);
$template = $object::load([
'object_name' => $field->getSetting('template_name')
], $connection);
$form = new IcingaServiceDictionaryMemberForm();
$form->setDb($connection);
if ($instance) {
$instanceObject = $object::create([
'imports' => [$template],
'object_name' => $instance,
'vars' => $instances[$instance]
], $connection);
$form->setObject($instanceObject);
} elseif ($action === 'add') {
$form->presetImports([$template->getObjectName()]);
} else {
return;
}
if ($instance) {
if (! isset($instances[$instance])) {
throw new NotFoundError("There is no such instance: $instance");
}
$subTitle = sprintf($this->translate('Modify instance: %s'), $instance);
} else {
$subTitle = $this->translate('Add a new instance');
}
$this->content()->add(Html::tag('h2', ['class' => 'dictionary-header'], $subTitle));
$form->handleRequest($this->getRequest());
$this->content()->add($form);
if ($form->succeeded()) {
$virtualObject = $form->getObject();
$name = $virtualObject->getObjectName();
$params = $form->getObject()->getVars();
$instances[$name] = $params;
if ($name !== $instance) { // Has been renamed
unset($instances[$instance]);
}
ksort($instances);
$object->set("vars.$varName", (object)$instances);
$object->store();
$this->redirectNow($this->url()->without(['instance', 'action']));
} elseif ($form->shouldBeDeleted()) {
unset($instances[$instance]);
if (empty($instances)) {
$object->set("vars.$varName", null)->store();
} else {
$object->set("vars.$varName", (object)$instances)->store();
}
$this->redirectNow($this->url()->without(['instance', 'action']));
}
}
protected function requireObject()
{
$connection = $this->db();
$hostName = $this->params->getRequired('host');
$serviceName = $this->params->get('service');
if ($serviceName) {
$host = IcingaHost::load($hostName, $connection);
$object = IcingaService::load([
'host_id' => $host->get('id'),
'object_name' => $serviceName,
], $connection);
} else {
$object = IcingaHost::load($hostName, $connection);
}
if (! $object->isObject()) {
throw new InvalidArgumentException(sprintf(
'Only single objects allowed, %s is a %s',
$object->getObjectName(),
$object->get('object_type')
));
}
return $object;
}
protected function shorten($string, $maxLen)
{
if (strlen($string) <= $maxLen) {
return $string;
}
return substr($string, 0, $maxLen) . '...';
}
protected function getFieldByName(IcingaObjectFieldLoader $loader, $name)
{
foreach ($loader->getFields() as $field) {
if ($field->get('varname') === $name) {
return $field;
}
}
throw new InvalidArgumentException("Found no configured field for '$name'");
}
/**
* @param IcingaObject $object
* @param $varName
* @return array
*/
protected function getCurrentInstances(IcingaObject $object, $varName)
{
$currentVars = $object->getVars();
if (isset($currentVars->$varName)) {
$currentValue = $currentVars->$varName;
} else {
$currentValue = (object)[];
}
if (is_object($currentValue)) {
$currentValue = (array)$currentValue;
} else {
throw new InvalidArgumentException(sprintf(
'"%s" is not a valid Dictionary',
json_encode($currentValue)
));
}
return $currentValue;
}
/**
* @param array $currentValue
* @param $subjects
* @return Hint|Table
*/
protected function prepareInstancesTable(array $currentValue)
{
$table = new Table();
$table->addAttributes([
'class' => 'common-table table-row-selectable'
]);
$table->getHeader()->add(
Table::row([
$this->translate('Key / Instance'),
$this->translate('Properties')
], ['class' => 'text-align-left'], 'th')
);
foreach ($currentValue as $key => $item) {
$table->add(Table::row([
Link::create($key, $this->url()->with('instance', $key)),
str_replace("\n", ' ', $this->shorten(PlainObjectRenderer::render($item), 512))
]));
}
return $table;
}
/**
* @param IcingaObject $object
* @param $varName
* @return string
*/
protected function prepareSubjectsLabel(IcingaObject $object, $varName)
{
if ($object instanceof IcingaService) {
$hostName = $object->get('host');
$subjects = $object->getObjectName() . " ($varName)";
} else {
$hostName = $object->getObjectName();
$subjects = sprintf(
$this->translate('%s instances'),
$varName
);
}
$this->addTitle(sprintf(
$this->translate('%s on %s'),
$subjects,
$hostName
));
return $subjects;
}
protected function addListActions(DirectorDatalist $list)
{
$this->actions()->add(

View File

@ -19,11 +19,17 @@ class DatafieldController extends ActionController
public function indexAction()
{
$edit = false;
if ($id = $this->params->get('id')) {
$edit = true;
}
$form = DirectorDatafieldForm::load()
->setDb($this->db());
if ($id = $this->params->get('id')) {
$form->loadObject((int) $id);
if ($edit) {
$form->loadObject($id);
$this->addTitle(
$this->translate('Modify %s'),
$form->getObject()->varname

View File

@ -3,10 +3,6 @@
namespace Icinga\Module\Director\Controllers;
use gipfl\Web\Widget\Hint;
use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Integration\Icingadb\IcingadbBackend;
use Icinga\Module\Director\Integration\MonitoringModule\Monitoring;
use Icinga\Module\Director\Web\Table\ObjectsTableService;
use ipl\Html\Html;
use gipfl\IcingaWeb2\Link;
use gipfl\IcingaWeb2\Url;
@ -14,7 +10,6 @@ use gipfl\IcingaWeb2\Widget\Tabs;
use Exception;
use Icinga\Module\Director\CustomVariable\CustomVariableDictionary;
use Icinga\Module\Director\Db\AppliedServiceSetLoader;
use Icinga\Module\Director\DirectorObject\Lookup\ServiceFinder;
use Icinga\Module\Director\Forms\IcingaAddServiceForm;
use Icinga\Module\Director\Forms\IcingaServiceForm;
use Icinga\Module\Director\Forms\IcingaServiceSetForm;
@ -27,52 +22,23 @@ use Icinga\Module\Director\Web\Controller\ObjectController;
use Icinga\Module\Director\Web\SelfService;
use Icinga\Module\Director\Web\Table\IcingaHostAppliedForServiceTable;
use Icinga\Module\Director\Web\Table\IcingaHostAppliedServicesTable;
use Icinga\Module\Director\Web\Table\IcingaHostServiceTable;
use Icinga\Module\Director\Web\Table\IcingaServiceSetServiceTable;
use Icinga\Module\Director\Web\Widget\HostServiceRedirector;
class HostController extends ObjectController
{
protected function checkDirectorPermissions()
{
$host = $this->getHostObject();
$auth = $this->Auth();
$backend = $this->backend();
if (
$this->isServiceAction()
&& $backend->canModifyService($host->getObjectName(), $this->getParam('service'))
) {
return;
}
if ($this->isServicesReadOnlyAction() && $auth->hasPermission($this->getServicesReadOnlyPermission())) {
return;
}
if ($auth->hasPermission(Permission::HOSTS)) { // faster
return;
}
if ($backend->canModifyHost($host->getObjectName())) {
return;
}
$this->assertPermission(Permission::HOSTS); // complain about default hosts permission
}
protected function isServicesReadOnlyAction()
{
return in_array($this->getRequest()->getActionName(), [
if (in_array($this->getRequest()->getActionName(), [
'servicesro',
'findservice',
'invalidservice',
]);
}
protected function isServiceAction()
{
return in_array($this->getRequest()->getActionName(), [
'servicesro',
'findservice',
'invalidservice',
'servicesetservice',
'appliedservice',
'inheritedservice',
]);
'invalidservice'
])) {
$this->assertPermission('director/monitoring/services-ro');
} else {
$this->assertPermission('director/hosts');
}
}
/**
@ -96,7 +62,6 @@ class HostController extends ObjectController
$this->addTitle($this->translate('Add Service to %s'), $host->getObjectName());
$this->content()->add(
IcingaAddServiceForm::load()
->setBranch($this->getBranch())
->setHost($host)
->setDb($this->db())
->handleRequest()
@ -108,10 +73,8 @@ class HostController extends ObjectController
$host = $this->getHostObject();
$this->addServicesHeader();
$this->addTitle($this->translate('Add Service Set to %s'), $host->getObjectName());
$this->content()->add(
IcingaServiceSetForm::load()
->setBranch($this->getBranch())
->setHost($host)
->setDb($this->db())
->handleRequest()
@ -137,38 +100,16 @@ class HostController extends ObjectController
));
}
/**
* @throws \Icinga\Exception\NotFoundError
*/
public function findserviceAction()
{
$auth = $this->Auth();
$host = $this->getHostObject();
$hostName = $host->getObjectName();
$serviceName = $this->params->get('service');
$info = ServiceFinder::find($host, $serviceName);
$backend = $this->backend();
if ($info && $auth->hasPermission(Permission::HOSTS)) {
$redirectUrl = $info->getUrl();
} elseif (
$info
&& (($backend instanceof Monitoring && $auth->hasPermission(Permission::MONITORING_HOSTS))
|| ($backend instanceof IcingadbBackend && $auth->hasPermission(Permission::ICINGADB_HOSTS))
)
&& $backend->canModifyService($hostName, $serviceName)
) {
$redirectUrl = $info->getUrl();
} elseif ($auth->hasPermission($this->getServicesReadOnlyPermission())) {
$redirectUrl = Url::fromPath('director/host/servicesro', [
'name' => $hostName,
'service' => $serviceName
]);
} else {
$redirectUrl = Url::fromPath('director/host/invalidservice', [
'name' => $hostName,
'service' => $serviceName,
]);
}
$this->redirectNow($redirectUrl);
$redirector = new HostServiceRedirector($host, $this->getAuth());
$this->redirectNow(
$redirector->getRedirectionUrl($this->params->get('service'))
);
}
/**
@ -216,16 +157,12 @@ class HostController extends ObjectController
public function servicesAction()
{
$this->addServicesHeader();
$db = $this->db();
$host = $this->getHostObject();
$this->addTitle($this->translate('Services: %s'), $host->getObjectName());
$branch = $this->getBranch();
$hostHasBeenCreatedInBranch = $branch->isBranch() && $host->get('id');
$content = $this->content();
$table = (new ObjectsTableService($this->db(), $this->Auth()))
->setHost($host)
->setBranch($branch)
->setTitle($this->translate('Individual Service objects'))
->removeQueryLimit();
$table = IcingaHostServiceTable::load($host)
->setTitle($this->translate('Individual Service objects'));
if (count($table)) {
$content->add($table);
@ -235,25 +172,18 @@ class HostController extends ObjectController
$parents = IcingaTemplateRepository::instanceByObject($this->object)
->getTemplatesFor($this->object, true);
foreach ($parents as $parent) {
$table = (new ObjectsTableService($this->db(), $this->Auth()))
->setBranch($branch)
->setHost($parent)
->setInheritedBy($host)
->removeQueryLimit();
$table = IcingaHostServiceTable::load($parent)->setInheritedBy($host);
if (count($table)) {
$content->add(
$table->setTitle(sprintf(
$this->translate('Inherited from %s'),
'Inherited from %s',
$parent->getObjectName()
))
);
}
}
if (! $hostHasBeenCreatedInBranch) {
$this->addHostServiceSetTables($host);
}
$this->addHostServiceSetTables($host);
foreach ($parents as $parent) {
$this->addHostServiceSetTables($parent, $host);
}
@ -265,10 +195,8 @@ class HostController extends ObjectController
$content->add(
IcingaServiceSetServiceTable::load($set)
// ->setHost($host)
->setBranch($branch)
->setAffectedHost($host)
->setTitle($title)
->removeQueryLimit()
);
}
@ -291,19 +219,15 @@ class HostController extends ObjectController
*/
public function servicesroAction()
{
$this->assertPermission($this->getServicesReadOnlyPermission());
$this->assertPermission('director/monitoring/services-ro');
$host = $this->getHostObject();
$service = $this->params->getRequired('service');
$db = $this->db();
$branch = $this->getBranch();
$this->controls()->setTabs(new Tabs());
$this->addSingleTab($this->translate('Configuration (read-only)'));
$this->addTitle($this->translate('Services on %s'), $host->getObjectName());
$content = $this->content();
$table = (new ObjectsTableService($db, $this->Auth()))
->setHost($host)
->setBranch($branch)
$table = IcingaHostServiceTable::load($host)
->setReadonly()
->highlightService($service)
->setTitle($this->translate('Individual Service objects'));
@ -316,10 +240,8 @@ class HostController extends ObjectController
$parents = IcingaTemplateRepository::instanceByObject($this->object)
->getTemplatesFor($this->object, true);
foreach ($parents as $parent) {
$table = (new ObjectsTableService($db, $this->Auth()))
$table = IcingaHostServiceTable::load($parent)
->setReadonly()
->setBranch($branch)
->setHost($parent)
->highlightService($service)
->setInheritedBy($host);
if (count($table)) {
@ -344,7 +266,6 @@ class HostController extends ObjectController
$content->add(
IcingaServiceSetServiceTable::load($set)
// ->setHost($host)
->setBranch($branch)
->setAffectedHost($host)
->setReadonly()
->highlightService($service)
@ -372,9 +293,6 @@ class HostController extends ObjectController
if ($affectedHost === null) {
$affectedHost = $host;
}
if ($host->get('id') === null) {
return;
}
$query = $db->getDbAdapter()->select()
->from(
@ -396,9 +314,7 @@ class HostController extends ObjectController
$title = sprintf($this->translate('%s (Service set)'), $name);
$table = IcingaServiceSetServiceTable::load($set)
->setHost($host)
->setBranch($this->getBranch())
->setAffectedHost($affectedHost)
->removeQueryLimit()
->setTitle($title);
if ($roService) {
$table->setReadonly()->highlightService($roService);
@ -434,7 +350,6 @@ class HostController extends ObjectController
$this->content()->add(
IcingaServiceForm::load()
->setDb($db)
->setBranch($this->getBranch())
->setHost($host)
->setApplyGenerated($parent)
->setObject($service)
@ -475,7 +390,6 @@ class HostController extends ObjectController
$form = IcingaServiceForm::load()
->setDb($db)
->setBranch($this->getBranch())
->setHost($host)
->setInheritedFrom($from->getObjectName())
->setObject($service)
@ -553,7 +467,6 @@ class HostController extends ObjectController
$form = IcingaServiceForm::load()
->setDb($db)
->setBranch($this->getBranch())
->setHost($host)
->setServiceSet($set)
->setObject($service)
@ -594,20 +507,20 @@ class HostController extends ObjectController
{
$host = $this->object;
try {
$backend = $this->backend();
if (
$host instanceof IcingaHost
&& $host->isObject()
&& $backend->hasHost($host->getObjectName())
$mon = $this->monitoring();
if ($host->isObject()
&& $mon->isAvailable()
&& $mon->hasHost($host->getObjectName())
) {
$this->actions()->add(
Link::create(
$this->translate('Show'),
$backend->getHostUrl($host->getObjectName()),
null,
['class' => 'icon-globe critical', 'data-base-target' => '_next']
)
);
$this->actions()->add(Link::create(
$this->translate('Show'),
'monitoring/host/show',
['host' => $host->getObjectName()],
[
'class' => 'icon-globe critical',
'data-base-target' => '_next'
]
));
// Intentionally placed here, show it only for deployed Hosts
$this->addOptionalInspectLink();
@ -619,7 +532,7 @@ class HostController extends ObjectController
protected function addOptionalInspectLink()
{
if (! $this->hasPermission(Permission::INSPECT)) {
if (! $this->hasPermission('director/inspect')) {
return;
}
@ -639,25 +552,11 @@ class HostController extends ObjectController
}
/**
* @return ?IcingaHost
* @return IcingaHost
*/
protected function getHostObject()
{
if ($this->object !== null) {
assert($this->object instanceof IcingaHost);
}
/** @var IcingaHost $this->object */
return $this->object;
}
/**
* Get readOnly permission of the service for the current backend
*
* @return string permission
*/
protected function getServicesReadOnlyPermission(): string
{
return $this->backend() instanceof IcingadbBackend
? Permission::ICINGADB_SERVICES_RO
: Permission::MONITORING_SERVICES_RO;
}
}

View File

@ -3,16 +3,12 @@
namespace Icinga\Module\Director\Controllers;
use Exception;
use gipfl\Web\Widget\Hint;
use Icinga\Module\Director\Data\Exporter;
use Icinga\Module\Director\Db\Branch\Branch;
use Icinga\Module\Director\Forms\ImportRowModifierForm;
use Icinga\Module\Director\Forms\ImportSourceForm;
use Icinga\Module\Director\Hook\ImportSourceHook;
use Icinga\Module\Director\Web\ActionBar\AutomationObjectActionBar;
use Icinga\Module\Director\Web\Controller\ActionController;
use Icinga\Module\Director\Objects\ImportSource;
use Icinga\Module\Director\Web\Controller\BranchHelper;
use Icinga\Module\Director\Web\Form\CloneImportSourceForm;
use Icinga\Module\Director\Web\Table\ImportrunTable;
use Icinga\Module\Director\Web\Table\ImportsourceHookTable;
@ -22,12 +18,9 @@ use Icinga\Module\Director\Web\Widget\ImportSourceDetails;
use InvalidArgumentException;
use gipfl\IcingaWeb2\Link;
use ipl\Html\Error;
use ipl\Html\Html;
class ImportsourceController extends ActionController
{
use BranchHelper;
/** @var ImportSource|null */
private $importSource;
@ -85,36 +78,24 @@ class ImportsourceController extends ActionController
$this->addMainActions();
$source = $this->getImportSource();
if ($this->params->get('format') === 'json') {
$this->sendJson($this->getResponse(), (new Exporter($this->db()))->export($source));
$this->sendJson($this->getResponse(), $source->export());
return;
}
$this->addTitle(
$this->translate('Import source: %s'),
$source->get('source_name')
)->setAutorefreshInterval(10);
$branch = $this->getBranch();
if ($this->getBranch()->isBranch()) {
$this->content()->add(Hint::info(Html::sprintf($this->translate(
'Please note that importing data will take place in your main Branch.'
. ' Modifications to Import Sources are not allowed while being in a Configuration Branch.'
. ' To get the full functionality, please deactivate %s'
), Branch::requireHook()->linkToBranch($branch, $this->getAuth(), $branch->getName()))));
}
$this->content()->add(new ImportSourceDetails($source));
}
public function addAction()
{
$this->addTitle($this->translate('Add import source'));
if ($this->showNotInBranch($this->translate('Creating Import Sources'))) {
return;
}
$this->content()->add(
ImportSourceForm::load()->setDb($this->db())
->setSuccessUrl('director/importsources')
->handleRequest()
);
$this->addTitle($this->translate('Add import source'))
->content()->add(
ImportSourceForm::load()->setDb($this->db())
->setSuccessUrl('director/importsources')
->handleRequest()
);
}
/**
@ -124,9 +105,6 @@ class ImportsourceController extends ActionController
{
$this->addMainActions();
$this->activateTabWithPostfix($this->translate('Modify'));
if ($this->showNotInBranch($this->translate('Modifying Import Sources'))) {
return;
}
$form = ImportSourceForm::load()
->setObject($this->getImportSource())
->setListUrl('director/importsources')
@ -146,9 +124,6 @@ class ImportsourceController extends ActionController
{
$this->addMainActions();
$this->activateTabWithPostfix($this->translate('Clone'));
if ($this->showNotInBranch($this->translate('Cloning Import Sources'))) {
return;
}
$source = $this->getImportSource();
$this->addTitle('Clone: %s', $source->get('source_name'));
$form = new CloneImportSourceForm($source);
@ -180,7 +155,9 @@ class ImportsourceController extends ActionController
'target' => '_blank',
'class' => 'icon-download',
]
));
))->add(Link::create('[..]', '#', null, [
'onclick' => 'javascript:$("table.raw-data-table").toggleClass("collapsed");'
]));
try {
(new ImportsourceHookTable())->setImportSource($source)->renderTo($this);
} catch (Exception $e) {
@ -229,13 +206,9 @@ class ImportsourceController extends ActionController
protected function requireImportSourceAndAddModifierTable()
{
$source = $this->getImportSource();
$table = PropertymodifierTable::load($source, $this->url());
if ($this->getBranch()->isBranch()) {
$table->setReadOnly();
} else {
$table->handleSortPriorityActions($this->getRequest(), $this->getResponse());
}
$table->renderTo($this);
PropertymodifierTable::load($source, $this->url())
->handleSortPriorityActions($this->getRequest(), $this->getResponse())
->renderTo($this);
return $source;
}
@ -280,10 +253,6 @@ class ImportsourceController extends ActionController
)->addBackToModifiersLink($source);
$this->tabs()->activate('modifier');
if ($this->showNotInBranch($this->translate('Modifying Import Sources'))) {
return;
}
$this->content()->prepend(
ImportRowModifierForm::load()->setDb($this->db())
->setSource($source)
@ -310,15 +279,12 @@ class ImportsourceController extends ActionController
)->addBackToModifiersLink($source);
$source = $this->requireImportSourceAndAddModifierTable();
$this->tabs()->activate('modifier');
if ($this->showNotInBranch($this->translate('Modifying Import Sources'))) {
return;
}
$listUrl = 'director/importsource/modifier?source_id='
. (int) $source->get('id');
$this->content()->prepend(
ImportRowModifierForm::load()->setDb($this->db())
->loadObject((int) $this->params->getRequired('id'))
->loadObject($this->params->getRequired('id'))
->setListUrl($listUrl)
->setSource($source)
->handleRequest()

View File

@ -30,7 +30,7 @@ class ImportsourcesController extends ActionController
}
$this->addTitle($this->translate('Import source'))
->setAutorefreshInterval(10)
->setAutoRefreshInterval(10)
->addAddLink(
$this->translate('Add a new Import Source'),
'director/importsource/add'

View File

@ -6,13 +6,10 @@ use gipfl\IcingaWeb2\Link;
use Icinga\Module\Director\Forms\DirectorJobForm;
use Icinga\Module\Director\Web\Controller\ActionController;
use Icinga\Module\Director\Objects\DirectorJob;
use Icinga\Module\Director\Web\Controller\BranchHelper;
use Icinga\Module\Director\Web\Widget\JobDetails;
class JobController extends ActionController
{
use BranchHelper;
/**
* @throws \Icinga\Exception\MissingParameterException
* @throws \Icinga\Exception\NotFoundError
@ -32,17 +29,13 @@ class JobController extends ActionController
{
$this
->addSingleTab($this->translate('New Job'))
->addTitle($this->translate('Add a new Job'));
if ($this->showNotInBranch($this->translate('Creating Jobs'))) {
return;
}
$this->content()->add(
DirectorJobForm::load()
->setSuccessUrl('director/job')
->setDb($this->db())
->handleRequest()
);
->addTitle($this->translate('Add a new Job'))
->content()->add(
DirectorJobForm::load()
->setSuccessUrl('director/job')
->setDb($this->db())
->handleRequest()
);
}
/**
@ -52,19 +45,16 @@ class JobController extends ActionController
public function editAction()
{
$job = $this->requireJob();
$this
->addJobTabs($job, 'edit')
->addTitle($this->translate('Job: %s'), $job->get('job_name'))
->addToBasketLink();
if ($this->showNotInBranch($this->translate('Modifying Jobs'))) {
return;
}
$form = DirectorJobForm::load()
->setListUrl('director/jobs')
->setObject($job)
->handleRequest();
$this->content()->add($form);
$this
->addJobTabs($job, 'edit')
->addTitle($this->translate('Job: %s'), $job->get('job_name'))
->addToBasketLink()
->content()->add($form);
}
/**

View File

@ -11,7 +11,7 @@ class JobsController extends ActionController
public function indexAction()
{
$this->addTitle($this->translate('Jobs'))
->setAutorefreshInterval(10)
->setAutoRefreshInterval(10)
->addAddLink($this->translate('Add a new Job'), 'director/job/add')
->tabs(new ImportTabs())->activate('jobs');

View File

@ -4,19 +4,13 @@ namespace Icinga\Module\Director\Controllers;
use Exception;
use Icinga\Module\Director\Forms\KickstartForm;
use Icinga\Module\Director\Web\Controller\BranchHelper;
class KickstartController extends DashboardController
{
use BranchHelper;
public function indexAction()
{
$this->addSingleTab($this->translate('Kickstart'))
->addTitle($this->translate('Director Kickstart Wizard'));
if ($this->showNotInBranch($this->translate('Kickstart'))) {
return;
}
$form = KickstartForm::load();
try {
$form->setEndpoint($this->db()->getDeploymentEndpoint());

View File

@ -2,7 +2,6 @@
namespace Icinga\Module\Director\Controllers;
use Icinga\Exception\NotFoundError;
use Icinga\Module\Director\Web\Controller\ObjectController;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaNotification;
@ -53,11 +52,6 @@ class NotificationController extends ObjectController
}
}
protected function hasBasketSupport()
{
return $this->object->isTemplate() || $this->object->isApplyRule();
}
protected function loadObject()
{
if ($this->object === null) {
@ -81,10 +75,6 @@ class NotificationController extends ObjectController
}
}
if (! $this->allowsObject($this->object)) {
throw new NotFoundError('No such object available');
}
return $this->object;
}
}

View File

@ -11,11 +11,6 @@ class ScheduledDowntimeController extends ObjectController
{
protected $objectBaseUrl = 'director/scheduled-downtime';
protected function checkDirectorPermissions()
{
$this->assertPermission('director/scheduled-downtimes');
}
public function rangesAction()
{
/** @var IcingaScheduledDowntime $object */

View File

@ -34,14 +34,4 @@ class ScheduledDowntimesController extends ObjectsController
{
return 'scheduled-downtime';
}
protected function assertApplyRulePermission()
{
return $this->assertPermission('director/scheduled-downtimes');
}
protected function checkDirectorPermissions()
{
$this->assertPermission('director/scheduled-downtimes');
}
}

View File

@ -62,7 +62,7 @@ class SchemaController extends ActionController
return file_get_contents(
sprintf(
'%s/schema/%s.sql',
$this->Module()->getBaseDir(),
$this->Module()->getBasedir(),
$type
)
);

View File

@ -194,7 +194,7 @@ class SelfServiceController extends ActionController
} else {
throw new ProgrammingError(
'Expected boolean value, got %s',
var_export($value, true)
var_export($value, 1)
);
}
}
@ -275,7 +275,7 @@ class SelfServiceController extends ActionController
// PluginsUrl => framework_plugins_url
];
$username = $settings->get('self-service/icinga_service_user');
if ($username) {
if (strlen($username)) {
$params['icinga_service_user'] = $username;
}
@ -397,14 +397,14 @@ class SelfServiceController extends ActionController
$params['parent_zone'] = $zoneName;
$params['ca_server'] = $master->getObjectName();
$params['parent_endpoints'] = $endpointNames;
$params['accept_config'] = $host->getSingleResolvedProperty('accept_config') === 'y';
$params['accept_config'] = $host->getSingleResolvedProperty('accept_config')=== 'y';
}
protected function addStringSettingsToParams(Settings $settings, array $keys, array &$params)
{
foreach ($keys as $key) {
$value = $settings->get("self-service/$key");
if ($value) {
if (strlen($value)) {
$params[$key] = $value;
}
}

View File

@ -3,13 +3,9 @@
namespace Icinga\Module\Director\Controllers;
use Exception;
use Icinga\Exception\NotFoundError;
use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
use Icinga\Module\Director\Db\Branch\UuidLookup;
use Icinga\Module\Director\Forms\IcingaServiceForm;
use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Web\Controller\ObjectController;
use Icinga\Module\Director\Objects\IcingaServiceSet;
use Icinga\Module\Director\Objects\IcingaService;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Web\Form\DirectorObjectForm;
@ -29,79 +25,64 @@ class ServiceController extends ObjectController
protected function checkDirectorPermissions()
{
if (
$this->host
&& $this->object
&& $this->backend()->canModifyService($this->host->getObjectName(), $this->object->getObjectName())
) {
return;
}
$this->assertPermission(Permission::HOSTS);
$this->assertPermission('director/hosts');
}
public function init()
{
// This happens in parent::init() too, but is required to take place before the next two lines
$this->enableStaticObjectLoader($this->getTableName());
// Hint: having Host and Set loaded first is important for UUID lookups with legacy URLs
$this->host = $this->getOptionalRelatedObjectFromParams('host', 'host');
$this->set = $this->getOptionalRelatedObjectFromParams('service_set', 'set');
if ($host = $this->params->get('host')) {
$this->host = IcingaHost::load($host, $this->db());
} elseif ($hostId = $this->params->get('host_id')) {
$this->host = IcingaHost::loadWithAutoIncId($hostId, $this->db());
} elseif ($set = $this->params->get('set')) {
$this->set = IcingaServiceSet::load(['object_name' => $set], $this->db());
} elseif ($apply = $this->params->get('apply')) {
$this->apply = IcingaService::load(
['object_name' => $apply, 'object_type' => 'template'],
$this->db()
);
}
parent::init();
$this->addOptionalHostTabs();
$this->addOptionalSetTabs();
}
protected function getOptionalRelatedObjectFromParams($type, $parameter)
{
if ($id = $this->params->get("{$parameter}_id")) {
$key = (int) $id;
} else {
$key = $this->params->get($parameter);
if ($this->host) {
$hostname = $this->host->getObjectName();
$tabs = new Tabs();
$tabs->add('host', [
'url' => 'director/host',
'urlParams' => ['name' => $hostname],
'label' => $this->translate('Host'),
])->add('services', [
'url' => 'director/host/services',
'urlParams' => ['name' => $hostname],
'label' => $this->translate('Services'),
]);
$this->addParamToTabs('host', $hostname);
$this->controls()->prependTabs($tabs);
}
if ($key !== null) {
$table = DbObjectTypeRegistry::tableNameByType($type);
$key = UuidLookup::findUuidForKey($key, $table, $this->db(), $this->getBranch());
return $this->loadSpecificObject($table, $key);
}
return null;
}
protected function loadOptionalObject(): void
{
parent::loadOptionalObject();
if ($this->object) {
if ($this->host === null) {
$this->host = $this->loadOptionalRelatedObject($this->object, 'host');
}
if ($this->set === null) {
$this->set = $this->loadOptionalRelatedObject($this->object, 'service_set');
}
}
}
protected function loadOptionalRelatedObject(IcingaObject $object, $relation)
{
$key = $object->getUnresolvedRelated($relation);
if ($key === null) {
if ($key = $object->get("{$relation}_id")) {
$key = (int) $key;
} else {
$key = $object->get($relation);
// We reach this when accessing Service Template Fields
if (! $this->set && $this->object->get('service_set_id')) {
$this->set = $this->object->getRelated('service_set');
}
}
if ($key === null) {
return null;
}
if ($this->set) {
$setName = $this->set->getObjectName();
$tabs = new Tabs();
$tabs->add('set', [
'url' => 'director/serviceset',
'urlParams' => ['name' => $setName],
'label' => $this->translate('ServiceSet'),
])->add('services', [
'url' => 'director/serviceset/services',
'urlParams' => ['name' => $setName],
'label' => $this->translate('Services'),
]);
$table = DbObjectTypeRegistry::tableNameByType($relation);
$uuid = UuidLookup::findUuidForKey($key, $table, $this->db(), $this->getBranch());
return $this->loadSpecificObject($table, $uuid);
$this->addParamToTabs('serviceset', $setName);
$this->controls()->prependTabs($tabs);
}
}
protected function addParamToTabs($name, $value)
@ -118,7 +99,6 @@ class ServiceController extends ObjectController
{
parent::addAction();
if ($this->host) {
// TODO: use setTitle. And figure out, where we use this old route.
$this->view->title = $this->host->object_name . ': ' . $this->view->title;
} elseif ($this->set) {
$this->view->title = sprintf(
@ -151,23 +131,18 @@ class ServiceController extends ObjectController
/** @var IcingaService $object */
$object = $this->object;
$this->addTitle($object->getObjectName());
if ($object->isTemplate() && $this->showNotInBranch($this->translate('Modifying Templates'))) {
return;
}
$form = IcingaServiceForm::load()->setDb($this->db());
$form->setBranch($this->getBranch());
if ($this->host) {
$this->actions()->add(Link::create(
$this->translate('back'),
'director/host/services',
['uuid' => $this->host->getUniqueId()->toString()],
['name' => $this->host->getObjectName()],
['class' => 'icon-left-big']
));
$form->setHost($this->host);
}
$form = IcingaServiceForm::load()->setDb($this->db());
if ($this->set) {
$form->setServiceSet($this->set);
}
@ -197,8 +172,7 @@ class ServiceController extends ObjectController
}
try {
if (
$object->isTemplate()
if ($object->isTemplate()
&& $object->getResolvedProperty('check_command_id')
) {
$this->view->actionLinks .= ' ' . $this->view->qlink(
@ -239,81 +213,33 @@ class ServiceController extends ObjectController
$this->content()->add($table);
}
protected function getLegacyKey()
{
if ($key = $this->params->get('id')) {
$key = (int) $key;
} else {
$key = $this->params->get('name');
}
if ($key === null) {
throw new \InvalidArgumentException('uuid, name or id required');
}
return $key;
}
protected function loadObject()
{
if ($this->params->has('uuid')) {
parent::loadObject();
return;
}
if ($this->object === null) {
if ($id = $this->params->get('id')) {
$this->object = IcingaService::loadWithAutoIncId(
(int) $id,
$this->db()
);
} elseif ($name = $this->params->get('name')) {
$params = array('object_name' => $name);
$db = $this->db();
$key = $this->getLegacyKey();
// Hint: not passing 'object' as type, we still have name-based links in previews and similar
$uuid = UuidLookup::findServiceUuid($this->db(), $this->getBranch(), null, $key, $this->host, $this->set);
if ($uuid === null) {
if (! $this->params->get('allowOverrides')) {
throw new NotFoundError('Not found');
if ($this->host) {
// $this->view->host = $this->host;
$params['host_id'] = $this->host->id;
}
if ($this->set) {
// $this->view->set = $this->set;
$params['service_set_id'] = $this->set->id;
}
$this->object = IcingaService::load($params, $db);
} else {
parent::loadObject();
}
} else {
$this->params->set('uuid', $uuid->toString());
parent::loadObject();
}
}
protected function addOptionalHostTabs()
{
if ($this->host === null) {
return;
}
$hostname = $this->host->getObjectName();
$tabs = new Tabs();
$urlParams = ['uuid' => $this->host->getUniqueId()->toString()];
$tabs->add('host', [
'url' => 'director/host',
'urlParams' => $urlParams,
'label' => $this->translate('Host'),
])->add('services', [
'url' => 'director/host/services',
'urlParams' => $urlParams,
'label' => $this->translate('Services'),
]);
$this->addParamToTabs('host', $hostname);
$this->controls()->prependTabs($tabs);
}
protected function addOptionalSetTabs()
{
if ($this->set === null) {
return;
}
$setName = $this->set->getObjectName();
$tabs = new Tabs();
$tabs->add('set', [
'url' => 'director/serviceset',
'urlParams' => ['name' => $setName],
'label' => $this->translate('ServiceSet'),
])->add('services', [
'url' => 'director/serviceset/services',
'urlParams' => ['name' => $setName],
'label' => $this->translate('Services'),
]);
$this->addParamToTabs('serviceset', $setName);
$this->controls()->prependTabs($tabs);
return $this->object;
}
}

View File

@ -3,7 +3,6 @@
namespace Icinga\Module\Director\Controllers;
use Icinga\Data\Filter\Filter;
use Icinga\Exception\NotFoundError;
use Icinga\Module\Director\Forms\IcingaServiceSetForm;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaServiceSet;
@ -72,9 +71,7 @@ class ServicesetController extends ObjectController
['class' => 'icon-plus']
));
IcingaServiceSetServiceTable::load($set)
->setBranch($this->getBranch())
->renderTo($this);
IcingaServiceSetServiceTable::load($set)->renderTo($this);
}
public function hostsAction()
@ -92,7 +89,7 @@ class ServicesetController extends ObjectController
$table->renderTo($this);
}
$filter = $set->get('assign_filter');
if ($filter !== null && \strlen($filter) > 0) {
if (\strlen($filter) > 0) {
$this->content()->add(
IcingaHostsMatchingFilterTable::load(Filter::fromQueryString($filter), $this->db())
);
@ -101,19 +98,15 @@ class ServicesetController extends ObjectController
protected function addServiceSetTabs()
{
$hexUuid = $this->object->getUniqueId()->toString();
$tabs = $this->tabs();
$name = $this->object->getObjectName();
$tabs->add('services', [
'url' => 'director/serviceset/services',
'urlParams' => ['uuid' => $hexUuid],
'urlParams' => ['name' => $name],
'label' => 'Services'
]);
if ($this->branch->isBranch()) {
return $this;
}
$tabs->add('hosts', [
])->add('hosts', [
'url' => 'director/serviceset/hosts',
'urlParams' => ['uuid' => $hexUuid],
'urlParams' => ['name' => $name],
'label' => 'Hosts'
]);
@ -137,10 +130,6 @@ class ServicesetController extends ObjectController
}
}
if (! $this->allowsObject($this->object)) {
throw new NotFoundError('No such object available');
}
return $this->object;
}
}

View File

@ -2,7 +2,6 @@
namespace Icinga\Module\Director\Controllers;
use Icinga\Module\Director\Restriction\HostgroupRestriction;
use ipl\Html\Html;
use Icinga\Exception\NotFoundError;
use Icinga\Module\Director\Hook\ImportSourceHook;
@ -109,8 +108,6 @@ class SuggestController extends ActionController
if ($type !== null) {
$query->where('object_type = ?', $type);
}
$restriction = new HostgroupRestriction($this->db(), $this->Auth());
$restriction->filterHostsQuery($query);
return $db->fetchCol($query);
}
@ -122,7 +119,7 @@ class SuggestController extends ActionController
protected function suggestServicenames()
{
$r = array();
$r=array();
$this->assertPermission('director/services');
$db = $this->db()->getDbAdapter();
$for_host = $this->getRequest()->getPost('for_host');
@ -152,7 +149,7 @@ class SuggestController extends ActionController
$matcher = HostApplyMatches::prepare($tmp_host);
foreach ($this->getAllApplyRules() as $rule) {
if ($matcher->matchesFilter($rule->filter)) { //TODO
$r[] = $rule->name;
$r[]=$rule->name;
}
}
}
@ -291,7 +288,7 @@ class SuggestController extends ActionController
$db = $this->db()->getDbAdapter();
$query = $db->select()
->from(['f' => 'director_datafield'], [])
->from(['f' =>'director_datafield'], [])
->join(
['sid' => 'director_datafield_setting'],
'sid.datafield_id = f.id AND sid.setting_name = \'datalist_id\'',
@ -369,7 +366,7 @@ class SuggestController extends ActionController
protected function getAllApplyRules()
{
$allApplyRules = $this->fetchAllApplyRules();
$allApplyRules=$this->fetchAllApplyRules();
foreach ($allApplyRules as $rule) {
$rule->filter = Filter::fromQueryString($rule->assign_filter);
}

View File

@ -2,18 +2,10 @@
namespace Icinga\Module\Director\Controllers;
use gipfl\Diff\HtmlRenderer\SideBySideDiff;
use gipfl\Diff\PhpDiff;
use gipfl\IcingaWeb2\Link;
use gipfl\Web\Widget\Hint;
use Icinga\Date\DateFormatter;
use Icinga\Module\Director\Data\Db\DbObjectStore;
use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
use Icinga\Module\Director\Db\Branch\Branch;
use Icinga\Module\Director\Db\Branch\BranchStore;
use Icinga\Module\Director\Db\Branch\BranchSupport;
use Icinga\Module\Director\Web\Controller\BranchHelper;
use Icinga\Module\Director\Web\Form\ClickHereForm;
use Icinga\Module\Director\Web\Table\BranchActivityTable;
use Icinga\Module\Director\Web\Widget\IcingaConfigDiff;
use Icinga\Module\Director\Web\Widget\UnorderedList;
use Icinga\Module\Director\Db\Cache\PrefetchCache;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
@ -35,38 +27,25 @@ use Icinga\Module\Director\Web\Table\SyncpropertyTable;
use Icinga\Module\Director\Web\Table\SyncRunTable;
use Icinga\Module\Director\Web\Tabs\SyncRuleTabs;
use Icinga\Module\Director\Web\Widget\SyncRunDetails;
use Icinga\Web\Notification;
use ipl\Html\Form;
use ipl\Html\Html;
use ipl\Html\ValidHtml;
class SyncruleController extends ActionController
{
use BranchHelper;
/**
* @throws \Icinga\Exception\NotFoundError
*/
public function indexAction()
{
$this->setAutorefreshInterval(10);
$this->setAutoRefreshInterval(10);
$rule = $this->requireSyncRule();
$this->tabs(new SyncRuleTabs($rule))->activate('show');
$ruleName = $rule->get('rule_name');
$this->addTitle($this->translate('Sync rule: %s'), $ruleName);
$checkForm = SyncCheckForm::load()->setSyncRule($rule)->handleRequest();
$store = new DbObjectStore($this->db(), $this->getBranch());
$runForm = new SyncRunForm($rule, $store);
$runForm->on(SyncRunForm::ON_SUCCESS, function (SyncRunForm $form) {
$message = $form->getSuccessMessage();
if ($message === null) {
Notification::error($this->translate('Synchronization failed'));
} else {
Notification::success($message);
}
$this->redirectNow($this->url());
});
$runForm->handleRequest($this->getServerRequest());
$runForm = SyncRunForm::load()->setSyncRule($rule)->handleRequest();
if ($lastRunId = $rule->getLastSyncRunId()) {
$run = SyncRun::load($lastRunId, $this->db());
@ -94,7 +73,7 @@ class SyncruleController extends ActionController
break;
case 'in-sync':
$c->add(Html::tag('p', null, sprintf(
$this->translate('This Sync Rule was last found to be in Sync at %s.'),
$this->translate('This Sync Rule was last found to by in Sync at %s.'),
$rule->get('last_attempt')
)));
/*
@ -121,15 +100,6 @@ class SyncruleController extends ActionController
}
$c->add($checkForm);
if ($this->hasBranch()) {
$objectType = $rule->get('object_type');
$table = DbObjectTypeRegistry::tableNameByType($objectType);
if (! BranchSupport::existsForTableName($table)) {
$this->showNotInBranch(sprintf($this->translate("Synchronizing '%s'"), $objectType));
return;
}
}
$c->add($runForm);
if ($run) {
@ -174,97 +144,31 @@ class SyncruleController extends ActionController
public function previewAction()
{
$rule = $this->requireSyncRule();
$branchSupport = BranchSupport::existsForSyncRule($rule);
$branchStore = new BranchStore($this->db());
$owner = $this->getAuth()->getUser()->getUsername();
if ($branchSupport) {
if ($this->getBranch()->isBranch()) {
$tmpBranchName = sprintf(
'%s/%s-%s',
Branch::PREFIX_SYNC_PREVIEW,
$this->getBranch()->getUuid()->toString(),
$rule->get('id')
);
// We could keep changes for preview on branch too
$branchStore->deleteByName($tmpBranchName);
$tmpBranch = $branchStore->cloneBranchForSync($this->getBranch(), $tmpBranchName, $owner);
$after = 1600000000; // a date in 2020, minus 10000000
} else {
$tmpBranchName = Branch::PREFIX_SYNC_PREVIEW . '/' . $rule->get('id');
$tmpBranch = $branchStore->fetchOrCreateByName($tmpBranchName, $owner);
$after = null;
}
$store = new DbObjectStore($this->db(), $tmpBranch);
} else {
$tmpBranch = $store = null;
}
// $rule->set('update_policy', 'replace');
$this->tabs(new SyncRuleTabs($rule))->activate('preview');
$this->addTitle($this->translate('Sync Preview'));
$sync = new Sync($rule, $store);
$keepBranchPreview = false;
if ($tmpBranch) {
if ($lastTime = $branchStore->getLastActivityTime($tmpBranch, $after)) {
if ((time() - $lastTime) > 100) {
$branchStore->wipeBranch($tmpBranch, $after);
} else {
$here = (new ClickHereForm())->handleRequest($this->getServerRequest());
if ($here->hasBeenClicked()) {
$branchStore->wipeBranch($tmpBranch, $after);
$this->redirectNow($this->url());
} else {
$keepBranchPreview = true;
}
$this->content()->add(Hint::info(Html::sprintf(
$this->translate('This preview has been generated %s, please click %s to regenerate it'),
DateFormatter::timeAgo($lastTime),
$here
)));
}
}
}
if (!$keepBranchPreview) {
$this->addTitle('Sync Preview');
$sync = new Sync($rule);
try {
$modifications = $sync->getExpectedModifications();
} catch (\Exception $e) {
$this->content()->add(Hint::error($e->getMessage()));
return;
}
if ($tmpBranch) {
try {
if (!$keepBranchPreview) {
$sync->apply();
}
} catch (\Exception $e) {
$this->content()->add(Hint::error($e->getMessage()));
return;
}
if (empty($modifications)) {
$this->content()->add(Hint::ok($this->translate(
'This Sync Rule is in sync and would currently not apply any changes'
)));
$changes = new BranchActivityTable($tmpBranch->getUuid(), $this->db());
$changes->disableObjectLink();
if (count($changes) === 0) {
$this->showInSync();
}
$changes->renderTo($this);
} else {
if (empty($modifications)) {
$this->showInSync();
return;
}
$this->showExpectedModificationSummary($modifications);
return;
}
}
protected function showInSync()
{
$this->content()->add(Hint::ok($this->translate(
'This Sync Rule is in sync and would currently not apply any changes'
)));
}
protected function showExpectedModificationSummary($modifications)
{
$create = [];
$modify = [];
$delete = [];
$modifiedProperties = [];
/** @var IcingaObject $object */
foreach ($modifications as $object) {
if ($object->hasBeenLoadedFromDb()) {
@ -380,7 +284,10 @@ class SyncruleController extends ActionController
protected function firstNames($objects, $max = 50)
{
$names = [];
$list = new UnorderedList([], ['class' => 'unordred-list']);
$list = new UnorderedList();
$list->addAttributes([
'style' => 'list-style-type: none; marign: 0; padding: 0',
]);
$total = count($objects);
$i = 0;
PrefetchCache::forget();
@ -402,7 +309,7 @@ class SyncruleController extends ActionController
$cfgOld = new IcingaConfig($this->db());
$oldObject->renderToConfig($cfgOld);
$object->renderToConfig($cfgNew);
foreach (IcingaConfigDiff::getDiffs($cfgOld, $cfgNew) as $file => $diff) {
foreach ($this->getConfigDiffs($cfgOld, $cfgNew) as $file => $diff) {
$names[$name . '___PRETITLE___' . $file] = Html::tag('h3', $file);
$names[$name . '___PREVIEW___' . $file] = $diff;
}
@ -427,7 +334,7 @@ class SyncruleController extends ActionController
$cfgOld = new IcingaConfig($this->db());
$oldObject->renderToConfig($cfgOld);
$object->renderToConfig($cfgNew);
foreach (IcingaConfigDiff::getDiffs($cfgOld, $cfgNew) as $file => $diff) {
foreach ($this->getConfigDiffs($cfgOld, $cfgNew) as $file => $diff) {
$names[$name . '___PRETITLE___' . $file] = Html::tag('h3', $file);
$names[$name . '___PREVIEW___' . $file] = $diff;
}
@ -457,11 +364,48 @@ class SyncruleController extends ActionController
return $list;
}
/**
* Stolen from elsewhere, should be de-duplicated
*
* @param IcingaConfig $oldConfig
* @param IcingaConfig $newConfig
* @return ValidHtml[]
*/
protected function getConfigDiffs(IcingaConfig $oldConfig, IcingaConfig $newConfig)
{
$oldFileNames = $oldConfig->getFileNames();
$newFileNames = $newConfig->getFileNames();
$fileNames = array_merge($oldFileNames, $newFileNames);
$diffs = [];
foreach ($fileNames as $filename) {
if (in_array($filename, $oldFileNames)) {
$left = $oldConfig->getFile($filename)->getContent();
} else {
$left = '';
}
if (in_array($filename, $newFileNames)) {
$right = $newConfig->getFile($filename)->getContent();
} else {
$right = '';
}
if ($left === $right) {
continue;
}
$diffs[$filename] = new SideBySideDiff(new PhpDiff($left, $right));
}
return $diffs;
}
protected function listModifiedProperties($properties)
{
$list = new UnorderedList();
foreach ($properties as $property => $cnt) {
$list->addItem("{$cnt}x $property");
$list->addItem("${cnt}x $property");
}
return $list;
@ -511,15 +455,9 @@ class SyncruleController extends ActionController
if (! $rule->hasSyncProperties()) {
$this->addPropertyHint($rule);
}
if ($this->showNotInBranch($this->translate('Modifying Sync Rules'))) {
return;
}
} else {
$this->addTitle($this->translate('Add sync rule'));
$this->tabs(new SyncRuleTabs())->activate('add');
if ($this->showNotInBranch($this->translate('Creating Sync Rules'))) {
return;
}
}
$form->handleRequest();
@ -552,9 +490,6 @@ class SyncruleController extends ActionController
['class' => 'icon-paste']
)
);
if ($this->showNotInBranch($this->translate('Cloning Sync Rules'))) {
return;
}
$form = new CloneSyncRuleForm($rule);
$this->content()->add($form);
@ -603,13 +538,6 @@ class SyncruleController extends ActionController
$ruleId = (int) $rule->get('id');
$form = SyncPropertyForm::load()->setDb($db);
$this->tabs(new SyncRuleTabs($rule))->activate('property');
$this->actions()->add(new Link(
$this->translate('back'),
'director/syncrule/property',
['rule_id' => $ruleId],
['class' => 'icon-left-big']
));
if ($id = $this->params->get('id')) {
$form->loadObject((int) $id);
$this->addTitle(
@ -617,21 +545,24 @@ class SyncruleController extends ActionController
$form->getObject()->get('destination_field'),
$rule->get('rule_name')
);
if ($this->showNotInBranch($this->translate('Modifying Sync Rules'))) {
return;
}
} else {
$this->addTitle(
$this->translate('Add sync property: %s'),
$rule->get('rule_name')
);
if ($this->showNotInBranch($this->translate('Modifying Sync Rules'))) {
return;
}
}
$form->setRule($rule);
$form->setSuccessUrl('director/syncrule/property', ['rule_id' => $ruleId]);
$this->actions()->add(new Link(
$this->translate('back'),
'director/syncrule/property',
['rule_id' => $ruleId],
['class' => 'icon-left-big']
));
$this->content()->add($form->handleRequest());
$this->tabs(new SyncRuleTabs($rule))->activate('property');
SyncpropertyTable::create($rule)
->handleSortPriorityActions($this->getRequest(), $this->getResponse())
->renderTo($this);
@ -642,7 +573,7 @@ class SyncruleController extends ActionController
*/
public function historyAction()
{
$this->setAutorefreshInterval(30);
$this->setAutoRefreshInterval(30);
$rule = $this->requireSyncRule();
$this->tabs(new SyncRuleTabs($rule))->activate('history');
$this->addTitle($this->translate('Sync history') . ': ' . $rule->get('rule_name'));
@ -651,7 +582,7 @@ class SyncruleController extends ActionController
$run = SyncRun::load($runId, $this->db());
$this->content()->add(new SyncRunDetails($run));
}
(new SyncRunTable($rule))->renderTo($this);
SyncRunTable::create($rule)->renderTo($this);
}
/**

View File

@ -23,7 +23,7 @@ class SyncrulesController extends ActionController
}
$this->addTitle($this->translate('Sync rule'))
->setAutorefreshInterval(10)
->setAutoRefreshInterval(10)
->addAddLink(
$this->translate('Add a new Sync Rule'),
'director/syncrule/add'

View File

@ -4,12 +4,9 @@ namespace Icinga\Module\Director\Controllers;
use Icinga\Module\Director\Forms\IcingaTemplateChoiceForm;
use Icinga\Module\Director\Web\Controller\ActionController;
use Icinga\Module\Director\Web\Controller\BranchHelper;
class TemplatechoiceController extends ActionController
{
use BranchHelper;
protected function checkDirectorPermissions()
{
$this->assertPermission('director/admin');
@ -27,15 +24,12 @@ class TemplatechoiceController extends ActionController
protected function prepare($type, $title)
{
$this->addSingleTab('Choice')
->addTitle($title);
$form = IcingaTemplateChoiceForm::create($type, $this->db())
->optionallyLoad($this->params->get('name'))
->setListUrl("director/templatechoices/$type")
->handleRequest();
if ($this->showNotInBranch($this->translate('Modifying Template Choices'))) {
return;
}
$this->content()->add($form);
$this->addSingleTab('Choice')
->addTitle($title)
->content()->add($form);
}
}

View File

@ -30,9 +30,4 @@ class TimeperiodController extends ObjectController
$this->content()->add($form->handleRequest());
IcingaTimePeriodRangeTable::load($object)->renderTo($this);
}
protected function hasBasketSupport()
{
return true;
}
}

View File

@ -10,9 +10,4 @@ class UserController extends ObjectController
{
$this->assertPermission('director/users');
}
protected function hasBasketSupport()
{
return true;
}
}

View File

@ -11,6 +11,9 @@ use Icinga\Module\Director\Web\Form\DirectorForm;
class AddToBasketForm extends DirectorForm
{
/** @var Basket */
private $basket;
private $type = '(has not been set)';
private $names = [];
@ -27,6 +30,7 @@ class AddToBasketForm extends DirectorForm
'b' => 'basket_name',
])->order('basket_name'));
$names = [];
$basket = null;
if ($this->hasBeenSent()) {
$basketName = $this->getSentValue('basket');
@ -34,17 +38,25 @@ class AddToBasketForm extends DirectorForm
$basket = Basket::load($basketName, $this->getDb());
}
}
$names = [];
$count = 0;
$type = $this->type;
foreach ($this->names as $name) {
if (! $basket || ! $basket->hasObject($this->type, $name)) {
if (! empty($names)) {
$names[] = ', ';
}
if ($basket && $basket->hasObject($type, $name)) {
$names[] = Html::tag('span', [
'style' => 'text-decoration: line-through'
], $name);
} else {
$count++;
$names[] = $name;
}
}
$this->addHtmlHint(
(new HtmlDocument())
->add(sprintf('The following objects will be added: %s', implode(", ", $names)))
);
$this->addHtmlHint((new HtmlDocument())->add([
'The following objects will be added: ',
$names
]));
$this->addElement('select', 'basket', [
'label' => $this->translate('Basket'),
'multiOptions' => $this->optionalEnum($enum),
@ -52,10 +64,10 @@ class AddToBasketForm extends DirectorForm
'class' => 'autosubmit',
]);
if (! empty($names)) {
if ($count > 0) {
$this->setSubmitLabel(sprintf(
$this->translate('Add %s objects'),
count($names)
$count
));
} else {
$this->setSubmitLabel($this->translate('Add'));
@ -100,18 +112,18 @@ class AddToBasketForm extends DirectorForm
'Configuration objects have been added to the chosen basket "%s"'
), $basketName));
return parent::onSuccess();
} else {
$this->addHtmlHint(Hint::error(Html::sprintf($this->translate(
'Please check your Basket configuration, %s does not support'
. ' single "%s" configuration objects'
), Link::create(
$basketName,
'director/basket',
['name' => $basketName],
['data-base-target' => '_next']
), $type)));
return false;
}
$this->addHtmlHint(Hint::error(Html::sprintf($this->translate(
'Please check your Basket configuration, %s does not support'
. ' single "%s" configuration objects'
), Link::create(
$basketName,
'director/basket',
['name' => $basketName],
['data-base-target' => '_next']
), $type)));
return false;
}
}

View File

@ -24,9 +24,6 @@ class BasketForm extends DirectorObjectForm
'IcingaTemplateChoiceService' => $this->translate('Service Template Choice'),
'ServiceTemplate' => $this->translate('Service Templates'),
'ServiceSet' => $this->translate('Service Sets'),
'UserGroup' => $this->translate('User Groups'),
'UserTemplate' => $this->translate('User Templates'),
'User' => $this->translate('Users'),
'NotificationTemplate' => $this->translate('Notification Templates'),
'Notification' => $this->translate('Notifications'),
'TimePeriod' => $this->translate('Time Periods'),
@ -52,9 +49,9 @@ class BasketForm extends DirectorObjectForm
$types = $this->getAvailableTypes();
$options = [
Basket::SELECTION_NONE => $this->translate('Ignore'),
Basket::SELECTION_ALL => $this->translate('All of them'),
Basket::SELECTION_CUSTOM => $this->translate('Custom Selection'),
'IGNORE' => $this->translate('Ignore'),
'ALL' => $this->translate('All of them'),
'[]' => $this->translate('Custom Selection'),
];
$this->addHtmlHint($this->translate(
@ -92,13 +89,13 @@ class BasketForm extends DirectorObjectForm
/** @var Basket $object */
$values = [];
foreach ($this->getAvailableTypes() as $type => $label) {
$values[$type] = Basket::SELECTION_NONE;
$values[$type] = 'IGNORE';
}
foreach ($object->getChosenObjects() as $type => $selection) {
if ($selection === true) {
$values[$type] = Basket::SELECTION_ALL;
$values[$type] = 'ALL';
} elseif (is_array($selection)) {
$values[$type] = Basket::SELECTION_CUSTOM;
$values[$type] = '[]';
}
}

View File

@ -25,12 +25,10 @@ class BasketUploadForm extends DirectorObjectForm
*/
public function setup()
{
if ($this->object === null) {
$this->addElement('text', 'basket_name', [
'label' => $this->translate('Basket Name'),
'required' => true,
]);
}
$this->addElement('text', 'basket_name', [
'label' => $this->translate('Basket Name'),
'required' => true,
]);
$this->setAttrib('enctype', 'multipart/form-data');
$this->addElement('file', 'uploaded_file', [
@ -55,6 +53,16 @@ class BasketUploadForm extends DirectorObjectForm
return '\\Icinga\\Module\\Director\\DirectorObject\\Automation\\Basket';
}
protected function setObjectSuccessUrl()
{
/** @var Basket $basket */
$basket = $this->object();
$this->setSuccessUrl(
'director/basket',
['name' => $basket->get('basket_name')]
);
}
/**
* @return bool
* @throws IcingaException
@ -65,8 +73,7 @@ class BasketUploadForm extends DirectorObjectForm
throw new IcingaException('Got no file');
}
if (
! isset($_FILES['uploaded_file']['tmp_name'])
if (! isset($_FILES['uploaded_file']['tmp_name'])
|| ! is_uploaded_file($_FILES['uploaded_file']['tmp_name'])
) {
$this->addError('Got no uploaded file');
@ -127,17 +134,13 @@ class BasketUploadForm extends DirectorObjectForm
$basket->set('owner_type', 'user');
$basket->set('owner_value', $this->getAuth()->getUser()->getUsername());
if ($basket->hasBeenLoadedFromDb()) {
$this->setSuccessUrl('director/basket/snapshots', ['name' => $basket->get('basket_name')]);
} else {
$this->setSuccessUrl('director/basket', ['name' => $basket->get('basket_name')]);
$basket->store($this->db);
}
$basket->store($this->db);
BasketSnapshot::forBasketFromJson(
$basket,
$this->rawUpload
)->store($this->db);
$this->setObjectSuccessUrl();
$this->beforeSuccessfulRedirect();
$this->redirectOnSuccess($this->translate('Basket has been uploaded'));
}

View File

@ -51,13 +51,11 @@ trait DeployFormsBug7530
];
foreach ($objectTypes as $objectType) {
if (
(int) $db->fetchOne(
$db->select()
if ((int) $db->fetchOne(
$db->select()
->from($objectType, 'COUNT(*)')
->where('zone_id IN (?)', $zoneIds)
) > 0
) {
) > 0) {
return true;
}
}
@ -72,8 +70,7 @@ trait DeployFormsBug7530
$version = $this->api->getVersion();
if ($version === null) {
throw new \RuntimeException($this->translate('Unable to detect your Icinga 2 Core version'));
} elseif (
\version_compare($version, '2.11.0', '>=')
} elseif (\version_compare($version, '2.11.0', '>=')
&& \version_compare($version, '2.12.0', '<')
) {
return true;

View File

@ -4,7 +4,6 @@ namespace Icinga\Module\Director\Forms;
use Icinga\Authentication\Auth;
use Icinga\Exception\IcingaException;
use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Core\DeploymentApiInterface;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Deployment\DeploymentInfo;
@ -83,7 +82,7 @@ class DeploymentLinkForm extends DirectorForm
);
}
$this->setAttrib('class', 'gipfl-inline-form');
$this->setAttrib('class', 'inline');
$this->addHtml(Icon::create('wrench'));
try {
// As this is shown for single objects, ignore errors caused by an
@ -101,7 +100,7 @@ class DeploymentLinkForm extends DirectorForm
protected function canDeploy()
{
return $this->auth->hasPermission(Permission::DEPLOY);
return $this->auth->hasPermission('director/deploy');
}
public function render(Zend_View_Interface $view = null)
@ -157,7 +156,7 @@ class DeploymentLinkForm extends DirectorForm
protected function deploymentFailed($checksum, $error = null)
{
$extra = $error ? ': ' . $error : '';
$extra = $error ? ': ' . $error: '';
if ($this->getRequest()->isApiRequest()) {
throw new IcingaException('Not yet');

View File

@ -140,7 +140,8 @@ class DirectorDatafieldForm extends DirectorObjectForm
$this->addElement('text', 'varname', array(
'label' => $this->translate('Field name'),
'description' => $this->translate(
'This will be the name of the custom variable in the rendered Icinga configuration.'
'The unique name of the field. This will be the name of the custom'
. ' variable in the rendered Icinga configuration.'
),
'required' => true,
));
@ -165,7 +166,7 @@ class DirectorDatafieldForm extends DirectorObjectForm
$this->addElement('select', 'category_id', [
'label' => $this->translate('Data Field Category'),
'multiOptions' => $this->optionalEnum($this->enumCategories()),
'multiOptions' => $this->optionalEnum($this->enumCategpories()),
]);
$error = false;
@ -175,7 +176,7 @@ class DirectorDatafieldForm extends DirectorObjectForm
$error = $e->getMessage();
$types = $this->optionalEnum(array());
}
$this->addElement('select', 'datatype', array(
'label' => $this->translate('Data type'),
'description' => $this->translate('Field type'),
@ -281,7 +282,7 @@ class DirectorDatafieldForm extends DirectorObjectForm
protected function enumDataTypes()
{
$hooks = Hook::all('Director\\DataType');
$enum = [null => $this->translate('- please choose -')];
$enum = array(null => '- please choose -');
/** @var DataTypeHook $hook */
foreach ($hooks as $hook) {
$enum[get_class($hook)] = $hook->getName();
@ -290,7 +291,7 @@ class DirectorDatafieldForm extends DirectorObjectForm
return $enum;
}
protected function enumCategories()
protected function enumCategpories()
{
$db = $this->getDb()->getDbAdapter();
return $db->fetchPairs(

View File

@ -3,7 +3,6 @@
namespace Icinga\Module\Director\Forms;
use gipfl\IcingaWeb2\Link;
use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Web\Form\DirectorObjectForm;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaService;
@ -30,13 +29,7 @@ class IcingaAddServiceForm extends DirectorObjectForm
);
}
$this->addSingleImportElement(true);
if (empty($this->enumServiceTemplates())) {
$this->setSubmitLabel(false);
return;
}
$this->addSingleImportElement();
if (! ($imports = $this->getSentOrObjectValue('imports'))) {
$this->setSubmitLabel($this->translate('Next'));
@ -89,7 +82,7 @@ class IcingaAddServiceForm extends DirectorObjectForm
if ($this->hasBeenSent()) {
$this->addError($this->translate('No service has been chosen'));
} else {
if ($this->hasPermission(Permission::ADMIN)) {
if ($this->hasPermission('director/admin')) {
$html = sprintf(
$this->translate('Please define a %s first'),
Link::create(
@ -107,12 +100,15 @@ class IcingaAddServiceForm extends DirectorObjectForm
return $this;
}
$this->addElement('text', 'imports', [
$this->addElement('select', 'imports', [
'label' => $this->translate('Service'),
'description' => $this->translate('Choose a service template'),
'description' => $this->translate(
'Choose a service template'
),
'required' => true,
'data-suggestion-context' => 'servicetemplates',
'class' => 'autosubmit director-suggest'
'multiOptions' => $this->optionalEnum($enum),
'class' => 'autosubmit'
]);
return $this;
@ -160,11 +156,7 @@ class IcingaAddServiceForm extends DirectorObjectForm
public function onSuccess()
{
if ($this->host !== null) {
if ($id = $this->host->get('id')) {
$this->object->set('host_id', $id);
} else {
$this->object->set('host', $this->host->getObjectName());
}
$this->object->set('host_id', $this->host->get('id'));
parent::onSuccess();
return;
}
@ -172,11 +164,10 @@ class IcingaAddServiceForm extends DirectorObjectForm
$plain = $this->object->toPlainObject();
$db = $this->object->getConnection();
// TODO: Test this:
foreach ($this->hosts as $host) {
$service = IcingaService::fromPlainObject($plain, $db)
->set('host_id', $host->get('id'));
$this->getDbObjectStore()->store($service);
IcingaService::fromPlainObject($plain, $db)
->set('host_id', $host->get('id'))
->store();
}
$msg = sprintf(

View File

@ -2,13 +2,8 @@
namespace Icinga\Module\Director\Forms;
use gipfl\Web\Widget\Hint;
use Icinga\Exception\IcingaException;
use Icinga\Module\Director\Acl;
use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Data\Db\DbObjectStore;
use Icinga\Module\Director\Db\Branch\Branch;
use Icinga\Module\Director\Objects\IcingaCommand;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Objects\IcingaService;
@ -22,25 +17,8 @@ class IcingaCloneObjectForm extends DirectorForm
protected $baseObjectUrl;
/** @var Branch */
protected $branch;
public function setup()
{
$isBranch = $this->branch && $this->branch->isBranch();
$branchOnly = $this->object->get('id') === null;
if (
$isBranch
&& $this->object->isTemplate()
&& ! $this->object instanceof IcingaServiceSet
) {
$this->addHtml(Hint::error($this->translate(
'Templates cannot be cloned in Configuration Branches'
)));
$this->submitLabel = false;
return;
}
$name = $this->object->getObjectName();
$this->addElement('text', 'new_object_name', array(
'label' => $this->translate('New name'),
@ -48,7 +26,7 @@ class IcingaCloneObjectForm extends DirectorForm
'value' => $name,
));
if (!$branchOnly && Acl::instance()->hasPermission(Permission::ADMIN)) {
if (Acl::instance()->hasPermission('director/admin')) {
$this->addElement('select', 'clone_type', array(
'label' => 'Clone type',
'required' => true,
@ -59,9 +37,8 @@ class IcingaCloneObjectForm extends DirectorForm
));
}
if (
!$branchOnly && ($this->object instanceof IcingaHost
|| $this->object instanceof IcingaServiceSet)
if ($this->object instanceof IcingaHost
|| $this->object instanceof IcingaServiceSet
) {
$this->addBoolean('clone_services', [
'label' => $this->translate('Clone Services'),
@ -71,7 +48,7 @@ class IcingaCloneObjectForm extends DirectorForm
], 'y');
}
if (!$branchOnly && $this->object instanceof IcingaHost) {
if ($this->object instanceof IcingaHost) {
$this->addBoolean('clone_service_sets', [
'label' => $this->translate('Clone Service Sets'),
'description' => $this->translate(
@ -103,10 +80,7 @@ class IcingaCloneObjectForm extends DirectorForm
}
}
if (
($this->object->isTemplate() || $this->object instanceof IcingaCommand)
&& $this->object->supportsFields()
) {
if ($this->object->isTemplate() && $this->object->supportsFields()) {
$this->addBoolean('clone_fields', [
'label' => $this->translate('Clone Template Fields'),
'description' => $this->translate(
@ -121,13 +95,6 @@ class IcingaCloneObjectForm extends DirectorForm
);
}
public function setBranch(Branch $branch)
{
$this->branch = $branch;
return $this;
}
public function setObjectBaseUrl($url)
{
$this->baseObjectUrl = $url;
@ -143,7 +110,7 @@ class IcingaCloneObjectForm extends DirectorForm
$connection = $object->getConnection();
$db = $connection->getDbAdapter();
$newName = $this->getValue('new_object_name');
$resolve = Acl::instance()->hasPermission(Permission::ADMIN)
$resolve = Acl::instance()->hasPermission('director/admin')
&& $this->getValue('clone_type') === 'flat';
$msg = sprintf(
@ -153,16 +120,6 @@ class IcingaCloneObjectForm extends DirectorForm
$object->getObjectName()
);
$isBranch = $this->branch && $this->branch->isBranch();
if (
$isBranch
&& $this->object->isTemplate()
&& ! $this->object instanceof IcingaServiceSet
) {
throw new IcingaException('Cloning templates is not available for Branches');
}
if ($object->isTemplate() && $object->getObjectName() === $newName) {
throw new IcingaException(
$this->translate('Name needs to be changed when cloning a Template')
@ -206,14 +163,13 @@ class IcingaCloneObjectForm extends DirectorForm
$fields = $db->fetchAll(
$db->select()
->from($table . '_field')
->where("{$type}_id = ?", $object->get('id'))
->where("${type}_id = ?", $object->get('id'))
);
} else {
$fields = [];
}
$store = new DbObjectStore($connection, $this->branch);
if ($store->store($new)) {
if ($new->store()) {
$newId = $new->get('id');
foreach ($services as $service) {
$clone = IcingaService::fromPlainObject(
@ -222,39 +178,22 @@ class IcingaCloneObjectForm extends DirectorForm
);
if ($new instanceof IcingaHost) {
if ($isBranch) {
$clone->set('host', $newName);
} else {
$clone->set('host_id', $newId);
}
$clone->set('host_id', $newId);
} elseif ($new instanceof IcingaServiceSet) {
if ($isBranch) {
$clone->set('service_set', $newName);
} else {
$clone->set('service_set_id', $newId);
}
$clone->set('service_set_id', $newId);
}
$store->store($clone);
$clone->store();
}
foreach ($sets as $set) {
$newSet = IcingaServiceSet::fromPlainObject(
IcingaServiceSet::fromPlainObject(
$set->toPlainObject(),
$connection
);
if ($isBranch) {
$newSet->set('host', $newName);
} else {
$newSet->set('host_id', $newId);
}
$store->store($newSet);
)->set('host_id', $newId)->store();
}
foreach ($fields as $row) {
$row->{"{$type}_id"} = $newId;
$row->{"${type}_id"} = $newId;
$db->insert($table . '_field', (array) $row);
}
@ -280,7 +219,6 @@ class IcingaCloneObjectForm extends DirectorForm
return $db->fetchPairs(
$db->select()
->from('icinga_service_set', ['id', 'object_name'])
->where('object_type = ?', 'template')
->order('object_name')
);
}

View File

@ -134,22 +134,16 @@ class IcingaCommandArgumentForm extends DirectorObjectForm
$cmd = $this->commandObject;
$msg = sprintf(
$this->translate('%s argument "%s" has been removed'),
'%s argument "%s" has been removed',
$this->translate($this->getObjectShortClassName()),
$object->argument_name
);
// TODO: remove argument_id, once verified that it is no longer in use
$url = $this->getSuccessUrl()->without('argument_id')->without('argument');
$url = $this->getSuccessUrl()->without('argument_id');
$cmd->arguments()->remove($object->argument_name);
if ($this->branch->isBranch()) {
$this->getDbObjectStore()->store($cmd);
if ($cmd->store()) {
$this->setSuccessUrl($url);
} else {
if ($cmd->store()) {
$this->setSuccessUrl($url);
}
}
$this->redirectOnSuccess($msg);
@ -173,17 +167,20 @@ class IcingaCommandArgumentForm extends DirectorObjectForm
$this->translate('The argument %s has successfully been stored'),
$object->get('argument_name')
);
$this->getDbObjectStore()->store($cmd);
$cmd->store($this->db);
} else {
if ($this->isApiRequest()) {
$this->setHttpResponseCode(304);
}
$msg = $this->translate('No action taken, object has not been modified');
}
$this->setSuccessUrl('director/command/arguments', [
'argument' => $object->get('argument_name'),
'name' => $cmd->getObjectName()
]);
$this->setSuccessUrl(
'director/command/arguments',
[
'argument_id' => $object->get('id'),
'name' => $cmd->getObjectName()
]
);
$this->redirectOnSuccess($msg);
}

View File

@ -28,6 +28,7 @@ class IcingaCommandForm extends DirectorObjectForm
'ClusterZoneCheck' => 'Icinga Cluster Zone Check Command',
'IdoCheck' => 'Ido Check Command',
'RandomCheck' => 'Random Check Command',
'CrlCheck' => 'Crl Check Command',
)
),
'required' => ! $this->isTemplate(),

View File

@ -5,7 +5,6 @@ namespace Icinga\Module\Director\Forms;
use Icinga\Module\Director\Data\Db\DbObject;
use Icinga\Module\Director\Web\Form\DirectorObjectForm;
use Icinga\Module\Director\Objects\IcingaDependency;
use Zend_Validate_Callback;
class IcingaDependencyForm extends DirectorObjectForm
{
@ -165,7 +164,7 @@ class IcingaDependencyForm extends DirectorObjectForm
], null);
$this->addBoolean('disable_notifications', [
'label' => $this->translate('Disable Notifications'),
'label' => $this->translate('Disable Notificiations'),
'description' => $this->translate(
'Whether to disable notifications when this dependency fails.'
. ' Defaults to true.'
@ -193,83 +192,38 @@ class IcingaDependencyForm extends DirectorObjectForm
$parentHost = $dependency->get('parent_host');
if ($parentHost === null) {
$parentHostVar = $dependency->get('parent_host_var');
if ($parentHostVar !== null && \strlen($parentHostVar) > 0) {
if (\strlen($parentHostVar) > 0) {
$parentHost = '$' . $dependency->get('parent_host_var') . '$';
}
}
$parentHostDescription = $this->translate('Optional. The parent host.');
$applyTo = $this->getSentOrObjectValue('apply_to');
$parentHostValidator = new Zend_Validate_Callback(function ($value) use ($applyTo) {
if ($applyTo === 'host' && $this->isCustomVar($value)) {
return explode('.', trim($value, '$'))[0] === 'host';
}
return true;
});
$parentHostValidator->setMessage(
$this->translate('The parent host cannot be a service custom variable for a host dependency'),
Zend_Validate_Callback::INVALID_VALUE
);
if ($applyTo === 'service') {
$additionalDescription = $this->translate(
'You might want to refer to Host or Service Custom Variables via $host|service.vars.varname$'
);
} else {
$additionalDescription = $this->translate(
'You might want to refer to Host Custom Variables via $host.vars.varname$'
);
}
$parentHostDescription .= ' ' . $additionalDescription;
$this->addElement('text', 'parent_host', [
'label' => $this->translate('Parent Host'),
'description' => $parentHostDescription,
'class' => "autosubmit director-suggest",
'label' => $this->translate('Parent Host'),
'description' => $this->translate(
'The parent host. You might want to refer Host Custom Variables'
. ' via $host.vars.varname$'
),
'class' => "autosubmit director-suggest",
'data-suggestion-context' => 'hostnames',
'order' => 10,
'required' => $this->isObject(),
'value' => $parentHost,
'validators' => [$parentHostValidator]
'order' => 10,
'required' => $this->isObject(),
'value' => $parentHost
]);
$sentParent = $this->getSentOrObjectValue('parent_host');
if (!empty($sentParent) || $dependency->isApplyRule()) {
$parentService = $dependency->get('parent_service');
if ($parentService === null) {
$parentServiceVar = $dependency->get('parent_service_by_name');
if ($parentServiceVar) {
$parentService = '$' . $parentServiceVar . '$';
}
}
$parentServiceDescription = $this->translate(
'Optional. The parent service. If omitted this dependency'
. ' object is treated as host dependency.'
);
$parentServiceDescription .= ' ' . $additionalDescription;
$parentServiceValidator = clone $parentHostValidator;
$parentServiceValidator->setMessage(
$this->translate('The parent service cannot be a service custom variable for a host dependency'),
Zend_Validate_Callback::INVALID_VALUE
);
$this->addElement('text', 'parent_service', [
'label' => $this->translate('Parent Service'),
'description' => $parentServiceDescription,
'class' => "autosubmit director-suggest",
'data-suggestion-context' => 'servicenames',
'data-suggestion-for-host' => $sentParent,
'order' => 20,
'value' => $parentService,
'validators' => [$parentServiceValidator]
]);
'label' => $this->translate('Parent Service'),
'description' => $this->translate(
'Optional. The parent service. If omitted this dependency'
. ' object is treated as host dependency.'
),
'class' => "autosubmit director-suggest",
'data-suggestion-context' => 'servicenames',
'data-suggestion-for-host' => $sentParent,
'order' => 20,
'value' => $parentService
]);
}
// If configuring Object, allow selection of child host and/or service,
@ -336,22 +290,11 @@ class IcingaDependencyForm extends DirectorObjectForm
protected function handleProperties(DbObject $object, &$values)
{
if ($this->hasBeenSent()) {
if (isset($values['parent_host'])) {
if ($this->isCustomVar($values['parent_host'])) {
$values['parent_host_var'] = \trim($values['parent_host'], '$');
$values['parent_host'] = '';
} else {
$values['parent_host_var'] = '';
}
}
if (isset($values['parent_service'])) {
if ($this->isCustomVar($values['parent_service'])) {
$values['parent_service_by_name'] = trim($values['parent_service'], '$');
$values['parent_service'] = '';
} else {
$values['parent_service_by_name'] = '';
}
if (isset($values['parent_host'])
&& $this->isCustomVar($values['parent_host'])
) {
$values['parent_host_var'] = \trim($values['parent_host'], '$');
$values['parent_host'] = '';
}
}
@ -360,6 +303,7 @@ class IcingaDependencyForm extends DirectorObjectForm
protected function isCustomVar($string)
{
return preg_match('/^\$(?:host|service)\.vars\..+\$$/', $string);
return \preg_match('/^\$(?:host)\.vars\..+\$$/', $string);
// Eventually: return \preg_match('/^\$(?:host|service)\.vars\..+\$$/', $string);
}
}

View File

@ -2,10 +2,7 @@
namespace Icinga\Module\Director\Forms;
use Exception;
use Icinga\Exception\AuthenticationException;
use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Auth\Restriction;
use Icinga\Module\Director\Repository\IcingaTemplateRepository;
use Icinga\Module\Director\Restriction\HostgroupRestriction;
use Icinga\Module\Director\Web\Form\DirectorObjectForm;
@ -84,13 +81,7 @@ class IcingaHostForm extends DirectorObjectForm
'class' => 'autosubmit',
]);
try {
$hasAgent = $this->getSentOrResolvedObjectValue('has_agent') === 'y';
} catch (Exception $e) {
$hasAgent = false;
}
if ($hasAgent) {
if ($this->getSentOrResolvedObjectValue('has_agent') === 'y') {
$this->addBoolean('master_should_connect', [
'label' => $this->translate('Establish connection'),
'description' => $this->translate(
@ -106,17 +97,6 @@ class IcingaHostForm extends DirectorObjectForm
$this->addHidden('command_endpoint_id', null);
$this->setSentValue('command_endpoint_id', null);
$settings = $this->object->getConnection()->settings();
if ($settings->get('feature_custom_endpoint') === 'y' && ! $this->isTemplate()) {
$this->addElement('text', 'custom_endpoint_name', [
'label' => $this->translate('Custom Endpoint Name'),
'description' => $this->translate(
'Use a different name for the generated endpoint object than the host name'
. ' and add a custom variable to allow services setting the correct command endpoint.'
),
]);
}
} else {
if ($this->isTemplate()) {
$this->addElement('select', 'command_endpoint_id', [
@ -143,7 +123,6 @@ class IcingaHostForm extends DirectorObjectForm
'master_should_connect',
'accept_config',
'command_endpoint_id',
'custom_endpoint_name',
'api_key',
];
$this->addDisplayGroup($elements, 'clustering', [
@ -171,7 +150,7 @@ class IcingaHostForm extends DirectorObjectForm
if ($this->hasBeenSent()) {
$this->addError($this->translate('No Host template has been chosen'));
} else {
if ($this->hasPermission(Permission::ADMIN)) {
if ($this->hasPermission('director/admin')) {
$html = sprintf(
$this->translate('Please define a %s first'),
Link::create(
@ -216,9 +195,8 @@ class IcingaHostForm extends DirectorObjectForm
*/
protected function addGroupsElement()
{
if (
$this->hasHostGroupRestriction()
&& ! $this->getAuth()->hasPermission(Permission::GROUPS_FOR_RESTRICTED_HOSTS)
if ($this->hasHostGroupRestriction()
&& ! $this->getAuth()->hasPermission('director/groups-for-restricted-hosts')
) {
return $this;
}
@ -272,6 +250,15 @@ class IcingaHostForm extends DirectorObjectForm
return $this;
}
protected function strikeGroupLinks(BaseHtmlElement $links)
{
/** @var BaseHtmlElement $link */
foreach ($links->getContent() as $link) {
$link->getAttributes()->add('style', 'text-decoration: strike');
}
$links->add('aha');
}
protected function getInheritedGroups()
{
if ($this->hasObject()) {
@ -296,7 +283,9 @@ class IcingaHostForm extends DirectorObjectForm
);
}
return Html::tag('span', ['class' => 'host-group-links'], $links);
return Html::tag('span', [
'style' => 'line-height: 2.5em; padding-left: 0.5em'
], $links);
}
protected function getAppliedGroups()
@ -310,7 +299,7 @@ class IcingaHostForm extends DirectorObjectForm
protected function hasHostGroupRestriction()
{
return $this->getAuth()->getRestrictions(Restriction::FILTER_HOSTGROUPS);
return $this->getAuth()->getRestrictions('director/filter/hostgroups');
}
/**

View File

@ -21,9 +21,6 @@ class IcingaMultiEditForm extends DirectorObjectForm
private $propertiesToPick;
/** @var array<string, string> Custom variable name map to its element's name in the form */
private $varNameMap = [];
public function setObjects($objects)
{
$this->objects = $objects;
@ -51,7 +48,6 @@ class IcingaMultiEditForm extends DirectorObjectForm
$loader = new IcingaObjectFieldLoader($object);
$loader->prepareElements($this);
$loader->addFieldsToForm($this);
$this->varNameMap = $loader->getNameMap();
if ($form = $this->relatedForm) {
if ($form instanceof DirectorObjectForm) {
@ -72,7 +68,8 @@ class IcingaMultiEditForm extends DirectorObjectForm
/** @var \Zend_Form_Element $el */
foreach ($this->getElements() as $el) {
if ($this->isCustomVar($el->getName())) {
$name = $el->getName();
if (substr($name, 0, 4) === 'var_') {
$this->makeVariants($el);
}
}
@ -140,8 +137,8 @@ class IcingaMultiEditForm extends DirectorObjectForm
continue;
}
if ($this->isCustomVar($property)) {
$property = 'vars.' . $this->varNameMap[$property];
if (substr($property, 0, 4) === 'var_') {
$property = 'vars.' . substr($property, 4);
}
foreach ($this->getObjects($objects) as $object) {
@ -150,26 +147,13 @@ class IcingaMultiEditForm extends DirectorObjectForm
}
}
/**
* Check if the given property is a custom var
*
* @param string $property
*
* @return bool
*/
protected function isCustomVar(string $property): bool
{
return substr($property, 0, 4) === 'var_';
}
protected function storeModifiedObjects()
{
$modified = 0;
$store = $this->getDbObjectStore();
foreach ($this->objects as $object) {
if ($object->hasBeenModified()) {
$modified++;
$store->store($object);
$object->store();
}
}
@ -237,7 +221,6 @@ class IcingaMultiEditForm extends DirectorObjectForm
$key = $element->getName();
$this->removeElement($key);
$label = $element->getLabel();
$group = $this->getDisplayGroupForElement($element);
$description = $element->getDescription();
@ -257,13 +240,11 @@ class IcingaMultiEditForm extends DirectorObjectForm
}
}
protected function getVariants($key)
{
$variants = array();
if ($this->isCustomVar($key)) {
$key = 'vars.' . $this->varNameMap[$key];
if (substr($key, 0, 4) === 'var_') {
$key = 'vars.' . substr($key, 4);
}
foreach ($this->objects as $name => $object) {
@ -328,9 +309,8 @@ class IcingaMultiEditForm extends DirectorObjectForm
$this->translate($this->object->getShortTableName())
);
$store = $this->getDbObjectStore();
foreach ($this->objects as $object) {
$store->delete($object);
$object->delete();
}
if ($this->listUrl) {

View File

@ -2,7 +2,6 @@
namespace Icinga\Module\Director\Forms;
use Icinga\Module\Director\DataType\DataTypeDirectorObject;
use Icinga\Module\Director\Web\Form\DirectorObjectForm;
class IcingaNotificationForm extends DirectorObjectForm
@ -122,32 +121,20 @@ class IcingaNotificationForm extends DirectorObjectForm
{
$users = $this->enumUsers();
if (empty($users)) {
$this->addElement('select', 'users', [
'label' => $this->translate('Users'),
'description' => $this->translate('No User object has been created yet'),
'multiOptions' => $this->optionalEnum([]),
]);
} else {
$this->addElement('extensibleSet', 'users', [
return $this;
}
$this->addElement(
'extensibleSet',
'users',
array(
'label' => $this->translate('Users'),
'description' => $this->translate(
'Users that should be notified by this notifications'
),
'multiOptions' => $this->optionalEnum($users)
]);
}
$this->addElement('select', 'users_var', [
'label' => $this->translate('Users Custom Variable'),
'multiOptions' => $this->enumDirectorObjectFields('user'),
'description' => $this->translate(
'If defined, Users from this Custom Variable will be combined with single users chosen below. '
. ' e.g.: when set to notification_contacts, this notification will pick Users from the Array'
. ' service.vars.notification_contacts and fall back to host.vars.notification_contacts, in'
. ' case the former one does not exist.'
. ' Only Array type DirectorObject Fields for User objects are eligible for this feature.'
)
]);
);
return $this;
}
@ -159,59 +146,24 @@ class IcingaNotificationForm extends DirectorObjectForm
{
$groups = $this->enumUsergroups();
if (empty($groups)) {
$this->addElement('select', 'user_groups', [
'label' => $this->translate('Users'),
'description' => $this->translate('No UserGroup object has been created yet'),
'multiOptions' => $this->optionalEnum([]),
]);
} else {
$this->addElement('extensibleSet', 'user_groups', [
return $this;
}
$this->addElement(
'extensibleSet',
'user_groups',
array(
'label' => $this->translate('User groups'),
'description' => $this->translate(
'User groups that should be notified by this notifications'
),
'multiOptions' => $this->optionalEnum($groups)
]);
}
$this->addElement('select', 'user_groups_var', [
'label' => $this->translate('User Groups Custom Variable'),
'multiOptions' => $this->enumDirectorObjectFields('usergroup'),
'description' => $this->translate(
'If defined, User Groups from this Custom Variable will be combined with single Groups chosen below. '
. ' e.g.: when set to notification_groups, this notification will pick User Groups from the Array'
. ' service.vars.notification_groups and fall back to host.vars.notification_groups, in'
. ' case the former one does not exist.'
. ' Only Array type DirectorObject Fields for User objects are eligible for this feature.'
)
]);
);
return $this;
}
protected function enumDirectorObjectFields($objectType, $dataType = 'array')
{
$db = $this->db->getDbAdapter();
$query = $db->select()
->from(['df' => 'director_datafield'], ['k' => 'df.varname', 'v' => 'df.varname'])
->join(
['dfs' => 'director_datafield_setting'],
$db->quoteInto('df.id = dfs.datafield_id AND dfs.setting_name = ?', 'icinga_object_type'),
[]
)
->join(
['dft' => 'director_datafield_setting'],
$db->quoteInto('df.id = dft.datafield_id AND dft.setting_name = ?', 'data_type'),
[]
)
->where('df.datatype = ?', DataTypeDirectorObject::class)
->where('dfs.setting_value = ?', $objectType)
->where('dft.setting_value = ?', $dataType)
->order('df.varname');
return $this->optionalEnum($db->fetchPairs($query));
}
/**
* @return self
*/
@ -244,7 +196,7 @@ class IcingaNotificationForm extends DirectorObjectForm
array(
'label' => $this->translate('First notification delay'),
'description' => $this->translate(
'Delay until the first notification should be sent'
'Delay unless the first notification should be sent'
) . '. ' . $this->getTimeValueInfo()
)
);

View File

@ -2,9 +2,6 @@
namespace Icinga\Module\Director\Forms;
use Icinga\Module\Director\DataType\DataTypeBoolean;
use Icinga\Module\Director\DataType\DataTypeString;
use Icinga\Module\Director\Field\FormFieldSuggestion;
use Icinga\Module\Director\Objects\IcingaCommand;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaObject;
@ -18,9 +15,6 @@ class IcingaObjectFieldForm extends DirectorObjectForm
/** @var IcingaObject Please note that $object would conflict with logic in parent class */
protected $icingaObject;
/** @var FormFieldSuggestion */
protected $fieldSuggestion;
public function setIcingaObject($object)
{
$this->icingaObject = $object;
@ -42,9 +36,22 @@ class IcingaObjectFieldForm extends DirectorObjectForm
. ' a specific set, shown as a dropdown.'
);
// TODO: remove assigned ones!
$existingFields = $this->db->enumDatafields();
$blacklistedVars = array();
$suggestedFields = array();
foreach ($existingFields as $id => $field) {
if (preg_match('/ \(([^\)]+)\)$/', $field, $m)) {
$blacklistedVars['$' . $m[1] . '$'] = $id;
}
}
// TODO: think about imported existing vars without fields
// TODO: extract vars from command line (-> dummy)
// TODO: do not suggest chosen ones
$argumentVars = array();
$argumentVarDescriptions = array();
if ($object instanceof IcingaCommand) {
$command = $object;
} elseif ($object->hasProperty('check_command_id')) {
@ -53,16 +60,56 @@ class IcingaObjectFieldForm extends DirectorObjectForm
$command = null;
}
$suggestions = $this->fieldSuggestion = new FormFieldSuggestion($command, $this->db->enumDatafields());
$fields = $suggestions->getCommandFields();
if ($command) {
foreach ($command->arguments() as $arg) {
if ($arg->argument_format === 'string') {
$val = $arg->argument_value;
// TODO: create var::extractMacros or so
$this->addElement('select', 'datafield_id', [
if (preg_match_all('/(\$[a-z0-9_]+\$)/i', $val, $m, PREG_PATTERN_ORDER)) {
foreach ($m[1] as $val) {
if (array_key_exists($val, $blacklistedVars)) {
$id = $blacklistedVars[$val];
// Hint: if not set it might already have been
// removed in this loop
if (array_key_exists($id, $existingFields)) {
$suggestedFields[$id] = $existingFields[$id];
unset($existingFields[$id]);
}
} else {
$argumentVars[$val] = $val;
$argumentVarDescriptions[$val] = $arg->description;
}
}
}
}
}
}
// Prepare combined fields array
$fields = array();
if (! empty($suggestedFields)) {
asort($existingFields);
$fields[$this->translate('Suggested fields')] = $suggestedFields;
}
if (! empty($argumentVars)) {
ksort($argumentVars);
$fields[$this->translate('Argument macros')] = $argumentVars;
}
if (! empty($existingFields)) {
$fields[$this->translate('Other available fields')] = $existingFields;
}
$this->addElement('select', 'datafield_id', array(
'label' => 'Field',
'required' => true,
'description' => 'Field to assign',
'class' => 'autosubmit',
'multiOptions' => $this->optionalEnum($fields)
]);
));
if (empty($fields)) {
// TODO: show message depending on permissions
@ -74,58 +121,67 @@ class IcingaObjectFieldForm extends DirectorObjectForm
}
if (($id = $this->getSentValue('datafield_id')) && ! ctype_digit($id)) {
$this->addElement('text', 'caption', [
$this->addElement('text', 'caption', array(
'label' => $this->translate('Caption'),
'required' => true,
'ignore' => true,
'value' => trim($id, '$'),
'description' => $this->translate(
'The caption which should be displayed to your users when this field'
. ' is shown'
)
]);
'description' => $this->translate('The caption which should be displayed')
));
$this->addElement('textarea', 'description', [
$this->addElement('textarea', 'description', array(
'label' => $this->translate('Description'),
'description' => $this->translate(
'An extended description for this field. Will be shown as soon as a'
. ' user puts the focus on this field'
),
'description' => $this->translate('A description about the field'),
'ignore' => true,
'value' => $command ? $suggestions->getDescription($id) : null,
'value' => array_key_exists($id, $argumentVarDescriptions) ? $argumentVarDescriptions[$id] : null,
'rows' => '3',
]);
));
}
$this->addElement('select', 'is_required', [
$this->addElement('select', 'is_required', array(
'label' => $this->translate('Mandatory'),
'description' => $this->translate('Whether this field should be mandatory'),
'required' => true,
'multiOptions' => [
'multiOptions' => array(
'n' => $this->translate('Optional'),
'y' => $this->translate('Mandatory'),
]
]);
)
));
if ($filterFields = $this->getFilterFields($object)) {
$this->addFilterElement('var_filter', [
$filterFields = array();
$prefix = null;
if ($object instanceof IcingaHost) {
$prefix = 'host.vars.';
} elseif ($object instanceof IcingaService) {
$prefix = 'service.vars.';
}
if ($prefix) {
$loader = new IcingaObjectFieldLoader($object);
$fields = $loader->getFields();
foreach ($fields as $varName => $field) {
$filterFields[$prefix . $field->varname] = $field->caption;
}
$this->addFilterElement('var_filter', array(
'description' => $this->translate(
'You might want to show this field only when certain conditions are met.'
. ' Otherwise it will not be available and values eventually set before'
. ' will be cleared once stored'
),
'columns' => $filterFields,
]);
));
$this->addDisplayGroup([$this->getElement('var_filter')], 'field_filter', [
'decorators' => [
$this->addDisplayGroup(array($this->getElement('var_filter')), 'field_filter', array(
'decorators' => array(
'FormElements',
['HtmlTag', ['tag' => 'dl']],
array('HtmlTag', array('tag' => 'dl')),
'Fieldset',
],
),
'order' => 30,
'legend' => $this->translate('Show based on filter')
]);
));
}
$this->setButtons();
@ -146,42 +202,18 @@ class IcingaObjectFieldForm extends DirectorObjectForm
$fieldId = $this->getValue('datafield_id');
if (! ctype_digit($fieldId)) {
$field = DirectorDatafield::create([
$field = DirectorDatafield::create(array(
'varname' => trim($fieldId, '$'),
'caption' => $this->getValue('caption'),
'description' => $this->getValue('description'),
'datatype' => $this->fieldSuggestion && $this->fieldSuggestion->isBoolean($fieldId)
? DataTypeBoolean::class
: DataTypeString::class
]);
'datatype' => 'Icinga\Module\Director\DataType\DataTypeString',
));
$field->store($this->getDb());
$this->setElementValue('datafield_id', $field->get('id'));
$this->object()->set('datafield_id', $field->get('id'));
}
$this->object()->set('var_filter', $this->getValue('var_filter'));
parent::onSuccess();
}
protected static function getFilterFields(IcingaObject $object): array
{
$filterFields = [];
$prefix = null;
if ($object instanceof IcingaHost) {
$prefix = 'host.vars.';
} elseif ($object instanceof IcingaService) {
$prefix = 'service.vars.';
}
if ($prefix) {
$loader = new IcingaObjectFieldLoader($object);
$fields = $loader->getFields();
foreach ($fields as $varName => $field) {
$filterFields[$prefix . $field->get('varname')] = $field->get('caption');
}
}
return $filterFields;
return parent::onSuccess();
}
}

View File

@ -22,10 +22,6 @@ class IcingaScheduledDowntimeForm extends DirectorObjectForm
'required' => true,
]);
}
if ($this->object()->isApplyRule()) {
$this->eventuallyAddNameRestriction('director/scheduled-downtime/apply/filter-by-name');
}
$this->addImportsElement();
$this->addElement('text', 'author', [
'label' => $this->translate('Author'),

View File

@ -21,7 +21,7 @@ class IcingaScheduledDowntimeRangeForm extends DirectorObjectForm
$this->addElement('text', 'range_key', [
'label' => $this->translate('Day(s)'),
'description' => $this->translate(
'Might be monday, tuesday or 2016-01-28 - have a look at the documentation for more examples'
'Might be, monday, tuesday, 2016-01-28 - have a look at the documentation for more examples'
),
]);

View File

@ -1,54 +0,0 @@
<?php
namespace Icinga\Module\Director\Forms;
use Icinga\Module\Director\Web\Form\DirectorObjectForm;
use Icinga\Module\Director\Objects\IcingaService;
class IcingaServiceDictionaryMemberForm extends DirectorObjectForm
{
/** @var IcingaService */
protected $object;
private $succeeded;
/**
* @throws \Zend_Form_Exception
*/
public function setup()
{
$this->addHidden('object_type', 'object');
$this->addElement('text', 'object_name', [
'label' => $this->translate('Name'),
'required' => !$this->object()->isApplyRule(),
'description' => $this->translate(
'Name for the instance you are going to create'
)
]);
$this->groupMainProperties()->setButtons();
}
protected function isNew()
{
return $this->object === null;
}
protected function deleteObject($object)
{
}
protected function getObjectClassname()
{
return IcingaService::class;
}
public function succeeded()
{
return $this->succeeded;
}
public function onSuccess()
{
$this->succeeded = true;
}
}

View File

@ -6,15 +6,12 @@ use gipfl\Web\Widget\Hint;
use Icinga\Data\Filter\Filter;
use Icinga\Exception\IcingaException;
use Icinga\Exception\ProgrammingError;
use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Data\PropertiesFilter\ArrayCustomVariablesFilter;
use Icinga\Module\Director\Exception\NestingError;
use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Web\Form\DirectorObjectForm;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaService;
use Icinga\Module\Director\Objects\IcingaServiceSet;
use Icinga\Module\Director\Web\Table\ObjectsTableHost;
use ipl\Html\Html;
use gipfl\IcingaWeb2\Link;
use RuntimeException;
@ -130,8 +127,6 @@ class IcingaServiceForm extends DirectorObjectForm
if (! $this->providesOverrides()) {
return;
}
$hasDeleteButton = false;
$isBranch = $this->branch && $this->branch->isBranch();
if ($this->hasBeenBlacklisted()) {
$this->addHtml(
@ -139,10 +134,7 @@ class IcingaServiceForm extends DirectorObjectForm
['name' => 'HINT_blacklisted']
);
$group = null;
if (! $isBranch) {
$this->addDeleteButton($this->translate('Reactivate'));
$hasDeleteButton = true;
}
$this->addDeleteButton($this->translate('Reactivate'));
$this->setSubmitLabel(false);
} else {
$this->addOverrideHint();
@ -171,13 +163,10 @@ class IcingaServiceForm extends DirectorObjectForm
$this->setSubmitLabel(false);
}
if (! $isBranch) {
$this->addDeleteButton($this->translate('Deactivate'));
$hasDeleteButton = true;
}
$this->addDeleteButton($this->translate('Deactivate'));
}
if (! $this->hasSubmitButton() && $hasDeleteButton) {
if (! $this->hasSubmitButton()) {
$this->addDisplayGroup([$this->deleteButtonName], 'buttons', [
'decorators' => [
'FormElements',
@ -198,16 +187,14 @@ class IcingaServiceForm extends DirectorObjectForm
}
/**
* Hint: could be moved elsewhere
*
* @param IcingaService $object
* @return IcingaObject|IcingaService|IcingaServiceSet
* @param IcingaService $service
* @return IcingaService
* @throws \Icinga\Exception\NotFoundError
*/
protected static function getFirstParent(IcingaObject $object)
protected function getFirstParent(IcingaService $service)
{
/** @var IcingaObject[] $objects */
$objects = $object->imports()->getObjects();
/** @var IcingaService[] $objects */
$objects = $service->imports()->getObjects();
if (empty($objects)) {
throw new RuntimeException('Something went wrong, got no parent');
}
@ -228,19 +215,13 @@ class IcingaServiceForm extends DirectorObjectForm
if ($this->blacklisted === null) {
$host = $this->host;
// Safety check, branches
$hostId = $host->get('id');
$service = $this->getServiceToBeBlacklisted();
$serviceId = $service->get('id');
if (! $hostId || ! $serviceId) {
return false;
}
$db = $this->db->getDbAdapter();
if ($this->providesOverrides()) {
$this->blacklisted = 1 === (int)$db->fetchOne(
$db->select()->from('icinga_host_service_blacklist', 'COUNT(*)')
->where('host_id = ?', $hostId)
->where('service_id = ?', $serviceId)
->where('host_id = ?', $host->get('id'))
->where('service_id = ?', $service->get('id'))
);
} else {
$this->blacklisted = false;
@ -282,12 +263,10 @@ class IcingaServiceForm extends DirectorObjectForm
$db = $this->db->getDbAdapter();
$host->unsetOverriddenServiceVars($this->object->getObjectName())->store();
if (
$db->insert('icinga_host_service_blacklist', [
if ($db->insert('icinga_host_service_blacklist', [
'host_id' => $host->get('id'),
'service_id' => $service->get('id')
])
) {
])) {
$msg = sprintf(
$this->translate('%s has been deactivated on %s'),
$service->getObjectName(),
@ -306,7 +285,7 @@ class IcingaServiceForm extends DirectorObjectForm
if ($this->set) {
return $this->object;
} else {
return self::getFirstParent($this->object);
return $this->getFirstParent($this->object);
}
}
@ -354,14 +333,14 @@ class IcingaServiceForm extends DirectorObjectForm
protected function setupServiceElements()
{
if ($this->object) {
$objectType = $this->object->get('object_type');
$objectType = $this->object->object_type;
} elseif ($this->preferredObjectType) {
$objectType = $this->preferredObjectType;
} else {
$objectType = 'template';
}
$this->addHidden('object_type', $objectType);
$forceCommandElements = $this->hasPermission(Permission::ADMIN);
$forceCommandElements = $this->hasPermission('director/admin');
$this->addNameElement()
->addHostObjectElement()
@ -443,6 +422,10 @@ class IcingaServiceForm extends DirectorObjectForm
$this->addHtmlHint($hint, ['name' => 'inheritance_hint']);
}
/**
* @throws IcingaException
* @throws ProgrammingError
*/
protected function setupOnHostForSet()
{
$msg = $this->translate(
@ -495,7 +478,7 @@ class IcingaServiceForm extends DirectorObjectForm
*/
protected function setupHostRelatedElements()
{
$this->addHidden('host', $this->host->getObjectName());
$this->addHidden('host_id', $this->host->id);
$this->addHidden('object_type', 'object');
$this->addImportsElement();
$imports = $this->getSentOrObjectValue('imports');
@ -518,7 +501,13 @@ class IcingaServiceForm extends DirectorObjectForm
->addExtraInfoElements()
->setButtons();
$this->setDefaultNameFromTemplate($imports);
if ($this->hasBeenSent()) {
$name = $this->getSentOrObjectValue('object_name');
if (!strlen($name)) {
$this->setElementValue('object_name', end($imports));
$this->object->object_name = end($imports);
}
}
}
/**
@ -536,7 +525,7 @@ class IcingaServiceForm extends DirectorObjectForm
*/
protected function setupSetRelatedElements()
{
$this->addHidden('service_set', $this->set->getObjectName());
$this->addHidden('service_set_id', $this->set->id);
$this->addHidden('object_type', 'apply');
$this->addImportsElement();
$this->setButtons();
@ -556,13 +545,19 @@ class IcingaServiceForm extends DirectorObjectForm
->addGroupsElement()
->groupMainProperties();
if ($this->hasPermission(Permission::ADMIN)) {
if ($this->hasPermission('director/admin')) {
$this->addCheckCommandElements(true)
->addCheckExecutionElements(true)
->addExtraInfoElements();
}
$this->setDefaultNameFromTemplate($imports);
if ($this->hasBeenSent()) {
$name = $this->getSentOrObjectValue('object_name');
if (!strlen($name)) {
$this->setElementValue('object_name', end($imports));
$this->object->object_name = end($imports);
}
}
}
public function setServiceSet(IcingaServiceSet $set)
@ -599,14 +594,14 @@ class IcingaServiceForm extends DirectorObjectForm
protected function addHostObjectElement()
{
if ($this->isObject()) {
$this->addElement('select', 'host', [
$this->addElement('select', 'host_id', array(
'label' => $this->translate('Host'),
'required' => true,
'multiOptions' => $this->optionalEnum($this->enumHostsAndTemplates()),
'description' => $this->translate(
'Choose the host this single service should be assigned to'
)
]);
));
}
return $this;
@ -708,35 +703,10 @@ class IcingaServiceForm extends DirectorObjectForm
protected function enumHostsAndTemplates()
{
if ($this->branch && $this->branch->isBranch()) {
return $this->enumHosts();
}
return [
$this->translate('Templates') => $this->enumHostTemplates(),
$this->translate('Hosts') => $this->enumHosts(),
];
}
protected function enumHostTemplates()
{
$names = array_values($this->db->enumHostTemplates());
return array_combine($names, $names);
}
protected function enumHosts()
{
$db = $this->db->getDbAdapter();
$table = new ObjectsTableHost($this->db, $this->getAuth());
if ($this->branch && $this->branch->isBranch()) {
$table->setBranchUuid($this->branch->getUuid());
}
$result = [];
foreach ($db->fetchAll($table->getQuery()->reset(\Zend_Db_Select::LIMIT_COUNT)) as $row) {
$result[$row->object_name] = $row->object_name;
}
return $result;
return array(
$this->translate('Templates') => $this->db->enumHostTemplates(),
$this->translate('Hosts') => $this->db->enumHosts(),
);
}
protected function enumServicegroups()
@ -753,6 +723,10 @@ class IcingaServiceForm extends DirectorObjectForm
return $db->fetchPairs($select);
}
/**
* @throws IcingaException
* @throws ProgrammingError
*/
protected function succeedForOverrides()
{
$vars = array();
@ -773,7 +747,7 @@ class IcingaServiceForm extends DirectorObjectForm
$this->translate($host->getObjectName())
);
$this->getDbObjectStore()->store($host);
$host->store();
} else {
if ($this->isApiRequest()) {
$this->setHttpResponseCode(304);
@ -785,27 +759,16 @@ class IcingaServiceForm extends DirectorObjectForm
$this->redirectOnSuccess($msg);
}
/**
* @throws IcingaException
* @throws ProgrammingError
*/
public function onSuccess()
{
if ($this->providesOverrides()) {
$this->succeedForOverrides();
return;
return $this->succeedForOverrides();
}
parent::onSuccess();
}
/**
* @param array $imports
*/
protected function setDefaultNameFromTemplate($imports)
{
if ($this->hasBeenSent()) {
$name = $this->getSentOrObjectValue('object_name');
if ($name === null || !strlen($name)) {
$this->setElementValue('object_name', end($imports));
$this->object->set('object_name', end($imports));
}
}
return parent::onSuccess();
}
}

View File

@ -2,7 +2,6 @@
namespace Icinga\Module\Director\Forms;
use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Web\Form\DirectorObjectForm;
@ -70,8 +69,7 @@ class IcingaServiceSetForm extends DirectorObjectForm
}
$this->addHidden('object_type', 'object');
$this->addHidden('host', $this->host->getObjectName());
$this->groupMainProperties();
$this->addHidden('host_id', $this->host->id);
}
public function setHost(IcingaHost $host)
@ -79,7 +77,6 @@ class IcingaServiceSetForm extends DirectorObjectForm
$this->host = $host;
return $this;
}
protected function addSingleImportsElement()
{
$enum = $this->enumAllowedTemplates();
@ -114,7 +111,7 @@ class IcingaServiceSetForm extends DirectorObjectForm
protected function addAssignmentElements()
{
if (! $this->hasPermission(Permission::SERVICE_SET_APPLY)) {
if (! $this->hasPermission('director/service_set/apply')) {
return $this;
}

View File

@ -19,7 +19,7 @@ class IcingaTemplateChoiceForm extends DirectorObjectForm
{
if ($name !== null) {
/** @var IcingaTemplateChoice $class - cheating IDE */
$class = $this->getObjectClassname();
$class = $this->getObjectClassName();
$this->setObject($class::load($name, $this->getDb()));
}
@ -91,15 +91,6 @@ class IcingaTemplateChoiceForm extends DirectorObjectForm
'value' => 1,
));
$this->addElement('select', 'required_template', [
'label' => $this->translate('Associated Template'),
'description' => $this->translate(
'Choose Choice Associated Template'
),
'required' => true,
'multiOptions' => $this->fetchUnboundTemplates(),
]);
$this->setButtons();
}
@ -133,7 +124,7 @@ class IcingaTemplateChoiceForm extends DirectorObjectForm
/** @var IcingaTemplateChoice $object */
$object = $this->object();
$this->setSuccessUrl(
'director/templatechoice/' . $object->getObjectShortTableName(),
'director/templatechoice/' . $object->getObjectshortTableName(),
$object->getUrlParams()
);
}

View File

@ -20,7 +20,7 @@ class IcingaTimePeriodRangeForm extends DirectorObjectForm
$this->addElement('text', 'range_key', array(
'label' => $this->translate('Day(s)'),
'description' => $this->translate(
'Might be monday, tuesday or 2016-01-28 - have a look at the documentation for more examples'
'Might be, monday, tuesday, 2016-01-28 - have a look at the documentation for more examples'
),
));

View File

@ -64,41 +64,6 @@ class ImportRowModifierForm extends DirectorObjectForm
$error = $e->getMessage();
$mods = $this->optionalEnum([]);
}
$this->addElement('YesNo', 'use_filter', [
'label' => $this->translate('Set based on filter'),
'ignore' => true,
'class' => 'autosubmit',
'required' => true,
]);
if ($this->hasBeenSent()) {
$useFilter = $this->getSentValue('use_filter');
if ($useFilter === null) {
$this->setElementValue('use_filter', $useFilter = 'n');
}
} elseif ($object = $this->getObject()) {
$expression = $object->get('filter_expression');
$useFilter = ($expression === null || strlen($expression) === 0) ? 'n' : 'y';
$this->setElementValue('use_filter', $useFilter);
} else {
$this->setElementValue('use_filter', $useFilter = 'n');
}
if ($useFilter === 'y') {
$this->addElement('text', 'filter_expression', [
'label' => $this->translate('Filter Expression'),
'description' => $this->translate(
'This allows to filter for specific parts within the given source expression.'
. ' You are allowed to refer all imported columns. Examples: host=www* would'
. ' set this property only for rows imported with a host property starting'
. ' with "www". Complex example: host=www*&!(address=127.*|address6=::1).'
. ' Please note, that CIDR notation based matches are also supported: '
. ' address=192.0.2.128/25| address=2001:db8::/32| address=::ffff:192.0.2.0/96'
),
'required' => true,
// TODO: validate filter
]);
}
$this->addElement('select', 'provider_class', [
'label' => $this->translate('Modifier'),
@ -198,10 +163,10 @@ class ImportRowModifierForm extends DirectorObjectForm
if ($class !== null) {
if (! class_exists($class)) {
throw new RuntimeException(sprintf(
throw new RuntimeException(
'The hooked class "%s" for this property modifier does no longer exist',
$class
));
);
}
$class::addSettingsFormFields($this);
@ -214,13 +179,4 @@ class ImportRowModifierForm extends DirectorObjectForm
return $this;
}
public function onSuccess()
{
if ($this->getValue('use_filter') === 'n') {
$this->getObject()->set('filter_expression', null);
}
parent::onSuccess();
}
}

View File

@ -41,18 +41,12 @@ class KickstartForm extends DirectorForm
$this->addResourceConfigElements();
$this->addResourceDisplayGroup();
if (
!$this->config()->get('db', 'resource')
|| ($this->config()->get('db', 'resource') !== $this->getResourceName())
) {
if (!$this->config()->get('db', 'resource')
|| ($this->config()->get('db', 'resource') !== $this->getResourceName())) {
return;
}
}
if (!$this->hasBeenSent() && !$this->tryDbConnection()) {
return;
}
if (!$this->migrations()->hasSchema()) {
$this->addHtmlHint($this->translate(
'No database schema has been created yet'
@ -77,7 +71,7 @@ class KickstartForm extends DirectorForm
$hint = Html::sprintf(
$this->translate('Your database looks good, you are ready to %s'),
Link::create(
$this->translate('start working with the Icinga Director'),
'start working with the Icinga Director',
'director',
null,
['data-base-target' => '_main']
@ -200,8 +194,35 @@ class KickstartForm extends DirectorForm
// Do not hinder the form from being stored
return;
}
if ($resourceName = $this->getResourceName()) {
$resourceConfig = ResourceFactory::getResourceConfig($resourceName);
if (! isset($resourceConfig->charset)
|| ! in_array($resourceConfig->charset, array('utf8', 'utf8mb4', 'UTF8', 'UTF-8'))
) {
if ($resource = $this->getElement('resource')) {
$resource->addError('Please change the encoding for the director database to utf8');
} else {
$this->addError('Please change the encoding for the director database to utf8');
}
}
$this->tryDbConnection();
$resource = $this->getResource();
$db = $resource->getDbAdapter();
try {
$db->fetchOne('SELECT 1');
} catch (Exception $e) {
$this->getElement('resource')
->addError('Could not connect to database: ' . $e->getMessage());
$hint = $this->translate(
'Please make sure that your database exists and your user has'
. ' been granted enough permissions'
);
$this->addHtmlHint($hint, array('name' => 'HINT_db_perms'));
}
}
}
/**
@ -322,7 +343,7 @@ class KickstartForm extends DirectorForm
)
);
$this->addHtmlHint(
Html::tag('pre', null, (string) $config),
Html::tag('pre', null, $config),
array('name' => 'HINT_config_store')
);
@ -355,10 +376,8 @@ class KickstartForm extends DirectorForm
}
}
if (
$this->getSubmitLabel() === $this->createDbLabel
|| $this->getSubmitLabel() === $this->migrateDbLabel
) {
if ($this->getSubmitLabel() === $this->createDbLabel
|| $this->getSubmitLabel() === $this->migrateDbLabel) {
$this->migrations()->applyPendingMigrations();
parent::onSuccess();
}
@ -447,41 +466,4 @@ class KickstartForm extends DirectorForm
return $resources;
}
protected function tryDbConnection()
{
if ($resourceName = $this->getResourceName()) {
$resourceConfig = ResourceFactory::getResourceConfig($resourceName);
if (
!isset($resourceConfig->charset)
|| !in_array($resourceConfig->charset, array('utf8', 'utf8mb4', 'UTF8', 'UTF-8'))
) {
if ($resource = $this->getElement('resource')) {
$resource->addError('Please change the encoding for the director database to utf8');
} else {
$this->addError('Please change the encoding for the director database to utf8');
}
}
$resource = $this->getResource();
$db = $resource->getDbAdapter();
try {
$db->fetchOne('SELECT 1');
return true;
} catch (Exception $e) {
$this->getElement('resource')
->addError('Could not connect to database: ' . $e->getMessage());
$hint = $this->translate(
'Please make sure that your database exists and your user has'
. ' been granted enough permissions'
);
$this->addHtmlHint($hint, array('name' => 'HINT_db_perms'));
}
}
return false;
}
}

View File

@ -17,7 +17,10 @@ class RemoveLinkForm extends DirectorForm
{
// Required to detect the right instance
$this->formName = 'RemoveSet' . sha1(json_encode($params));
parent::__construct(['data-base-target' => '_self']);
parent::__construct([
'style' => 'float: right',
'data-base-target' => '_self'
]);
$this->label = $label;
$this->title = $title;
foreach ($params as $name => $value) {
@ -35,7 +38,7 @@ class RemoveLinkForm extends DirectorForm
public function setup()
{
$this->addAttribs(['class' => ['inline', 'remove-link-form']]);
$this->setAttrib('class', 'inline');
$this->addHtml(Icon::create('cancel'));
$this->addSubmitButton($this->label, [
'class' => 'link-button',

View File

@ -105,18 +105,6 @@ class SettingsForm extends DirectorForm
));
}
$this->addBoolean('feature_custom_endpoint', [
'label' => $this->translate('Feature: Custom Endpoint Name'),
'description' => $this->translate(
'Enabled the feature for custom endpoint names,'
. ' where you can choose a different name for the generated endpoint object.'
. ' This uses some Icinga config snippets and a special custom variable.'
. ' Please do NOT enable this, unless you really need divergent endpoint names!'
),
'value' => $settings->getStoredValue('feature_custom_endpoint')
]);
$this->addElement('select', 'config_format', array(
'label' => $this->translate('Configuration format'),
'multiOptions' => $this->eventuallyConfiguredEnum(

View File

@ -2,7 +2,6 @@
namespace Icinga\Module\Director\Forms;
use Icinga\Module\Director\Objects\DirectorActivityLog;
use Icinga\Module\Director\Objects\SyncRule;
use Icinga\Module\Director\Web\Form\DirectorForm;
@ -32,20 +31,16 @@ class SyncCheckForm extends DirectorForm
$this->notifySuccess(
$this->translate(('This Sync Rule would apply new changes'))
);
$sum = [
DirectorActivityLog::ACTION_CREATE => 0,
DirectorActivityLog::ACTION_MODIFY => 0,
DirectorActivityLog::ACTION_DELETE => 0
];
$sum = array('create' => 0, 'modify' => 0, 'delete' => 0);
// TODO: Preview them? Like "hosta, hostb and 4 more would be...
foreach ($this->rule->getExpectedModifications() as $object) {
if ($object->shouldBeRemoved()) {
$sum[DirectorActivityLog::ACTION_DELETE]++;
$sum['delete']++;
} elseif (! $object->hasBeenLoadedFromDb()) {
$sum[DirectorActivityLog::ACTION_CREATE]++;
$sum['create']++;
} elseif ($object->hasBeenModified()) {
$sum[DirectorActivityLog::ACTION_MODIFY]++;
$sum['modify']++;
}
}
@ -55,7 +50,7 @@ class SyncCheckForm extends DirectorForm
} elseif ($sum['modify'] > 1) {
}
*/
$html = '<pre>' . print_r($sum, true) . '</pre>';
$html = '<pre>' . print_r($sum, 1) . '</pre>';
$this->addHtml($html);
} elseif ($this->rule->get('sync_state') === 'in-sync') {

View File

@ -25,7 +25,7 @@ class SyncPropertyForm extends DirectorObjectForm
private $dummyObject;
public const EXPRESSION = '__EXPRESSION__';
const EXPRESSION = '__EXPRESSION__';
/**
* @throws \Zend_Form_Exception
@ -93,8 +93,7 @@ class SyncPropertyForm extends DirectorObjectForm
$this->setElementValue('use_filter', $useFilter = 'n');
}
} else {
$expression = $this->getObject()->filter_expression;
$useFilter = ($expression === null || strlen($expression) === 0) ? 'n' : 'y';
$useFilter = strlen($this->getObject()->filter_expression) ? 'y' : 'n';
$this->setElementValue('use_filter', $useFilter);
}

View File

@ -74,22 +74,8 @@ class SyncRuleForm extends DirectorObjectForm
. ' longer exist at your import source.'
),
'required' => true,
'class' => 'autosubmit',
]);
if ($this->getSentOrObjectValue('purge_existing') === 'y') {
$this->addElement('select', 'purge_action', [
'label' => $this->translate('Purge Action'),
'description' => $this->translate(
'Whether to delete or to disable objects subject to purge'
),
'multiOptions' => $this->optionalEnum([
'delete' => $this->translate('Delete'),
'disable' => $this->translate('Disable'),
]),
'required' => true,
]);
}
]);
$this->addElement('text', 'filter_expression', [
'label' => $this->translate('Filter Expression'),

View File

@ -2,66 +2,44 @@
namespace Icinga\Module\Director\Forms;
use gipfl\Translation\TranslationHelper;
use gipfl\Web\Form;
use Icinga\Module\Director\Data\Db\DbObjectStore;
use Icinga\Module\Director\Import\Sync;
use Icinga\Module\Director\Objects\SyncRule;
use Icinga\Module\Director\Web\Form\DirectorForm;
class SyncRunForm extends Form
class SyncRunForm extends DirectorForm
{
use TranslationHelper;
protected $defaultDecoratorClass = null;
/** @var ?string */
protected $successMessage = null;
/** @var SyncRule */
protected $rule;
/** @var DbObjectStore */
protected $store;
public function __construct(SyncRule $rule, DbObjectStore $store)
public function setSyncRule(SyncRule $rule)
{
$this->rule = $rule;
$this->store = $store;
return $this;
}
public function assemble()
public function setup()
{
if ($this->store->getBranch()->isBranch()) {
$label = sprintf($this->translate('Sync to Branch: %s'), $this->store->getBranch()->getName());
} else {
$label = $this->translate('Trigger this Sync');
}
$this->addElement('submit', 'submit', [
'label' => $label,
]);
}
/**
* @return string|null
*/
public function getSuccessMessage()
{
return $this->successMessage;
$this->submitLabel = false;
$this->addElement('submit', 'submit', array(
'label' => $this->translate('Trigger this Sync'),
'decorators' => array('ViewHelper')
));
}
public function onSuccess()
{
$sync = new Sync($this->rule, $this->store);
if ($sync->hasModifications()) {
if ($sync->apply()) {
// and changed
$this->successMessage = $this->translate(('Source has successfully been synchronized'));
} else {
$this->successMessage = $this->translate('Nothing changed, rule is in sync');
}
$rule = $this->rule;
$changed = $rule->applyChanges();
if ($changed) {
$this->setSuccessMessage(
$this->translate(('Source has successfully been synchronized'))
);
} elseif ($rule->get('sync_state') === 'in-sync') {
$this->notifySuccess(
$this->translate('Nothing changed, rule is in sync')
);
} else {
// Used to be $rule->get('sync_state') === 'in-sync', $changed = $rule->applyChanges();
$this->successMessage = $this->translate('Nothing to do, rule is in sync');
$this->addError($this->translate('Synchronization failed'));
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1839,7 +1839,7 @@ msgstr ""
"essere basate sui gruppi."
#: application/forms/IcingaNotificationForm.php:199
msgid "Delay until the first notification should be sent"
msgid "Delay unless the first notification should be sent"
msgstr "Ritarda prima di spedire la prima notifica"
#: application/forms/IcingaObjectFieldForm.php:193
@ -1979,7 +1979,7 @@ msgid "Disable Checks"
msgstr "Disattivare i Checks"
#: application/forms/IcingaDependencyForm.php:167
msgid "Disable Notifications"
msgid "Disable Notificiations"
msgstr "Disattivare le notifiche"
#: application/forms/SettingsForm.php:54
@ -3429,7 +3429,7 @@ msgstr "Regole su Unisci"
#: application/forms/IcingaScheduledDowntimeRangeForm.php:24
#, fuzzy
msgid ""
"Might be monday, tuesday or 2016-01-28 - have a look at the documentation for "
"Might be, monday, tuesday, 2016-01-28 - have a look at the documentation for "
"more examples"
msgstr ""
"Per esempio lunedí, martedí, 2020-01-28 - consulta la documentazione per "
@ -7428,4 +7428,4 @@ msgid "e.g. "
msgstr "per es."
msgid "start using"
msgstr "inizia ad utilizzare"
msgstr "inizia ad utilizzare"

View File

@ -1665,7 +1665,7 @@ msgstr "サービスグループを定義すると、より構造がわかりま
"最適です。 通知と許可はグループに基づいている場合があります。"
#: ../../../../modules/director/application/forms/IcingaNotificationForm.php:196
msgid "Delay until the first notification should be sent"
msgid "Delay unless the first notification should be sent"
msgstr "最初の通知時間"
#: ../../../../modules/director/application/forms/IcingaObjectFieldForm.php:185
@ -1791,7 +1791,7 @@ msgstr "監視を無効化"
# smori
#: ../../../../modules/director/application/forms/IcingaDependencyForm.php:164
msgid "Disable Notifications"
msgid "Disable Notificiations"
msgstr "通知を無効化"
#: ../../../../modules/director/application/forms/SettingsForm.php:54
@ -3058,7 +3058,7 @@ msgstr "マージポリシー"
#: ../../../../modules/director/application/forms/IcingaTimePeriodRangeForm.php:23
msgid ""
"Might be monday, tuesday or 2016-01-28 - have a look at the documentation for "
"Might be, monday, tuesday, 2016-01-28 - have a look at the documentation for "
"more examples"
msgstr "monday, tuesday, 2016-01-28といった書式で指定します。"
"より多くの例についてはドキュメントを見てください"

View File

@ -44,16 +44,14 @@ class Zend_View_Helper_FormDataFilter extends Zend_View_Helper_FormElement
{
$info = $this->_getInfo($name, $value, $attribs);
extract($info); // id, name, value, attribs, options, listsep, disable
if ($attribs) {
if (array_key_exists('columns', $attribs)) {
$this->setColumns($attribs['columns']);
unset($attribs['columns']);
}
if (array_key_exists('columns', $attribs)) {
$this->setColumns($attribs['columns']);
unset($attribs['columns']);
}
if (array_key_exists('suggestionContext', $attribs)) {
$this->setSuggestionContext($attribs['suggestionContext']);
unset($attribs['suggestionContext']);
}
if (array_key_exists('suggestionContext', $attribs)) {
$this->setSuggestionContext($attribs['suggestionContext']);
unset($attribs['suggestionContext']);
}
// TODO: check for columns in attribs, preserve & remove them from the
@ -219,7 +217,7 @@ class Zend_View_Helper_FormDataFilter extends Zend_View_Helper_FormElement
} elseif (substr($col, $prefixLen, 5) === 'vars.') {
$var = substr($col, $prefixLen + 5);
return $this->text($filter, "DataListValues!{$var}");
return $this->text($filter, "DataListValues!${var}");
}
}
@ -238,7 +236,7 @@ class Zend_View_Helper_FormDataFilter extends Zend_View_Helper_FormElement
$filter->getExpression(),
[
'class' => 'director-suggest',
'data-suggestion-context' => "{$type}groupnames",
'data-suggestion-context' => "${type}groupnames",
]
);
}

View File

@ -8,8 +8,8 @@ use ipl\Html\HtmlDocument;
*
* We're rendering the following fields:
*
* - {$name}[_value]:
* - {$name}[_sent]:
* - ${name}[_value]:
* - ${name}[_sent]:
*
* Avoid complaints about class names:
* @codingStandardsIgnoreStart
@ -26,20 +26,20 @@ class Zend_View_Helper_FormStoredPassword extends Zend_View_Helper_FormElement
$res = new HtmlDocument();
$el = Html::tag('input', [
'type' => 'password',
'name' => "{$name}[_value]",
'name' => "${name}[_value]",
'id' => $id,
]);
$res->add($el);
$res->add(Html::tag('input', [
'type' => 'hidden',
'name' => "{$name}[_sent]",
'name' => "${name}[_sent]",
'value' => 'y'
]));
if ($sentValue !== null && \strlen($sentValue)) {
if (\strlen($sentValue)) {
$el->getAttributes()->set('value', $sentValue);
} elseif ($value !== null && \strlen($value) > 0) {
} elseif (\strlen($value) > 0) {
$el->getAttributes()->set('value', '__UNCHANGED_VALUE__');
}

View File

@ -1,5 +1,4 @@
<?php
// Avoid complaints about missing namespace and invalid class name
// @codingStandardsIgnoreStart

View File

@ -1,3 +1,8 @@
<?php
use Icinga\Application\Modules\Manager;
?>
<div class="controls">
<?= $this->tabs ?>
<h1><?= $this->escape($this->title) ?></h1>

View File

@ -1,99 +1,72 @@
<?php
use Icinga\Application\Icinga;
use Icinga\Application\Modules\Module;
use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Auth\Restriction;
use Icinga\Web\Window;
/** @var Module $this */
if ($this->getConfig()->get('frontend', 'disabled', 'no') === 'yes') {
return;
}
$this->providePermission('director/api', $this->translate('Allow to access the director API'));
$this->providePermission('director/audit', $this->translate('Allow to access the full audit log'));
$this->providePermission(
'director/showconfig',
$this->translate('Allow to show configuration (could contain sensitive information)')
);
$this->providePermission(
'director/showsql',
$this->translate('Allow to show the full executed SQL queries in some places')
);
$this->providePermission('director/deploy', $this->translate('Allow to deploy configuration'));
$this->providePermission('director/hosts', $this->translate('Allow to configure hosts'));
$this->providePermission('director/services', $this->translate('Allow to configure services'));
$this->providePermission('director/servicesets', $this->translate('Allow to configure service sets'));
$this->providePermission('director/service_set/apply', $this->translate('Allow to define Service Set Apply Rules'));
$this->providePermission('director/users', $this->translate('Allow to configure users'));
$this->providePermission('director/notifications', $this->translate('Allow to configure notifications'));
$this->providePermission(
'director/inspect',
$this->translate(
'Allow to inspect objects through the Icinga 2 API (could contain sensitive information)'
)
);
$this->providePermission(
'director/monitoring/services-ro',
$this->translate('Allow readonly users to see where a Service came from')
);
$this->providePermission('director/*', $this->translate('Allow unrestricted access to Icinga Director'));
$monitoringExists = Module::exists('monitoring');
$icingadbExists = Module::exists('icingadb');
$this->provideRestriction(
'director/filter/hostgroups',
$this->translate(
'Limit access to the given comma-separated list of hostgroups'
)
);
$this->providePermission(
'director/groups-for-restricted-hosts',
$this->translate('Allow users with Hostgroup restrictions to access the Groups field')
);
$this->providePermission(Permission::ALL_PERMISSIONS, $this->translate('Allow unrestricted access to Icinga Director'));
$this->providePermission(Permission::API, $this->translate('Allow to access the director API'));
$this->providePermission(Permission::AUDIT, $this->translate('Allow to access the full audit log'));
$this->providePermission(Permission::DEPLOY, $this->translate('Allow to deploy configuration'));
$this->providePermission(Permission::INSPECT, $this->translate(
'Allow to inspect objects through the Icinga 2 API (could contain sensitive information)'
));
$this->providePermission(Permission::SHOW_CONFIG, $this->translate(
'Allow to show configuration (could contain sensitive information)'
));
$this->providePermission(Permission::SHOW_SQL, $this->translate(
'Allow to show the full executed SQL queries in some places'
));
$this->providePermission(Permission::GROUPS_FOR_RESTRICTED_HOSTS, $this->translate(
'Allow users with Hostgroup restrictions to access the Groups field'
));
$this->providePermission(Permission::HOSTS, $this->translate('Allow to configure hosts'));
$this->providePermission(Permission::NOTIFICATIONS, $this->translate(
'Allow to configure notifications (unrestricted)'
));
$this->providePermission(Permission::SERVICES, $this->translate('Allow to configure services'));
$this->providePermission(Permission::SERVICE_SETS, $this->translate('Allow to configure service sets'));
$this->providePermission(Permission::SERVICE_SET_APPLY, $this->translate('Allow to define Service Set Apply Rules'));
$this->providePermission(Permission::USERS, $this->translate('Allow to configure users'));
$this->providePermission(Permission::SCHEDULED_DOWNTIMES, $this->translate(
'Allow to configure notifications (unrestricted)'
));
$this->provideRestriction(
'director/service/apply/filter-by-name',
$this->translate(
'Filter available service apply rules'
)
);
if ($monitoringExists) {
$this->providePermission(Permission::MONITORING_HOSTS, $this->translate(
'Allow users to modify Hosts they are allowed to see in the monitoring module'
));
$this->providePermission(Permission::MONITORING_SERVICES, $this->translate(
'Allow users to modify Service they are allowed to see in the monitoring module'
));
$this->providePermission(Permission::MONITORING_SERVICES_RO, $this->translate(
'Allow readonly users to see where a Service came from'
));
}
$this->provideRestriction(
'director/notification/apply/filter-by-name',
$this->translate(
'Filter available notification apply rules'
)
);
if ($icingadbExists) {
$this->providePermission(Permission::ICINGADB_HOSTS, $this->translate(
'Allow users to modify Hosts they are allowed to see in Icinga DB Web'
));
$this->providePermission(Permission::ICINGADB_SERVICES, $this->translate(
'Allow users to modify Service they are allowed to see in Icinga DB Web'
));
$this->providePermission(Permission::ICINGADB_SERVICES_RO, $this->translate(
'Allow readonly users to see where a Service came from'
));
}
if ($monitoringExists) {
$this->provideRestriction(Restriction::MONITORING_RW_OBJECT_FILTER, $this->translate(
'Additional (monitoring module) object filter to further restrict write access'
));
}
if ($icingadbExists) {
$this->provideRestriction(Restriction::ICINGADB_RW_OBJECT_FILTER, $this->translate(
'Additional (Icinga DB Web) object filter to further restrict write access'
));
}
$this->provideRestriction(Restriction::FILTER_HOSTGROUPS, $this->translate(
'Limit access to the given comma-separated list of hostgroups'
));
$this->provideRestriction(Restriction::NOTIFICATION_APPLY_FILTER_BY_NAME, $this->translate(
'Filter available notification apply rules'
));
$this->provideRestriction(Restriction::SCHEDULED_DOWNTIME_APPLY_FILTER_BY_NAME, $this->translate(
'Filter available scheduled downtime rules'
));
$this->provideRestriction(Restriction::SERVICE_APPLY_FILTER_BY_NAME, $this->translate(
'Filter available service apply rules'
));
$this->provideRestriction(Restriction::SERVICE_SET_FILTER_BY_NAME, $this->translate(
'Filter available service set templates. Use asterisks (*) as wildcards,'
. ' like in DB* or *net*'
));
$this->provideRestriction(
'director/service_set/filter-by-name',
$this->translate(
'Filter available service set templates. Use asterisks (*) as wildcards,'
. ' like in DB* or *net*'
)
);
$this->provideSearchUrl($this->translate('Host configs'), 'director/hosts?limit=10', 60);
@ -110,10 +83,10 @@ $this->provideRestriction(
);
*/
$this->provideConfigTab('config', [
$this->provideConfigTab('config', array(
'title' => 'Configuration',
'url' => 'settings'
]);
));
$mainTitle = N_('Icinga Director');
try {
@ -140,38 +113,41 @@ try {
$mainTitle .= ' (?!)';
}
// Hint: director/admin and director/deployments are intentionally
$section = $this->menuSection($mainTitle)
->setUrl('director')
->setPriority(60)
->setIcon('cubes')
->setRenderer(['SummaryNavigationItemRenderer', 'state' => 'critical']);
$section = $this->menuSection(
$mainTitle
)->setUrl('director')->setPriority(60)->setIcon(
'cubes'
)->setRenderer(array(
'SummaryNavigationItemRenderer',
'state' => 'critical'
));
$section->add(N_('Hosts'))
->setUrl('director/dashboard?name=hosts')
->setPermission(Permission::HOSTS)
->setPermission('director/hosts')
->setPriority(30);
$section->add(N_('Services'))
->setUrl('director/dashboard?name=services')
->setPermission(Permission::SERVICES)
->setPermission('director/services')
->setPriority(40);
$section->add(N_('Commands'))
->setUrl('director/dashboard?name=commands')
->setPermission(Permission::ADMIN)
->setPermission('director/admin')
->setPriority(50);
$section->add(N_('Notifications'))
->setUrl('director/dashboard?name=notifications')
->setPermission(Permission::NOTIFICATIONS)
->setPermission('director/notifications')
->setPriority(70);
$section->add(N_('Automation'))
->setUrl('director/importsources')
->setPermission(Permission::ADMIN)
->setPermission('director/admin')
->setPriority(901);
$section->add(N_('Activity log'))
->setUrl('director/config/activities')
->setPriority(902)
->setPermission(Permission::AUDIT)
->setPermission('director/audit')
->setRenderer('ConfigHealthItemRenderer');
$section->add(N_('Deployments'))
->setUrl('director/config/deployments')
->setPriority(902)
->setPermission(Permission::DEPLOYMENTS);
->setPermission('director/deployments');

View File

@ -1,75 +1,147 @@
<!-- {% if index %} -->
# Installing Icinga Director
<a id="Installation"></a>Installation
=====================================
The recommended way to install Icinga Director and its dependencies 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 Director
and if it is not already set up, it is best to do this first.
These are the instructions for manual Director installations. You can
learn more about how to automate this in the [automation](03-Automation.md) section
of this documentation. In case you already installed Director and want to upgrade
to the latest version, please [read on here](05-Upgrading.md).
The following steps will guide you through installing and setting up Icinga Director.
Requirements
------------
To upgrade an existing Icinga Director installation to a newer version,
see the [upgrading](05-Upgrading.md) documentation for the necessary steps.
* Icinga 2 (&gt;= 2.6.0)
* It is recommended to use the latest feature release of Icinga 2
* All versions since 2.4.3 should also work fine, but
we do no longer test and support them.
* Some features require newer Icinga 2 releases
* Flapping requires 2.8 for the thresholds to work - and at least 2.7 on all
nodes
* Icinga Web 2 (&gt;= 2.6.0). All versions since 2.2 should also work fine, but
might show smaller UI bugs and are not actively tested
* The following Icinga modules must be installed and enabled:
* [ipl](https://github.com/Icinga/icingaweb2-module-ipl) (>=0.3.0)
* [incubator](https://github.com/Icinga/icingaweb2-module-incubator) (>=0.21.0)
* [reactbundle](https://github.com/Icinga/icingaweb2-module-reactbundle) (>=0.7.0)
* A database, MySQL (&gt;= 5.1) or PostgreSQL (&gt;= 9.1). MariaDB and other
MySQL forks are also fine. Mentioned versions are the required minimum,
for MySQL we suggest using at least 5.5.3, for PostgreSQL 9.4.
* PHP (>= 5.6.3). For best performance please consider use 7.x or 8.x
* php-pdo-mysql and/or php-pdo-pgsql
* php-curl
* php-iconv
* php-pcntl (might already be built into your PHP binary)
* php-posix (on RHEL/CentOS this is php-process, or rh-php7x-php-process)
* php-sockets (might already be built into your PHP binary)
* php-mbstring and php-json (already required by Icinga Web 2)
If you want to automate the installation, configuration and upgrade,
you can learn more about it in the [automation](03-Automation.md) section of this documentation.
Optional Requirements
---------------------
* For IBM DB2 Imports: php-pdo-ibm
* For MSSQL Imports: php-mssql or php-pdo-dblib (or -sybase on some platforms)
* For Oracle DB Imports: php-oci8 or php-pdo-oci
* For Sqlite Imports: php-pdo-sqlite
## Optional Requirements
Database
--------
The following requirements are not necessary for installation,
but may be needed later if you want to import from one of the listed sources:
### Create an empty Icinga Director database
* For **IBM Db2** imports: `php-pdo-ibm`
* For **Microsoft SQL Server** imports: `php-mssql`, `php-pdo-dblib` or `php-sybase` depending on your platform
* For **Oracle Database** imports: `php-oci8` or `php-pdo-oci` depending on your platform
* For **SQLite** imports: `php-pdo-sqlite`
<!-- {% else %} -->
<!-- {% if not icingaDocs %} -->
HINT: You should replace `some-password` with a secure custom password.
## Installing Icinga Director Package
#### MySQL (or MariaDB)
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-director` package
or install [from source](02-Installation.md.d/From-Source.md).
<!-- {% endif %} -->
mysql -e "CREATE DATABASE director CHARACTER SET 'utf8';
CREATE USER director@localhost IDENTIFIED BY 'some-password';
GRANT ALL ON director.* TO director@localhost;"
## Setting up the Database
In case your MySQL root user is password-protected, please add `-p` to this
command.
A MySQL (≥5.7), MariaDB (≥10.1), or PostgreSQL (≥9.6) database is required to run Icinga Director.
Please follow the steps listed for your target database, to set up the database and the user.
The schema will be imported later via the web interface.
#### PostgreSQL
### Setting up a MySQL or MariaDB Database
> **Warning**
> Make sure to replace `CHANGEME` with a secure password.
psql -q -c "CREATE DATABASE director WITH ENCODING 'UTF8';"
psql director -q -c "CREATE USER director WITH PASSWORD 'some-password';
GRANT ALL PRIVILEGES ON DATABASE director TO director;
CREATE EXTENSION pgcrypto;"
```
mysql -e "CREATE DATABASE director CHARACTER SET 'utf8';
CREATE USER director@localhost IDENTIFIED BY 'CHANGEME';
GRANT ALL ON director.* TO director@localhost;"
```
Hint: pgcrypto helps to boost performance, but is currently optional. In case you
do not have it available on your platform and/or do not know how to solve this
just leave away the 'CREATE EXTENSION' part.
### Setting up a PostgreSQL Database
Web-based Configuration
-----------------------
> **Warning**
> Make sure to replace `CHANGEME` with a secure password.
The following steps should guide you through the web-based Kickstart wizard.
In case you prefer automated configuration, you should check the dedicated
[documentation section](03-Automation.md).
```
psql -q -c "CREATE DATABASE director WITH ENCODING 'UTF8';"
psql director -q -c "CREATE USER director WITH PASSWORD 'CHANGEME';
GRANT ALL PRIVILEGES ON DATABASE director TO director;
CREATE EXTENSION pgcrypto;"
```
### Create a Database resource
## Configuring Icinga Director
In your web frontend please go to `Configuration / Application / Resources`
and create a new database resource pointing to your newly created database.
Please make sure that you choose `utf8` as an encoding.
Log in to your running Icinga Web setup with a privileged user
and follow the steps below to configure Icinga Director:
1. Create a new resource for the Icinga Director [database](#setting-up-the-database) via the
`Configuration → Application → Resources` menu.
Please make sure that you configure `utf8` as encoding.
2. Select `Icinga Director` directly from the main menu
and you will be taken to the kickstart wizard. Follow the instructions and you are done!
<!-- {% endif %} --><!-- {# end else if index #} -->
### Install the Director module
As with any Icinga Web 2 module, installation is pretty straight-forward. In
case you're installing it from source all you have to do is to drop the director
module in one of your module paths. You can examine (and set) the module path(s)
in `Configuration / Application`. In a typical environment you'll probably drop the
module to `/usr/share/icingaweb2/modules/director`. Please note that the directory
name MUST be `director` and not `icingaweb2-module-director` or anything else.
#### Installation from release tarball
Download the [latest version](https://github.com/Icinga/icingaweb2-module-director/releases)
and extract it to a folder named `director` in one of your Icinga Web 2 module path directories.
You might want to use a script as follows for this task:
ICINGAWEB_MODULEPATH="/usr/share/icingaweb2/modules"
REPO_URL="https://github.com/icinga/icingaweb2-module-director"
TARGET_DIR="${ICINGAWEB_MODULEPATH}/director"
MODULE_VERSION="1.8.2"
URL="${REPO_URL}/archive/v${MODULE_VERSION}.tar.gz"
install -d -m 0755 "${TARGET_DIR}"
wget -q -O - "$URL" | tar xfz - -C "${TARGET_DIR}" --strip-components 1
Proceed to enabling the module.
#### Installation from GIT repository
Another convenient method is the installation directly from our GIT repository.
Just clone the repository to one of your Icinga Web 2 module path directories.
It will be immediately ready for use:
ICINGAWEB_MODULEPATH="/usr/share/icingaweb2/modules"
REPO_URL="https://github.com/icinga/icingaweb2-module-director"
TARGET_DIR="${ICINGAWEB_MODULEPATH}/director"
MODULE_VERSION="1.8.2"
git clone "${REPO_URL}" "${TARGET_DIR}" --branch v${MODULE_VERSION}
You can now directly use our current GIT master or check out a specific version.
cd "${TARGET_DIR}" && git checkout "v${MODULE_VERSION}"
Proceed to enabling the module.
#### Enable the newly installed module
Enable the `director` module either on the CLI by running
icingacli module enable director
Or go to your Icinga Web 2 frontend, choose `Configuration / Modules`,
select the `director` module and choose `State: enable`.
### Run the graphical kickstart wizard
Choose either `Icinga Director` directly from the main menu or
navigate into `Configuration / Modules / director` and select the `Configuration`
tab.
Either way you'll reach the kickstart wizards. Follow the instructions and
you're all done!

View File

@ -1,83 +0,0 @@
# Installing Icinga Director from Source
These are the instructions for manual Director installations.
Please see the Icinga Web documentation on
[how to install modules](https://icinga.com/docs/icinga-web-2/latest/doc/08-Modules/#installation) from source.
Make sure you use `director` as the module name. The following requirements must also be met.
## Requirements
* PHP (≥7.3)
* Director v1.10 is the last version with support for PHP v5.6
* [Icinga 2](https://github.com/Icinga/icinga2) (≥2.8.0)
* It is recommended to use the latest feature release of Icinga 2
* All versions since 2.4.3 should also work fine, but
we do no longer test and support them.
* Some features require newer Icinga 2 releases
* Flapping requires 2.8 for the thresholds to work - and at least 2.7 on all
nodes
* [Icinga Web](https://github.com/Icinga/icingaweb2) (≥2.8.0). All versions since 2.2 should also work fine, but
might show smaller UI bugs and are not actively tested
* The following Icinga modules must be installed and enabled:
* [incubator](https://github.com/Icinga/icingaweb2-module-incubator) (≥0.22.0)
* If you are using Icinga Web <2.9.0, the following modules are also required
* [ipl](https://github.com/Icinga/icingaweb2-module-ipl) (≥0.5.0)
* [reactbundle](https://github.com/Icinga/icingaweb2-module-reactbundle) (≥0.9.0)
* A database: MariaDB (≥10.1), MySQL (≥5.7), PostgreSQL (≥9.6). Other
forks and older versions might work, but are neither tested nor supported
* `php-pdo-mysql` and/or `php-pdo-pgsql`
* `php-curl`
* `php-iconv`
* `php-pcntl` (might already be built into your PHP binary)
* `php-posix` or `php-process` depending on your platform
* `php-sockets` (might already be built into your PHP binary)
## Installing from Release Tarball
Download the [latest version](https://github.com/Icinga/icingaweb2-module-director/releases)
and extract it to a folder named `director` in one of your Icinga Web module path directories.
You might want to use a script as follows for this task:
```shell
MODULE_VERSION="1.11.4"
ICINGAWEB_MODULEPATH="/usr/share/icingaweb2/modules"
REPO_URL="https://github.com/icinga/icingaweb2-module-director"
TARGET_DIR="${ICINGAWEB_MODULEPATH}/director"
URL="${REPO_URL}/archive/v${MODULE_VERSION}.tar.gz"
install -d -m 0755 "${TARGET_DIR}"
wget -q -O - "$URL" | tar xfz - -C "${TARGET_DIR}" --strip-components 1
icingacli module enable director
```
## Installing from Git Repository
Another convenient method is to install directly from our Git repository.
Simply clone the repository in one of your Icinga web module path directories.
You might want to use a script as follows for this task:
```shell
MODULE_VERSION="1.11.4"
ICINGAWEB_MODULEPATH="/usr/share/icingaweb2/modules"
REPO_URL="https://github.com/icinga/icingaweb2-module-director"
TARGET_DIR="${ICINGAWEB_MODULEPATH}/director"
git clone "${REPO_URL}" "${TARGET_DIR}" --branch v${MODULE_VERSION}
icingacli module enable director
```
## Setting up the Director Daemon
For manual installations, the daemon user, its directory, and the systemd service need to be set up:
```shell
useradd -r -g icingaweb2 -d /var/lib/icingadirector -s /sbin/nologin icingadirector
install -d -o icingadirector -g icingaweb2 -m 0750 /var/lib/icingadirector
install -pm 0644 contrib/systemd/icinga-director.service /etc/systemd/system
systemctl daemon-reload
systemctl enable --now icinga-director
```
<!-- {% include "02-Installation.md" %} -->

View File

@ -5,7 +5,7 @@ Icinga Director is very upgrade-friendly. We never had any complaint referring
data loss on upgrade. But to be on the safe side, please always [backup](#backup-first)
your database before running an upgrade.
Then drop the new version to your Icinga Web 2 module folder, and you're all done.
Then drop the new version to your Icinga Web 2 module folder and you're all done.
Eventually refresh the page in your browser<sup>[[1]](#footnote1)</sup>, and you
are ready to go.
@ -15,9 +15,6 @@ you will be told so in your frontend.
Please read more about:
* [Database Backup](#backup-first)
* [Upgrading to 1.11.x](#upgrade-to-1.11.x)
* [Upgrading to 1.10.x](#upgrade-to-1.10.x)
* [Upgrading to 1.9.x](#upgrade-to-1.9.x)
* [Upgrading to 1.8.x](#upgrade-to-1.8.x)
* [Upgrading to 1.7.x](#upgrade-to-1.7.x)
* [Upgrading to 1.6.x](#upgrade-to-1.6.x)
@ -42,36 +39,11 @@ use the tools provided by your database backend, like `mysqldump` or `pg_dump`.
Restoring from a backup is trivial, and Director will always be able to apply
pending database migrations to an imported old database snapshot.
<a name="upgrade-to-1.11.x"></a>Upgrading to 1.11.x
--------------------------------------------------
Before upgrading, please upgrade the [incubator module](https://github.com/Icinga/icingaweb2-module-incubator)
to (at least) v0.22.0. As always, you'll be prompted to apply pending Database
Migrations.
<a name="upgrade-to-1.10.x"></a>Upgrading to 1.10.x
--------------------------------------------------
Please check module dependencies, we raised some of them. In case you're missing
one of them, the Web UI will tell you after the upgrade. You'll then be prompted
to apply pending Database Migrations.
PHP 7.3 is now claimed to be required, but we still support 5.6+ on Director
v1.10.x. Same goes for database dependencies: you should upgrade them to recent
versions, but v1.10 still works on the ones supported with v1.9.x.
<a name="upgrade-to-1.9.x"></a>Upgrading to 1.9.x
-------------------------------------------------
Please check module dependencies, we raised some of them. In case you're missing
one of them, the Web UI will tell you after the upgrade. You'll then be prompted
to apply pending Database Migrations.
<a name="upgrade-to-1.8.x"></a>Upgrading to 1.8.x
-------------------------------------------------
Before upgrading, please upgrade the [incubator module](https://github.com/Icinga/icingaweb2-module-incubator)
to (at least) v0.6.0. As always, you'll be prompted to apply pending Database
to (at least) v0.21.0. As always, you'll be prompted to apply pending Database
Migrations.
<a name="upgrade-to-1.7.x"></a>Upgrading to 1.7.x

View File

@ -94,7 +94,7 @@ slightly to fit ITL Commands in case you're using those (snmpv3_*_type VS _alg).
Your Cisco Health checks assigned to all:
* routers or switches
* manufactured by Cisco
* manifactured by Cisco
* with SNMP credentials, regardless of which version
...might then look as follows:

View File

@ -75,12 +75,6 @@ icingacli director host create localhost \
--json '{ "address": "127.0.0.1", "vars": { "test": [ "one", "two" ] } }'
```
Passing JSON via STDIN is also possible:
```shell
icingacli director host create localhost --json < my-host.json
```
### Delete a specific object
@ -132,18 +126,17 @@ Use this command to modify specific properties of an existing Icinga object.
#### Options
| Option | Description |
|----------------------------|-------------------------------------------------------|
| `--<key> <value>` | Provide all properties as single command line options |
| `--append-<key> <value>` | Appends to array values, like `imports`, |
| | `groups` or `vars.system_owners` |
| `--remove-<key> [<value>]` | Remove a specific property, eventually only |
| | when matching `value`. In case the property is an |
| | array it will remove just `value` when given |
| `--json` | Otherwise provide all options as a JSON string |
| `--replace` | Replace all object properties with the given ones |
| `--auto-create` | Create the object in case it does not exist |
| `--allow-overrides` | Set variable overrides for virtual Services |
| Option | Description |
|-------------------|-------------------------------------------------------|
| `--<key> <value>` | Provide all properties as single command line options |
| `--append-<key> <value>` | Appends to array values, like `imports`, |
| | `groups` or `vars.system_owners` |
| `--remove-<key> [<value>]` | Remove a specific property, eventually only |
| | when matching `value`. In case the property is an |
| | array it will remove just `value` when given |
| `--json` | Otherwise provide all options as a JSON string |
| `--replace` | Replace all object properties with the given ones |
| `--auto-create` | Create the object in case it does not exist |
#### Examples
@ -191,16 +184,16 @@ in JSON format.
#### Options
| Option | Description |
|-------------------|------------------------------------------------------|
| `--resolved` | Resolve all inherited properties and show a flat |
| | object |
| `--json` | Use JSON format |
| `--no-pretty` | JSON is pretty-printed per default (for PHP >= 5.4) |
| | Use this flag to enforce unformatted JSON |
| `--no-defaults` | Per default JSON output skips null or default values |
| | With this flag you will get all properties |
| `--with-services` | For hosts only, also shows attached services |
| Option | Description |
|-----------------|---------------------------------------------------------|
| `--resolved` | Resolve all inherited properties and show a flat object |
| | object |
| `--json` | Use JSON format |
| `--no-pretty` | JSON is pretty-printed per default (for PHP >= 5.4) |
| | Use this flag to enforce unformatted JSON |
| `--no-defaults` | Per default JSON output skips null or default values |
| | With this flag you will get all properties |
### Clone an existing object
@ -288,7 +281,7 @@ on this object would have been removed.
The Icinga Director distincts between the following object types:
| Type | Description |
| Type | Description
|-------------------|-------------------------------------------------------------|
| `object` | The default object type. A host, a command and similar |
| `template` | An Icinga template |
@ -463,22 +456,7 @@ Config with checksum b330febd0820493fb12921ad8f5ea42102a5c871 already exists
### Config deployment
#### Usage
`icingacli director config deploy [options]`
#### Options
| Option | Description |
|----------------------------|------------------------------------------------------------------|
| `--checksum <checksum>` | Optionally deploy a specific configuration |
| `--force` | Force a deployment, even when the configuration hasn't changed |
| `--wait <seconds>` | Optionally wait until Icinga completed it's restart |
| `--grace-period <seconds>` | Do not deploy if a deployment took place less than <seconds> ago |
#### Examples
You do not need to explicitly render your config before deploying it to your
You do not need to explicitely render your config before deploying it to your
Icinga 2 master node. Just trigger a deployment, it will re-render the current
config:
@ -512,13 +490,6 @@ version the `deploy` command allows you to provide a specific checksum:
icingacli director config deploy --checksum b330febd0820493fb12921ad8f5ea42102a5c871
```
When using `icingacli` deployments in an automated way, and want to avoid fast
consecutive deployments, you can provide a grace period:
```shell
icingacli director config deploy --grace-period 300
```
### Deployments status
In case you want to fetch the information about the deployments status,
you can call the following CLI command:

View File

@ -39,15 +39,12 @@ URL scheme and supported methods
We support GET, POST, PUT and DELETE.
| Method | Meaning |
|--------|---------------------------------------------------------------------|
| GET | Read / fetch data. Not allowed to run operations with the potential |
| | to cause any harm |
| POST | Trigger actions, create or modify objects. Can also be used to |
| | partially modify objects |
| PUT | Creates or replaces objects, cannot be used to modify single object |
| | properties |
| DELETE | Remove a specific object |
| Method | Meaning
| ------ | ------------------------------------------------------------
| GET | Read / fetch data. Not allowed to run operations with the potential to cause any harm
| POST | Trigger actions, create or modify objects. Can also be used to partially modify objects
| PUT | Creates or replaces objects, cannot be used to modify single object properties
| DELETE | Remove a specific object
TODO: more examples showing the difference between POST and PUT
@ -116,37 +113,23 @@ Icinga Objects
### Special parameters
| Parameter | Description |
|----------------|-------------------------------------------------------------|
| resolved | Resolve all inherited properties and show a flat object |
| withNull | Retrieve default (null) properties also |
| withServices | Show services attached to a host. `resolved` and `withNull` |
| | are applied for services too |
| allowOverrides | Set variable overrides for virtual Services |
| showStacktrace | Returns the related stack trace, in case an error occurs |
#### Resolve object properties
In case you add the `resolved` parameter to your URL, all inherited object
In case you add the `resolve` parameter to your URL, all inherited object
properties will be resolved. Such a URL could look as follows:
director/host?name=hostname.example.com&resolved
#### Retrieve default (null) properties also
#### Retrieve all properties
TODO: adjust the code to fix this, current implementation has `withNull`
Per default properties with `null` value are skipped when shipping a result.
You can influence this behavior with the `properties` parameter. Just append
`&withNull` to your URL:
You can influence this behavior with the properties parameter. Just append
`properties=ALL` to your URL:
director/host?name=hostname.example.com&withNull
#### Fetch host with it's services
This is what the `withServices` parameter exists:
director/host?name=hostname.example.com&withServices
director/host?name=hostname.example.com&properties=all
#### Retrieve only specific properties
@ -158,23 +141,6 @@ when they have no (`null`) value:
director/host?name=hostname.example.com&properties=object_name,address,vars
#### Override vars for inherited/applied Services
Enabling `allowOverrides` allows you to let Director figure out, whether your
modified Custom Variables need to be applied to a specific individual Service,
or whether setting Overrides at Host level is the way to go.
POST director/service?name=Uptime&host=hostname.example.com&allowOverrides
```json
{ "vars.uptime_warning": 300 }
```
In case `Uptime` is an Apply Rule, calling this without `allowOverrides` will
trigger a 404 response. Please note that when modifying the Host object, the
body for response 200 will show the Host object, as that's the one that has
been modified.
### Example
GET director/host?name=pe2015.example.com
@ -257,7 +223,7 @@ You can of course also use the API to trigger specific actions. Deploying the co
More
----
Currently, we do not handle Last-Modified und ETag headers. This would involve some work, but could be a cool feature. Let us know your ideas!
Currently we do not handle Last-Modified und ETag headers. This would involve some work, but could be a cool feature. Let us know your ideas!
Sample scenario
@ -560,8 +526,8 @@ Another possibility is to pass a list of checksums to fetch the status of
specific deployments and (activity log) activities.
Following, you can see an example of how to do it:
GET director/config/deployment-status?configs=617b9cbad9e141cfc3f4cb636ec684bd60073be2,
617b9cbad9e141cfc3f4cb636ec684bd60073be1&activitiess=617b9cbad9e141cfc3f4cb636ec684bd60073be1,
GET director/config/deployment-status?config_checksums=617b9cbad9e141cfc3f4cb636ec684bd60073be2,
617b9cbad9e141cfc3f4cb636ec684bd60073be1&activity_log_checksums=617b9cbad9e141cfc3f4cb636ec684bd60073be1,
028b3a19ca7457f5fc9dbb5e4ea527eaf61616a2
```json

View File

@ -4,495 +4,12 @@
Please make sure to always read our [Upgrading](05-Upgrading.md) documentation
before switching to a new version.
v1.11.4
-------
### Security
- Rest API endpoints accessible to restricted users ([GHSA-3233-ggc5-m3qg](https://github.com/Icinga/icingaweb2-module-director/security/advisories/GHSA-3233-ggc5-m3qg))
1.8.2
-----
### UI
- Fix editing of custom variables for multi-selected objects [#2950](https://github.com/Icinga/icingaweb2-module-director/issues/2950)
- Fix: Check for the existence of service templates to add services [#1249](https://github.com/Icinga/icingaweb2-module-director/issues/1249)
### Import and Sync
- Fix erratic job behavior during summer and winter time change (no issue)
### Integration
- Fix custom variable renderer for service apply for rules (no issue)
- Fix custom variable renderer for array type data lists [#2960](https://github.com/Icinga/icingaweb2-module-director/issues/2960)
### Database Schema
- Fix MySQL 8.4 nonstandard foreign keys deprecation [#2885](https://github.com/Icinga/icingaweb2-module-director/issues/2885)
### Fixed Issues
You can find issues related to this release on our [roadmap](https://github.com/Icinga/icingaweb2-module-director/milestone/39?closed=1)
v1.11.3
-------
### UI
* FIX: Property sort tables does not cause CSRF token validation anymore (#2937)
* FIX: No error when clicking `modify` action link for services belonging to service set in Icinga DB (#2938)
* FIX: No crashing of Host template form when invalid check command is entered (#2941)
### Internals
* FIX: Filter can be now removed in import source modifiers (#2939)
v1.11.2
-------
### UI
* FIX: No more errors when changing import source modifier priorities (#2270)
* FIX: Choosing `HTTP proxy` in import source type `REST API` no longer causes deprecation notice (#2889)
* FIX: Deleting data lists when using PostgreSQL as backend no longer yields errors (#2913)
* FIX: Previewing sync rules with boolean properties now functions without errors (#2905)
* FIX: Basket snapshots correctly display content if present (#2901)
* FIX: Time periods now include `Add to basket` functionality (#2542)
### API
* FIX: API updates for notifications return 304 instead of 200 when unchanged (#2882)
### Internals
* FIX: The `apply_changes` setting from sync jobs restored from a basket is applied properly (#2904)
* FIX: The `Get host by name (DNS lookup)` modifier handles failed lookups without errors (#2877)
### Director Branches
* FIX: Cloning a host retains its services and service sets (#2897)
* FIX: Service sets are now clonable (#2890)
### Integrations
* Show Director labels in monitoring module and Icinga DB custom vars section (#2239)
### Fixed issues
* You can find issues and feature requests related to this release on our
[roadmap](https://github.com/Icinga/icingaweb2-module-director/milestone/36?closed=1)
v1.11.1
-------
### UI
* FIX: Data fields are now suggested for service templates without a check command (#2815, #2826)
* FIX: Unsetting a parent host or service of a dependency is now correctly stored (no issue)
* FIX: The activity log now avoids a bug in PHP introduced with version 8.1.25 (#2828)
### Internals
* FIX: UserGroup creation failed since v1.10.0 (#2784)
* FIX: Hostgroup names consisting only of digits are now correctly handled (#2821)
* FIX: Improved compatibility with PHP 8.1 and 8.2 (#2819, #2827)
* FIX: The parent host or service of a dependency can now be reliably referenced by custom variable (#2289)
* FIX: Services in service sets are now fully restored once a removed set is restored (#1065)
### Integrations
* FIX: Icinga DB integration now works even if the monitoring module is not available (#2635)
* FIX: Conformity with the content security policy introduced with Icinga Web v2.12 (#2845)
### Fixed issues
* You can find issues and feature requests related to this release on our
[roadmap](https://github.com/Icinga/icingaweb2-module-director/milestone/35?closed=1)
v1.11.0
-------
Icinga Director v1.11 ships a nice new feature, which has been requested by
many users: it is now possible to let Notification rules pick User and/or User
Groups from Host and Service custom variables.
![Notification User Vars](screenshot/director/82_changelog/v1.11.0/82-1.11.0-05-notification_user-var.png)
Behind the scenes, some Icinga DSL assures that Icinga behaves as expected:
![Notification User Vars rendered](screenshot/director/82_changelog/v1.11.0/82-1.11.0-06-notification_user-var_rendered.png)
The IcingaDB module is now supported. This release also ships a fallback template
for Icinga for Windows 1.11 (and Icinga 2.14). To improve security for those
relying on default settings, all Host templates are per default rendered to all
non-global zones in a redundant way, instead of being rendered to the main global
zone.
For those who want to store Secrets in their Host template definitions, this now
hinders them from reaching your Agent Endpoints. In addition to this, with this
release you now have more control over target Zones for services belonging to
Service Sets. Also, some issues related to permissions and restrictions have been
addressed.
Various little Import and Sync issues have been addressed, automated Service
Template import has been fixed. In addition to some minor Property modifier
improvements, they can now be applied in a conditional way. Such filter rules
also support CIDR notation. Most prominent use-case: as vSphereDB now ships ALL
guest IP addresses, you can pick specific addresses for specific use-cases /
host properties based on their network range:
![CIDR-based filters](screenshot/director/82_changelog/v1.11.0/82-1.11.0-01-cidr_based_filters.png)
This release now officially supports PHP 8.2, addresses issues when integrating
with the IcingaDB web module, and some dark-mode glitches. Search functionality
has been improved, Preview Diff shows more details, editing multiple single
Services with the very same name has been fixed, and the interactive Startup log
renderer now wraps log lines in a nice way:
![Wrap Startup Log lines](screenshot/director/82_changelog/v1.11.0/82-1.11.0-02-wrap_startup_log_lines.png)
Performance has greatly been improved for those, who trigger a lot of single API
requests, instead of relying on Import/Sync mechanism. Our template usage overview
now also summarizes and links objects used in Services belonging to Service Sets:
![Template Usage overview](screenshot/director/82_changelog/v1.11.0/82-1.11.0-03-template_usage.png)
Commands can now be cloned with their field definitions, and custom variables
from set_if flags make now part of the "suggested fields" list.
In addition to major refactoring and technical improvements related to our
configuration baskets, it's now also possible to upload additional snapshots for
existing baskets:
![Basket Snapshot upload](screenshot/director/82_changelog/v1.11.0/82-1.11.0-04-upload_basket_snapshot.png)
Speaking of baskets, they have been reworked in a way giving especially Icinga
Partners and Plugin authors more control over related objects. As UUIDs are now
supported in Baskets too, it is for example possible to rename an object with a
new Basket Snapshot.
### Breaking Changes
* Module and system dependencies have been raised, [Upgrading](05-Upgrading.md)
and [Installation](02-Installation.md) documentations contain related details
### UI
* FEATURE: allow to clone commands with fields (#2264)
* FEATURE: Data Fields are now sorted in a case-insensitive way (#2358)
* FEATURE: Data Field search is now case-insensitive (#2359)
* FEATURE: Deployment Log now breaks lines (#2677)
* FEATURE: Sort Template trees by name (#2691)
* FEATURE: Branch and Sync diff/preview now shows related host for services (#2736)
* FEATURE: Show more details for assign filter parsing errors (#2667)
* FEATURE: Fields from set_if are now being proposed (#514)
* FEATURE: Template usage table now shows Set members (#2378)
* FIX: do not fail for (some) Service Dependencies (#2669, #1142)
* FIX: Service Sets can now be searched by Service name in branches too (#2738)
* FIX: Template usage table had no header (#2780)
* FIX: Strikethrough for deactivated services in applied Service Set (#2746, #2766)
* FIX: editing multi-selected services with the same name has been fixed (#2798, 2801, #2599)
* FIX: Service Sets table with PostgreSQL (#2799)
* FIX: Dark mode for some clone forms (#2670)
### Icinga Configuration
* FEATURE: render fallback template for IfW 1.11 for Icinga &lt; 2.14 (#2776)
* FEATURE: render host templates to all non-global zones per default (#2410)
* FEATURE: notifications can pick user(groups) from host/service vars (#462)
* FIX: render Set Services to individual zones where required (#1589, #2356)
* FIX: special characters like &amp; and | caused trouble in filters (#2667)
### Import and Sync
* FEATURE: regular expression based modifier allows explicit NULL on no match (#2705)
* FEATURE: property modifiers can now be applied based on filters (#2756)
* FEATURE: CIDR notation (network ranges) is supported in such filters (#2757)
* FEATURE: trigger group membership resolution on group sync conditionally (#2812)
* FIX: synchronizing Service (and -Set) Templates has been fixed (#2745, #2217)
* FIX: null properties with Sync policy "ignore" are now being ignored (#2657)
* FIX: Import Source shows available columns for Core API Import (#2763)
* FIX: JSON-decode now explicitly fails for non-string inputs (#2810)
### Integrations
* FIX: don't throw an error, when editing a Service via IcingaDB link (#2533, #2563)
# Configuration Baskets
* FEATURE: it's now possible to upload snapshots for existing baskets (#1952)
* FIX: basket now shows where to expect changes for lists from snapshots (#2791)
* FIX: Icinga DSL parts for Commands are now restored from Baskets (#2811)
### REST API
* FIX: Commands give 204 w/o ghost changes for same properties (#2660)
### Permissions and Restrictions
* FIX: monitoring-related permission checks have been refactored (#2712)
* FIX: Hostgroup-Filters have not been applied to Overview tables (#2775)
* FIX: error editing Hosts with hostgroup restriction in place (#2164, #2809)
### Configuration Branches
* FEATURE: with this release, directorbranches v1.3 supports a "default branch" (#2688)
* FEATURE: users with default branches get warnings in the main branch (#2689)
* FIX: create a branched set, add services, modify them (#2710)
### Health Check
* FIX: complaint about overdue jobs was not correct (#2680, #2681)
### Internals
* FEATURE: support PHP 8.2 (#2777, #2792)
* FEATURE: Unit Tests are now being triggered as GitHub workflows (no issue)
* FIX: group membership is no longer resolved when not needed (#2048)
* FIX: require Auth object for all object tables (#2808)
* FIX: group membership is no longer resolved when not needed (#2048)
### Fixed issues
* You can find issues and feature requests related to this release on our
[roadmap](https://github.com/Icinga/icingaweb2-module-director/milestone/33?closed=1)
v1.10.2
-------
This is a minor bugfix release, addressing some Sync-related issues: purge for
objects with uppercase characters now works as expected, automated Sync jobs run
again, and manually triggered Sync has been fixed on PostgreSQL.
Some UI glitches have been addressed, and a few problems appearing only in
certain conditions - related to Configuration Baskets, our Self Service REST API
and the Activity Log.
### Fixed issues
* You can find issues and feature requests related to this release on our
[roadmap](https://github.com/Icinga/icingaweb2-module-director/milestone/31?closed=1)
### UI
* FEATURE: improve Service Set table layout (#2648)
* FIX: modifying single time-period ranges had no effect (#2525)
* FIX: activity log pagination is now on a single line (#2649)
### Import and Sync
* FIX: triggering Sync manually produced an error on PostgreSQL (#2636)
* FIX: purge stopped working for objects with uppercase characters (#2627)
* FIX: Notification Apply rule is now possible (wasn't since v1.8) (#2142, #2634)
* FIX: nested property access with intermediate NULL values now gives NULL (#2474, #2584)
* FIX: automated Sync jobs stopped working (#2633)
### Configuration Baskets
* FEATURE: more details shown in error messages related to invalid characters (#2646)
* FIX: snapshots for Baskets containing Baskets failed since v1.10 (#2644)
### REST API
* FIX: Self Service API returned invalid JSON on PHP 8.1 (#2614)
### Internals
* FIX: issue with empty activity log, deprecate outdated method (#2630)
v1.10.1
-------
This is a minor bugfix release, addressing issues with modifying services via
the monitoring module, Sync problems and a copy and paste error in the DB schema,
which caused problems for fresh installations since v1.10.
Please note that a long-standing issue for our Sync Rules has been fixed: with
"merge" policy, NULL properties have been ignored for quite some time. This has
now been fixed. If in doubt, please **preview** your Sync Rules to make sure,
that they behave as expected.
This release brings a small schema migration, cleaning up invalid Sync history
entries. If in doubt, please create a [database backup](05-Upgrading.md#backup-first) first.
### Fixed issues
* You can find issues and feature requests related to this release on our
[roadmap](https://github.com/Icinga/icingaweb2-module-director/milestone/30?closed=1)
### Import and Sync
* FIX: sync lower-cased all object names since v1.10 (#2608)
* FIX: sync for Datalist entries has been fixed (#2618)
* FIX: Sync now applied NULL values with merge policy (#2623)
* FIX: Sync created Sync History entries for every preview (#2632)
* FIX: "Purge" stopped working for Sync (#2627)
### UI
* FIX: "Modify" Services via the monitoring module (#2615, #2619)
### Configuration Baskets
* FIX: restore Import/Sync/Job when exported with v1.10 (#2620)
* FIX: restoring Job with ImportSource or SyncRule (#2528)
### Database Schema
* FIX: new DB schema failed due to duplicate line in SQL statement (#2609)
v1.10.0
-------
An advanced **Sync Preview** is one of the features I'd love to highlight in
v1.10.0. We have been able to leverage our Configuration Branch support as
an underlying technology for this:
![Sync Preview - List](https://user-images.githubusercontent.com/553008/191472888-33849b3e-9d96-4113-b960-92708769e90d.png)
In case you have lots of changes, you can browse all of them - formerly this
hasn't been possible. Also, this now allows you to inspect every single upcoming
change before applying the Sync:
![Sync Preview - Details](https://user-images.githubusercontent.com/553008/191472900-1968691e-a758-4c99-99ce-059bc3689356.png)
This has been possible based on the code we implemented to support the
[Director Branches](https://icinga.com/docs/icinga-director-branches/latest/)
module. In case you never heard about it,
[here](https://icinga.com/blog/2022/07/21/releasing-icinga-director-branches/)
you can find the related announcement.
This release also contains a lot of related fixes and new Features. It is now
possible to deal with **Service Sets** in Configuration Branches, the **commit
remark** can be proposed with a merge request, and the Activity Log shows not
only who has merged the change, but also the **original author**.
Powerful new features have been implemented for those who love to orchestrate
the Director from the outside. Please check our
[CLI](https://github.com/Icinga/icingaweb2-module-director/blob/v1.10.0/doc/60-CLI.md)
and [REST API](https://github.com/Icinga/icingaweb2-module-director/blob/v1.10.0/doc/70-REST-API.md)
documentation for related details, especially look for --with-services (withServices)
and --allow-overrides (allowOverrides).
CLI now supports **JSON on STDIN**, REST API can request detailed stack traces
in case an error occurs.
### Breaking Changes
* Module and system dependencies have been raised, [Upgrading](05-Upgrading.md)
and [Installation](02-Installation.md) documentations contain related details
### Fixed issues
* You can find issues and feature requests related to this release on our
[roadmap](https://github.com/Icinga/icingaweb2-module-director/milestone/27?closed=1)
### User Interface
* FIX: links from Service Previews (Icinga DSL) to templates (#2554)
* FIX: daemon health visualization on systems w/o /proc filesystem (#2544)
### Import and Sync
* FIX: Sync now compares keys in a case-insensitive way (#2598, #2419, #1140)
* FIX: Sync now preserves Self Service API keys in override mode (#2590)
* FEATURE: clone a row for nested Dictionary/Hash entries (#2555)
* FEATURE: Sync in "override" mode now preserves Self Service API keys (#2590)
* FEATURE: split a row in multiple ones, based on a Dictionary (#2555)
* FEATURE: it's now possible to sync to a configuration branch (#2552)
* FEATURE: Sync preview now allows to navigate single changes (#2607)
### Configuration Baskets
* BREAKING: configuration baskets no longer contain originalId (#2549)
* FEATURE: exporting/snapshot-logic has been centralized (#2549)
### Configuration Branches
* FIX: PostgreSQL now allows for the same object in multiple branches (#2605)
* FEATURE: merge comments can now be proposed (#2604)
* FEATURE: activity log now shows author and committer (#2606)
### Integrations
* FIX: Monitoring Hooks are no longer provided with disable Director UI (#2597)
* FIX: cleanup for IcingaDbCube (#2484)
### Kickstart
* FIX: breaking change in ipl/html, affected setups with ro INI files (#2595)
* FEATURE: better explanation for missing DSL bodies fetched from core (#2557)
### REST API
* FIX: addressing service templates by name has been fixed (#2487)
* FIX: allow for object_name in body only (#2576)
* FIX: notice on PHP 8.1 (#2575)
* FEATURE: Stack traces can now be requested (#2570)
* FEATURE: Hosts can now be exported with their services (#2568)
* FEATURE: "magic" variable overrides are now supported (#2569)
### CLI
* FIX: config deploy doesn't try to wait in case of no deployment (#2522)
* FIX: renderer now shows full service sets (#2550)
* FEATURE: improved wording for deployment error messages (#2523)
* FEATURE: JSON can now be shipped via STDIN (#1570)
* FEATURE: improved readability for some error messages (#2567)
* FEATURE: allows showing hosts with their services (#2565)
* FEATURE: allow showing resolved Host services (#2571)
* FEATURE: "magic" variable overrides are now supported (#2560)
* FEATURE: error messages are now friendlier (#2567)
* FEATURE: STDIN support for --json is now available (#1570)
### Activity Log
* FIX: deleted objects might have been missing related properties (#2559)
### Deployment Log
* FEATURE: visualization performance has been improved (#2551)
### Internals
* FEATURE: there is now a centralized Exporter implementation (#2549)
1.9.1
-----
### Fixed issues
* You can find issues and feature requests related to this release on our
[roadmap](https://github.com/Icinga/icingaweb2-module-director/milestone/28?closed=1)
### User Interface
* FIX: DataList-backed fields failed to validate (#2475)
* FIX: No Host list limit when adding a single service globally (#2481)
* FIX: Cleared activity log caused exception (#2505, #2506)
* FEATURE: Icinga Web 2.10 dark mode support (#2433)
### Configuration Baskets
* FIX: failed to export Baskets with Service Sets (#2488)
* FIX: Sync Rule restore from snapshot on name change (#2467)
* FIX: Do not export UUIDs with Service Sets (#2488)
### CLI
* FEATURE: Allow to define deployment grace period on CLI (#2499)
### Integrations
* FIX: Cleanup IcingaDbCubeLinks (#2484)
### DB Schema
* FIX: applying DB Schema migrations failed on PostgreSQL (#2482)
1.9.0
-----
### Breaking Changes
* Module dependencies have been raised, [Upgrading](05-Upgrading.md) and
[Installation](02-Installation.md) documentations contain related details
### Fixed issues
* You can find issues and feature requests related to this release on our
[roadmap](https://github.com/Icinga/icingaweb2-module-director/milestone/25?closed=1)
### Import and Sync
* FIX: string property modifiers now preserve NULL values (#2371)
* FIX: "to int" property modifiers now fails for non-string values (#2372)
* FEATURE: introduce 'disable' as your purge action on Sync (#2285)
* FEATURE: there is now a simple "group by" Property Modifier (#2317)
### Configuration Baskets
* FIX: Notification Apply Rules have not been exported (#2335)
* FIX: Restore now supports the set_if_format switch (#2291)
* FEATURE: it's now possible to purge objects of specific types (#2201)
* FEATURE: exporting Users, User-Templates and -Groups is now possible (#2328)
* FEATURE: Data Field Categories are now supported (#2256)
### Permissions and Restrictions
* FEATURE: allow using monitoring module permissions (#2304)
* FEATURE: it's now possible to grant (global) access to scheduled downtimes (#2086)
### Configuration / Templating
* FEATURE: offering choices based on a specific imports is now possible (#1178)
### User Interface
* FIX: allow switching DB config while connection is failing (#2300)
* FIX: Links to duplicate services in Sets didn't check for deactivation (#2323)
* FIX: SQL error for Data Fields table on PostgreSQL (#2310)
* FIX: SQL error when searching for Data Field Categories (#2367)
* FIX: Icon used for Notifications has been changed (#2455)
* FEATURE: show "deprecated" flag on object attribute inspection (#2312)
* FEATURE: Service Template for single Host services provides auto-completion (#1974)
### CLI
* FEATURE: config deployment now allows to --wait for an Icinga restart (#2314)
### Activity log
* FEATURE: Activity log now allows for remarks (addon module required, #2471)
### Documentation
* FIX: configure the daemon with main setup instructions (#2296, #2320)
### Internals
* FEATURE: PHP 8.1 is now supported, works once available in Icinga Web (#2435)
* FEATURE: Config Branches have been implemented, leveraged via Hook/Addon (#2376)
* FEATURE: UUIDs have been implemented for most Icinga objects, more to come
* FEATURE: new Deployment Hook, triggers onCollect(ing) Icinga startup info (#2315)
1.8.1
-----

View File

@ -12,12 +12,16 @@ there is probably already someone running them from time to time. So, just
lean back with full trust in our development toolchain and spend your time
elsewhere ;-) Cheers!
### Tests on GitHub
### Tests on Travis-CI
When pushing to [GitHub](https://github.com/Icinga/icingaweb2-module-director/)
or sending pull requests, Unit-Tests are automatically triggered.
or sending pull requests, Unit-Tests are automatically triggered on
[Travis-CI](https://travis-ci.org/Icinga/icingaweb2-module-director):
![Build Status](https://github.com/Icinga/icingaweb2-module-director/workflows/PHP%20Tests/badge.svg?branch=master)
[![Build Status](https://travis-ci.org/Icinga/icingaweb2-module-director.svg?branch=master)](https://travis-ci.org/Icinga/icingaweb2-module-director)
We run our tests against MySQL and PostgreSQL, with PHP versions ranging from
5.3 to 7.1, including nightly builds.
### Tests for supported Platforms

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

View File

@ -56,7 +56,7 @@ class Acl
public function listRoleNames()
{
return array_map(
[$this, 'getNameForRole'],
array($this, 'getNameForRole'),
$this->getUser()->getRoles()
);
}

View File

@ -35,20 +35,19 @@ class MemoryLimit
$val = trim($string);
if (preg_match('/^(\d+)([KMG])$/', $val, $m)) {
$val = (int) $m[1];
$val = $m[1];
switch ($m[2]) {
case 'G':
$val *= 1024;
// no break
// Intentional fall-through
case 'M':
$val *= 1024;
// no break
// Intentional fall-through
case 'K':
$val *= 1024;
}
}
return (int) $val;
return intval($val);
}
}

View File

@ -1,36 +0,0 @@
<?php
namespace Icinga\Module\Director\Auth;
use Icinga\Authentication\Auth;
use Icinga\Data\Filter\Filter;
class MonitoringRestriction
{
public static function getObjectsFilter(Auth $auth): Filter
{
$restriction = Filter::matchAny();
$restriction->setAllowedFilterColumns([
'host_name',
'hostgroup_name',
'instance_name',
'service_description',
'servicegroup_name',
function ($c) {
return preg_match('/^_(?:host|service)_/i', $c);
}
]);
foreach ($auth->getRestrictions(Restriction::MONITORING_RW_OBJECT_FILTER) as $filter) {
if ($filter === '*') {
return Filter::matchAll();
}
$restriction->addFilter(Filter::fromQueryString($filter));
}
if ($restriction->isEmpty()) {
return Filter::matchAll();
}
return $restriction;
}
}

View File

@ -1,31 +0,0 @@
<?php
namespace Icinga\Module\Director\Auth;
class Permission
{
public const ALL_PERMISSIONS = 'director/*';
public const ADMIN = 'director/admin'; // internal, assign ALL_PERMISSONS
public const API = 'director/api';
public const AUDIT = 'director/audit';
public const DEPLOY = 'director/deploy';
public const DEPLOYMENTS = 'director/deployments'; // internal, assign ALL_PERMISSONS
public const GROUPS_FOR_RESTRICTED_HOSTS = 'director/groups-for-restricted-hosts';
public const HOSTS = 'director/hosts';
public const HOST_GROUPS = 'director/hostgroups'; // internal, assign ALL_PERMISSIONS
public const INSPECT = 'director/inspect';
public const MONITORING_SERVICES_RO = 'director/monitoring/services-ro';
public const MONITORING_SERVICES = 'director/monitoring/services';
public const MONITORING_HOSTS = 'director/monitoring/hosts';
public const ICINGADB_SERVICES_RO = 'director/icingadb/services-ro';
public const ICINGADB_SERVICES = 'director/icingadb/services';
public const ICINGADB_HOSTS = 'director/icingadb/hosts';
public const NOTIFICATIONS = 'director/notifications';
public const SCHEDULED_DOWNTIMES = 'director/scheduled-downtimes';
public const SERVICES = 'director/services';
public const SERVICE_SETS = 'director/servicesets';
public const SERVICE_SET_APPLY = 'director/service_set/apply';
public const SHOW_CONFIG = 'director/showconfig';
public const SHOW_SQL = 'director/showsql';
public const USERS = 'director/users';
}

View File

@ -1,17 +0,0 @@
<?php
namespace Icinga\Module\Director\Auth;
class Restriction
{
public const MONITORING_RW_OBJECT_FILTER = 'director/monitoring/rw-object-filter';
public const ICINGADB_RW_OBJECT_FILTER = 'director/icingadb/rw-object-filter';
public const FILTER_HOSTGROUPS = 'director/filter/hostgroups';
// Hint: by-name-Filters are being fetched with variable names, like "director/$type/apply/filter-by-name"
public const NOTIFICATION_APPLY_FILTER_BY_NAME = 'director/notification/apply/filter-by-name';
public const SCHEDULED_DOWNTIME_APPLY_FILTER_BY_NAME = 'director/scheduled-downtime/apply/filter-by-name';
public const SERVICE_APPLY_FILTER_BY_NAME = 'director/service/apply/filter-by-name';
public const SERVICE_SET_FILTER_BY_NAME = 'director/service_set/filter-by-name';
public const DB_RESOURCE = 'director/db_resource';
}

View File

@ -2,12 +2,11 @@
namespace Icinga\Module\Director\Cli;
use gipfl\Json\JsonDecodeException;
use gipfl\Json\JsonString;
use Icinga\Cli\Command as CliCommand;
use Icinga\Module\Director\Application\MemoryLimit;
use Icinga\Module\Director\Core\CoreApi;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Exception\JsonException;
use Icinga\Module\Director\Objects\IcingaEndpoint;
use Icinga\Application\Config;
use RuntimeException;
@ -22,7 +21,7 @@ class Command extends CliCommand
protected function renderJson($object, $pretty = true)
{
return JsonString::encode($object, $pretty ? JSON_PRETTY_PRINT : null) . "\n";
return json_encode($object, $pretty ? JSON_PRETTY_PRINT : null) . "\n";
}
/**
@ -31,17 +30,15 @@ class Command extends CliCommand
*/
protected function parseJson($json)
{
try {
return JsonString::decode($json);
} catch (JsonDecodeException $e) {
$this->fail('Invalid JSON: %s', $e->getMessage());
$res = json_decode($json);
if ($res === null) {
$this->fail('Invalid JSON: %s', $this->getLastJsonError());
}
return $res;
}
/**
* @param string $msg
* @return never-return
*/
public function fail($msg)
{
$args = func_get_args();
@ -49,8 +46,16 @@ class Command extends CliCommand
if (count($args)) {
$msg = vsprintf($msg, $args);
}
echo $this->screen->colorize("ERROR", 'red') . ": $msg\n";
exit(1);
throw new RuntimeException($msg);
}
/**
* @return string
*/
protected function getLastJsonError()
{
return JsonException::getJsonErrorMessage(json_last_error());
}
/**
@ -82,9 +87,9 @@ class Command extends CliCommand
{
MemoryLimit::raiseTo('1024M');
ini_set('max_execution_time', '0');
ini_set('max_execution_time', 0);
if (version_compare(PHP_VERSION, '7.0.0') < 0) {
ini_set('zend.enable_gc', '0');
ini_set('zend.enable_gc', 0);
}
return $this;

View File

@ -4,11 +4,6 @@ namespace Icinga\Module\Director\Cli;
use Icinga\Cli\Params;
use Icinga\Exception\MissingParameterException;
use Icinga\Module\Director\Data\Db\DbObject;
use Icinga\Module\Director\Data\Exporter;
use Icinga\Module\Director\Data\PropertyMangler;
use Icinga\Module\Director\IcingaConfig\IcingaConfig;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaObject;
use InvalidArgumentException;
@ -39,58 +34,28 @@ class ObjectCommand extends Command
*
* OPTIONS
*
* --resolved Resolve all inherited properties and show a flat
* object
* --json Use JSON format
* --no-pretty JSON is pretty-printed per default. Use this flag
* to enforce un-formatted JSON
* --no-defaults Per default JSON output ships null or default
* values. This flag skips those properties
* --with-services For hosts only, also shows attached services
* --all-services For hosts only, show applied and inherited services
* too
* --resolved Resolve all inherited properties and show a flat
* object
* --json Use JSON format
* --no-pretty JSON is pretty-printed per default (for PHP >= 5.4)
* Use this flag to enforce unformatted JSON
* --no-defaults Per default JSON output ships null or default values
* With this flag you will skip those properties
*/
public function showAction()
{
$db = $this->db();
$object = $this->getObject();
$exporter = new Exporter($db);
$resolve = (bool) $this->params->shift('resolved');
$withServices = (bool) $this->params->get('with-services');
$allServices = (bool) $this->params->get('all-services');
if ($withServices) {
if (!$object instanceof IcingaHost) {
$this->fail('--with-services is available for Hosts only');
}
$exporter->enableHostServices();
if ($this->params->shift('resolved')) {
$object = $object::fromPlainObject($object->toPlainObject(true), $db);
}
if ($allServices) {
if (!$object instanceof IcingaHost) {
$this->fail('--all-services is available for Hosts only');
}
$exporter->serviceLoader()->resolveHostServices();
}
$exporter->resolveObjects($resolve);
$exporter->showDefaults($this->params->shift('no-defaults', false));
if ($this->params->shift('json')) {
echo $this->renderJson($exporter->export($object), !$this->params->shift('no-pretty'));
$noDefaults = $this->params->shift('no-defaults', false);
$data = $object->toPlainObject(false, $noDefaults);
echo $this->renderJson($data, !$this->params->shift('no-pretty'));
} else {
$config = new IcingaConfig($db);
if ($resolve) {
$object = $object::fromPlainObject($object->toPlainObject(true, false, null, false), $db);
}
$object->renderToConfig($config);
if ($withServices) {
foreach ($exporter->serviceLoader()->fetchServicesForHost($object) as $service) {
$service->renderToConfig($config);
}
}
foreach ($config->getFiles() as $filename => $content) {
printf("/** %s **/\n\n", $filename);
echo $content;
}
echo $object;
}
}
@ -122,9 +87,36 @@ class ObjectCommand extends Command
public function createAction()
{
$type = $this->getType();
$props = $this->getObjectProperties();
$name = $props['object_name'];
$object = IcingaObject::createByType($type, $props, $this->db());
$name = $this->params->shift();
$props = $this->remainingParams();
if (! array_key_exists('object_type', $props)) {
$props['object_type'] = 'object';
}
if ($name) {
if (array_key_exists('object_name', $props)) {
if ($name !== $props['object_name']) {
$this->fail(sprintf(
"Name '%s' conflicts with object_name '%s'\n",
$name,
$props['object_name']
));
}
} else {
$props['object_name'] = $name;
}
}
if (! array_key_exists('object_name', $props)) {
$this->fail('Cannot create an object with at least an object name');
}
$object = IcingaObject::createByType(
$type,
$props,
$this->db()
);
if ($object->store()) {
printf("%s '%s' has been created\n", $type, $name);
@ -189,38 +181,33 @@ class ObjectCommand extends Command
public function setAction()
{
$name = $this->getName();
$type = $this->getType();
if ($this->params->shift('auto-create') && ! $this->exists($name)) {
$action = 'created';
$object = $this->create($type, $name);
$object = $this->create($name);
} else {
$action = 'modified';
$object = $this->getObject();
}
$appends = self::stripPrefixedProperties($this->params, 'append-');
$remove = self::stripPrefixedProperties($this->params, 'remove-');
$appends = $this->stripPrefixedProperties($this->params, 'append-');
$remove = $this->stripPrefixedProperties($this->params, 'remove-');
if ($this->params->shift('replace')) {
$object->replaceWith($this->create($type, $name, $this->remainingParams()));
$new = $this->create($name)->setProperties($this->remainingParams());
$object->replaceWith($new);
} else {
$object->setProperties($this->remainingParams());
}
PropertyMangler::appendToArrayProperties($object, $appends);
PropertyMangler::removeProperties($object, $remove);
$this->persistChanges($object, $type, $name, $action);
}
protected function persistChanges(DbObject $object, $type, $name, $action)
{
$this->appendToArrayProperties($object, $appends);
$this->removeProperties($object, $remove);
if ($object->hasBeenModified() && $object->store()) {
printf("%s '%s' has been %s\n", $type, $name, $action);
printf("%s '%s' has been %s\n", $this->getType(), $this->name, $action);
exit(0);
}
printf("%s '%s' has not been modified\n", $type, $name);
printf("%s '%s' has not been modified\n", $this->getType(), $name);
exit(0);
}
@ -339,7 +326,58 @@ class ObjectCommand extends Command
exit(0);
}
protected static function stripPrefixedProperties(Params $params, $prefix = 'append-')
protected function appendToArrayProperties(IcingaObject $object, $properties)
{
foreach ($properties as $key => $value) {
$current = $object->$key;
if ($current === null) {
$current = [$value];
} elseif (is_array($current)) {
$current[] = $value;
} else {
throw new InvalidArgumentException(sprintf(
'I can only append to arrays, %s is %s',
$key,
var_export($current, 1)
));
}
$object->$key = $current;
}
}
protected function removeProperties(IcingaObject $object, $properties)
{
foreach ($properties as $key => $value) {
if ($value === true) {
$object->$key = null;
}
$current = $object->$key;
if ($current === null) {
continue;
} elseif (is_array($current)) {
$new = [];
foreach ($current as $item) {
if ($item !== $value) {
$new[] = $item;
}
}
$object->$key = $new;
} elseif (is_string($current)) {
if ($current === $value) {
$object->$key = null;
}
} else {
throw new InvalidArgumentException(sprintf(
'I can only remove strings or from arrays, %s is %s',
$key,
var_export($current, 1)
));
}
}
}
protected function stripPrefixedProperties(Params $params, $prefix = 'append-')
{
$appends = [];
$len = strlen($prefix);
@ -357,37 +395,6 @@ class ObjectCommand extends Command
return $appends;
}
protected function getObjectProperties()
{
$name = $this->params->shift();
$props = $this->remainingParams();
if (! array_key_exists('object_type', $props)) {
$props['object_type'] = 'object';
}
// Normalize object_name, compare to given name
if ($name) {
if (array_key_exists('object_name', $props)) {
if ($name !== $props['object_name']) {
$this->fail(sprintf(
"Name '%s' conflicts with object_name '%s'\n",
$name,
$props['object_name']
));
}
} else {
$props['object_name'] = $name;
}
} else {
if (! array_key_exists('object_name', $props)) {
$this->fail('Cannot create an object with at least an object name');
}
}
return $props;
}
protected function shiftOneOrMoreNames()
{
$names = array();
@ -405,36 +412,12 @@ class ObjectCommand extends Command
protected function remainingParams()
{
if ($json = $this->params->shift('json')) {
if ($json === true) {
$json = $this->readFromStdin();
if ($json === null) {
$this->fail('Please pass JSON either via STDIN or via --json');
}
}
return (array) $this->parseJson($json);
} else {
return $this->params->getParams();
}
}
protected function readFromStdin()
{
if (!defined('STDIN')) {
define('STDIN', fopen('php://stdin', 'r'));
}
$inputIsTty = function_exists('posix_isatty') && posix_isatty(STDIN);
if ($inputIsTty) {
return null;
}
$stdin = file_get_contents('php://stdin');
if (! $stdin) {
return null;
}
return $stdin;
}
protected function exists($name)
{
return IcingaObject::existsByType(
@ -453,12 +436,16 @@ class ObjectCommand extends Command
);
}
protected function create($type, $name, $properties = [])
protected function create($name)
{
return IcingaObject::createByType($type, $properties + [
'object_type' => 'object',
'object_name' => $name
], $this->db());
return IcingaObject::createByType(
$this->getType(),
array(
'object_type' => 'object',
'object_name' => $name
),
$this->db()
);
}
/**

View File

@ -47,9 +47,8 @@ class CoreApi implements DeploymentApiInterface
{
$version = $this->getVersion();
if (
$version === null ||
((version_compare($version, '2.8.2', '>=') && version_compare($version, '2.10.2', '<')))
if (version_compare($version, '2.8.2', '>=')
&& version_compare($version, '2.10.2', '<')
) {
$this->client->disconnect();
$this->client->setKeepAlive(false);
@ -570,7 +569,7 @@ constants
'icon_image_alt' => 'icon_image_alt',
];
if (version_compare($this->getVersion() ?? '', '2.8.0', '>=')) {
if (version_compare($this->getVersion(), '2.8.0', '>=')) {
$params['flapping_threshold_high'] = 'flapping_threshold_high';
$params['flapping_threshold_low'] = 'flapping_threshold_low';
}
@ -623,7 +622,7 @@ constants
{
IcingaCommand::setPluginDir($this->getConstant('PluginDir'));
$objects = $this->getDirectorObjects('Command', "{$type}Commands", [
$objects = $this->getDirectorObjects('Command', "${type}Commands", [
'arguments' => 'arguments',
// 'env' => 'env',
'timeout' => 'timeout',
@ -682,8 +681,7 @@ constants
continue;
}
if (
in_array('startup.log', $availableFiles)
if (in_array('startup.log', $availableFiles)
&& in_array('status', $availableFiles)
) {
if ($this->getStagedFile($stage, 'status') === '0') {
@ -702,12 +700,6 @@ constants
$deployment->set('stage_collected', 'y');
$deployment->store();
/** @var DeploymentHook[] $hooks */
$hooks = Hook::all('director/Deployment');
foreach ($hooks as $hook) {
$hook->onCollect($deployment);
}
}
foreach ($missing as $deployment) {

View File

@ -8,11 +8,7 @@ class Json
{
public static function encode($mixed, $flags = null)
{
if ($flags === null) {
$result = \json_encode($mixed);
} else {
$result = \json_encode($mixed, $flags);
}
$result = \json_encode($mixed, $flags);
if ($result === false && json_last_error() !== JSON_ERROR_NONE) {
throw JsonEncodeException::forLastJsonError();

Some files were not shown because too many files have changed in this diff Show More