icingaweb2-module-director/library/Director/Web/Controller/ObjectController.php

601 lines
18 KiB
PHP
Raw Normal View History

<?php
namespace Icinga\Module\Director\Web\Controller;
2020-10-26 18:50:13 +01:00
use gipfl\Web\Widget\Hint;
use Icinga\Exception\IcingaException;
use Icinga\Exception\InvalidPropertyException;
use Icinga\Exception\NotFoundError;
use Icinga\Exception\ProgrammingError;
use Icinga\Module\Director\Deployment\DeploymentInfo;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
use Icinga\Module\Director\Exception\NestingError;
use Icinga\Module\Director\Forms\DeploymentLinkForm;
2017-07-25 10:16:15 +02:00
use Icinga\Module\Director\Forms\IcingaCloneObjectForm;
use Icinga\Module\Director\Forms\IcingaObjectFieldForm;
use Icinga\Module\Director\Objects\IcingaCommand;
use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Objects\IcingaObjectGroup;
use Icinga\Module\Director\Objects\IcingaService;
use Icinga\Module\Director\Objects\IcingaServiceSet;
use Icinga\Module\Director\RestApi\IcingaObjectHandler;
use Icinga\Module\Director\Web\Controller\Extension\ObjectRestrictions;
2016-11-16 16:02:28 +01:00
use Icinga\Module\Director\Web\Form\DirectorObjectForm;
use Icinga\Module\Director\Web\ObjectPreview;
use Icinga\Module\Director\Web\Table\ActivityLogTable;
use Icinga\Module\Director\Web\Table\GroupMemberTable;
use Icinga\Module\Director\Web\Table\IcingaObjectDatafieldTable;
use Icinga\Module\Director\Web\Tabs\ObjectTabs;
use gipfl\IcingaWeb2\Link;
abstract class ObjectController extends ActionController
{
use ObjectRestrictions;
/** @var IcingaObject */
protected $object;
2017-08-16 08:08:08 +02:00
/** @var bool This controller handles REST API requests */
2015-12-10 13:11:21 +01:00
protected $isApified = true;
2017-08-16 08:08:08 +02:00
/** @var array Allowed object types we are allowed to edit anyways */
protected $allowedExternals = array(
'apiuser',
'endpoint'
);
protected $type;
/** @var string|null */
protected $objectBaseUrl;
public function init()
{
2015-12-10 13:11:21 +01:00
parent::init();
if ($this->getRequest()->isApiRequest()) {
2017-08-16 08:08:08 +02:00
$handler = new IcingaObjectHandler($this->getRequest(), $this->getResponse(), $this->db());
try {
$this->eventuallyLoadObject();
} catch (NotFoundError $e) {
// Silently ignore the error, the handler will complain
$handler->sendJsonError($e, 404);
// TODO: nice shutdown
exit;
}
2017-08-16 08:08:08 +02:00
$handler->setApi($this->api());
if ($this->object) {
$handler->setObject($this->object);
}
2017-08-16 08:08:08 +02:00
$handler->dispatch();
// Hint: also here, hard exit. There is too much magic going on.
// Letting this bubble up smoothly would be "correct", but proved
// to be too fragile. Web 2, all kinds of pre/postDispatch magic,
// different view renderers - hard exit is the only safe bet right
// now.
exit;
2017-08-16 08:08:08 +02:00
} else {
$this->eventuallyLoadObject();
if ($this->getRequest()->getActionName() === 'add') {
$this->addSingleTab(
sprintf($this->translate('Add %s'), ucfirst($this->getType())),
null,
'add'
);
} else {
$this->tabs(new ObjectTabs(
$this->getRequest()->getControllerName(),
$this->getAuth(),
$this->object
));
}
}
}
/**
* @throws NotFoundError
*/
public function indexAction()
{
2017-08-16 08:08:08 +02:00
if (! $this->getRequest()->isApiRequest()) {
$this->redirectToPreviewForExternals()
->editAction();
}
}
public function addAction()
{
$this->tabs()->activate('add');
$url = sprintf('director/%ss', $this->getPluralType());
$imports = $this->params->get('imports');
$form = $this->loadObjectForm()
->presetImports($imports)
->setSuccessUrl($url);
if ($oType = $this->params->get('type', 'object')) {
$form->setPreferredObjectType($oType);
}
if ($oType === 'template') {
$this->addTemplate();
} else {
$this->addObject();
}
$form->handleRequest();
$this->content()->add($form);
2015-12-10 13:19:16 +01:00
}
/**
* @throws NotFoundError
*/
public function editAction()
{
$object = $this->requireObject();
$this->tabs()->activate('modify');
$this->addObjectTitle()
->addObjectForm($object)
->addActionClone()
->addActionUsage()
->addActionBasket();
}
/**
* @throws NotFoundError
* @throws \Icinga\Security\SecurityException
*/
public function renderAction()
{
$this->assertTypePermission()
->assertPermission('director/showconfig');
$this->tabs()->activate('render');
$preview = new ObjectPreview($this->requireObject(), $this->getRequest());
if ($this->object->isExternal()) {
$this->addActionClone();
}
$this->addActionBasket();
$preview->renderTo($this);
}
/**
* @throws NotFoundError
*/
2015-12-10 13:19:16 +01:00
public function cloneAction()
{
$this->assertTypePermission();
$object = $this->requireObject();
2017-07-25 10:16:15 +02:00
$form = IcingaCloneObjectForm::load()
->setObject($object)
->setObjectBaseUrl($this->getObjectBaseUrl())
2017-07-25 10:16:15 +02:00
->handleRequest();
if ($object->isExternal()) {
$this->tabs()->activate('render');
} else {
$this->tabs()->activate('modify');
}
$this->addTitle($this->translate('Clone: %s'), $object->getObjectName())
->addBackToObjectLink()
->content()->add($form);
}
/**
* @throws NotFoundError
* @throws \Icinga\Security\SecurityException
*/
2015-08-02 15:01:47 +02:00
public function fieldsAction()
{
$this->assertPermission('director/admin');
2017-07-29 00:12:34 +02:00
$object = $this->requireObject();
2015-08-02 15:01:47 +02:00
$type = $this->getType();
$this->addTitle(
2016-05-03 09:09:01 +02:00
$this->translate('Custom fields: %s'),
2017-07-29 00:12:34 +02:00
$object->getObjectName()
2015-08-02 15:01:47 +02:00
);
2017-07-29 00:12:34 +02:00
$this->tabs()->activate('fields');
try {
$this->addFieldsFormAndTable($object, $type);
} catch (NestingError $e) {
2020-10-26 18:50:13 +01:00
$this->content()->add(Hint::error($e->getMessage()));
}
}
2015-08-02 15:01:47 +02:00
protected function addFieldsFormAndTable($object, $type)
{
$form = IcingaObjectFieldForm::load()
->setDb($this->db())
2015-09-14 16:54:43 +02:00
->setIcingaObject($object);
if ($id = $this->params->get('field_id')) {
$form->loadObject([
"${type}_id" => $object->id,
'datafield_id' => $id
]);
2016-02-28 10:58:42 +01:00
$this->actions()->add(Link::create(
2016-02-28 10:58:42 +01:00
$this->translate('back'),
$this->url()->without('field_id'),
2016-02-28 10:58:42 +01:00
null,
['class' => 'icon-left-big']
));
2015-09-14 16:54:43 +02:00
}
$form->handleRequest();
$this->content()->add($form);
$table = new IcingaObjectDatafieldTable($object);
$table->getAttributes()->set('data-base-target', '_self');
$table->renderTo($this);
2015-08-02 15:01:47 +02:00
}
/**
* @throws NotFoundError
* @throws \Icinga\Security\SecurityException
*/
public function historyAction()
{
$this
->assertTypePermission()
->assertPermission('director/audit')
2017-07-29 00:12:34 +02:00
->setAutorefreshInterval(10)
->tabs()->activate('history');
$name = $this->requireObject()->getObjectName();
$this->addTitle($this->translate('Activity Log: %s'), $name);
$db = $this->db();
$objectTable = $this->object->getTableName();
$table = (new ActivityLogTable($db))
2017-07-29 00:12:34 +02:00
->setLastDeployedId($db->getLastDeploymentActivityLogId())
->filterObject($objectTable, $name);
if ($host = $this->params->get('host')) {
$table->filterHost($host);
}
$table->renderTo($this);
}
/**
* @throws NotFoundError
*/
public function membershipAction()
{
2017-07-29 00:12:34 +02:00
$object = $this->requireObject();
if (! $object instanceof IcingaObjectGroup) {
throw new NotFoundError('Not Found');
}
2017-07-29 00:12:34 +02:00
$this
->addTitle($this->translate('Group membership: %s'), $object->getObjectName())
->setAutorefreshInterval(15)
->tabs()->activate('membership');
2017-07-29 00:12:34 +02:00
$type = substr($this->getType(), 0, -5);
GroupMemberTable::create($type, $this->db())
->setGroup($object)
->renderTo($this);
}
/**
* @return $this
* @throws NotFoundError
*/
protected function addObjectTitle()
{
$object = $this->requireObject();
$name = $object->getObjectName();
if ($object->isTemplate()) {
$this->addTitle($this->translate('Template: %s'), $name);
} else {
$this->addTitle($name);
}
return $this;
}
2018-10-15 14:52:03 +02:00
/**
* @return $this
* @throws NotFoundError
*/
protected function addActionUsage()
{
$type = $this->getType();
$object = $this->requireObject();
2018-10-15 14:52:03 +02:00
if ($object->isTemplate() && $type !== 'serviceSet') {
$this->actions()->add([
Link::create(
$this->translate('Usage'),
"director/${type}template/usage",
['name' => $object->getObjectName()],
['class' => 'icon-sitemap']
)
]);
}
return $this;
}
protected function addActionClone()
{
$this->actions()->add(Link::create(
$this->translate('Clone'),
$this->getObjectBaseUrl() . '/clone',
$this->object->getUrlParams(),
array('class' => 'icon-paste')
));
return $this;
}
/**
* @return $this
*/
protected function addActionBasket()
{
if ($this->hasBasketSupport()) {
$object = $this->object;
if ($object instanceof ExportInterface) {
if ($object instanceof IcingaCommand) {
if ($object->isExternal()) {
$type = 'ExternalCommand';
} elseif ($object->isTemplate()) {
$type = 'CommandTemplate';
} else {
$type = 'Command';
}
} elseif ($object instanceof IcingaServiceSet) {
$type = 'ServiceSet';
} elseif ($object->isTemplate()) {
$type = ucfirst($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');
$this->addTitle(
$this->translate('Add new Icinga %s template'),
$this->getTranslatedType()
);
}
protected function addObject()
{
$this->assertTypePermission();
$imports = $this->params->get('imports');
if (is_string($imports) && strlen($imports)) {
$this->addTitle(
$this->translate('Add %s: %s'),
$this->getTranslatedType(),
$imports
);
} else {
$this->addTitle(
$this->translate('Add new Icinga %s'),
$this->getTranslatedType()
);
}
}
protected function redirectToPreviewForExternals()
{
if ($this->object
&& $this->object->isExternal()
&& ! in_array($this->object->getShortTableName(), $this->allowedExternals)
) {
$this->redirectNow(
$this->getRequest()->getUrl()->setPath(sprintf('director/%s/render', $this->getType()))
);
}
return $this;
}
protected function getType()
{
if ($this->type === null) {
// Strip final 's' and upcase an eventual 'group'
$this->type = preg_replace(
array('/group$/', '/period$/', '/argument$/', '/apiuser$/', '/set$/'),
array('Group', 'Period', 'Argument', 'ApiUser', 'Set'),
$this->getRequest()->getControllerName()
);
}
return $this->type;
}
protected function getPluralType()
{
return $this->getType() . 's';
}
protected function getTranslatedType()
{
return $this->translate(ucfirst($this->getType()));
}
protected function assertTypePermission()
{
$type = strtolower($this->getPluralType());
// TODO: Check getPluralType usage, fix it there.
if ($type === 'scheduleddowntimes') {
$type = 'scheduled-downtimes';
}
return $this->assertPermission("director/$type");
}
2017-08-16 08:08:08 +02:00
protected function eventuallyLoadObject()
{
if (null !== $this->params->get('name') || $this->params->get('id')) {
$this->loadObject();
}
}
protected function loadObject()
{
if ($this->object) {
throw new ProgrammingError('Loading an object twice is not very efficient');
}
if ($this->object === null) {
2017-08-16 08:08:08 +02:00
if ($id = $this->params->get('id')) {
$this->object = IcingaObject::loadByType(
$this->getType(),
(int) $id,
$this->db()
);
} elseif (null !== ($name = $this->params->get('name'))) {
$this->object = IcingaObject::loadByType(
$this->getType(),
$name,
$this->db()
);
if (! $this->allowsObject($this->object)) {
$this->object = null;
throw new NotFoundError('No such object available');
}
} elseif ($this->getRequest()->isApiRequest()) {
if ($this->getRequest()->isGet()) {
$this->getResponse()->setHttpResponseCode(422);
throw new InvalidPropertyException(
'Cannot load object, missing parameters'
);
}
}
if ($this->object !== null) {
$this->addDeploymentLink();
}
}
return $this->object;
}
protected function addDeploymentLink()
{
try {
$info = new DeploymentInfo($this->db());
$info->setObject($this->object);
if (! $this->getRequest()->isApiRequest()) {
$this->actions()->add(
DeploymentLinkForm::create(
$this->db(),
$info,
$this->Auth(),
$this->api()
)->handleRequest()
);
}
} catch (IcingaException $e) {
// pass (deployment may not be set up yet)
}
}
protected function addBackToObjectLink()
{
$params = [
'name' => $this->object->getObjectName(),
];
if ($this->object instanceof IcingaService) {
if (($host = $this->object->get('host')) !== null) {
$params['host'] = $host;
2019-03-19 15:43:20 +01:00
} elseif (($set = $this->object->get('service_set')) !== null) {
$params['set'] = $set;
}
}
$this->actions()->add(Link::create(
$this->translate('back'),
$this->getObjectBaseUrl(),
$params,
['class' => 'icon-left-big']
));
return $this;
}
protected function addObjectForm(IcingaObject $object = null)
{
$form = $this->loadObjectForm($object);
$this->content()->add($form);
$form->handleRequest();
return $this;
}
protected function loadObjectForm(IcingaObject $object = null)
{
/** @var DirectorObjectForm $class */
$class = sprintf(
'Icinga\\Module\\Director\\Forms\\Icinga%sForm',
ucfirst($this->getType())
);
$form = $class::load()
->setDb($this->db())
->setAuth($this->Auth());
if ($object !== null) {
$form->setObject($object);
}
$this->onObjectFormLoaded($form);
return $form;
}
protected function getObjectBaseUrl()
{
return $this->objectBaseUrl ?: 'director/' . strtolower($this->getType());
}
protected function hasBasketSupport()
{
return $this->object->isTemplate() || $this->object->isGroup();
}
protected function onObjectFormLoaded(DirectorObjectForm $form)
{
}
/**
* @return IcingaObject
* @throws NotFoundError
*/
protected function requireObject()
{
if (! $this->object) {
$this->getResponse()->setHttpResponseCode(404);
if (null === $this->params->get('name')) {
throw new NotFoundError('You need to pass a "name" parameter to access a specific object');
} else {
throw new NotFoundError('No such object available');
}
}
2017-07-29 00:12:34 +02:00
return $this->object;
2016-03-18 11:44:48 +01:00
}
}