ObjectController: refactor some base actions...

...and provide new helpers
This commit is contained in:
Thomas Gelf 2017-08-16 23:53:37 +02:00
parent 1a72e89f05
commit 75181ea7a2

View File

@ -2,24 +2,22 @@
namespace Icinga\Module\Director\Web\Controller; namespace Icinga\Module\Director\Web\Controller;
use Exception;
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\Exception\DuplicateKeyException;
use Icinga\Module\Director\Exception\NestingError;
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;
use Icinga\Module\Director\Objects\IcingaObject; use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Objects\IcingaObjectGroup; use Icinga\Module\Director\Objects\IcingaObjectGroup;
use Icinga\Module\Director\RestApi\IcingaObjectHandler;
use Icinga\Module\Director\Web\Controller\Extension\ObjectRestrictions; use Icinga\Module\Director\Web\Controller\Extension\ObjectRestrictions;
use Icinga\Module\Director\Web\Form\DirectorObjectForm; 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\ActivityLogTable;
use Icinga\Module\Director\Web\Table\GroupMemberTable; use Icinga\Module\Director\Web\Table\GroupMemberTable;
use Icinga\Module\Director\Web\Table\IcingaObjectDatafieldTable;
use Icinga\Module\Director\Web\Tabs\ObjectTabs; use Icinga\Module\Director\Web\Tabs\ObjectTabs;
use ipl\Html\Html;
use ipl\Html\Link; use ipl\Html\Link;
abstract class ObjectController extends ActionController abstract class ObjectController extends ActionController
@ -38,6 +36,8 @@ abstract class ObjectController extends ActionController
'endpoint' 'endpoint'
); );
protected $type;
public function init() public function init()
{ {
parent::init(); parent::init();
@ -61,125 +61,62 @@ abstract class ObjectController extends ActionController
$this->redirectToPreviewForExternals() $this->redirectToPreviewForExternals()
->editAction(); ->editAction();
} }
$this->editAction();
}
public function editAction()
{
$type = $this->getType();
$object = $this->requireObject();
$name = $object->getObjectName();
$this->addTitle($this->translate('Template: %s'), $name);
$this->tabs()->activate('modify');
if ($object->isTemplate()) {
$this->actions()->add([
Link::create(
$this->translate('Usage'),
"director/${type}template/usage",
['name' => $name],
['class' => 'icon-sitemap']
)
]);
}
$formName = 'icinga' . ucfirst($type);
/** @var DirectorObjectForm $form */
$form = $this->loadForm($formName);
$form
->setDb($this->db())
->setAuth($this->Auth())
->setObject($object);
$this->beforeHandlingEditRequest($form);
$form->handleRequest();
$this->content()->add($form);
$this->actions()->add($this->createCloneLink());
}
protected function createCloneLink()
{
return Link::create(
$this->translate('Clone'),
'director/' . $this->getType() .'/clone',
$this->object->getUrlParams(),
array('class' => 'icon-paste')
);
} }
public function addAction() public function addAction()
{ {
$imports = $this->params->shift('imports');
$this->tabs()->activate('add'); $this->tabs()->activate('add');
$type = $this->getType(); $url = sprintf('director/%ss', $this->getPluralType());
$ltype = strtolower($type);
$url = sprintf('director/%ss', $ltype); $imports = $this->params->shift('imports');
/** @var DirectorObjectForm $form */ $form = $this->loadObjectForm()
$form = $this->loadForm('icinga' . ucfirst($type))
->setDb($this->db())
->setAuth($this->Auth())
->presetImports($imports) ->presetImports($imports)
->setSuccessUrl($url); ->setSuccessUrl($url);
if ($oType = $this->params->shift('type')) { if ($oType = $this->params->shift('type', 'object')) {
$form->setPreferredObjectType($oType); $form->setPreferredObjectType($oType);
} }
if ($oType === 'template') { if ($oType === 'template') {
$this->assertPermission('director/admin'); $this->addTemplate();
$this->addTitle(
$this->translate('Add new Icinga %s template'),
ucfirst($ltype)
);
} else { } else {
$this->assertPermission("director/${ltype}s"); $this->addObject();
if (is_string($imports) && strlen($imports)) {
$this->addTitle(
$this->translate('Add %s: %s'),
$this->translate(ucfirst($ltype)),
$imports
);
} else {
$this->addTitle(
$this->translate('Add new Icinga %s'),
ucfirst($ltype)
);
}
} }
$this->beforeHandlingAddRequest($form);
$form->handleRequest(); $form->handleRequest();
$this->content()->add($form); $this->content()->add($form);
} }
protected function beforeHandlingAddRequest($form) public function editAction()
{ {
$object = $this->requireObject();
$this->tabs()->activate('modify');
$this->addObjectTitle()
->addObjectForm($object)
->addActionClone()
->addActionUsage();
} }
protected function beforeHandlingEditRequest($form) public function renderAction()
{ {
$this->assertPermission('director/showconfig');
$this->tabs()->activate('render');
$preview = new ObjectPreview($this->requireObject(), $this->getRequest());
$preview->renderTo($this);
} }
public function cloneAction() public function cloneAction()
{ {
$type = $this->getType(); $this->assertPermission('director/' . strtolower($this->getPluralType()));
$ltype = strtolower($type); $object = $this->requireObject();
$this->assertPermission('director/' . $ltype);
$this->tabs()->activate('modify');
$this->addTitle($this->translate('Clone Icinga %s'), ucfirst($type));
$form = IcingaCloneObjectForm::load() $form = IcingaCloneObjectForm::load()
->setObject($this->object) ->setObject($object)
->handleRequest(); ->handleRequest();
$this->content()->add($form);
$this->actions()->add(Link::create( $this->tabs()->activate('modify');
$this->translate('back'), $this->addTitle($this->translate('Clone: %s'), $object->getObjectName())
'director/' . $ltype, ->addBackToObjectLink()
['name' => $this->object->getObjectName()], ->content()->add($form);
['class' => 'icon-left-big']
));
} }
public function fieldsAction() public function fieldsAction()
@ -199,10 +136,10 @@ abstract class ObjectController extends ActionController
->setIcingaObject($object); ->setIcingaObject($object);
if ($id = $this->params->get('field_id')) { if ($id = $this->params->get('field_id')) {
$form->loadObject(array( $form->loadObject([
$type . '_id' => $object->id, "${type}_id" => $object->id,
'datafield_id' => $id 'datafield_id' => $id
)); ]);
$this->actions()->add(Link::create( $this->actions()->add(Link::create(
$this->translate('back'), $this->translate('back'),
@ -212,9 +149,10 @@ abstract class ObjectController extends ActionController
)); ));
} }
$form->handleRequest(); $form->handleRequest();
$this->content()->add($form);
$table = $this->loadTable('icingaObjectDatafield')->setObject($object); $table = new IcingaObjectDatafieldTable($object);
$this->content()->add([$form, $table]); $table->attributes()->set('data-base-target', '_self');
$table->renderTo($this);
} }
public function historyAction() public function historyAction()
@ -252,14 +190,112 @@ abstract class ObjectController extends ActionController
->renderTo($this); ->renderTo($this);
} }
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;
}
protected function addActionUsage()
{
$type = $this->getType();
$object = $this->requireObject();
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'),
'director/' . $this->getType() .'/clone',
$this->object->getUrlParams(),
array('class' => 'icon-paste')
));
return $this;
}
protected function addTemplate()
{
$this->assertPermission('director/admin');
$this->addTitle(
$this->translate('Add new Icinga %s template'),
$this->getTranslatedType()
);
}
protected function addObject()
{
$this->assertPermission('director/' . $this->getPluralType());
$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() protected function getType()
{ {
// Strip final 's' and upcase an eventual 'group' if ($this->type === null) {
return preg_replace( // Strip final 's' and upcase an eventual 'group'
array('/group$/', '/period$/', '/argument$/', '/apiuser$/', '/set$/'), $this->type = preg_replace(
array('Group', 'Period', 'Argument', 'ApiUser', 'Set'), array('/group$/', '/period$/', '/argument$/', '/apiuser$/', '/set$/'),
$this->getRequest()->getControllerName() 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 eventuallyLoadObject() protected function eventuallyLoadObject()
@ -300,133 +336,73 @@ abstract class ObjectController extends ActionController
} }
if ($this->object !== null) { if ($this->object !== null) {
$info = new DeploymentInfo($this->db()); $this->addDeploymentLink();
$info->setObject($this->object);
if (! $this->getRequest()->isApiRequest()) {
$this->actions()->add(
DeploymentLinkForm::create($this->db(), $info, $this->Auth(), $this->api())->handleRequest()
);
}
} }
} }
return $this->object; return $this->object;
} }
protected function handleApiRequest() protected function addDeploymentLink()
{ {
$response = $this->getResponse(); $info = new DeploymentInfo($this->db());
try { $info->setObject($this->object);
$this->loadObject();
$this->processApiRequest();
} catch (NotFoundError $e) {
$response->setHttpResponseCode(404);
$this->sendJson($response, (object) ['error' => $e->getMessage()]);
return;
} catch (DuplicateKeyException $e) {
$response->setHttpResponseCode(422);
$this->sendJson($response, (object) ['error' => $e->getMessage()]);
return;
} catch (Exception $e) {
if ($response->getHttpResponseCode() === 200) {
$response->setHttpResponseCode(500);
}
$this->sendJson($response, (object) ['error' => $e->getMessage()]); if (! $this->getRequest()->isApiRequest()) {
} $this->actions()->add(
DeploymentLinkForm::create(
if ($this->getRequest()->getActionName() !== 'index') { $this->db(),
throw new NotFoundError('Not found'); $info,
$this->Auth(),
$this->api()
)->handleRequest()
);
} }
} }
protected function processApiRequest() protected function addBackToObjectLink()
{ {
$request = $this->getRequest(); $this->actions()->add(Link::create(
$db = $this->db(); $this->translate('back'),
'director/' . strtolower($this->getType()),
['name' => $this->object->getObjectName()],
['class' => 'icon-left-big']
));
switch ($request->getMethod()) { return $this;
case 'DELETE':
$this->requireObject();
$obj = $this->object->toPlainObject(false, true);
$this->loadForm(
'icingaDeleteObject'
)->setObject($this->object)->setRequest($request)->onSuccess();
$this->sendJson($this->getResponse(), $obj);
break;
case 'POST':
case 'PUT':
$type = $this->getType();
$data = json_decode($request->getRawBody());
if ($data === null) {
$this->getResponse()->setHttpResponseCode(400);
throw new IcingaException(
'Invalid JSON: %s' . $request->getRawBody(),
$this->getLastJsonError()
);
} else {
$data = (array) $data;
}
if ($object = $this->object) {
if ($request->getMethod() === 'POST') {
$object->setProperties($data);
} else {
$data = array_merge(
array(
'object_type' => $object->object_type,
'object_name' => $object->object_name
),
$data
);
$object->replaceWith(
IcingaObject::createByType($type, $data, $db)
);
}
} else {
$object = IcingaObject::createByType($type, $data, $db);
}
$response = $this->getResponse();
if ($object->hasBeenModified()) {
$status = $object->hasBeenLoadedFromDb() ? 200 : 201;
$object->store();
$response->setHttpResponseCode($status);
} else {
$response->setHttpResponseCode(304);
}
$this->sendJson($response, $object->toPlainObject(false, true));
break;
case 'GET':
$this->requireObject();
$this->sendJson(
$this->getResponse(),
$this->object->toPlainObject(
$this->params->shift('resolved'),
! $this->params->shift('withNull'),
$this->params->shift('properties')
)
);
break;
default:
$request->getResponse()->setHttpResponseCode(400);
throw new Exception('Unsupported method ' . $request->getMethod());
}
} }
protected function countUndeployedChanges() protected function addObjectForm(IcingaObject $object = null)
{ {
if ($this->object === null) { $form = $this->loadObjectForm($object);
return 0; $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);
} }
return $this->db()->countActivitiesSinceLastDeployedConfig($this->object); $this->onObjectFormLoaded($form);
return $form;
}
protected function onObjectFormLoaded(DirectorObjectForm $form)
{
} }
protected function requireObject() protected function requireObject()