diff --git a/application/controllers/BasketController.php b/application/controllers/BasketController.php index 54076c29..14072705 100644 --- a/application/controllers/BasketController.php +++ b/application/controllers/BasketController.php @@ -12,6 +12,7 @@ use Icinga\Module\Director\Db; use Icinga\Module\Director\DirectorObject\Automation\Basket; use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshot; use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshotFieldResolver; +use Icinga\Module\Director\Forms\AddToBasketForm; use Icinga\Module\Director\Forms\BasketCreateSnapshotForm; use Icinga\Module\Director\Forms\BasketForm; 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() { $this->actions()->add( diff --git a/application/controllers/HostsController.php b/application/controllers/HostsController.php index 9091943c..6a2e6ecd 100644 --- a/application/controllers/HostsController.php +++ b/application/controllers/HostsController.php @@ -2,9 +2,11 @@ namespace Icinga\Module\Director\Controllers; +use dipl\Web\Url; use Icinga\Data\Filter\Filter; use Icinga\Data\Filter\FilterChain; use Icinga\Data\Filter\FilterExpression; +use Icinga\Module\Director\DirectorObject\Automation\ExportInterface; use Icinga\Module\Director\Forms\IcingaAddServiceForm; use Icinga\Module\Director\Forms\IcingaAddServiceSetForm; 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() { $this->addSingleTab($this->translate('Add Service')); diff --git a/application/controllers/ImportsourceController.php b/application/controllers/ImportsourceController.php index 5a59fa9e..3e5d8903 100644 --- a/application/controllers/ImportsourceController.php +++ b/application/controllers/ImportsourceController.php @@ -49,6 +49,17 @@ class ImportsourceController extends ActionController $this->actions(new AutomationObjectActionBar( $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'] + )); } /** diff --git a/application/controllers/JobController.php b/application/controllers/JobController.php index c4db76bd..68fd0277 100644 --- a/application/controllers/JobController.php +++ b/application/controllers/JobController.php @@ -2,6 +2,7 @@ namespace Icinga\Module\Director\Controllers; +use dipl\Html\Link; use Icinga\Module\Director\Forms\DirectorJobForm; use Icinga\Module\Director\Web\Controller\ActionController; use Icinga\Module\Director\Objects\DirectorJob; @@ -19,6 +20,7 @@ class JobController extends ActionController $this ->addJobTabs($job, 'show') ->addTitle($this->translate('Job: %s'), $job->get('job_name')) + ->addToBasketLink() ->content()->add(new JobDetails($job)); } @@ -50,6 +52,7 @@ class JobController extends ActionController $this ->addJobTabs($job, 'edit') ->addTitle($this->translate('Job: %s'), $job->get('job_name')) + ->addToBasketLink() ->content()->add($form); } @@ -63,6 +66,27 @@ class JobController extends ActionController 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) { $id = $job->get('id'); diff --git a/application/controllers/SyncruleController.php b/application/controllers/SyncruleController.php index f1cb12ea..42ac4575 100644 --- a/application/controllers/SyncruleController.php +++ b/application/controllers/SyncruleController.php @@ -45,6 +45,15 @@ class SyncruleController extends ActionController $this->addPropertyHint($rule); return; } + $this->actions()->add(Link::create( + $this->translate('Add to Basket'), + 'director/basket/add', + [ + 'type' => 'SyncRule', + 'names' => $rule->getUniqueIdentifier() + ], + ['class' => 'icon-tag'] + )); if (! $run) { $this->warning($this->translate('This Sync Rule has never been run before.')); @@ -159,6 +168,15 @@ class SyncruleController extends ActionController ['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()) { $this->addPropertyHint($rule); diff --git a/application/forms/AddToBasketForm.php b/application/forms/AddToBasketForm.php new file mode 100644 index 00000000..165a53ff --- /dev/null +++ b/application/forms/AddToBasketForm.php @@ -0,0 +1,131 @@ +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; + } + } +} diff --git a/library/Director/Dashboard/Dashlet/BasketDashlet.php b/library/Director/Dashboard/Dashlet/BasketDashlet.php index 12027552..10f2b815 100644 --- a/library/Director/Dashboard/Dashlet/BasketDashlet.php +++ b/library/Director/Dashboard/Dashlet/BasketDashlet.php @@ -8,13 +8,13 @@ class BasketDashlet extends Dashlet public function getTitle() { - return $this->translate('Object Basket'); + return $this->translate('Configuration Baskets'); } public function getSummary() { return $this->translate( - 'Preserve specific objects in a specific state' + 'Preserve specific configuration objects in a specific state' ); } diff --git a/library/Director/DirectorObject/Automation/Basket.php b/library/Director/DirectorObject/Automation/Basket.php index 386e9973..1301cb3c 100644 --- a/library/Director/DirectorObject/Automation/Basket.php +++ b/library/Director/DirectorObject/Automation/Basket.php @@ -63,7 +63,16 @@ class Basket extends DbObject 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) @@ -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 ExportInterface[]|bool $objects */ public function addObjects($type, $objects = true) { + BasketSnapshot::assertValidType($type); + // '1' -> from Form! if ($objects === 'ALL') { $objects = true; } elseif ($objects === null || $objects === 'IGNORE') { return; } elseif ($objects === '[]') { + if (isset($this->chosenObjects[$type])) { + if (! is_array($this->chosenObjects[$type])) { + $this->chosenObjects[$type] = []; + } + } else { + $this->chosenObjects[$type] = []; + } $objects = []; } @@ -112,14 +132,42 @@ class Basket extends DbObject $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 string $object */ public function addObject($type, $object) { - // TODO: make sure array exists - and is not boolean - $this->chosenObjects[$type][] = $object; + if (is_array($this->chosenObjects[$type])) { + $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) diff --git a/library/Director/DirectorObject/Automation/BasketSnapshot.php b/library/Director/DirectorObject/Automation/BasketSnapshot.php index fb63da53..d4fbc6d1 100644 --- a/library/Director/DirectorObject/Automation/BasketSnapshot.php +++ b/library/Director/DirectorObject/Automation/BasketSnapshot.php @@ -8,11 +8,29 @@ use Icinga\Module\Director\Data\Db\DbObject; use Icinga\Module\Director\Objects\DirectorDatafield; use Icinga\Module\Director\Objects\IcingaCommand; use Icinga\Module\Director\Objects\IcingaObject; +use InvalidArgumentException; use RuntimeException; -use Zend_Db_Adapter_Abstract as ZfDbAdapter; 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 $content; @@ -47,27 +65,23 @@ class BasketSnapshot extends DbObject '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) { - $types = [ - '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', - ]; + static::assertValidType($type); - return $types[$type]; + return self::$typeClasses[$type]; } /** @@ -189,21 +203,36 @@ class BasketSnapshot extends DbObject $new = $class::import($object, $connection, $replace); if ($new->hasBeenModified()) { if ($new instanceof IcingaObject && $new->supportsImports()) { - $changed[$new->getObjectName()] = $new; + /** @var ExportInterface $new */ + $changed[$new->getUniqueIdentifier()] = $new; } else { $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); } } - - if ($new instanceof IcingaObject) { - $fieldResolver->relinkObjectFields($new, $object); - } + $allObjects[spl_object_hash($new)] = $object; } /** @var IcingaObject $object */ foreach ($changed as $object) { $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(); @@ -302,9 +331,11 @@ class BasketSnapshot extends DbObject if ($dummy instanceof IcingaCommand) { $select = $db->select()->from($dummy->getTableName()) ->where('object_type != ?', 'external_object'); - } else { + } elseif (! $dummy->isGroup()) { $select = $db->select()->from($dummy->getTableName()) ->where('object_type = ?', 'template'); + } else { + $select = $db->select()->from($dummy->getTableName()); } $all = $class::loadAll($this->getConnection(), $select); } else { diff --git a/library/Director/Objects/IcingaHost.php b/library/Director/Objects/IcingaHost.php index 0aceafe6..3e8e45be 100644 --- a/library/Director/Objects/IcingaHost.php +++ b/library/Director/Objects/IcingaHost.php @@ -6,6 +6,7 @@ use Icinga\Data\Db\DbConnection; use Icinga\Exception\NotFoundError; use Icinga\Module\Director\Data\PropertiesFilter; use Icinga\Module\Director\Db; +use Icinga\Module\Director\DirectorObject\Automation\ExportInterface; use Icinga\Module\Director\Exception\DuplicateKeyException; use Icinga\Module\Director\IcingaConfig\IcingaConfig; use Icinga\Module\Director\IcingaConfig\IcingaLegacyConfigHelper as c1; @@ -13,7 +14,7 @@ use Icinga\Module\Director\Objects\Extension\FlappingSupport; use InvalidArgumentException; use RuntimeException; -class IcingaHost extends IcingaObject +class IcingaHost extends IcingaObject implements ExportInterface { use FlappingSupport; diff --git a/library/Director/Objects/IcingaService.php b/library/Director/Objects/IcingaService.php index fc13baec..e97a709f 100644 --- a/library/Director/Objects/IcingaService.php +++ b/library/Director/Objects/IcingaService.php @@ -7,6 +7,7 @@ use Icinga\Exception\IcingaException; use Icinga\Module\Director\Data\PropertiesFilter; use Icinga\Module\Director\Db; use Icinga\Module\Director\Db\Cache\PrefetchCache; +use Icinga\Module\Director\DirectorObject\Automation\ExportInterface; use Icinga\Module\Director\Exception\DuplicateKeyException; use Icinga\Module\Director\IcingaConfig\IcingaConfig; use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c; @@ -16,7 +17,7 @@ use Icinga\Module\Director\Resolver\HostServiceBlacklist; use InvalidArgumentException; use RuntimeException; -class IcingaService extends IcingaObject +class IcingaService extends IcingaObject implements ExportInterface { use FlappingSupport; diff --git a/library/Director/Web/Controller/ObjectController.php b/library/Director/Web/Controller/ObjectController.php index c1a0d4e3..83b67260 100644 --- a/library/Director/Web/Controller/ObjectController.php +++ b/library/Director/Web/Controller/ObjectController.php @@ -6,6 +6,7 @@ use Icinga\Exception\IcingaException; use Icinga\Exception\InvalidPropertyException; use Icinga\Exception\NotFoundError; use Icinga\Module\Director\Deployment\DeploymentInfo; +use Icinga\Module\Director\DirectorObject\Automation\ExportInterface; use Icinga\Module\Director\Forms\DeploymentLinkForm; use Icinga\Module\Director\Forms\IcingaCloneObjectForm; use Icinga\Module\Director\Forms\IcingaObjectFieldForm; @@ -79,6 +80,9 @@ abstract class ObjectController extends ActionController } } + /** + * @throws NotFoundError + */ public function indexAction() { if (! $this->getRequest()->isApiRequest()) { @@ -111,6 +115,9 @@ abstract class ObjectController extends ActionController $this->content()->add($form); } + /** + * @throws NotFoundError + */ public function editAction() { $object = $this->requireObject(); @@ -118,9 +125,14 @@ abstract class ObjectController extends ActionController $this->addObjectTitle() ->addObjectForm($object) ->addActionClone() - ->addActionUsage(); + ->addActionUsage() + ->addActionBasket(); } + /** + * @throws NotFoundError + * @throws \Icinga\Security\SecurityException + */ public function renderAction() { $this->assertTypePermission() @@ -133,6 +145,9 @@ abstract class ObjectController extends ActionController $preview->renderTo($this); } + /** + * @throws NotFoundError + */ public function cloneAction() { $this->assertTypePermission(); @@ -151,6 +166,10 @@ abstract class ObjectController extends ActionController ->content()->add($form); } + /** + * @throws NotFoundError + * @throws \Icinga\Security\SecurityException + */ public function fieldsAction() { $this->assertPermission('director/admin'); @@ -187,6 +206,10 @@ abstract class ObjectController extends ActionController $table->renderTo($this); } + /** + * @throws NotFoundError + * @throws \Icinga\Security\SecurityException + */ public function historyAction() { $this @@ -206,6 +229,9 @@ abstract class ObjectController extends ActionController ->renderTo($this); } + /** + * @throws NotFoundError + */ public function membershipAction() { $object = $this->requireObject(); @@ -224,6 +250,10 @@ abstract class ObjectController extends ActionController ->renderTo($this); } + /** + * @return $this + * @throws NotFoundError + */ protected function addObjectTitle() { $object = $this->requireObject(); @@ -271,6 +301,37 @@ abstract class ObjectController extends ActionController 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() { $this->assertPermission('director/admin'); @@ -450,10 +511,20 @@ abstract class ObjectController extends ActionController return $form; } + protected function hasBasketSupport() + { + return $this->object->isTemplate() || $this->object->isGroup(); + } + protected function onObjectFormLoaded(DirectorObjectForm $form) { } + /** + * @return IcingaObject + * @throws NotFoundError + * @throws \Zend_Controller_Response_Exception + */ protected function requireObject() { if (! $this->object) { diff --git a/library/Director/Web/Controller/ObjectsController.php b/library/Director/Web/Controller/ObjectsController.php index a07aa781..618a4035 100644 --- a/library/Director/Web/Controller/ObjectsController.php +++ b/library/Director/Web/Controller/ObjectsController.php @@ -56,7 +56,6 @@ abstract class ObjectsController extends ActionController /** * @return IcingaObjectsHandler - * @throws \Icinga\Exception\ConfigurationError * @throws NotFoundError */ protected function apiRequestHandler() @@ -82,7 +81,6 @@ abstract class ObjectsController extends ActionController } /** - * @throws \Icinga\Exception\ConfigurationError * @throws \Icinga\Exception\Http\HttpNotFoundException * @throws NotFoundError */ @@ -124,7 +122,6 @@ abstract class ObjectsController extends ActionController /** * @return ObjectsTable - * @throws \Icinga\Exception\ConfigurationError */ protected function getTable() { @@ -134,9 +131,24 @@ abstract class ObjectsController extends ActionController /** * @throws NotFoundError - * @throws \Icinga\Exception\ConfigurationError + */ + public function edittemplatesAction() + { + $this->commonForEdit(); + } + + /** + * @throws NotFoundError */ public function editAction() + { + $this->commonForEdit(); + } + + /** + * @throws NotFoundError + */ + public function commonForEdit() { $type = ucfirst($this->getType()); @@ -293,7 +305,7 @@ abstract class ObjectsController extends ActionController /** * @return array - * @throws \Icinga\Exception\ConfigurationError + * @throws NotFoundError */ protected function loadMultiObjectsFromParams() { @@ -341,7 +353,6 @@ abstract class ObjectsController extends ActionController /** * @param ZfQueryBasedTable $table * @return ZfQueryBasedTable - * @throws \Icinga\Exception\ConfigurationError * @throws NotFoundError */ protected function eventuallyFilterCommand(ZfQueryBasedTable $table) diff --git a/library/Director/Web/Controller/TemplateController.php b/library/Director/Web/Controller/TemplateController.php index 001ff126..b2768143 100644 --- a/library/Director/Web/Controller/TemplateController.php +++ b/library/Director/Web/Controller/TemplateController.php @@ -2,6 +2,7 @@ namespace Icinga\Module\Director\Web\Controller; +use Icinga\Module\Director\DirectorObject\Automation\ExportInterface; use Icinga\Module\Director\Objects\IcingaObject; use Icinga\Module\Director\Web\Controller\Extension\DirectorDb; use Icinga\Module\Director\Web\Table\ApplyRulesTable; @@ -115,6 +116,18 @@ abstract class TemplateController extends CompatController ['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([], [ 'class' => 'vertical-action-list' ]); diff --git a/library/Director/Web/ObjectPreview.php b/library/Director/Web/ObjectPreview.php index d7032dde..96eda238 100644 --- a/library/Director/Web/ObjectPreview.php +++ b/library/Director/Web/ObjectPreview.php @@ -29,8 +29,7 @@ class ObjectPreview /** * @param ControlsAndContent $cc - * @throws \Icinga\Exception\IcingaException - * @throws \Icinga\Exception\ProgrammingError + * @throws \Icinga\Exception\NotFoundError */ public function renderTo(ControlsAndContent $cc) { diff --git a/library/Director/Web/Table/TemplatesTable.php b/library/Director/Web/Table/TemplatesTable.php index e5ca183f..45bb235d 100644 --- a/library/Director/Web/Table/TemplatesTable.php +++ b/library/Director/Web/Table/TemplatesTable.php @@ -2,6 +2,7 @@ namespace Icinga\Module\Director\Web\Table; +use dipl\Web\Table\Extension\MultiSelect; use Icinga\Authentication\Auth; use Icinga\Data\Filter\Filter; use Icinga\Module\Director\Db; @@ -17,6 +18,8 @@ use Zend_Db_Select as ZfSelect; class TemplatesTable extends ZfQueryBasedTable { + use MultiSelect; + protected $searchColumns = ['o.object_name']; private $type; @@ -28,6 +31,16 @@ class TemplatesTable extends ZfQueryBasedTable return $table; } + protected function assemble() + { + $type = $this->type; + $this->enableMultiSelect( + "director/${type}s/edittemplates", + "director/${type}template", + ['name'] + ); + } + public function getType() { return $this->type;