2018-10-06 16:55:52 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Icinga\Module\Director\Controllers;
|
|
|
|
|
|
|
|
use dipl\Html\Link;
|
|
|
|
use dipl\Web\Widget\NameValueTable;
|
|
|
|
use Exception;
|
2018-10-08 06:16:31 +02:00
|
|
|
use Icinga\Date\DateFormatter;
|
2018-10-06 16:55:52 +02:00
|
|
|
use Icinga\Module\Director\ConfigDiff;
|
|
|
|
use Icinga\Module\Director\Core\Json;
|
|
|
|
use Icinga\Module\Director\Db;
|
|
|
|
use Icinga\Module\Director\DirectorObject\Automation\Basket;
|
|
|
|
use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshot;
|
2018-10-11 23:13:51 +02:00
|
|
|
use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshotFieldResolver;
|
2018-10-15 14:49:47 +02:00
|
|
|
use Icinga\Module\Director\Forms\AddToBasketForm;
|
2018-10-06 16:55:52 +02:00
|
|
|
use Icinga\Module\Director\Forms\BasketCreateSnapshotForm;
|
|
|
|
use Icinga\Module\Director\Forms\BasketForm;
|
2018-12-10 15:36:59 +01:00
|
|
|
use Icinga\Module\Director\Forms\BasketUploadForm;
|
2018-10-06 16:55:52 +02:00
|
|
|
use Icinga\Module\Director\Forms\RestoreBasketForm;
|
|
|
|
use Icinga\Module\Director\Web\Controller\ActionController;
|
|
|
|
use dipl\Html\Html;
|
|
|
|
use Icinga\Module\Director\Web\Table\BasketSnapshotTable;
|
|
|
|
|
|
|
|
class BasketController extends ActionController
|
|
|
|
{
|
|
|
|
protected $isApified = true;
|
|
|
|
|
|
|
|
protected function basketTabs()
|
|
|
|
{
|
2018-10-09 14:09:11 +02:00
|
|
|
$name = $this->params->get('name');
|
2018-10-06 16:55:52 +02:00
|
|
|
return $this->tabs()->add('show', [
|
|
|
|
'label' => $this->translate('Basket'),
|
|
|
|
'url' => 'director/basket',
|
2018-10-09 14:09:11 +02:00
|
|
|
'urlParams' => ['name' => $name]
|
2018-10-06 16:55:52 +02:00
|
|
|
])->add('snapshots', [
|
|
|
|
'label' => $this->translate('Snapshots'),
|
|
|
|
'url' => 'director/basket/snapshots',
|
2018-10-09 14:09:11 +02:00
|
|
|
'urlParams' => ['name' => $name]
|
2018-10-06 16:55:52 +02:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @throws \Icinga\Exception\NotFoundError
|
2018-10-11 23:13:51 +02:00
|
|
|
* @throws \Icinga\Exception\MissingParameterException
|
2018-10-06 16:55:52 +02:00
|
|
|
*/
|
|
|
|
public function indexAction()
|
|
|
|
{
|
|
|
|
$this->actions()->add(
|
|
|
|
Link::create(
|
|
|
|
$this->translate('Back'),
|
|
|
|
'director/baskets',
|
|
|
|
null,
|
2018-10-06 17:06:42 +02:00
|
|
|
['class' => 'icon-left-big']
|
2018-10-06 16:55:52 +02:00
|
|
|
)
|
|
|
|
);
|
2018-10-09 14:09:11 +02:00
|
|
|
$basket = $this->requireBasket();
|
2018-10-06 16:55:52 +02:00
|
|
|
$this->basketTabs()->activate('show');
|
|
|
|
$this->addTitle($basket->get('basket_name'));
|
|
|
|
if ($basket->isEmpty()) {
|
|
|
|
$this->content()->add(Html::tag('p', [
|
|
|
|
'class' => 'information'
|
|
|
|
], $this->translate('This basket is empty')));
|
|
|
|
}
|
|
|
|
$this->content()->add(
|
|
|
|
(new BasketForm())->setObject($basket)->handleRequest()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-11-25 22:02:26 +01:00
|
|
|
/**
|
|
|
|
* @throws \Icinga\Exception\MissingParameterException
|
|
|
|
*/
|
2018-10-15 14:49:47 +02:00
|
|
|
public function addAction()
|
|
|
|
{
|
2018-11-25 22:02:26 +01:00
|
|
|
$this->actions()->add(
|
|
|
|
Link::create(
|
|
|
|
$this->translate('Baskets'),
|
|
|
|
'director/baskets',
|
|
|
|
null,
|
|
|
|
['class' => 'icon-tag']
|
|
|
|
)
|
|
|
|
);
|
2018-10-15 14:49:47 +02:00
|
|
|
$this->addSingleTab($this->translate('Add to Basket'));
|
|
|
|
$this->addTitle($this->translate('Add chosen objects to a Configuration Basket'));
|
|
|
|
$form = new AddToBasketForm();
|
|
|
|
$form->setDb($this->db())
|
|
|
|
->setType($this->params->getRequired('type'))
|
|
|
|
->setNames($this->url()->getParams()->getValues('names'))
|
|
|
|
->handleRequest();
|
|
|
|
$this->content()->add($form);
|
|
|
|
}
|
|
|
|
|
2018-10-06 16:55:52 +02:00
|
|
|
public function createAction()
|
|
|
|
{
|
|
|
|
$this->actions()->add(
|
|
|
|
Link::create(
|
|
|
|
$this->translate('back'),
|
|
|
|
'director/baskets',
|
|
|
|
null,
|
2018-10-06 17:06:42 +02:00
|
|
|
['class' => 'icon-left-big']
|
2018-10-06 16:55:52 +02:00
|
|
|
)
|
|
|
|
);
|
|
|
|
$this->addSingleTab($this->translate('Create Basket'));
|
|
|
|
$this->addTitle($this->translate('Create a new Configuration Basket'));
|
|
|
|
$form = (new BasketForm())
|
|
|
|
->setDb($this->db())
|
|
|
|
->handleRequest();
|
|
|
|
$this->content()->add($form);
|
|
|
|
}
|
|
|
|
|
2018-12-10 15:36:59 +01:00
|
|
|
public function uploadAction()
|
|
|
|
{
|
|
|
|
$this->actions()->add(
|
|
|
|
Link::create(
|
|
|
|
$this->translate('back'),
|
|
|
|
'director/baskets',
|
|
|
|
null,
|
|
|
|
['class' => 'icon-left-big']
|
|
|
|
)
|
|
|
|
);
|
|
|
|
$this->addSingleTab($this->translate('Upload a Basket'));
|
|
|
|
$this->addTitle($this->translate('Upload a Configuration Basket'));
|
|
|
|
$form = (new BasketUploadForm())
|
|
|
|
->setDb($this->db())
|
|
|
|
->handleRequest();
|
|
|
|
$this->content()->add($form);
|
|
|
|
}
|
|
|
|
|
2018-10-06 17:06:42 +02:00
|
|
|
/**
|
|
|
|
* @throws \Icinga\Exception\NotFoundError
|
|
|
|
*/
|
2018-10-06 16:55:52 +02:00
|
|
|
public function snapshotsAction()
|
|
|
|
{
|
2018-10-09 14:09:11 +02:00
|
|
|
$name = $this->params->get('name');
|
|
|
|
if ($name === null || $name === '') {
|
2018-10-06 16:55:52 +02:00
|
|
|
$basket = null;
|
|
|
|
} else {
|
2018-10-09 14:09:11 +02:00
|
|
|
$basket = Basket::load($name, $this->db());
|
2018-10-06 16:55:52 +02:00
|
|
|
}
|
|
|
|
if ($basket === null) {
|
|
|
|
$this->addTitle($this->translate('Basket Snapshots'));
|
|
|
|
$this->addSingleTab($this->translate('Snapshots'));
|
|
|
|
} else {
|
|
|
|
$this->addTitle(sprintf(
|
2018-12-10 18:34:14 +01:00
|
|
|
$this->translate('%s: Snapshots'),
|
2018-10-06 16:55:52 +02:00
|
|
|
$basket->get('basket_name')
|
|
|
|
));
|
|
|
|
$this->basketTabs()->activate('snapshots');
|
|
|
|
}
|
|
|
|
if ($basket !== null) {
|
|
|
|
$this->content()->add(
|
|
|
|
(new BasketCreateSnapshotForm())
|
|
|
|
->setBasket($basket)
|
|
|
|
->handleRequest()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
$table = new BasketSnapshotTable($this->db());
|
|
|
|
if ($basket !== null) {
|
|
|
|
$table->setBasket($basket);
|
|
|
|
}
|
|
|
|
|
|
|
|
$table->renderTo($this);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @throws \Icinga\Exception\MissingParameterException
|
|
|
|
* @throws \Icinga\Exception\NotFoundError
|
|
|
|
*/
|
|
|
|
public function snapshotAction()
|
|
|
|
{
|
2018-10-09 14:09:11 +02:00
|
|
|
$basket = $this->requireBasket();
|
2018-10-06 16:55:52 +02:00
|
|
|
$snapshot = BasketSnapshot::load([
|
2018-10-09 14:09:11 +02:00
|
|
|
'basket_uuid' => $basket->get('uuid'),
|
2018-10-06 16:55:52 +02:00
|
|
|
'ts_create' => $this->params->getRequired('ts'),
|
|
|
|
], $this->db());
|
2018-10-08 06:16:31 +02:00
|
|
|
$snapSum = bin2hex($snapshot->get('content_checksum'));
|
2018-10-06 16:55:52 +02:00
|
|
|
|
2018-10-09 14:11:48 +02:00
|
|
|
if ($this->params->get('action') === 'download') {
|
|
|
|
$this->getResponse()->setHeader('Content-Type', 'application/json', true);
|
2018-12-10 15:36:59 +01:00
|
|
|
$this->getResponse()->setHeader('Content-Disposition', sprintf(
|
|
|
|
'attachment; filename=Director-Basket_%s_%s.json',
|
|
|
|
str_replace([' ', '"'], ['_', '_'], iconv(
|
|
|
|
'UTF-8',
|
|
|
|
'ISO-8859-11//IGNORE',
|
|
|
|
$basket->get('basket_name')
|
|
|
|
)),
|
|
|
|
substr($snapSum, 0, 7)
|
|
|
|
));
|
2018-10-09 14:11:48 +02:00
|
|
|
echo $snapshot->getJsonDump();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-10-06 16:55:52 +02:00
|
|
|
$this->addTitle(
|
|
|
|
$this->translate('%s: %s (Snapshot)'),
|
|
|
|
$basket->get('basket_name'),
|
2018-10-08 06:16:31 +02:00
|
|
|
substr($snapSum, 0, 7)
|
2018-10-06 16:55:52 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
$this->actions()->add([
|
|
|
|
Link::create(
|
|
|
|
$this->translate('Show Basket'),
|
|
|
|
'director/basket',
|
2018-10-09 14:09:11 +02:00
|
|
|
['name' => $basket->get('basket_name')],
|
2018-10-06 16:55:52 +02:00
|
|
|
['data-base-target' => '_next']
|
|
|
|
),
|
|
|
|
Link::create(
|
|
|
|
$this->translate('Restore'),
|
|
|
|
$this->url()->with('action', 'restore'),
|
|
|
|
null,
|
|
|
|
['class' => 'icon-rewind']
|
2018-10-09 14:11:48 +02:00
|
|
|
),
|
|
|
|
Link::create(
|
|
|
|
$this->translate('Download'),
|
2018-11-15 12:42:06 +01:00
|
|
|
$this->url()
|
|
|
|
->with([
|
|
|
|
'action' => 'download',
|
|
|
|
'dbResourceName' => $this->getDbResourceName()
|
|
|
|
]),
|
2018-10-09 14:11:48 +02:00
|
|
|
null,
|
|
|
|
[
|
|
|
|
'class' => 'icon-download',
|
|
|
|
'target' => '_blank'
|
|
|
|
]
|
|
|
|
),
|
2018-10-06 17:13:14 +02:00
|
|
|
]);
|
2018-10-06 16:55:52 +02:00
|
|
|
|
2018-10-08 06:16:31 +02:00
|
|
|
$properties = new NameValueTable();
|
|
|
|
$properties->addNameValuePairs([
|
|
|
|
$this->translate('Created') => DateFormatter::formatDateTime($snapshot->get('ts_create') / 1000),
|
|
|
|
$this->translate('Content Checksum') => bin2hex($snapshot->get('content_checksum')),
|
|
|
|
]);
|
|
|
|
$this->content()->add($properties);
|
|
|
|
|
2018-10-06 16:55:52 +02:00
|
|
|
if ($this->params->get('action') === 'restore') {
|
|
|
|
$form = new RestoreBasketForm();
|
|
|
|
$form
|
|
|
|
->setSnapshot($snapshot)
|
|
|
|
->handleRequest();
|
|
|
|
$this->content()->add($form);
|
|
|
|
$targetDbName = $form->getValue('target_db');
|
|
|
|
$connection = $form->getDb();
|
|
|
|
} else {
|
|
|
|
$targetDbName = null;
|
|
|
|
$connection = $this->db();
|
|
|
|
}
|
|
|
|
|
|
|
|
$json = $snapshot->getJsonDump();
|
|
|
|
$this->addSingleTab($this->translate('Snapshot'));
|
|
|
|
$all = Json::decode($json);
|
2018-10-11 23:13:51 +02:00
|
|
|
$fieldResolver = new BasketSnapshotFieldResolver($all, $connection);
|
2018-10-06 16:55:52 +02:00
|
|
|
foreach ($all as $type => $objects) {
|
2018-10-09 00:39:38 +02:00
|
|
|
if ($type === 'Datafield') {
|
2018-10-11 23:13:51 +02:00
|
|
|
// TODO: we should now be able to show all fields and link
|
|
|
|
// to a "diff" for the ones that should be created
|
2018-10-09 00:39:38 +02:00
|
|
|
// $this->content()->add(Html::tag('h2', sprintf('+%d Datafield(s)', count($objects))));
|
|
|
|
continue;
|
|
|
|
}
|
2018-10-06 16:55:52 +02:00
|
|
|
$table = new NameValueTable();
|
|
|
|
$table->setAttribute('data-base-target', '_next');
|
|
|
|
foreach ($objects as $key => $object) {
|
|
|
|
$linkParams = [
|
2018-10-09 14:09:11 +02:00
|
|
|
'name' => $basket->get('basket_name'),
|
2018-10-06 16:55:52 +02:00
|
|
|
'checksum' => $this->params->get('checksum'),
|
|
|
|
'ts' => $this->params->get('ts'),
|
|
|
|
'type' => $type,
|
|
|
|
'key' => $key,
|
|
|
|
];
|
|
|
|
if ($targetDbName !== null) {
|
|
|
|
$linkParams['target_db'] = $targetDbName;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
$current = BasketSnapshot::instanceByIdentifier($type, $key, $connection);
|
|
|
|
if ($current === null) {
|
|
|
|
$table->addNameValueRow(
|
|
|
|
$key,
|
2018-10-06 17:13:14 +02:00
|
|
|
Link::create(
|
2018-10-06 16:55:52 +02:00
|
|
|
Html::tag('strong', ['style' => 'color: green'], $this->translate('new')),
|
|
|
|
'director/basket/snapshotobject',
|
|
|
|
$linkParams
|
|
|
|
)
|
|
|
|
);
|
|
|
|
continue;
|
|
|
|
}
|
2018-10-11 23:13:51 +02:00
|
|
|
$currentExport = $current->export();
|
|
|
|
$fieldResolver->tweakTargetIds($currentExport);
|
2018-12-10 17:52:56 +01:00
|
|
|
|
|
|
|
// Ignore originalId
|
|
|
|
if (isset($currentExport->originalId)) {
|
|
|
|
unset($currentExport->originalId);
|
|
|
|
}
|
|
|
|
if (isset($object->originalId)) {
|
|
|
|
unset($object->originalId);
|
|
|
|
}
|
2018-10-11 23:13:51 +02:00
|
|
|
$hasChanged = Json::encode($currentExport) !== Json::encode($object);
|
2018-10-06 16:55:52 +02:00
|
|
|
$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,
|
|
|
|
$e->getMessage()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$this->content()->add(Html::tag('h2', $type));
|
|
|
|
$this->content()->add($table);
|
|
|
|
}
|
2018-10-09 00:39:38 +02:00
|
|
|
$this->content()->add(Html::tag('div', ['style' => 'height: 5em']));
|
2018-10-06 16:55:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @throws \Icinga\Exception\MissingParameterException
|
|
|
|
* @throws \Icinga\Exception\NotFoundError
|
|
|
|
*/
|
|
|
|
public function snapshotobjectAction()
|
|
|
|
{
|
2018-10-09 14:09:11 +02:00
|
|
|
$basket = $this->requireBasket();
|
2018-10-06 16:55:52 +02:00
|
|
|
$snapshot = BasketSnapshot::load([
|
2018-10-09 14:09:11 +02:00
|
|
|
'basket_uuid' => $basket->get('uuid'),
|
2018-10-06 16:55:52 +02:00
|
|
|
'ts_create' => $this->params->getRequired('ts'),
|
|
|
|
], $this->db());
|
|
|
|
$snapshotUrl = $this->url()->without('type')->without('key')->setPath('director/basket/snapshot');
|
|
|
|
$type = $this->params->get('type');
|
|
|
|
$key = $this->params->get('key');
|
|
|
|
|
|
|
|
$this->addTitle($this->translate('Single Object Diff'));
|
|
|
|
$this->content()->add(Html::tag('p', [
|
|
|
|
'class' => 'information'
|
|
|
|
], Html::sprintf(
|
|
|
|
$this->translate('Comparing %s "%s" from Snapshot "%s" to current config'),
|
|
|
|
$type,
|
|
|
|
$key,
|
|
|
|
Link::create(
|
2018-10-09 14:11:48 +02:00
|
|
|
substr(bin2hex($snapshot->get('content_checksum')), 0, 7),
|
2018-10-06 16:55:52 +02:00
|
|
|
$snapshotUrl,
|
|
|
|
null,
|
|
|
|
['data-base-target' => '_next']
|
|
|
|
)
|
|
|
|
)));
|
|
|
|
$this->actions()->add([
|
|
|
|
Link::create(
|
|
|
|
$this->translate('back'),
|
|
|
|
$snapshotUrl,
|
|
|
|
null,
|
|
|
|
['class' => 'icon-left-big']
|
|
|
|
),
|
2018-12-10 15:52:24 +01:00
|
|
|
/*
|
2018-10-06 16:55:52 +02:00
|
|
|
Link::create(
|
|
|
|
$this->translate('Restore'),
|
|
|
|
$this->url()->with('action', 'restore'),
|
|
|
|
null,
|
|
|
|
['class' => 'icon-rewind']
|
|
|
|
)
|
2018-12-10 15:52:24 +01:00
|
|
|
*/
|
2018-10-06 16:55:52 +02:00
|
|
|
]);
|
|
|
|
|
|
|
|
$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);
|
|
|
|
}
|
2018-10-11 23:13:51 +02:00
|
|
|
$fieldResolver = new BasketSnapshotFieldResolver($objects, $connection);
|
2018-10-08 06:16:31 +02:00
|
|
|
$objectFromBasket = $objects->$type->$key;
|
2018-10-06 16:55:52 +02:00
|
|
|
$current = BasketSnapshot::instanceByIdentifier($type, $key, $connection);
|
2018-10-08 18:17:12 +02:00
|
|
|
if ($current === null) {
|
|
|
|
$current = '';
|
|
|
|
} else {
|
2018-10-11 23:13:51 +02:00
|
|
|
$exported = $current->export();
|
|
|
|
$fieldResolver->tweakTargetIds($exported);
|
|
|
|
$current = Json::encode($exported, JSON_PRETTY_PRINT);
|
2018-10-08 18:17:12 +02:00
|
|
|
}
|
|
|
|
|
2018-10-06 16:55:52 +02:00
|
|
|
$this->content()->add(
|
|
|
|
ConfigDiff::create(
|
2018-10-08 18:17:12 +02:00
|
|
|
$current,
|
2018-10-08 06:16:31 +02:00
|
|
|
Json::encode($objectFromBasket, JSON_PRETTY_PRINT)
|
2018-10-06 16:55:52 +02:00
|
|
|
)->setHtmlRenderer('Inline')
|
|
|
|
);
|
|
|
|
}
|
2018-10-09 14:09:11 +02:00
|
|
|
|
2018-10-11 23:13:51 +02:00
|
|
|
/**
|
|
|
|
* @return Basket
|
|
|
|
* @throws \Icinga\Exception\MissingParameterException
|
|
|
|
* @throws \Icinga\Exception\NotFoundError
|
|
|
|
*/
|
2018-10-09 14:09:11 +02:00
|
|
|
protected function requireBasket()
|
|
|
|
{
|
|
|
|
return Basket::load($this->params->getRequired('name'), $this->db());
|
|
|
|
}
|
2018-10-06 16:55:52 +02:00
|
|
|
}
|