420 lines
15 KiB
PHP
420 lines
15 KiB
PHP
<?php
|
|
|
|
namespace Icinga\Module\Director\Controllers;
|
|
|
|
use Exception;
|
|
use gipfl\Diff\HtmlRenderer\InlineDiff;
|
|
use gipfl\Diff\PhpDiff;
|
|
use gipfl\IcingaWeb2\Link;
|
|
use gipfl\Web\Table\NameValueTable;
|
|
use gipfl\Web\Widget\Hint;
|
|
use Icinga\Date\DateFormatter;
|
|
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\Forms\AddToBasketForm;
|
|
use Icinga\Module\Director\Forms\BasketCreateSnapshotForm;
|
|
use Icinga\Module\Director\Forms\BasketForm;
|
|
use Icinga\Module\Director\Forms\BasketUploadForm;
|
|
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
|
|
{
|
|
protected $isApified = true;
|
|
|
|
protected function basketTabs()
|
|
{
|
|
$name = $this->params->get('name');
|
|
return $this->tabs()->add('show', [
|
|
'label' => $this->translate('Basket'),
|
|
'url' => 'director/basket',
|
|
'urlParams' => ['name' => $name]
|
|
])->add('snapshots', [
|
|
'label' => $this->translate('Snapshots'),
|
|
'url' => 'director/basket/snapshots',
|
|
'urlParams' => ['name' => $name]
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @throws \Icinga\Exception\NotFoundError
|
|
* @throws \Icinga\Exception\MissingParameterException
|
|
*/
|
|
public function indexAction()
|
|
{
|
|
$this->actions()->add(
|
|
Link::create(
|
|
$this->translate('Back'),
|
|
'director/baskets',
|
|
null,
|
|
['class' => 'icon-left-big']
|
|
)
|
|
);
|
|
$basket = $this->requireBasket();
|
|
$this->basketTabs()->activate('show');
|
|
$this->addTitle($basket->get('basket_name'));
|
|
if ($basket->isEmpty()) {
|
|
$this->content()->add(Hint::info($this->translate('This basket is empty')));
|
|
}
|
|
$this->content()->add(
|
|
(new BasketForm())->setObject($basket)->handleRequest()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @throws \Icinga\Exception\MissingParameterException
|
|
*/
|
|
public function addAction()
|
|
{
|
|
$this->actions()->add(
|
|
Link::create(
|
|
$this->translate('Baskets'),
|
|
'director/baskets',
|
|
null,
|
|
['class' => 'icon-tag']
|
|
)
|
|
);
|
|
$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);
|
|
}
|
|
|
|
public function createAction()
|
|
{
|
|
$this->actions()->add(
|
|
Link::create(
|
|
$this->translate('back'),
|
|
'director/baskets',
|
|
null,
|
|
['class' => 'icon-left-big']
|
|
)
|
|
);
|
|
$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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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
|
|
*/
|
|
public function snapshotsAction()
|
|
{
|
|
$name = $this->params->get('name');
|
|
if ($name === null || $name === '') {
|
|
$basket = null;
|
|
} else {
|
|
$basket = Basket::load($name, $this->db());
|
|
}
|
|
if ($basket === null) {
|
|
$this->addTitle($this->translate('Basket Snapshots'));
|
|
$this->addSingleTab($this->translate('Snapshots'));
|
|
} else {
|
|
$this->addTitle(sprintf(
|
|
$this->translate('%s: Snapshots'),
|
|
$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(
|
|
(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()
|
|
{
|
|
$basket = $this->requireBasket();
|
|
$snapshot = BasketSnapshot::load([
|
|
'basket_uuid' => $basket->get('uuid'),
|
|
'ts_create' => $this->params->getRequired('ts'),
|
|
], $this->db());
|
|
$snapSum = bin2hex($snapshot->get('content_checksum'));
|
|
|
|
if ($this->params->get('action') === 'download') {
|
|
$this->getResponse()->setHeader('Content-Type', 'application/json', true);
|
|
$this->getResponse()->setHeader('Content-Disposition', sprintf(
|
|
'attachment; filename=Director-Basket_%s_%s.json',
|
|
str_replace([' ', '"'], ['_', '_'], iconv(
|
|
'UTF-8',
|
|
'ISO-8859-1//IGNORE',
|
|
$basket->get('basket_name')
|
|
)),
|
|
substr($snapSum, 0, 7)
|
|
));
|
|
echo $snapshot->getJsonDump();
|
|
return;
|
|
}
|
|
|
|
$this->addTitle(
|
|
$this->translate('%s: %s (Snapshot)'),
|
|
$basket->get('basket_name'),
|
|
substr($snapSum, 0, 7)
|
|
);
|
|
|
|
$this->actions()->add([
|
|
Link::create(
|
|
$this->translate('Show Basket'),
|
|
'director/basket',
|
|
['name' => $basket->get('basket_name')],
|
|
['data-base-target' => '_next']
|
|
),
|
|
Link::create(
|
|
$this->translate('Restore'),
|
|
$this->url()->with('action', 'restore'),
|
|
null,
|
|
['class' => 'icon-rewind']
|
|
),
|
|
Link::create(
|
|
$this->translate('Download'),
|
|
$this->url()
|
|
->with([
|
|
'action' => 'download',
|
|
'dbResourceName' => $this->getDbResourceName()
|
|
]),
|
|
null,
|
|
[
|
|
'class' => 'icon-download',
|
|
'target' => '_blank'
|
|
]
|
|
),
|
|
]);
|
|
|
|
$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);
|
|
|
|
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();
|
|
}
|
|
|
|
$this->addSingleTab($this->translate('Snapshot'));
|
|
$diff = new BasketDiff($snapshot, $connection);
|
|
foreach ($diff->getBasketObjects() 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
|
|
// $this->content()->add(Html::tag('h2', sprintf('+%d Datafield(s)', count($objects))));
|
|
continue;
|
|
}
|
|
$table = new NameValueTable();
|
|
$table->addAttributes([
|
|
'class' => ['table-basket-changes', 'table-row-selectable'],
|
|
'data-base-target' => '_next',
|
|
]);
|
|
foreach ($objects as $key => $object) {
|
|
$linkParams = [
|
|
'name' => $basket->get('basket_name'),
|
|
'checksum' => $this->params->get('checksum'),
|
|
'ts' => $this->params->get('ts'),
|
|
'type' => $type,
|
|
'key' => $key,
|
|
];
|
|
if ($targetDbName !== null) {
|
|
$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'),
|
|
'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']
|
|
);
|
|
}
|
|
$table->addNameValueRow($key, $link);
|
|
} catch (Exception $e) {
|
|
$table->addNameValueRow(
|
|
$key,
|
|
Html::tag('a', sprintf(
|
|
'%s (%s:%d)',
|
|
$e->getMessage(),
|
|
basename($e->getFile()),
|
|
$e->getLine()
|
|
))
|
|
);
|
|
}
|
|
}
|
|
$this->content()->add(Html::tag('h2', $type));
|
|
$this->content()->add($table);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws \Icinga\Exception\MissingParameterException
|
|
* @throws \Icinga\Exception\NotFoundError
|
|
*/
|
|
public function snapshotobjectAction()
|
|
{
|
|
$basket = $this->requireBasket();
|
|
$snapshot = BasketSnapshot::load([
|
|
'basket_uuid' => $basket->get('uuid'),
|
|
'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(Hint::info(Html::sprintf(
|
|
$this->translate('Comparing %s "%s" from Snapshot "%s" to current config'),
|
|
$type,
|
|
$key,
|
|
Link::create(
|
|
substr(bin2hex($snapshot->get('content_checksum')), 0, 7),
|
|
$snapshotUrl,
|
|
null,
|
|
['data-base-target' => '_next']
|
|
)
|
|
)));
|
|
$this->actions()->add([
|
|
Link::create(
|
|
$this->translate('back'),
|
|
$snapshotUrl,
|
|
null,
|
|
['class' => 'icon-left-big']
|
|
),
|
|
/*
|
|
Link::create(
|
|
$this->translate('Restore'),
|
|
$this->url()->with('action', 'restore'),
|
|
null,
|
|
['class' => 'icon-rewind']
|
|
)
|
|
*/
|
|
]);
|
|
|
|
$this->addSingleTab($this->translate('Snapshot'));
|
|
$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);
|
|
}
|
|
$basketJson = $diff->getBasketString($type, $key);
|
|
$currentJson = $diff->getCurrentString($type, $key, $uuid);
|
|
if ($currentJson === $basketJson) {
|
|
$this->content()->add([
|
|
Hint::ok('Basket equals current object'),
|
|
Html::tag('pre', $currentJson)
|
|
]);
|
|
} else {
|
|
$this->content()->add(new InlineDiff(new PhpDiff($currentJson, $basketJson)));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return Basket
|
|
* @throws \Icinga\Exception\MissingParameterException
|
|
* @throws \Icinga\Exception\NotFoundError
|
|
*/
|
|
protected function requireBasket()
|
|
{
|
|
return Basket::load($this->params->getRequired('name'), $this->db());
|
|
}
|
|
}
|