basket: cleanup, fix/finish custom selection

fixes #1630
This commit is contained in:
Thomas Gelf 2018-10-15 14:49:47 +02:00
parent 83dc9dc6c3
commit f871e0bd7d
16 changed files with 453 additions and 41 deletions

View File

@ -12,6 +12,7 @@ use Icinga\Module\Director\Db;
use Icinga\Module\Director\DirectorObject\Automation\Basket; use Icinga\Module\Director\DirectorObject\Automation\Basket;
use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshot; use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshot;
use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshotFieldResolver; use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshotFieldResolver;
use Icinga\Module\Director\Forms\AddToBasketForm;
use Icinga\Module\Director\Forms\BasketCreateSnapshotForm; use Icinga\Module\Director\Forms\BasketCreateSnapshotForm;
use Icinga\Module\Director\Forms\BasketForm; use Icinga\Module\Director\Forms\BasketForm;
use Icinga\Module\Director\Forms\RestoreBasketForm; use Icinga\Module\Director\Forms\RestoreBasketForm;
@ -64,6 +65,18 @@ class BasketController extends ActionController
); );
} }
public function addAction()
{
$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() public function createAction()
{ {
$this->actions()->add( $this->actions()->add(

View File

@ -2,9 +2,11 @@
namespace Icinga\Module\Director\Controllers; namespace Icinga\Module\Director\Controllers;
use dipl\Web\Url;
use Icinga\Data\Filter\Filter; use Icinga\Data\Filter\Filter;
use Icinga\Data\Filter\FilterChain; use Icinga\Data\Filter\FilterChain;
use Icinga\Data\Filter\FilterExpression; use Icinga\Data\Filter\FilterExpression;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
use Icinga\Module\Director\Forms\IcingaAddServiceForm; use Icinga\Module\Director\Forms\IcingaAddServiceForm;
use Icinga\Module\Director\Forms\IcingaAddServiceSetForm; use Icinga\Module\Director\Forms\IcingaAddServiceSetForm;
use Icinga\Module\Director\Objects\IcingaHost; use Icinga\Module\Director\Objects\IcingaHost;
@ -47,6 +49,31 @@ class HostsController extends ObjectsController
)); ));
} }
public function edittemplatesAction()
{
parent::editAction();
$objects = $this->loadMultiObjectsFromParams();
$names = [];
/** @var ExportInterface $object */
foreach ($objects as $object) {
$names[] = $object->getUniqueIdentifier();
}
$url = Url::fromPath('director/basket/add', [
'type' => 'HostTemplate',
]);
$url->getParams()->addValues('names', $names);
$this->actions()->add(Link::create(
$this->translate('Add to Basket'),
$url,
null,
['class' => 'icon-tag']
));
}
public function addserviceAction() public function addserviceAction()
{ {
$this->addSingleTab($this->translate('Add Service')); $this->addSingleTab($this->translate('Add Service'));

View File

@ -49,6 +49,17 @@ class ImportsourceController extends ActionController
$this->actions(new AutomationObjectActionBar( $this->actions(new AutomationObjectActionBar(
$this->getRequest() $this->getRequest()
)); ));
$source = $this->getImportSource();
$this->actions()->add(Link::create(
$this->translate('Add to Basket'),
'director/basket/add',
[
'type' => 'ImportSource',
'names' => $source->getUniqueIdentifier()
],
['class' => 'icon-tag']
));
} }
/** /**

View File

@ -2,6 +2,7 @@
namespace Icinga\Module\Director\Controllers; namespace Icinga\Module\Director\Controllers;
use dipl\Html\Link;
use Icinga\Module\Director\Forms\DirectorJobForm; use Icinga\Module\Director\Forms\DirectorJobForm;
use Icinga\Module\Director\Web\Controller\ActionController; use Icinga\Module\Director\Web\Controller\ActionController;
use Icinga\Module\Director\Objects\DirectorJob; use Icinga\Module\Director\Objects\DirectorJob;
@ -19,6 +20,7 @@ class JobController extends ActionController
$this $this
->addJobTabs($job, 'show') ->addJobTabs($job, 'show')
->addTitle($this->translate('Job: %s'), $job->get('job_name')) ->addTitle($this->translate('Job: %s'), $job->get('job_name'))
->addToBasketLink()
->content()->add(new JobDetails($job)); ->content()->add(new JobDetails($job));
} }
@ -50,6 +52,7 @@ class JobController extends ActionController
$this $this
->addJobTabs($job, 'edit') ->addJobTabs($job, 'edit')
->addTitle($this->translate('Job: %s'), $job->get('job_name')) ->addTitle($this->translate('Job: %s'), $job->get('job_name'))
->addToBasketLink()
->content()->add($form); ->content()->add($form);
} }
@ -63,6 +66,27 @@ class JobController extends ActionController
return DirectorJob::loadWithAutoIncId((int) $this->params->getRequired('id'), $this->db()); return DirectorJob::loadWithAutoIncId((int) $this->params->getRequired('id'), $this->db());
} }
/**
* @return $this
* @throws \Icinga\Exception\MissingParameterException
* @throws \Icinga\Exception\NotFoundError
*/
protected function addToBasketLink()
{
$job = $this->requireJob();
$this->actions()->add(Link::create(
$this->translate('Add to Basket'),
'director/basket/add',
[
'type' => 'DirectorJob',
'names' => $job->getUniqueIdentifier()
],
['class' => 'icon-tag']
));
return $this;
}
protected function addJobTabs(DirectorJob $job, $active) protected function addJobTabs(DirectorJob $job, $active)
{ {
$id = $job->get('id'); $id = $job->get('id');

View File

@ -45,6 +45,15 @@ class SyncruleController extends ActionController
$this->addPropertyHint($rule); $this->addPropertyHint($rule);
return; return;
} }
$this->actions()->add(Link::create(
$this->translate('Add to Basket'),
'director/basket/add',
[
'type' => 'SyncRule',
'names' => $rule->getUniqueIdentifier()
],
['class' => 'icon-tag']
));
if (! $run) { if (! $run) {
$this->warning($this->translate('This Sync Rule has never been run before.')); $this->warning($this->translate('This Sync Rule has never been run before.'));
@ -159,6 +168,15 @@ class SyncruleController extends ActionController
['class' => 'icon-paste'] ['class' => 'icon-paste']
) )
); );
$this->actions()->add(Link::create(
$this->translate('Add to Basket'),
'director/basket/add',
[
'type' => 'SyncRule',
'names' => $rule->getUniqueIdentifier()
],
['class' => 'icon-tag']
));
if (! $rule->hasSyncProperties()) { if (! $rule->hasSyncProperties()) {
$this->addPropertyHint($rule); $this->addPropertyHint($rule);

View File

@ -0,0 +1,131 @@
<?php
namespace Icinga\Module\Director\Forms;
use dipl\Html\Html;
use dipl\Html\HtmlDocument;
use dipl\Html\Link;
use Icinga\Exception\NotFoundError;
use Icinga\Module\Director\DirectorObject\Automation\Basket;
use Icinga\Module\Director\Web\Form\DirectorForm;
class AddToBasketForm extends DirectorForm
{
/** @var Basket */
private $basket;
private $type = '(has not been set)';
private $names = [];
/**
* @throws \Zend_Form_Exception
* @throws \Icinga\Exception\NotFoundError
*/
public function setup()
{
$baskets = Basket::loadAll($this->getDb());
$enum = [];
foreach ($baskets as $basket) {
$enum[$basket->get('basket_name')] = $basket->get('basket_name');
}
$names = [];
$basket = null;
if ($this->hasBeenSent()) {
$basketName = $this->getSentValue('basket');
if ($basketName) {
$basket = Basket::load($basketName, $this->getDb());
}
}
$count = 0;
$type = $this->type;
foreach ($this->names as $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([
'The following objects will be added: ',
$names
]));
$this->addElement('select', 'basket', [
'label' => $this->translate('Basket'),
'multiOptions' => $this->optionalEnum($enum),
'required' => true,
'class' => 'autosubmit',
]);
if ($count > 0) {
$this->setSubmitLabel(sprintf(
$this->translate('Add %s objects'),
$count
));
} else {
$this->setSubmitLabel($this->translate('Add'));
$this->addSubmitButtonIfSet();
$this->getElement($this->submitButtonName)->setAttrib('disabled', true);
}
}
public function setType($type)
{
$this->type = $type;
return $this;
}
public function setNames($names)
{
$this->names = $names;
return $this;
}
/**
* @throws \Icinga\Exception\NotFoundError
* @throws \Icinga\Module\Director\Exception\DuplicateKeyException
*/
public function onSuccess()
{
$type = $this->type;
$basket = Basket::load($this->getValue('basket'), $this->getDb());
$basketName = $basket->get('basket_name');
if (empty($this->names)) {
$this->getElement('basket')->addErrorMessage($this->translate(
'No object has been chosen'
));
}
if ($basket->supportsCustomSelectionFor($type)) {
$basket->addObjects($type, $this->names);
$basket->store();
$this->setSuccessMessage(sprintf($this->translate(
'Configuration objects have been added to the chosen basket "%s"'
), $basketName));
return parent::onSuccess();
} else {
$this->addHtmlHint(Html::tag('p', [
'class' => '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

@ -8,13 +8,13 @@ class BasketDashlet extends Dashlet
public function getTitle() public function getTitle()
{ {
return $this->translate('Object Basket'); return $this->translate('Configuration Baskets');
} }
public function getSummary() public function getSummary()
{ {
return $this->translate( return $this->translate(
'Preserve specific objects in a specific state' 'Preserve specific configuration objects in a specific state'
); );
} }

View File

@ -63,7 +63,16 @@ class Basket extends DbObject
protected function onLoadFromDb() protected function onLoadFromDb()
{ {
$this->chosenObjects = Json::decode($this->get('objects')); $this->chosenObjects = (array) Json::decode($this->get('objects'));
}
public function supportsCustomSelectionFor($type)
{
if (! array_key_exists($type, $this->chosenObjects)) {
return false;
}
return is_array($this->chosenObjects[$type]);
} }
public function setObjects($objects) public function setObjects($objects)
@ -81,17 +90,28 @@ class Basket extends DbObject
} }
/** /**
* This is a weird method, as it is required to deal with raw form data
*
* @param $type * @param $type
* @param ExportInterface[]|bool $objects * @param ExportInterface[]|bool $objects
*/ */
public function addObjects($type, $objects = true) public function addObjects($type, $objects = true)
{ {
BasketSnapshot::assertValidType($type);
// '1' -> from Form! // '1' -> from Form!
if ($objects === 'ALL') { if ($objects === 'ALL') {
$objects = true; $objects = true;
} elseif ($objects === null || $objects === 'IGNORE') { } elseif ($objects === null || $objects === 'IGNORE') {
return; return;
} elseif ($objects === '[]') { } elseif ($objects === '[]') {
if (isset($this->chosenObjects[$type])) {
if (! is_array($this->chosenObjects[$type])) {
$this->chosenObjects[$type] = [];
}
} else {
$this->chosenObjects[$type] = [];
}
$objects = []; $objects = [];
} }
@ -112,14 +132,42 @@ class Basket extends DbObject
$this->reallySet('objects', Json::encode($this->chosenObjects)); $this->reallySet('objects', Json::encode($this->chosenObjects));
} }
public function hasObject($type, $object)
{
if (! $this->hasType($type)) {
return false;
}
if ($this->chosenObjects[$type] === true) {
return true;
}
if ($object instanceof ExportInterface) {
$object = $object->getUniqueIdentifier();
}
if (is_array($this->chosenObjects[$type])) {
return in_array($object, $this->chosenObjects[$type]);
} else {
return false;
}
}
/** /**
* @param $type * @param $type
* @param string $object * @param string $object
*/ */
public function addObject($type, $object) public function addObject($type, $object)
{ {
// TODO: make sure array exists - and is not boolean if (is_array($this->chosenObjects[$type])) {
$this->chosenObjects[$type][] = $object; $this->chosenObjects[$type][] = $object;
} else {
throw new \InvalidArgumentException(sprintf(
'The Basket "%s" has not been configured for single objects of type "%s"',
$this->get('basket_name'),
$type
));
}
} }
public function hasType($type) public function hasType($type)

View File

@ -8,11 +8,29 @@ use Icinga\Module\Director\Data\Db\DbObject;
use Icinga\Module\Director\Objects\DirectorDatafield; use Icinga\Module\Director\Objects\DirectorDatafield;
use Icinga\Module\Director\Objects\IcingaCommand; use Icinga\Module\Director\Objects\IcingaCommand;
use Icinga\Module\Director\Objects\IcingaObject; use Icinga\Module\Director\Objects\IcingaObject;
use InvalidArgumentException;
use RuntimeException; use RuntimeException;
use Zend_Db_Adapter_Abstract as ZfDbAdapter;
class BasketSnapshot extends DbObject class BasketSnapshot extends DbObject
{ {
protected static $typeClasses = [
'Datafield' => '\\Icinga\\Module\\Director\\Objects\\DirectorDatafield',
'Command' => '\\Icinga\\Module\\Director\\Objects\\IcingaCommand',
'HostGroup' => '\\Icinga\\Module\\Director\\Objects\\IcingaHostGroup',
'IcingaTemplateChoiceHost' => '\\Icinga\\Module\\Director\\Objects\\IcingaTemplateChoiceHost',
'HostTemplate' => '\\Icinga\\Module\\Director\\Objects\\IcingaHost',
'ServiceGroup' => '\\Icinga\\Module\\Director\\Objects\\IcingaServiceGroup',
'IcingaTemplateChoiceService' => '\\Icinga\\Module\\Director\\Objects\\IcingaTemplateChoiceService',
'ServiceTemplate' => '\\Icinga\\Module\\Director\\Objects\\IcingaService',
'ServiceSet' => '\\Icinga\\Module\\Director\\Objects\\IcingaServiceSet',
'Notification' => '\\Icinga\\Module\\Director\\Objects\\IcingaNotification',
'Dependency' => '\\Icinga\\Module\\Director\\Objects\\IcingaDependency',
'ImportSource' => '\\Icinga\\Module\\Director\\Objects\\ImportSource',
'SyncRule' => '\\Icinga\\Module\\Director\\Objects\\SyncRule',
'DirectorJob' => '\\Icinga\\Module\\Director\\Objects\\DirectorJob',
'Basket' => '\\Icinga\\Module\\Director\\DirectorObject\\Automation\\Automation',
];
protected $objects = []; protected $objects = [];
protected $content; protected $content;
@ -47,27 +65,23 @@ class BasketSnapshot extends DbObject
'ts_create' => null, 'ts_create' => null,
]; ];
public static function supports($type)
{
return isset(self::$typeClasses[$type]);
}
public static function assertValidType($type)
{
if (! static::supports($type)) {
throw new InvalidArgumentException("Basket does not support '$type'");
}
}
public static function getClassForType($type) public static function getClassForType($type)
{ {
$types = [ static::assertValidType($type);
'Datafield' => '\\Icinga\\Module\\Director\\Objects\\DirectorDatafield',
'Command' => '\\Icinga\\Module\\Director\\Objects\\IcingaCommand',
'HostGroup' => '\\Icinga\\Module\\Director\\Objects\\IcingaHostGroup',
'IcingaTemplateChoiceHost' => '\\Icinga\\Module\\Director\\Objects\\IcingaTemplateChoiceHost',
'HostTemplate' => '\\Icinga\\Module\\Director\\Objects\\IcingaHost',
'ServiceGroup' => '\\Icinga\\Module\\Director\\Objects\\IcingaServiceGroup',
'IcingaTemplateChoiceService' => '\\Icinga\\Module\\Director\\Objects\\IcingaTemplateChoiceService',
'ServiceTemplate' => '\\Icinga\\Module\\Director\\Objects\\IcingaService',
'ServiceSet' => '\\Icinga\\Module\\Director\\Objects\\IcingaServiceSet',
'Notification' => '\\Icinga\\Module\\Director\\Objects\\IcingaNotification',
'Dependency' => '\\Icinga\\Module\\Director\\Objects\\IcingaDependency',
'ImportSource' => '\\Icinga\\Module\\Director\\Objects\\ImportSource',
'SyncRule' => '\\Icinga\\Module\\Director\\Objects\\SyncRule',
'DirectorJob' => '\\Icinga\\Module\\Director\\Objects\\DirectorJob',
'Basket' => '\\Icinga\\Module\\Director\\DirectorObject\\Automation\\Automation',
];
return $types[$type]; return self::$typeClasses[$type];
} }
/** /**
@ -189,21 +203,36 @@ class BasketSnapshot extends DbObject
$new = $class::import($object, $connection, $replace); $new = $class::import($object, $connection, $replace);
if ($new->hasBeenModified()) { if ($new->hasBeenModified()) {
if ($new instanceof IcingaObject && $new->supportsImports()) { if ($new instanceof IcingaObject && $new->supportsImports()) {
$changed[$new->getObjectName()] = $new; /** @var ExportInterface $new */
$changed[$new->getUniqueIdentifier()] = $new;
} else { } else {
$new->store(); $new->store();
// Linking fields right now, as we're not in $changed
if ($new instanceof IcingaObject) {
$fieldResolver->relinkObjectFields($new, $object);
}
}
} else {
// No modification on the object, still, fields might have
// been changed
if ($new instanceof IcingaObject) {
$fieldResolver->relinkObjectFields($new, $object);
} }
} }
$allObjects[spl_object_hash($new)] = $object;
if ($new instanceof IcingaObject) {
$fieldResolver->relinkObjectFields($new, $object);
}
} }
/** @var IcingaObject $object */ /** @var IcingaObject $object */
foreach ($changed as $object) { foreach ($changed as $object) {
$this->recursivelyStore($object, $changed); $this->recursivelyStore($object, $changed);
} }
foreach ($changed as $key => $new) {
// Store related fields. As objects might have formerly been
// unstored, let's to it right here
if ($new instanceof IcingaObject) {
$fieldResolver->relinkObjectFields($new, $objects[$key]);
}
}
} }
} }
$db->commit(); $db->commit();
@ -302,9 +331,11 @@ class BasketSnapshot extends DbObject
if ($dummy instanceof IcingaCommand) { if ($dummy instanceof IcingaCommand) {
$select = $db->select()->from($dummy->getTableName()) $select = $db->select()->from($dummy->getTableName())
->where('object_type != ?', 'external_object'); ->where('object_type != ?', 'external_object');
} else { } elseif (! $dummy->isGroup()) {
$select = $db->select()->from($dummy->getTableName()) $select = $db->select()->from($dummy->getTableName())
->where('object_type = ?', 'template'); ->where('object_type = ?', 'template');
} else {
$select = $db->select()->from($dummy->getTableName());
} }
$all = $class::loadAll($this->getConnection(), $select); $all = $class::loadAll($this->getConnection(), $select);
} else { } else {

View File

@ -6,6 +6,7 @@ use Icinga\Data\Db\DbConnection;
use Icinga\Exception\NotFoundError; use Icinga\Exception\NotFoundError;
use Icinga\Module\Director\Data\PropertiesFilter; use Icinga\Module\Director\Data\PropertiesFilter;
use Icinga\Module\Director\Db; use Icinga\Module\Director\Db;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
use Icinga\Module\Director\Exception\DuplicateKeyException; use Icinga\Module\Director\Exception\DuplicateKeyException;
use Icinga\Module\Director\IcingaConfig\IcingaConfig; use Icinga\Module\Director\IcingaConfig\IcingaConfig;
use Icinga\Module\Director\IcingaConfig\IcingaLegacyConfigHelper as c1; use Icinga\Module\Director\IcingaConfig\IcingaLegacyConfigHelper as c1;
@ -13,7 +14,7 @@ use Icinga\Module\Director\Objects\Extension\FlappingSupport;
use InvalidArgumentException; use InvalidArgumentException;
use RuntimeException; use RuntimeException;
class IcingaHost extends IcingaObject class IcingaHost extends IcingaObject implements ExportInterface
{ {
use FlappingSupport; use FlappingSupport;

View File

@ -7,6 +7,7 @@ use Icinga\Exception\IcingaException;
use Icinga\Module\Director\Data\PropertiesFilter; use Icinga\Module\Director\Data\PropertiesFilter;
use Icinga\Module\Director\Db; use Icinga\Module\Director\Db;
use Icinga\Module\Director\Db\Cache\PrefetchCache; use Icinga\Module\Director\Db\Cache\PrefetchCache;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
use Icinga\Module\Director\Exception\DuplicateKeyException; use Icinga\Module\Director\Exception\DuplicateKeyException;
use Icinga\Module\Director\IcingaConfig\IcingaConfig; use Icinga\Module\Director\IcingaConfig\IcingaConfig;
use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c; use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c;
@ -16,7 +17,7 @@ use Icinga\Module\Director\Resolver\HostServiceBlacklist;
use InvalidArgumentException; use InvalidArgumentException;
use RuntimeException; use RuntimeException;
class IcingaService extends IcingaObject class IcingaService extends IcingaObject implements ExportInterface
{ {
use FlappingSupport; use FlappingSupport;

View File

@ -6,6 +6,7 @@ use Icinga\Exception\IcingaException;
use Icinga\Exception\InvalidPropertyException; use Icinga\Exception\InvalidPropertyException;
use Icinga\Exception\NotFoundError; use Icinga\Exception\NotFoundError;
use Icinga\Module\Director\Deployment\DeploymentInfo; use Icinga\Module\Director\Deployment\DeploymentInfo;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
use Icinga\Module\Director\Forms\DeploymentLinkForm; use Icinga\Module\Director\Forms\DeploymentLinkForm;
use Icinga\Module\Director\Forms\IcingaCloneObjectForm; use Icinga\Module\Director\Forms\IcingaCloneObjectForm;
use Icinga\Module\Director\Forms\IcingaObjectFieldForm; use Icinga\Module\Director\Forms\IcingaObjectFieldForm;
@ -79,6 +80,9 @@ abstract class ObjectController extends ActionController
} }
} }
/**
* @throws NotFoundError
*/
public function indexAction() public function indexAction()
{ {
if (! $this->getRequest()->isApiRequest()) { if (! $this->getRequest()->isApiRequest()) {
@ -111,6 +115,9 @@ abstract class ObjectController extends ActionController
$this->content()->add($form); $this->content()->add($form);
} }
/**
* @throws NotFoundError
*/
public function editAction() public function editAction()
{ {
$object = $this->requireObject(); $object = $this->requireObject();
@ -118,9 +125,14 @@ abstract class ObjectController extends ActionController
$this->addObjectTitle() $this->addObjectTitle()
->addObjectForm($object) ->addObjectForm($object)
->addActionClone() ->addActionClone()
->addActionUsage(); ->addActionUsage()
->addActionBasket();
} }
/**
* @throws NotFoundError
* @throws \Icinga\Security\SecurityException
*/
public function renderAction() public function renderAction()
{ {
$this->assertTypePermission() $this->assertTypePermission()
@ -133,6 +145,9 @@ abstract class ObjectController extends ActionController
$preview->renderTo($this); $preview->renderTo($this);
} }
/**
* @throws NotFoundError
*/
public function cloneAction() public function cloneAction()
{ {
$this->assertTypePermission(); $this->assertTypePermission();
@ -151,6 +166,10 @@ abstract class ObjectController extends ActionController
->content()->add($form); ->content()->add($form);
} }
/**
* @throws NotFoundError
* @throws \Icinga\Security\SecurityException
*/
public function fieldsAction() public function fieldsAction()
{ {
$this->assertPermission('director/admin'); $this->assertPermission('director/admin');
@ -187,6 +206,10 @@ abstract class ObjectController extends ActionController
$table->renderTo($this); $table->renderTo($this);
} }
/**
* @throws NotFoundError
* @throws \Icinga\Security\SecurityException
*/
public function historyAction() public function historyAction()
{ {
$this $this
@ -206,6 +229,9 @@ abstract class ObjectController extends ActionController
->renderTo($this); ->renderTo($this);
} }
/**
* @throws NotFoundError
*/
public function membershipAction() public function membershipAction()
{ {
$object = $this->requireObject(); $object = $this->requireObject();
@ -224,6 +250,10 @@ abstract class ObjectController extends ActionController
->renderTo($this); ->renderTo($this);
} }
/**
* @return $this
* @throws NotFoundError
*/
protected function addObjectTitle() protected function addObjectTitle()
{ {
$object = $this->requireObject(); $object = $this->requireObject();
@ -271,6 +301,37 @@ abstract class ObjectController extends ActionController
return $this; return $this;
} }
/**
* @return $this
*/
protected function addActionBasket()
{
if ($this->hasBasketSupport()) {
$object = $this->object;
if ($object instanceof ExportInterface) {
if ($object->isTemplate()) {
$type = $this->getType() . 'Template';
} elseif ($object->isGroup()) {
$type = ucfirst($this->getType());
} else {
// Command? Sure?
$type = ucfirst($this->getType());
}
$this->actions()->add(Link::create(
$this->translate('Add to Basket'),
'director/basket/add',
[
'type' => $type,
'names' => $object->getUniqueIdentifier()
],
['class' => 'icon-tag']
));
}
}
return $this;
}
protected function addTemplate() protected function addTemplate()
{ {
$this->assertPermission('director/admin'); $this->assertPermission('director/admin');
@ -450,10 +511,20 @@ abstract class ObjectController extends ActionController
return $form; return $form;
} }
protected function hasBasketSupport()
{
return $this->object->isTemplate() || $this->object->isGroup();
}
protected function onObjectFormLoaded(DirectorObjectForm $form) protected function onObjectFormLoaded(DirectorObjectForm $form)
{ {
} }
/**
* @return IcingaObject
* @throws NotFoundError
* @throws \Zend_Controller_Response_Exception
*/
protected function requireObject() protected function requireObject()
{ {
if (! $this->object) { if (! $this->object) {

View File

@ -56,7 +56,6 @@ abstract class ObjectsController extends ActionController
/** /**
* @return IcingaObjectsHandler * @return IcingaObjectsHandler
* @throws \Icinga\Exception\ConfigurationError
* @throws NotFoundError * @throws NotFoundError
*/ */
protected function apiRequestHandler() protected function apiRequestHandler()
@ -82,7 +81,6 @@ abstract class ObjectsController extends ActionController
} }
/** /**
* @throws \Icinga\Exception\ConfigurationError
* @throws \Icinga\Exception\Http\HttpNotFoundException * @throws \Icinga\Exception\Http\HttpNotFoundException
* @throws NotFoundError * @throws NotFoundError
*/ */
@ -124,7 +122,6 @@ abstract class ObjectsController extends ActionController
/** /**
* @return ObjectsTable * @return ObjectsTable
* @throws \Icinga\Exception\ConfigurationError
*/ */
protected function getTable() protected function getTable()
{ {
@ -134,9 +131,24 @@ abstract class ObjectsController extends ActionController
/** /**
* @throws NotFoundError * @throws NotFoundError
* @throws \Icinga\Exception\ConfigurationError */
public function edittemplatesAction()
{
$this->commonForEdit();
}
/**
* @throws NotFoundError
*/ */
public function editAction() public function editAction()
{
$this->commonForEdit();
}
/**
* @throws NotFoundError
*/
public function commonForEdit()
{ {
$type = ucfirst($this->getType()); $type = ucfirst($this->getType());
@ -293,7 +305,7 @@ abstract class ObjectsController extends ActionController
/** /**
* @return array * @return array
* @throws \Icinga\Exception\ConfigurationError * @throws NotFoundError
*/ */
protected function loadMultiObjectsFromParams() protected function loadMultiObjectsFromParams()
{ {
@ -341,7 +353,6 @@ abstract class ObjectsController extends ActionController
/** /**
* @param ZfQueryBasedTable $table * @param ZfQueryBasedTable $table
* @return ZfQueryBasedTable * @return ZfQueryBasedTable
* @throws \Icinga\Exception\ConfigurationError
* @throws NotFoundError * @throws NotFoundError
*/ */
protected function eventuallyFilterCommand(ZfQueryBasedTable $table) protected function eventuallyFilterCommand(ZfQueryBasedTable $table)

View File

@ -2,6 +2,7 @@
namespace Icinga\Module\Director\Web\Controller; namespace Icinga\Module\Director\Web\Controller;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
use Icinga\Module\Director\Objects\IcingaObject; use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Web\Controller\Extension\DirectorDb; use Icinga\Module\Director\Web\Controller\Extension\DirectorDb;
use Icinga\Module\Director\Web\Table\ApplyRulesTable; use Icinga\Module\Director\Web\Table\ApplyRulesTable;
@ -115,6 +116,18 @@ abstract class TemplateController extends CompatController
['class' => 'icon-edit'] ['class' => 'icon-edit']
) )
]); ]);
if ($template instanceof ExportInterface) {
$this->actions()->add(Link::create(
$this->translate('Add to Basket'),
'director/basket/add',
[
'type' => ucfirst($this->getType()) . 'Template',
'names' => $template->getUniqueIdentifier()
],
['class' => 'icon-tag']
));
}
$list = new UnorderedList([], [ $list = new UnorderedList([], [
'class' => 'vertical-action-list' 'class' => 'vertical-action-list'
]); ]);

View File

@ -29,8 +29,7 @@ class ObjectPreview
/** /**
* @param ControlsAndContent $cc * @param ControlsAndContent $cc
* @throws \Icinga\Exception\IcingaException * @throws \Icinga\Exception\NotFoundError
* @throws \Icinga\Exception\ProgrammingError
*/ */
public function renderTo(ControlsAndContent $cc) public function renderTo(ControlsAndContent $cc)
{ {

View File

@ -2,6 +2,7 @@
namespace Icinga\Module\Director\Web\Table; namespace Icinga\Module\Director\Web\Table;
use dipl\Web\Table\Extension\MultiSelect;
use Icinga\Authentication\Auth; use Icinga\Authentication\Auth;
use Icinga\Data\Filter\Filter; use Icinga\Data\Filter\Filter;
use Icinga\Module\Director\Db; use Icinga\Module\Director\Db;
@ -17,6 +18,8 @@ use Zend_Db_Select as ZfSelect;
class TemplatesTable extends ZfQueryBasedTable class TemplatesTable extends ZfQueryBasedTable
{ {
use MultiSelect;
protected $searchColumns = ['o.object_name']; protected $searchColumns = ['o.object_name'];
private $type; private $type;
@ -28,6 +31,16 @@ class TemplatesTable extends ZfQueryBasedTable
return $table; return $table;
} }
protected function assemble()
{
$type = $this->type;
$this->enableMultiSelect(
"director/${type}s/edittemplates",
"director/${type}template",
['name']
);
}
public function getType() public function getType()
{ {
return $this->type; return $this->type;