diff options
Diffstat (limited to 'library/Director/Web/Controller/ObjectsController.php')
-rw-r--r-- | library/Director/Web/Controller/ObjectsController.php | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/library/Director/Web/Controller/ObjectsController.php b/library/Director/Web/Controller/ObjectsController.php new file mode 100644 index 0000000..8c10b44 --- /dev/null +++ b/library/Director/Web/Controller/ObjectsController.php @@ -0,0 +1,548 @@ +<?php + +namespace Icinga\Module\Director\Web\Controller; + +use gipfl\IcingaWeb2\Table\ZfQueryBasedTable; +use Icinga\Data\Filter\FilterChain; +use Icinga\Data\Filter\FilterExpression; +use Icinga\Exception\NotFoundError; +use Icinga\Data\Filter\Filter; +use Icinga\Module\Director\Data\Db\DbObjectStore; +use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry; +use Icinga\Module\Director\Forms\IcingaMultiEditForm; +use Icinga\Module\Director\Objects\IcingaCommand; +use Icinga\Module\Director\Objects\IcingaHost; +use Icinga\Module\Director\Objects\IcingaObject; +use Icinga\Module\Director\RestApi\IcingaObjectsHandler; +use Icinga\Module\Director\Web\ActionBar\ObjectsActionBar; +use Icinga\Module\Director\Web\ActionBar\TemplateActionBar; +use Icinga\Module\Director\Web\Form\FormLoader; +use Icinga\Module\Director\Web\Table\ApplyRulesTable; +use Icinga\Module\Director\Web\Table\ObjectSetTable; +use Icinga\Module\Director\Web\Table\ObjectsTable; +use Icinga\Module\Director\Web\Table\TemplatesTable; +use Icinga\Module\Director\Web\Tabs\ObjectsTabs; +use Icinga\Module\Director\Web\Tree\TemplateTreeRenderer; +use gipfl\IcingaWeb2\Link; +use Icinga\Module\Director\Web\Widget\AdditionalTableActions; +use Icinga\Module\Director\Web\Widget\BranchedObjectsHint; +use InvalidArgumentException; +use Ramsey\Uuid\Uuid; + +abstract class ObjectsController extends ActionController +{ + use BranchHelper; + + protected $isApified = true; + + /** @var ObjectsTable */ + protected $table; + + protected function checkDirectorPermissions() + { + if ($this->getRequest()->getActionName() !== 'sets') { + $this->assertPermission('director/' . $this->getPluralBaseType()); + } + } + + /** + * @return $this + * @throws \Icinga\Exception\Http\HttpNotFoundException + */ + protected function addObjectsTabs() + { + $tabName = $this->getRequest()->getActionName(); + if (substr($this->getType(), -5) === 'Group') { + $tabName = 'groups'; + } + $this->tabs(new ObjectsTabs( + $this->getBaseType(), + $this->Auth(), + $this->getBaseObjectUrl() + ))->activate($tabName); + + return $this; + } + + /** + * @return IcingaObjectsHandler + * @throws NotFoundError + */ + protected function apiRequestHandler() + { + $request = $this->getRequest(); + $table = $this->getTable(); + if ($request->getControllerName() === 'services' + && $host = $this->params->get('host') + ) { + $host = IcingaHost::load($host, $this->db()); + $table->getQuery()->where('o.host_id = ?', $host->get('id')); + } + + if ($request->getActionName() === 'templates') { + $table->filterObjectType('template'); + } elseif ($request->getActionName() === 'applyrules') { + $table->filterObjectType('apply'); + } + $search = $this->params->get('q'); + if ($search !== null && \strlen($search) > 0) { + $table->search($search); + } + + return (new IcingaObjectsHandler( + $request, + $this->getResponse(), + $this->db() + ))->setTable($table); + } + + /** + * @throws \Icinga\Exception\Http\HttpNotFoundException + * @throws NotFoundError + */ + public function indexAction() + { + if ($this->getRequest()->isApiRequest()) { + $this->apiRequestHandler()->dispatch(); + return; + } + + $type = $this->getType(); + if ($this->params->get('format') === 'json') { + $filename = sprintf( + "director-${type}_%s.json", + date('YmdHis') + ); + $this->getResponse()->setHeader('Content-disposition', "attachment; filename=$filename", true); + $this->apiRequestHandler()->dispatch(); + return; + } + + $this + ->addObjectsTabs() + ->setAutorefreshInterval(10) + ->addTitle($this->translate(ucfirst($this->getPluralType()))) + ->actions(new ObjectsActionBar($this->getBaseObjectUrl(), $this->url())); + + $this->content()->add(new BranchedObjectsHint($this->getBranch(), $this->Auth())); + + if ($type === 'command' && $this->params->get('type') === 'external_object') { + $this->tabs()->activate('external'); + } + + // Hint: might be used in controllers extending this + $this->table = $this->eventuallyFilterCommand($this->getTable()); + + $this->table->renderTo($this); + (new AdditionalTableActions($this->getAuth(), $this->url(), $this->table)) + ->appendTo($this->actions()); + } + + /** + * @return ObjectsTable + */ + protected function getTable() + { + $table = ObjectsTable::create($this->getType(), $this->db()) + ->setAuth($this->getAuth()) + ->setBranchUuid($this->getBranchUuid()) + ->setBaseObjectUrl($this->getBaseObjectUrl()); + + return $table; + } + + /** + * @return ApplyRulesTable + * @throws NotFoundError + */ + protected function getApplyRulesTable() + { + $table = new ApplyRulesTable($this->db()); + $table->setType($this->getType()) + ->setBaseObjectUrl($this->getBaseObjectUrl()); + $this->eventuallyFilterCommand($table); + + return $table; + } + + /** + * @throws NotFoundError + */ + public function edittemplatesAction() + { + $this->commonForEdit(); + } + + /** + * @throws NotFoundError + */ + public function editAction() + { + $this->commonForEdit(); + } + + /** + * @throws NotFoundError + */ + public function commonForEdit() + { + $type = ucfirst($this->getType()); + + if (empty($this->multiEdit)) { + throw new NotFoundError('Cannot edit multiple "%s" instances', $type); + } + + $objects = $this->loadMultiObjectsFromParams(); + if (empty($objects)) { + throw new NotFoundError('No "%s" instances have been loaded', $type); + } + $formName = 'icinga' . $type; + $form = IcingaMultiEditForm::load() + ->setBranch($this->getBranch()) + ->setObjects($objects) + ->pickElementsFrom($this->loadForm($formName), $this->multiEdit); + if ($type === 'Service') { + $form->setListUrl('director/services'); + } elseif ($type === 'Host') { + $form->setListUrl('director/hosts'); + } + + $form->handleRequest(); + + $this + ->addSingleTab($this->translate('Multiple objects')) + ->addTitle( + $this->translate('Modify %d objects'), + count($objects) + )->content()->add($form); + } + + /** + * Loads the TemplatesTable or the TemplateTreeRenderer + * + * Passing render=tree switches to the tree view. + * @throws \Icinga\Exception\Http\HttpNotFoundException + * @throws \Icinga\Security\SecurityException + * @throws NotFoundError + */ + public function templatesAction() + { + if ($this->getRequest()->isApiRequest()) { + $this->apiRequestHandler()->dispatch(); + return; + } + $type = $this->getType(); + + if ($this->params->get('format') === 'json') { + $filename = sprintf( + "director-${type}-templates_%s.json", + date('YmdHis') + ); + $this->getResponse()->setHeader('Content-disposition', "attachment; filename=$filename", true); + $this->apiRequestHandler()->dispatch(); + return; + } + + $shortType = IcingaObject::createByType($type)->getShortTableName(); + $this + ->assertPermission('director/admin') + ->addObjectsTabs() + ->setAutorefreshInterval(10) + ->addTitle( + $this->translate('All your %s Templates'), + $this->translate(ucfirst($type)) + ) + ->actions(new TemplateActionBar($shortType, $this->url())); + + if ($this->params->get('render') === 'tree') { + TemplateTreeRenderer::showType($shortType, $this, $this->db()); + } else { + $table = TemplatesTable::create($shortType, $this->db()); + $this->eventuallyFilterCommand($table); + $table->renderTo($this); + (new AdditionalTableActions($this->getAuth(), $this->url(), $table)) + ->appendTo($this->actions()); + } + } + + /** + * @return $this + * @throws \Icinga\Security\SecurityException + */ + protected function assertApplyRulePermission() + { + return $this->assertPermission('director/admin'); + } + + /** + * @throws \Icinga\Exception\Http\HttpNotFoundException + * @throws \Icinga\Security\SecurityException + * @throws NotFoundError + */ + public function applyrulesAction() + { + if ($this->getRequest()->isApiRequest()) { + $this->apiRequestHandler()->dispatch(); + return; + } + + $type = $this->getType(); + + if ($this->params->get('format') === 'json') { + $filename = sprintf( + "director-${type}-applyrules_%s.json", + date('YmdHis') + ); + $this->getResponse()->setHeader('Content-disposition', "attachment; filename=$filename", true); + $this->apiRequestHandler()->dispatch(); + return; + } + + $tType = $this->translate(ucfirst($type)); + $this + ->assertApplyRulePermission() + ->addObjectsTabs() + ->setAutorefreshInterval(10) + ->addTitle( + $this->translate('All your %s Apply Rules'), + $tType + ); + $baseUrl = 'director/' . $this->getBaseObjectUrl(); + $this->actions() + //->add($this->getBackToDashboardLink()) + ->add( + Link::create( + $this->translate('Add'), + "${baseUrl}/add", + ['type' => 'apply'], + [ + 'title' => sprintf( + $this->translate('Create a new %s Apply Rule'), + $tType + ), + 'class' => 'icon-plus', + 'data-base-target' => '_next' + ] + ) + ); + + $table = $this->getApplyRulesTable(); + $table->renderTo($this); + (new AdditionalTableActions($this->getAuth(), $this->url(), $table)) + ->appendTo($this->actions()); + } + + /** + * @throws NotFoundError + * @throws \Icinga\Exception\Http\HttpNotFoundException + * @throws \Icinga\Security\SecurityException + */ + public function setsAction() + { + $type = $this->getType(); + $tType = $this->translate(ucfirst($type)); + $this + ->assertPermission('director/' . $this->getBaseType() . 'sets') + ->addObjectsTabs() + ->requireSupportFor('Sets') + ->setAutorefreshInterval(10) + ->addTitle( + $this->translate('Icinga %s Sets'), + $tType + ); + + $this->actions()->add( + Link::create( + $this->translate('Add'), + "director/${type}set/add", + null, + [ + 'title' => sprintf( + $this->translate('Create a new %s Set'), + $tType + ), + 'class' => 'icon-plus', + 'data-base-target' => '_next' + ] + ) + ); + + ObjectSetTable::create($type, $this->db(), $this->getAuth()) + ->setBranch($this->getBranch()) + ->renderTo($this); + } + + /** + * @return array + * @throws NotFoundError + */ + protected function loadMultiObjectsFromParams() + { + $filter = Filter::fromQueryString($this->params->toString()); + $type = $this->getType(); + $objects = array(); + $db = $this->db(); + $class = DbObjectTypeRegistry::classByType($type); + $table = DbObjectTypeRegistry::tableNameByType($type); + $store = new DbObjectStore($db, $this->getBranch()); + + /** @var $filter FilterChain */ + foreach ($filter->filters() as $sub) { + /** @var $sub FilterChain */ + foreach ($sub->filters() as $ex) { + /** @var $ex FilterChain|FilterExpression */ + $col = $ex->getColumn(); + if ($ex->isExpression()) { + if ($col === 'name') { + $name = $ex->getExpression(); + if ($type === 'service') { + $key = [ + 'object_type' => 'template', + 'object_name' => $name + ]; + } else { + $key = $name; + } + $objects[$name] = $class::load($key, $db); + } elseif ($col === 'id') { + $name = $ex->getExpression(); + $objects[$name] = $class::load($name, $db); + } elseif ($col === 'uuid') { + $object = $store->load($table, Uuid::fromString($ex->getExpression())); + $objects[$object->getObjectName()] = $object; + } else { + throw new InvalidArgumentException("'$col' is no a valid key component for '$type'"); + } + } + } + } + + return $objects; + } + + /** + * @param string $name + * + * @return \Icinga\Module\Director\Web\Form\QuickForm + */ + public function loadForm($name) + { + $form = FormLoader::load($name, $this->Module()); + if ($this->getRequest()->isApiRequest()) { + // TODO: Ask form for API support? + $form->setApiRequest(); + } + + return $form; + } + + /** + * @param ZfQueryBasedTable $table + * @return ZfQueryBasedTable + * @throws NotFoundError + */ + protected function eventuallyFilterCommand(ZfQueryBasedTable $table) + { + if ($this->params->get('command')) { + $command = IcingaCommand::load($this->params->get('command'), $this->db()); + switch ($this->getBaseType()) { + case 'host': + case 'service': + $table->getQuery()->where( + $this->db()->getDbAdapter()->quoteInto( + '(o.check_command_id = ? OR o.event_command_id = ?)', + $command->getAutoincId() + ) + ); + break; + case 'notification': + $table->getQuery()->where( + 'o.command_id = ?', + $command->getAutoincId() + ); + break; + } + } + + return $table; + } + + /** + * @param $feature + * @return $this + * @throws NotFoundError + */ + protected function requireSupportFor($feature) + { + if ($this->supports($feature) !== true) { + throw new NotFoundError( + '%s does not support %s', + $this->getType(), + $feature + ); + } + + return $this; + } + + /** + * @param $feature + * @return bool + */ + protected function supports($feature) + { + $func = "supports$feature"; + return IcingaObject::createByType($this->getType())->$func(); + } + + /** + * @return string + */ + protected function getBaseType() + { + $type = $this->getType(); + if (substr($type, -5) === 'Group') { + return substr($type, 0, -5); + } else { + return $type; + } + } + + protected function getBaseObjectUrl() + { + return $this->getType(); + } + + /** + * @return string + */ + protected function getType() + { + // Strip final 's' and upcase an eventual 'group' + return preg_replace( + array('/group$/', '/period$/', '/argument$/', '/apiuser$/', '/dependencie$/', '/set$/'), + array('Group', 'Period', 'Argument', 'ApiUser', 'dependency', 'Set'), + str_replace( + 'template', + '', + substr($this->getRequest()->getControllerName(), 0, -1) + ) + ); + } + + /** + * @return string + */ + protected function getPluralType() + { + return preg_replace('/cys$/', 'cies', $this->getType() . 's'); + } + + /** + * @return string + */ + protected function getPluralBaseType() + { + return preg_replace('/cys$/', 'cies', $this->getBaseType() . 's'); + } +} |