From 5419d4428c86c488a43124f85e5407d7cbae6541 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 14 Apr 2024 15:17:47 +0200 Subject: Adding upstream version 1.11.1. Signed-off-by: Daniel Baumann --- .../Director/Web/Controller/ActionController.php | 26 ++- library/Director/Web/Controller/BranchHelper.php | 40 +++- .../Web/Controller/Extension/DirectorDb.php | 3 +- .../Director/Web/Controller/Extension/RestApi.php | 3 +- .../Extension/SingleObjectApiHandler.php | 236 ------------------- .../Director/Web/Controller/ObjectController.php | 30 ++- .../Director/Web/Controller/ObjectsController.php | 28 ++- .../Director/Web/Controller/TemplateController.php | 35 ++- .../Director/Web/Form/CloneImportSourceForm.php | 30 +-- library/Director/Web/Form/CloneSyncRuleForm.php | 26 +-- library/Director/Web/Form/CsrfToken.php | 2 +- library/Director/Web/Form/DbSelectorForm.php | 2 +- library/Director/Web/Form/DirectorForm.php | 2 +- library/Director/Web/Form/DirectorObjectForm.php | 14 +- library/Director/Web/Form/Element/DataFilter.php | 20 +- .../Director/Web/Form/Element/ExtensibleSet.php | 2 +- .../Director/Web/Form/IcingaObjectFieldLoader.php | 2 +- .../Web/Form/IplElement/ExtensibleSetElement.php | 6 +- library/Director/Web/Form/QuickForm.php | 4 + library/Director/Web/SelfService.php | 14 +- library/Director/Web/Table/ActivityLogTable.php | 19 +- library/Director/Web/Table/ApplyRulesTable.php | 107 ++++++++- library/Director/Web/Table/BasketSnapshotTable.php | 5 +- library/Director/Web/Table/BranchActivityTable.php | 13 +- library/Director/Web/Table/ChoicesTable.php | 4 +- library/Director/Web/Table/CustomvarTable.php | 6 +- .../Director/Web/Table/CustomvarVariantsTable.php | 6 +- library/Director/Web/Table/DatafieldTable.php | 17 +- .../Web/Table/DependencyTemplateUsageTable.php | 12 +- library/Director/Web/Table/DeploymentLogTable.php | 3 +- library/Director/Web/Table/GroupMemberTable.php | 18 +- .../Director/Web/Table/HostTemplateUsageTable.php | 8 - .../Web/Table/IcingaServiceSetServiceTable.php | 31 ++- .../Director/Web/Table/IntlZfQueryBasedTable.php | 51 +++++ .../Web/Table/NotificationTemplateUsageTable.php | 12 +- library/Director/Web/Table/ObjectSetTable.php | 68 +++++- library/Director/Web/Table/ObjectsTable.php | 77 +++++-- library/Director/Web/Table/ObjectsTableService.php | 6 + .../Director/Web/Table/ObjectsTableSetMembers.php | 255 +++++++++++++++++++++ .../Director/Web/Table/ReadOnlyFormAvpTable.php | 113 --------- .../Web/Table/ServiceTemplateUsageTable.php | 26 ++- library/Director/Web/Table/SyncRunTable.php | 9 +- .../Director/Web/Table/TableWithBranchSupport.php | 3 + library/Director/Web/Table/TemplateUsageTable.php | 104 +++++---- library/Director/Web/Table/TemplatesTable.php | 22 +- library/Director/Web/Tabs/InfraTabs.php | 7 +- library/Director/Web/Tabs/MainTabs.php | 5 +- library/Director/Web/Tabs/ObjectTabs.php | 35 ++- library/Director/Web/Tabs/ObjectsTabs.php | 58 ++--- library/Director/Web/Tree/TemplateTreeRenderer.php | 2 +- library/Director/Web/Widget/ActivityLogInfo.php | 46 +++- .../Director/Web/Widget/AdditionalTableActions.php | 5 +- library/Director/Web/Widget/BranchedObjectHint.php | 48 ++-- .../Director/Web/Widget/BranchedObjectsHint.php | 7 +- library/Director/Web/Widget/DeploymentInfo.php | 27 ++- .../Director/Web/Widget/IcingaObjectInspection.php | 2 +- 56 files changed, 1036 insertions(+), 726 deletions(-) delete mode 100644 library/Director/Web/Controller/Extension/SingleObjectApiHandler.php create mode 100644 library/Director/Web/Table/IntlZfQueryBasedTable.php create mode 100644 library/Director/Web/Table/ObjectsTableSetMembers.php delete mode 100644 library/Director/Web/Table/ReadOnlyFormAvpTable.php (limited to 'library/Director/Web') diff --git a/library/Director/Web/Controller/ActionController.php b/library/Director/Web/Controller/ActionController.php index 6282a16..e851d82 100644 --- a/library/Director/Web/Controller/ActionController.php +++ b/library/Director/Web/Controller/ActionController.php @@ -4,10 +4,14 @@ namespace Icinga\Module\Director\Web\Controller; use gipfl\Translation\StaticTranslator; use Icinga\Application\Benchmark; +use Icinga\Application\Modules\Module; use Icinga\Data\Paginatable; use Icinga\Exception\NotFoundError; use Icinga\Exception\ProgrammingError; -use Icinga\Module\Director\Monitoring; +use Icinga\Module\Director\Integration\Icingadb\IcingadbBackend; +use Icinga\Module\Director\Integration\BackendInterface; +use Icinga\Module\Director\Integration\MonitoringModule\Monitoring; +use Icinga\Module\Director\ProvidedHook\Icingadb\IcingadbSupport; use Icinga\Module\Director\Web\Controller\Extension\CoreApi; use Icinga\Module\Director\Web\Controller\Extension\DirectorDb; use Icinga\Module\Director\Web\Controller\Extension\RestApi; @@ -36,8 +40,8 @@ abstract class ActionController extends Controller implements ControlsAndContent /** @var UrlParams Hint for IDE, somehow does not work in web */ protected $params; - /** @var Monitoring */ - private $monitoring; + /** @var BackendInterface */ + private $backend; /** * @throws SecurityException @@ -219,7 +223,7 @@ abstract class ActionController extends Controller implements ControlsAndContent // Hint -> $this->view->compact is the only way since v2.8.0 if ($this->view->compact || $this->getOriginalUrl()->getParam('view') === 'compact') { if ($this->view->controls) { - $this->controls()->getAttributes()->add('style', 'display: none;'); + $this->controls()->getAttributes()->add('class', 'compact'); } } } else { @@ -240,14 +244,18 @@ abstract class ActionController extends Controller implements ControlsAndContent } /** - * @return Monitoring + * @return BackendInterface */ - protected function monitoring() + protected function backend(): BackendInterface { - if ($this->monitoring === null) { - $this->monitoring = new Monitoring; + if ($this->backend === null) { + if (Module::exists('icingadb') && IcingadbSupport::useIcingaDbAsBackend()) { + $this->backend = new IcingadbBackend(); + } else { + $this->backend = new Monitoring($this->getAuth()); + } } - return $this->monitoring; + return $this->backend; } } diff --git a/library/Director/Web/Controller/BranchHelper.php b/library/Director/Web/Controller/BranchHelper.php index ac2a480..89aa6c1 100644 --- a/library/Director/Web/Controller/BranchHelper.php +++ b/library/Director/Web/Controller/BranchHelper.php @@ -3,12 +3,13 @@ namespace Icinga\Module\Director\Web\Controller; use Icinga\Module\Director\Data\Db\DbObjectStore; -use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry; use Icinga\Module\Director\Db\Branch\Branch; use Icinga\Module\Director\Db\Branch\BranchStore; use Icinga\Module\Director\Db\Branch\BranchSupport; +use Icinga\Module\Director\Db\Branch\PreferredBranchSupport; use Icinga\Module\Director\Objects\IcingaObject; use Icinga\Module\Director\Web\Widget\NotInBranchedHint; +use Ramsey\Uuid\UuidInterface; trait BranchHelper { @@ -18,15 +19,21 @@ trait BranchHelper /** @var BranchStore */ protected $branchStore; + /** @var ?bool */ + protected $hasPreferredBranch = null; + /** - * @return false|\Ramsey\Uuid\UuidInterface + * @return ?UuidInterface */ - protected function getBranchUuid() + protected function getBranchUuid(): ?UuidInterface { return $this->getBranch()->getUuid(); } - protected function getBranch() + /** + * @return Branch + */ + protected function getBranch(): Branch { if ($this->branch === null) { /** @var ActionController $this */ @@ -39,7 +46,7 @@ trait BranchHelper /** * @return BranchStore */ - protected function getBranchStore() + protected function getBranchStore(): BranchStore { if ($this->branchStore === null) { $this->branchStore = new BranchStore($this->db()); @@ -48,12 +55,15 @@ trait BranchHelper return $this->branchStore; } - protected function hasBranch() + /** + * @return bool + */ + protected function hasBranch(): bool { return $this->getBranchUuid() !== null; } - protected function enableStaticObjectLoader($table) + protected function enableStaticObjectLoader($table): void { if (BranchSupport::existsForTableName($table)) { IcingaObject::setDbObjectStore(new DbObjectStore($this->db(), $this->getBranch())); @@ -64,7 +74,7 @@ trait BranchHelper * @param string $subject * @return bool */ - protected function showNotInBranch($subject) + protected function showNotInBranch($subject): bool { if ($this->getBranch()->isBranch()) { $this->content()->add(new NotInBranchedHint($subject, $this->getBranch(), $this->Auth())); @@ -73,4 +83,18 @@ trait BranchHelper return false; } + + protected function hasPreferredBranch(): bool + { + if ($this->hasPreferredBranch === null) { + $implementation = Branch::optionalHook(); + if ($implementation instanceof PreferredBranchSupport) { + $this->hasPreferredBranch = $implementation->hasPreferredBranch($this->Auth()); + } else { + $this->hasPreferredBranch = false; + } + } + + return $this->hasPreferredBranch; + } } diff --git a/library/Director/Web/Controller/Extension/DirectorDb.php b/library/Director/Web/Controller/Extension/DirectorDb.php index 03bec81..c36e358 100644 --- a/library/Director/Web/Controller/Extension/DirectorDb.php +++ b/library/Director/Web/Controller/Extension/DirectorDb.php @@ -2,6 +2,7 @@ namespace Icinga\Module\Director\Web\Controller\Extension; +use Icinga\Module\Director\Auth\Restriction; use Icinga\Module\Director\Db; use Icinga\Module\Director\Web\Controller\ActionController; use Icinga\Module\Director\Web\Window; @@ -65,7 +66,7 @@ trait DirectorDb $auth = $this->Auth(); $available = $this->listAvailableDbResourceNames(); - if ($resourceNames = $auth->getRestrictions('director/db_resource')) { + if ($resourceNames = $auth->getRestrictions(Restriction::DB_RESOURCE)) { $names = []; foreach ($resourceNames as $rNames) { foreach ($this->splitList($rNames) as $name) { diff --git a/library/Director/Web/Controller/Extension/RestApi.php b/library/Director/Web/Controller/Extension/RestApi.php index 3158f49..fb10c86 100644 --- a/library/Director/Web/Controller/Extension/RestApi.php +++ b/library/Director/Web/Controller/Extension/RestApi.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Director\Web\Controller\Extension; use Icinga\Exception\AuthenticationException; use Icinga\Exception\NotFoundError; +use Icinga\Module\Director\Auth\Permission; use Icinga\Module\Director\Exception\JsonException; use Icinga\Web\Response; use InvalidArgumentException; @@ -55,7 +56,7 @@ trait RestApi */ protected function assertApiPermission() { - if (! $this->hasPermission('director/api')) { + if (! $this->hasPermission(Permission::API)) { throw new AuthenticationException('You are not allowed to access this API'); } } diff --git a/library/Director/Web/Controller/Extension/SingleObjectApiHandler.php b/library/Director/Web/Controller/Extension/SingleObjectApiHandler.php deleted file mode 100644 index bc51548..0000000 --- a/library/Director/Web/Controller/Extension/SingleObjectApiHandler.php +++ /dev/null @@ -1,236 +0,0 @@ -type = $type; - $this->request = $request; - $this->response = $response; - $this->params = $request->getUrl()->getParams(); - } - - public function runFailSafe() - { - try { - $this->loadObject(); - $this->run(); - } catch (NotFoundError $e) { - $this->sendJsonError($e->getMessage(), 404); - } catch (Exception $e) { - $response = $this->response; - if ($response->getHttpResponseCode() === 200) { - $response->setHttpResponseCode(500); - } - - $this->sendJsonError($e->getMessage()); - } - } - - protected function retrieveObject() - { - $this->requireObject(); - $this->sendJson( - $this->object->toPlainObject( - $this->params->shift('resolved'), - ! $this->params->shift('withNull'), - $this->params->shift('properties') - ) - ); - } - - protected function deleteObject() - { - $this->requireObject(); - $obj = $this->object->toPlainObject(false, true); - $form = new IcingaDeleteObjectForm(); - $form->setObject($this->object) - ->setRequest($this->request) - ->onSuccess(); - - $this->sendJson($obj); - } - - protected function storeObject() - { - $data = json_decode($this->request->getRawBody()); - - if ($data === null) { - $this->response->setHttpResponseCode(400); - throw new IcingaException( - 'Invalid JSON: %s' . $this->request->getRawBody(), - $this->getLastJsonError() - ); - } else { - $data = (array) $data; - } - - if ($object = $this->object) { - if ($this->request->getMethod() === 'POST') { - $object->setProperties($data); - } else { - $data = array_merge([ - 'object_type' => $object->object_type, - 'object_name' => $object->object_name - ], $data); - $object->replaceWith( - IcingaObject::createByType($this->type, $data, $db) - ); - } - } else { - $object = IcingaObject::createByType($this->type, $data, $db); - } - - if ($object->hasBeenModified()) { - $status = $object->hasBeenLoadedFromDb() ? 200 : 201; - $object->store(); - $this->response->setHttpResponseCode($status); - } else { - $this->response->setHttpResponseCode(304); - } - - $this->sendJson($object->toPlainObject(false, true)); - } - - public function run() - { - switch ($this->request->getMethod()) { - case 'DELETE': - $this->deleteObject(); - break; - - case 'POST': - case 'PUT': - $this->storeObject(); - break; - - case 'GET': - $this->retrieveObject(); - break; - - default: - $this->response->setHttpResponseCode(400); - throw new IcingaException( - 'Unsupported method: %s', - $this->request->getMethod() - ); - } - } - - protected function requireObject() - { - if (! $this->object) { - $this->response->setHttpResponseCode(404); - if (! $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'); - } - } - } - - // TODO: just return json_last_error_msg() for PHP >= 5.5.0 - protected function getLastJsonError() - { - switch (json_last_error()) { - case JSON_ERROR_DEPTH: - return 'The maximum stack depth has been exceeded'; - case JSON_ERROR_CTRL_CHAR: - return 'Control character error, possibly incorrectly encoded'; - case JSON_ERROR_STATE_MISMATCH: - return 'Invalid or malformed JSON'; - case JSON_ERROR_SYNTAX: - return 'Syntax error'; - case JSON_ERROR_UTF8: - return 'Malformed UTF-8 characters, possibly incorrectly encoded'; - default: - return 'An error occured when parsing a JSON string'; - } - } - - protected function sendJson($object) - { - $this->response->setHeader('Content-Type', 'application/json', true); - $this->_helper->layout()->disableLayout(); - $this->_helper->viewRenderer->setNoRender(true); - echo json_encode($object, JSON_PRETTY_PRINT) . "\n"; - } - - protected function sendJsonError($message, $code = null) - { - $response = $this->response; - - if ($code !== null) { - $response->setHttpResponseCode((int) $code); - } - - $this->sendJson((object) ['error' => $message]); - } - - protected function loadObject() - { - if ($this->object === null) { - if ($name = $this->params->get('name')) { - $this->object = IcingaObject::loadByType( - $this->type, - $name, - $this->db() - ); - - if (! $this->allowsObject($this->object)) { - $this->object = null; - throw new NotFoundError('No such object available'); - } - } elseif ($id = $this->params->get('id')) { - $this->object = IcingaObject::loadByType( - $this->type, - (int) $id, - $this->db() - ); - } elseif ($this->request->isApiRequest()) { - if ($this->request->isGet()) { - $this->response->setHttpResponseCode(422); - - throw new InvalidPropertyException( - 'Cannot load object, missing parameters' - ); - } - } - } - - return $this->object; - } - - protected function allowsObject(IcingaObject $object) - { - return true; - } -} diff --git a/library/Director/Web/Controller/ObjectController.php b/library/Director/Web/Controller/ObjectController.php index 0c06937..994a717 100644 --- a/library/Director/Web/Controller/ObjectController.php +++ b/library/Director/Web/Controller/ObjectController.php @@ -10,6 +10,7 @@ use Icinga\Exception\ProgrammingError; use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry; use Icinga\Module\Director\Db\Branch\Branch; use Icinga\Module\Director\Db\Branch\BranchedObject; +use Icinga\Module\Director\Db\Branch\BranchSupport; use Icinga\Module\Director\Db\Branch\UuidLookup; use Icinga\Module\Director\Deployment\DeploymentInfo; use Icinga\Module\Director\DirectorObject\Automation\ExportInterface; @@ -61,8 +62,11 @@ abstract class ObjectController extends ActionController public function init() { - parent::init(); $this->enableStaticObjectLoader($this->getTableName()); + if (! $this->getRequest()->isApiRequest()) { + $this->loadOptionalObject(); + } + parent::init(); if ($this->getRequest()->isApiRequest()) { $this->initializeRestApi(); } else { @@ -97,7 +101,6 @@ abstract class ObjectController extends ActionController protected function initializeWebRequest() { - $this->loadOptionalObject(); if ($this->getRequest()->getActionName() === 'add') { $this->addSingleTab( sprintf($this->translate('Add %s'), ucfirst($this->getType())), @@ -151,8 +154,11 @@ abstract class ObjectController extends ActionController $this->addObject(); } $branch = $this->getBranch(); - if ($branch->isBranch() && ! $this->getRequest()->isApiRequest()) { - $this->content()->add(new BranchedObjectHint($branch, $this->Auth())); + if (! $this->getRequest()->isApiRequest()) { + $hasPreferred = $this->hasPreferredBranch(); + if ($branch->isBranch() || $hasPreferred) { + $this->content()->add(new BranchedObjectHint($branch, $this->Auth(), null, $hasPreferred)); + } } $form->handleRequest(); @@ -267,7 +273,7 @@ abstract class ObjectController extends ActionController if ($id = $this->params->get('field_id')) { $form->loadObject([ - "${type}_id" => $object->id, + "{$type}_id" => $object->id, 'datafield_id' => $id ]); @@ -362,7 +368,7 @@ abstract class ObjectController extends ActionController $this->actions()->add([ Link::create( $this->translate('Usage'), - "director/${type}template/usage", + "director/{$type}template/usage", ['name' => $object->getObjectName()], ['class' => 'icon-sitemap'] ) @@ -558,8 +564,16 @@ abstract class ObjectController extends ActionController if (! $this->allowsObject($object)) { throw new NotFoundError('No such object available'); } - if ($showHint && $branch->isBranch() && $object->isObject() && ! $this->getRequest()->isApiRequest()) { - $this->content()->add(new BranchedObjectHint($branch, $this->Auth(), $branchedObject)); + if ($showHint) { + $hasPreferredBranch = $this->hasPreferredBranch(); + if (($hasPreferredBranch || $branch->isBranch()) + && $object->isObject() + && ! $this->getRequest()->isApiRequest() + ) { + $this->content()->add( + new BranchedObjectHint($branch, $this->Auth(), $branchedObject, $hasPreferredBranch) + ); + } } return $object; diff --git a/library/Director/Web/Controller/ObjectsController.php b/library/Director/Web/Controller/ObjectsController.php index 8c10b44..c4c96c5 100644 --- a/library/Director/Web/Controller/ObjectsController.php +++ b/library/Director/Web/Controller/ObjectsController.php @@ -13,6 +13,7 @@ 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\Objects\IcingaService; use Icinga\Module\Director\RestApi\IcingaObjectsHandler; use Icinga\Module\Director\Web\ActionBar\ObjectsActionBar; use Icinga\Module\Director\Web\ActionBar\TemplateActionBar; @@ -84,8 +85,9 @@ abstract class ObjectsController extends ActionController } elseif ($request->getActionName() === 'applyrules') { $table->filterObjectType('apply'); } + /** @var ?string $search */ $search = $this->params->get('q'); - if ($search !== null && \strlen($search) > 0) { + if ($search) { $table->search($search); } @@ -110,7 +112,7 @@ abstract class ObjectsController extends ActionController $type = $this->getType(); if ($this->params->get('format') === 'json') { $filename = sprintf( - "director-${type}_%s.json", + "director-{$type}_%s.json", date('YmdHis') ); $this->getResponse()->setHeader('Content-disposition', "attachment; filename=$filename", true); @@ -124,7 +126,7 @@ abstract class ObjectsController extends ActionController ->addTitle($this->translate(ucfirst($this->getPluralType()))) ->actions(new ObjectsActionBar($this->getBaseObjectUrl(), $this->url())); - $this->content()->add(new BranchedObjectsHint($this->getBranch(), $this->Auth())); + $this->content()->add(new BranchedObjectsHint($this->getBranch(), $this->Auth(), $this->hasPreferredBranch())); if ($type === 'command' && $this->params->get('type') === 'external_object') { $this->tabs()->activate('external'); @@ -143,8 +145,7 @@ abstract class ObjectsController extends ActionController */ protected function getTable() { - $table = ObjectsTable::create($this->getType(), $this->db()) - ->setAuth($this->getAuth()) + $table = ObjectsTable::create($this->getType(), $this->db(), $this->getAuth()) ->setBranchUuid($this->getBranchUuid()) ->setBaseObjectUrl($this->getBaseObjectUrl()); @@ -157,7 +158,7 @@ abstract class ObjectsController extends ActionController */ protected function getApplyRulesTable() { - $table = new ApplyRulesTable($this->db()); + $table = (new ApplyRulesTable($this->db()))->setBranch($this->getBranch()); $table->setType($this->getType()) ->setBaseObjectUrl($this->getBaseObjectUrl()); $this->eventuallyFilterCommand($table); @@ -235,7 +236,7 @@ abstract class ObjectsController extends ActionController if ($this->params->get('format') === 'json') { $filename = sprintf( - "director-${type}-templates_%s.json", + "director-{$type}-templates_%s.json", date('YmdHis') ); $this->getResponse()->setHeader('Content-disposition', "attachment; filename=$filename", true); @@ -290,7 +291,7 @@ abstract class ObjectsController extends ActionController if ($this->params->get('format') === 'json') { $filename = sprintf( - "director-${type}-applyrules_%s.json", + "director-{$type}-applyrules_%s.json", date('YmdHis') ); $this->getResponse()->setHeader('Content-disposition', "attachment; filename=$filename", true); @@ -313,7 +314,7 @@ abstract class ObjectsController extends ActionController ->add( Link::create( $this->translate('Add'), - "${baseUrl}/add", + "{$baseUrl}/add", ['type' => 'apply'], [ 'title' => sprintf( @@ -354,7 +355,7 @@ abstract class ObjectsController extends ActionController $this->actions()->add( Link::create( $this->translate('Add'), - "director/${type}set/add", + "director/{$type}set/add", null, [ 'title' => sprintf( @@ -409,7 +410,12 @@ abstract class ObjectsController extends ActionController $objects[$name] = $class::load($name, $db); } elseif ($col === 'uuid') { $object = $store->load($table, Uuid::fromString($ex->getExpression())); - $objects[$object->getObjectName()] = $object; + if ($object instanceof IcingaService) { + $host = $object->getRelated('host'); + $objects[$host->getObjectName() . ': ' . $object->getObjectName()] = $object; + } else { + $objects[$object->getObjectName()] = $object; + } } else { throw new InvalidArgumentException("'$col' is no a valid key component for '$type'"); } diff --git a/library/Director/Web/Controller/TemplateController.php b/library/Director/Web/Controller/TemplateController.php index c368a82..4f0898a 100644 --- a/library/Director/Web/Controller/TemplateController.php +++ b/library/Director/Web/Controller/TemplateController.php @@ -3,6 +3,7 @@ namespace Icinga\Module\Director\Web\Controller; use gipfl\Web\Widget\Hint; +use Icinga\Module\Director\Auth\Permission; use Icinga\Module\Director\DirectorObject\Automation\ExportInterface; use Icinga\Module\Director\Exception\NestingError; use Icinga\Module\Director\Objects\IcingaCommand; @@ -10,6 +11,7 @@ use Icinga\Module\Director\Objects\IcingaObject; use Icinga\Module\Director\Web\Controller\Extension\DirectorDb; use Icinga\Module\Director\Web\Table\ApplyRulesTable; use Icinga\Module\Director\Web\Table\ObjectsTable; +use Icinga\Module\Director\Web\Table\ObjectsTableSetMembers; use Icinga\Module\Director\Web\Table\TemplatesTable; use Icinga\Module\Director\Web\Table\TemplateUsageTable; use Icinga\Module\Director\Web\Tabs\ObjectTabs; @@ -23,6 +25,8 @@ abstract class TemplateController extends CompatController { use DirectorDb; + use BranchHelper; + /** @var IcingaObject */ protected $template; @@ -39,9 +43,29 @@ abstract class TemplateController extends CompatController $template->getObjectName() )->addBackToUsageLink($template); - ObjectsTable::create($this->getType(), $this->db()) - ->setAuth($this->Auth()) + ObjectsTable::create($this->getType(), $this->db(), $this->Auth()) + ->setBranch($this->getBranch()) + ->setBaseObjectUrl($this->getBaseObjectUrl()) + ->filterTemplate($template, $this->getInheritance()) + ->renderTo($this); + } + + public function setmembersAction() + { + $template = $this->requireTemplate(); + $plural = $this->getTranslatedPluralType(); + $this + ->addSingleTab($plural) + ->setAutorefreshInterval(10) + ->addTitle( + $this->translate('%s in service sets based on %s'), + $plural, + $template->getObjectName() + )->addBackToUsageLink($template); + + ObjectsTableSetMembers::create($this->getType(), $this->db(), $this->Auth()) ->setBaseObjectUrl($this->getBaseObjectUrl()) + ->setBranch($this->getBranch()) ->filterTemplate($template, $this->getInheritance()) ->renderTo($this); } @@ -60,6 +84,7 @@ abstract class TemplateController extends CompatController ApplyRulesTable::create($type, $this->db()) ->setBaseObjectUrl($this->getBaseObjectUrl()) + ->setBranch($this->getBranch()) ->filterTemplate($template, $this->params->get('inheritance', 'direct')) ->renderTo($this); } @@ -93,7 +118,7 @@ abstract class TemplateController extends CompatController $this->actions()->add( Link::create( $this->translate('Back'), - "director/${type}template/usage", + "director/{$type}template/usage", ['name' => $template->getObjectName()], ['class' => 'icon-left-big'] ) @@ -152,7 +177,7 @@ abstract class TemplateController extends CompatController )] )); } - if ($auth->hasPermission('director/admin')) { + if ($auth->hasPermission(Permission::ADMIN)) { $list->addItem(new FormattedString( $this->translate('Create a new %s inheriting from this one'), [Link::create( @@ -188,7 +213,7 @@ abstract class TemplateController extends CompatController try { $this->content()->add( - TemplateUsageTable::forTemplate($template) + TemplateUsageTable::forTemplate($template, $this->Auth(), $this->getBranch()) ); } catch (NestingError $e) { $this->content()->add(Hint::error($e->getMessage())); diff --git a/library/Director/Web/Form/CloneImportSourceForm.php b/library/Director/Web/Form/CloneImportSourceForm.php index 0849dd4..46dc7a3 100644 --- a/library/Director/Web/Form/CloneImportSourceForm.php +++ b/library/Director/Web/Form/CloneImportSourceForm.php @@ -2,8 +2,10 @@ namespace Icinga\Module\Director\Web\Form; +use gipfl\Web\Form; use Icinga\Module\Director\Data\Exporter; -use ipl\Html\Form; +use Icinga\Module\Director\Data\ObjectImporter; +use Icinga\Module\Director\Db; use ipl\Html\FormDecorator\DdDtDecorator; use gipfl\Translation\TranslationHelper; use gipfl\IcingaWeb2\Url; @@ -36,37 +38,25 @@ class CloneImportSourceForm extends Form ]); } - /** - * @return \Icinga\Module\Director\Db - */ - protected function getTargetDb() - { - return $this->source->getConnection(); - } - - /** - * @throws \Icinga\Module\Director\Exception\DuplicateKeyException - */ public function onSuccess() { - $db = $this->getTargetDb(); + $db = $this->source->getConnection(); + assert($db instanceof Db); $export = (new Exporter($db))->export($this->source); $newName = $this->getElement('source_name')->getValue(); $export->source_name = $newName; - unset($export->originalId); + unset($export->uuid); + if (ImportSource::existsWithName($newName, $db)) { $this->getElement('source_name')->addMessage('Name already exists'); } - $this->newSource = ImportSource::import($export, $db); + $importer = new ObjectImporter($db); + $this->newSource = $importer->import(ImportSource::class, $export); $this->newSource->store(); } public function getSuccessUrl() { - if ($this->newSource === null) { - return parent::getSuccessUrl(); - } else { - return Url::fromPath('director/importsource', ['id' => $this->newSource->get('id')]); - } + return Url::fromPath('director/importsource', ['id' => $this->newSource->get('id')]); } } diff --git a/library/Director/Web/Form/CloneSyncRuleForm.php b/library/Director/Web/Form/CloneSyncRuleForm.php index f90b593..ccd61ec 100644 --- a/library/Director/Web/Form/CloneSyncRuleForm.php +++ b/library/Director/Web/Form/CloneSyncRuleForm.php @@ -2,8 +2,10 @@ namespace Icinga\Module\Director\Web\Form; +use gipfl\Web\Form; use Icinga\Module\Director\Data\Exporter; -use ipl\Html\Form; +use Icinga\Module\Director\Data\ObjectImporter; +use Icinga\Module\Director\Db; use ipl\Html\FormDecorator\DdDtDecorator; use gipfl\Translation\TranslationHelper; use gipfl\IcingaWeb2\Url; @@ -36,41 +38,31 @@ class CloneSyncRuleForm extends Form ]); } - /** - * @return \Icinga\Module\Director\Db - */ - protected function getTargetDb() - { - return $this->rule->getConnection(); - } - /** * @throws \Icinga\Exception\NotFoundError * @throws \Icinga\Module\Director\Exception\DuplicateKeyException */ public function onSuccess() { - $db = $this->getTargetDb(); + $db = $this->rule->getConnection(); + assert($db instanceof Db); $exporter = new Exporter($db); $export = $exporter->export($this->rule); $newName = $this->getValue('rule_name'); $export->rule_name = $newName; - unset($export->originalId); + unset($export->uuid); if (SyncRule::existsWithName($newName, $db)) { $this->getElement('rule_name')->addMessage('Name already exists'); } - $this->newRule = SyncRule::import($export, $db); + $importer = new ObjectImporter($db); + $this->newRule = $importer->import(SyncRule::class, $export); $this->newRule->store(); } public function getSuccessUrl() { - if ($this->newRule === null) { - return parent::getSuccessUrl(); - } else { - return Url::fromPath('director/syncrule', ['id' => $this->newRule->get('id')]); - } + return Url::fromPath('director/syncrule', ['id' => $this->newRule->get('id')]); } } diff --git a/library/Director/Web/Form/CsrfToken.php b/library/Director/Web/Form/CsrfToken.php index 24edf88..f6c29ec 100644 --- a/library/Director/Web/Form/CsrfToken.php +++ b/library/Director/Web/Form/CsrfToken.php @@ -17,7 +17,7 @@ class CsrfToken return false; } - list($seed, $token) = explode('|', $elementValue); + list($seed, $token) = explode('|', $token); if (!is_numeric($seed)) { return false; diff --git a/library/Director/Web/Form/DbSelectorForm.php b/library/Director/Web/Form/DbSelectorForm.php index 52fe5ea..8b4f432 100644 --- a/library/Director/Web/Form/DbSelectorForm.php +++ b/library/Director/Web/Form/DbSelectorForm.php @@ -69,7 +69,7 @@ class DbSelectorForm extends Form $params = []; } - if (array_key_exists($name, $params)) { + if (is_array($params) && array_key_exists($name, $params)) { return $params[$name]; } diff --git a/library/Director/Web/Form/DirectorForm.php b/library/Director/Web/Form/DirectorForm.php index 145be5b..36c0577 100644 --- a/library/Director/Web/Form/DirectorForm.php +++ b/library/Director/Web/Form/DirectorForm.php @@ -34,7 +34,7 @@ abstract class DirectorForm extends QuickForm public static function load() { return new static([ - 'icingaModule' => Icinga::App()->getModuleManager()->getModule('director') + 'icingaModule' => Icinga::app()->getModuleManager()->getModule('director') ]); } diff --git a/library/Director/Web/Form/DirectorObjectForm.php b/library/Director/Web/Form/DirectorObjectForm.php index b70bd7b..abbd4f0 100644 --- a/library/Director/Web/Form/DirectorObjectForm.php +++ b/library/Director/Web/Form/DirectorObjectForm.php @@ -5,6 +5,7 @@ namespace Icinga\Module\Director\Web\Form; use Exception; use gipfl\IcingaWeb2\Url; use Icinga\Authentication\Auth; +use Icinga\Module\Director\Auth\Permission; use Icinga\Module\Director\Data\Db\DbObjectStore; use Icinga\Module\Director\Db; use Icinga\Module\Director\Data\Db\DbObject; @@ -443,7 +444,7 @@ abstract class DirectorObjectForm extends DirectorForm $this->setInheritedValue( $el, $object->getRelatedObjectName($k, $v), - $origins->{"${k}_id"} + $origins->{"{$k}_id"} ); } } @@ -540,7 +541,9 @@ abstract class DirectorObjectForm extends DirectorForm 'inherited_groups', 'applied_groups', 'users', + 'users_var', 'user_groups', + 'user_groups_var', 'apply_to', 'command_id', // Notification 'notification_interval', @@ -788,7 +791,9 @@ abstract class DirectorObjectForm extends DirectorForm return; } - $post = $values = $this->getRequest()->getPost(); + /** @var array $post */ + $post = $this->getRequest()->getPost(); + $values = $post; foreach ($post as $key => $value) { if (preg_match('/^(.+?)_(\d+)__(MOVE_DOWN|MOVE_UP|REMOVE)$/', $key, $m)) { @@ -1239,7 +1244,7 @@ abstract class DirectorObjectForm extends DirectorForm if ($this->hasBeenSent()) { $this->addError($this->translate('No template has been chosen')); } else { - if ($this->hasPermission('director/admin')) { + if ($this->hasPermission(Permission::ADMIN)) { $html = $this->translate('Please define a related template first'); } else { $html = $this->translate('No related template has been provided yet'); @@ -1274,7 +1279,7 @@ abstract class DirectorObjectForm extends DirectorForm 'required' => $required, 'spellcheck' => 'false', 'hideOptions' => $choiceNames, - 'suggest' => "${type}templates", + 'suggest' => "{$type}templates", // 'multiOptions' => $this->optionallyAddFromEnum($enum), 'sorted' => true, 'value' => $this->presetImports, @@ -1516,6 +1521,7 @@ abstract class DirectorObjectForm extends DirectorForm return []; } + /** @var int|string $id */ $id = $object->get('id'); if (array_key_exists($id, $tpl)) { diff --git a/library/Director/Web/Form/Element/DataFilter.php b/library/Director/Web/Form/Element/DataFilter.php index adae07d..7beb651 100644 --- a/library/Director/Web/Form/Element/DataFilter.php +++ b/library/Director/Web/Form/Element/DataFilter.php @@ -2,6 +2,7 @@ namespace Icinga\Module\Director\Web\Form\Element; +use gipfl\Json\JsonString; use Icinga\Data\Filter\Filter; use Icinga\Data\Filter\FilterChain; use Icinga\Data\Filter\FilterExpression; @@ -268,13 +269,13 @@ class DataFilter extends FormElement return Filter::expression( $entry['column'], '=', - json_encode(true) + $this->jsonEncode(true) ); } elseif ($entry['sign'] === 'false') { return Filter::expression( $entry['column'], '=', - json_encode(false) + $this->jsonEncode(false) ); } elseif ($entry['sign'] === 'in') { if (array_key_exists('value', $entry)) { @@ -291,13 +292,13 @@ class DataFilter extends FormElement return Filter::expression( $entry['column'], '=', - json_encode($value) + $this->jsonEncode($value) ); } elseif ($entry['sign'] === 'contains') { $value = array_key_exists('value', $entry) ? $entry['value'] : null; return Filter::expression( - json_encode($value), + $this->jsonEncode($value), '=', $entry['column'] ); @@ -307,11 +308,20 @@ class DataFilter extends FormElement return Filter::expression( $entry['column'], $entry['sign'], - json_encode($value) + $this->jsonEncode($value) ); } } + protected function jsonEncode($string) + { + return preg_replace( + ['/&/u', '/\|/u', '/!/u', '/=/u', '/>/u', '/getShortTableName()); - foreach (Hook::all("Director\\${type}Field") as $hook) { + foreach (Hook::all("Director\\{$type}Field") as $hook) { if ($hook->wants($object)) { $id = $object->get('id'); $spec = $hook->getFieldSpec($object); diff --git a/library/Director/Web/Form/IplElement/ExtensibleSetElement.php b/library/Director/Web/Form/IplElement/ExtensibleSetElement.php index a4dbb20..b723d47 100644 --- a/library/Director/Web/Form/IplElement/ExtensibleSetElement.php +++ b/library/Director/Web/Form/IplElement/ExtensibleSetElement.php @@ -26,6 +26,8 @@ class ExtensibleSetElement extends BaseHtmlElement private $description; + private $descriptions; + private $multiOptions; private $validOptions; @@ -109,7 +111,7 @@ class ExtensibleSetElement extends BaseHtmlElement if (null !== $value && ! is_array($value)) { throw new ProgrammingError( 'Got unexpected value, no array: %s', - var_export($value, 1) + var_export($value, true) ); } @@ -323,7 +325,7 @@ class ExtensibleSetElement extends BaseHtmlElement } else { return \sprintf( $this->translate('%s (not an Array!)'), - \var_export($this->inherited, 1) + \var_export($this->inherited, true) ); } } diff --git a/library/Director/Web/Form/QuickForm.php b/library/Director/Web/Form/QuickForm.php index 91c8f00..6100ec9 100644 --- a/library/Director/Web/Form/QuickForm.php +++ b/library/Director/Web/Form/QuickForm.php @@ -620,6 +620,10 @@ abstract class QuickForm extends QuickBaseForm $this->hasBeenSent = true; } elseif ($req->isPost()) { $post = $req->getPost(); + if (! CsrfToken::isValid($post[self::CSRF])) { + throw new Exception('Invalid CSRF token provided'); + } + $this->hasBeenSent = array_key_exists(self::ID, $post) && $post[self::ID] === $this->getName(); } else { diff --git a/library/Director/Web/SelfService.php b/library/Director/Web/SelfService.php index 33756b7..723cbf6 100644 --- a/library/Director/Web/SelfService.php +++ b/library/Director/Web/SelfService.php @@ -155,19 +155,6 @@ class SelfService ['class' => 'logfile'], $wizard->renderIcinga4WindowsWizardCommand($key) ), - Html::tag('h3', $this->translate('Icinga 2 Powershell Module')), - Html::tag('p', Html::sprintf( - $this->translate('In case you\'re using the legacy %s, please run:'), - Html::tag('a', [ - 'href' => 'https://github.com/Icinga/icinga2-powershell-module', - 'target' => '_blank', - ], $this->translate('Icinga 2 Powershell Module')) - )), - Html::tag( - 'pre', - ['class' => 'logfile'], - $wizard->renderPowershellModuleInstaller($key) - ), ]; } @@ -264,6 +251,7 @@ class SelfService null, ['class' => 'icon-download', 'target' => '_blank'] ), + Html::tag('p', null, $this->translate('Just download and run this script on your Linux Client Machine:')), Html::tag('pre', $class, $wizard->renderLinuxInstaller()) ]); diff --git a/library/Director/Web/Table/ActivityLogTable.php b/library/Director/Web/Table/ActivityLogTable.php index 5460bc2..a57b86e 100644 --- a/library/Director/Web/Table/ActivityLogTable.php +++ b/library/Director/Web/Table/ActivityLogTable.php @@ -2,14 +2,15 @@ namespace Icinga\Module\Director\Web\Table; -use gipfl\Format\LocalTimeFormat; +use DateTime; use gipfl\IcingaWeb2\Link; -use gipfl\IcingaWeb2\Table\ZfQueryBasedTable; +use Icinga\Module\Director\Auth\Permission; use Icinga\Module\Director\Util; +use IntlDateFormatter; use ipl\Html\Html; use ipl\Html\HtmlElement; -class ActivityLogTable extends ZfQueryBasedTable +class ActivityLogTable extends IntlZfQueryBasedTable { protected $filters = []; @@ -27,9 +28,6 @@ class ActivityLogTable extends ZfQueryBasedTable 'object_type', ]; - /** @var LocalTimeFormat */ - protected $timeFormat; - protected $ranges = []; /** @var ?object */ @@ -44,7 +42,6 @@ class ActivityLogTable extends ZfQueryBasedTable public function __construct($db) { parent::__construct($db); - $this->timeFormat = new LocalTimeFormat(); } public function assemble() @@ -96,7 +93,9 @@ class ActivityLogTable extends ZfQueryBasedTable if (! $this->hasObjectFilter) { $columns[] = $this->makeRangeInfo($row->id); } - $columns[] = $this::td($this->timeFormat->getTime($row->ts_change_time)); + + + $columns[] = $this::td($this->getTime($row->ts_change_time)); return $this::tr($columns)->addAttributes(['class' => $action]); } @@ -107,7 +106,7 @@ class ActivityLogTable extends ZfQueryBasedTable */ protected function renderDayIfNew($timestamp) { - $day = $this->getDateFormatter()->getFullDay($timestamp); + $day = $this->getDateFormatter()->format((new DateTime())->setTimestamp($timestamp)); if ($this->lastDay !== $day) { $this->nextHeader()->add( @@ -206,7 +205,7 @@ class ActivityLogTable extends ZfQueryBasedTable $type = substr($type, 7); } - if (Util::hasPermission('director/showconfig')) { + if (Util::hasPermission(Permission::SHOW_CONFIG)) { // Later on replacing, service_set -> serviceset // multi column key :( diff --git a/library/Director/Web/Table/ApplyRulesTable.php b/library/Director/Web/Table/ApplyRulesTable.php index a861bac..a5379b5 100644 --- a/library/Director/Web/Table/ApplyRulesTable.php +++ b/library/Director/Web/Table/ApplyRulesTable.php @@ -6,6 +6,7 @@ use Icinga\Authentication\Auth; use Icinga\Data\Filter\Filter; use Icinga\Exception\IcingaException; use Icinga\Module\Director\Db; +use Icinga\Module\Director\Db\DbSelectParenthesis; use Icinga\Module\Director\Db\DbUtil; use Icinga\Module\Director\Db\IcingaObjectFilterHelper; use Icinga\Module\Director\IcingaConfig\AssignRenderer; @@ -20,6 +21,8 @@ use Zend_Db_Select as ZfSelect; class ApplyRulesTable extends ZfQueryBasedTable { + use TableWithBranchSupport; + protected $searchColumns = [ 'o.object_name', 'o.assign_filter', @@ -94,10 +97,14 @@ class ApplyRulesTable extends ZfQueryBasedTable // NOT (YET) static::td($this->createActionLinks($row))->setSeparator(' ') ]); + $classes = $this->getRowClasses($row); + if ($row->disabled === 'y') { - $tr->getAttributes()->add('class', 'disabled'); + $classes[] = 'disabled'; } + $tr->getAttributes()->add('class', $classes); + return $tr; } @@ -117,7 +124,8 @@ class ApplyRulesTable extends ZfQueryBasedTable $this->getQuery(), $template, 'o', - $inheritance + $inheritance, + $this->branchUuid ); return $this; @@ -146,7 +154,7 @@ class ApplyRulesTable extends ZfQueryBasedTable $links = []; $links[] = Link::create( Icon::create('sitemap'), - "${baseUrl}template/applytargets", + "{$baseUrl}template/applytargets", ['id' => $row->id], ['title' => $this->translate('Show affected Objects')] ); @@ -196,6 +204,15 @@ class ApplyRulesTable extends ZfQueryBasedTable return FilterRenderer::applyToQuery($filter, $query); } + protected function getRowClasses($row) + { + // TODO: remove isset, to figure out where it is missing + if (isset($row->branch_uuid) && $row->branch_uuid !== null) { + return ['branch_modified']; + } + return []; + } + /** * @return IcingaObject @@ -216,6 +233,7 @@ class ApplyRulesTable extends ZfQueryBasedTable 'id' => 'o.id', 'uuid' => 'o.uuid', 'object_name' => 'o.object_name', + 'object_type' => 'o.object_type', 'disabled' => 'o.disabled', 'assign_filter' => 'o.assign_filter', 'apply_for' => '(NULL)', @@ -224,17 +242,92 @@ class ApplyRulesTable extends ZfQueryBasedTable if ($table === 'icinga_service') { $columns['apply_for'] = 'o.apply_for'; } + + $conn = $this->connection(); $query = $this->db()->select()->from( ['o' => $table], $columns - )->where( - "object_type = 'apply'" )->order('o.object_name'); - if ($this->type === 'service') { - $query->where('service_set_id IS NULL'); + if ($this->branchUuid) { + $columns = $this->branchifyColumns($columns); + $columns['branch_uuid'] = 'bo.branch_uuid'; + if ($conn->isPgsql()) { + $columns['imports'] = 'CONCAT(\'[\', ARRAY_TO_STRING(ARRAY_AGG' + . '(CONCAT(\'"\', sub_o.object_name, \'"\')), \',\'), \']\')'; + } else { + $columns['imports'] = 'CONCAT(\'[\', ' + . 'GROUP_CONCAT(CONCAT(\'"\', sub_o.object_name, \'"\')), \']\')'; + } + + $this->stripSearchColumnAliases(); + + $query->reset('columns'); + $right = clone($query); + + $query->columns($columns) + ->joinLeft( + ['oi' => $table . '_inheritance'], + 'o.id = oi.' . $this->getType() . '_id', + [] + )->joinLeft( + ['sub_o' => $table], + 'sub_o.id = oi.parent_' . $this->getType() . '_id', + [] + )->group(['o.id', 'bo.uuid', 'bo.branch_uuid']); + + $query->joinLeft( + ['bo' => "branched_$table"], + // TODO: PgHexFunc + $this->db()->quoteInto( + 'bo.uuid = o.uuid AND bo.branch_uuid = ?', + DbUtil::quoteBinaryLegacy($this->branchUuid->getBytes(), $this->db()) + ), + [] + )->where("(bo.branch_deleted IS NULL OR bo.branch_deleted = 'n')"); + + if ($this->type === 'service') { + $query->where('o.service_set_id IS NULL AND bo.service_set IS NULL'); + } + + $columns['imports'] = 'bo.imports'; + + $right->columns($columns) + ->joinRight( + ['bo' => "branched_$table"], + 'bo.uuid = o.uuid', + [] + ) + ->where('o.uuid IS NULL') + ->where('bo.branch_uuid = ?', $conn->quoteBinary($this->branchUuid->getBytes())); + + $query = $this->db()->select()->union([ + 'l' => new DbSelectParenthesis($query), + 'r' => new DbSelectParenthesis($right), + ]); + + $query = $this->db()->select()->from(['u' => $query]); + $query->order('object_name')->limit(100); + } else { + if ($this->type === 'service') { + $query->where('service_set_id IS NULL'); + } } + $query->where( + "object_type = 'apply'" + ); + + $this->applyRestrictions($query); + return $this->applyRestrictions($query); } + + /** + * @return Db + */ + public function connection() + { + return parent::connection(); + } } diff --git a/library/Director/Web/Table/BasketSnapshotTable.php b/library/Director/Web/Table/BasketSnapshotTable.php index 08f808a..6fe0e16 100644 --- a/library/Director/Web/Table/BasketSnapshotTable.php +++ b/library/Director/Web/Table/BasketSnapshotTable.php @@ -4,13 +4,12 @@ namespace Icinga\Module\Director\Web\Table; use ipl\Html\Html; use gipfl\IcingaWeb2\Link; -use gipfl\IcingaWeb2\Table\ZfQueryBasedTable; use Icinga\Date\DateFormatter; use Icinga\Module\Director\Core\Json; use Icinga\Module\Director\DirectorObject\Automation\Basket; use RuntimeException; -class BasketSnapshotTable extends ZfQueryBasedTable +class BasketSnapshotTable extends IntlZfQueryBasedTable { use DbHelper; @@ -67,7 +66,7 @@ class BasketSnapshotTable extends ZfQueryBasedTable if (! is_object($summary) && ! is_array($summary)) { throw new RuntimeException(sprintf( 'Got invalid basket summary: %s ', - var_export($summary, 1) + var_export($summary, true) )); } diff --git a/library/Director/Web/Table/BranchActivityTable.php b/library/Director/Web/Table/BranchActivityTable.php index e7131ef..cbf940d 100644 --- a/library/Director/Web/Table/BranchActivityTable.php +++ b/library/Director/Web/Table/BranchActivityTable.php @@ -2,15 +2,14 @@ namespace Icinga\Module\Director\Web\Table; -use gipfl\Format\LocalTimeFormat; +use Icinga\Module\Director\Auth\Permission; use Icinga\Module\Director\Db; use Icinga\Module\Director\Db\Branch\BranchActivity; use Icinga\Module\Director\Util; use gipfl\IcingaWeb2\Link; -use gipfl\IcingaWeb2\Table\ZfQueryBasedTable; use Ramsey\Uuid\UuidInterface; -class BranchActivityTable extends ZfQueryBasedTable +class BranchActivityTable extends IntlZfQueryBasedTable { protected $extraParams = []; @@ -20,16 +19,12 @@ class BranchActivityTable extends ZfQueryBasedTable /** @var ?UuidInterface */ protected $objectUuid; - /** @var LocalTimeFormat */ - protected $timeFormat; - protected $linkToObject = true; public function __construct(UuidInterface $branchUuid, $db, UuidInterface $objectUuid = null) { $this->branchUuid = $branchUuid; $this->objectUuid = $objectUuid; - $this->timeFormat = new LocalTimeFormat(); parent::__construct($db); } @@ -45,7 +40,7 @@ class BranchActivityTable extends ZfQueryBasedTable $activity = BranchActivity::fromDbRow($row); return $this::tr([ $this::td($this->makeBranchLink($activity))->setSeparator(' '), - $this::td($this->timeFormat->getTime($ts)) + $this::td($this->getTime($ts)) ])->addAttributes(['class' => ['action-' . $activity->getAction(), 'branched']]); } @@ -75,7 +70,7 @@ class BranchActivityTable extends ZfQueryBasedTable { $type = preg_replace('/^icinga_/', '', $activity->getObjectTable()); - if (Util::hasPermission('director/showconfig')) { + if (Util::hasPermission(Permission::SHOW_CONFIG)) { // Later on replacing, service_set -> serviceset return [ '[' . $activity->getAuthor() . ']', diff --git a/library/Director/Web/Table/ChoicesTable.php b/library/Director/Web/Table/ChoicesTable.php index 4ba2460..9574520 100644 --- a/library/Director/Web/Table/ChoicesTable.php +++ b/library/Director/Web/Table/ChoicesTable.php @@ -44,7 +44,7 @@ class ChoicesTable extends ZfQueryBasedTable public function renderRow($row) { $type = $this->getType(); - $url = Url::fromPath("director/templatechoice/${type}", [ + $url = Url::fromPath("director/templatechoice/{$type}", [ 'name' => $row->object_name ]); @@ -56,7 +56,7 @@ class ChoicesTable extends ZfQueryBasedTable protected function prepareQuery() { $type = $this->getType(); - $table = "icinga_${type}_template_choice"; + $table = "icinga_{$type}_template_choice"; return $this->db() ->select() ->from(['o' => $table], 'object_name') diff --git a/library/Director/Web/Table/CustomvarTable.php b/library/Director/Web/Table/CustomvarTable.php index f9a3844..1aead19 100644 --- a/library/Director/Web/Table/CustomvarTable.php +++ b/library/Director/Web/Table/CustomvarTable.php @@ -91,11 +91,11 @@ class CustomvarTable extends ZfQueryBasedTable $columns["cnt_$type"] = 'COUNT(*)'; $columns["distinct_$type"] = 'COUNT(DISTINCT varvalue)'; return $db->select()->from( - ['v' => "icinga_${type}_var"], + ['v' => "icinga_{$type}_var"], $columns )->join( - ['o' => "icinga_${type}"], - "o.id = v.${type}_id", + ['o' => "icinga_{$type}"], + "o.id = v.{$type}_id", [] )->where('o.object_type != ?', 'external_object')->group('varname'); } diff --git a/library/Director/Web/Table/CustomvarVariantsTable.php b/library/Director/Web/Table/CustomvarVariantsTable.php index 80fca70..8447e36 100644 --- a/library/Director/Web/Table/CustomvarVariantsTable.php +++ b/library/Director/Web/Table/CustomvarVariantsTable.php @@ -108,11 +108,11 @@ class CustomvarVariantsTable extends ZfQueryBasedTable $columns["cnt_$type"] = 'COUNT(*)'; $columns['format'] = 'v.format'; return $db->select()->from( - ['v' => "icinga_${type}_var"], + ['v' => "icinga_{$type}_var"], $columns )->join( - ['o' => "icinga_${type}"], - "o.id = v.${type}_id", + ['o' => "icinga_{$type}"], + "o.id = v.{$type}_id", [] )->where( 'v.varname = ?', diff --git a/library/Director/Web/Table/DatafieldTable.php b/library/Director/Web/Table/DatafieldTable.php index 4b321d7..0062626 100644 --- a/library/Director/Web/Table/DatafieldTable.php +++ b/library/Director/Web/Table/DatafieldTable.php @@ -5,12 +5,13 @@ namespace Icinga\Module\Director\Web\Table; use gipfl\IcingaWeb2\Link; use gipfl\IcingaWeb2\Table\ZfQueryBasedTable; use Zend_Db_Adapter_Abstract as ZfDbAdapter; +use Zend_Db_Expr as DbExpr; use Zend_Db_Select as ZfDbSelect; class DatafieldTable extends ZfQueryBasedTable { protected $searchColumns = [ - 'df.varname', + 'lc_varname', 'df.caption', ]; @@ -19,6 +20,7 @@ class DatafieldTable extends ZfQueryBasedTable return [ 'id' => 'df.id', 'varname' => 'df.varname', + 'lc_varname' => new DbExpr('LOWER(df.varname)'), 'caption' => 'df.caption', 'description' => 'df.description', 'datatype' => 'df.datatype', @@ -88,6 +90,15 @@ class DatafieldTable extends ZfQueryBasedTable )->group('df.id')->group('df.varname')->group('dfc.category_name')->order('caption ASC'); } + public function search($search) + { + if ($search !== null) { + $search = strtolower($search); + } + + return parent::search($search); + } + /** * @param $type * @param ZfDbAdapter $db @@ -96,7 +107,7 @@ class DatafieldTable extends ZfQueryBasedTable */ protected function makeDatafieldSub($type, ZfDbAdapter $db) { - return $db->select()->from("icinga_${type}_field", [ + return $db->select()->from("icinga_{$type}_field", [ 'cnt' => 'COUNT(*)', 'datafield_id' ])->group('datafield_id'); @@ -110,7 +121,7 @@ class DatafieldTable extends ZfQueryBasedTable */ protected function makeVarSub($type, ZfDbAdapter $db) { - return $db->select()->from("icinga_${type}_var", [ + return $db->select()->from("icinga_{$type}_var", [ 'cnt' => 'COUNT(*)', 'varname' ])->group('varname'); diff --git a/library/Director/Web/Table/DependencyTemplateUsageTable.php b/library/Director/Web/Table/DependencyTemplateUsageTable.php index d7537c5..2c1de50 100644 --- a/library/Director/Web/Table/DependencyTemplateUsageTable.php +++ b/library/Director/Web/Table/DependencyTemplateUsageTable.php @@ -2,6 +2,8 @@ namespace Icinga\Module\Director\Web\Table; +use Icinga\Module\Director\Db; + class DependencyTemplateUsageTable extends TemplateUsageTable { public function getTypes() @@ -12,11 +14,15 @@ class DependencyTemplateUsageTable extends TemplateUsageTable ]; } - protected function getTypeSummaryDefinitions() + protected function getSummaryTables(string $templateType, Db $connection) { return [ - 'templates' => $this->getSummaryLine('template'), - 'applyrules' => $this->getSummaryLine('apply'), + 'templates' => TemplatesTable::create( + $templateType, + $connection + ), + 'applyrules' => ApplyRulesTable::create($templateType, $connection) + ->setBranchUuid($this->branchUuid) ]; } } diff --git a/library/Director/Web/Table/DeploymentLogTable.php b/library/Director/Web/Table/DeploymentLogTable.php index 2d5cb94..4849218 100644 --- a/library/Director/Web/Table/DeploymentLogTable.php +++ b/library/Director/Web/Table/DeploymentLogTable.php @@ -3,10 +3,9 @@ namespace Icinga\Module\Director\Web\Table; use gipfl\IcingaWeb2\Link; -use gipfl\IcingaWeb2\Table\ZfQueryBasedTable; use Icinga\Date\DateFormatter; -class DeploymentLogTable extends ZfQueryBasedTable +class DeploymentLogTable extends IntlZfQueryBasedTable { use DbHelper; diff --git a/library/Director/Web/Table/GroupMemberTable.php b/library/Director/Web/Table/GroupMemberTable.php index b0814ad..c24c6af 100644 --- a/library/Director/Web/Table/GroupMemberTable.php +++ b/library/Director/Web/Table/GroupMemberTable.php @@ -109,7 +109,7 @@ class GroupMemberTable extends ZfQueryBasedTable ]; } - $url = Url::fromPath("director/${type}", $params); + $url = Url::fromPath("director/{$type}", $params); $tr = $this::tr(); @@ -163,7 +163,7 @@ class GroupMemberTable extends ZfQueryBasedTable 'o.id', 'o.object_type', 'o.object_name', - 'membership_type' => "CASE WHEN go.${type}_id IS NULL THEN 'apply' ELSE 'direct' END" + 'membership_type' => "CASE WHEN go.{$type}_id IS NULL THEN 'apply' ELSE 'direct' END" ]; if ($this->group === null) { @@ -176,19 +176,19 @@ class GroupMemberTable extends ZfQueryBasedTable } $query = $this->db()->select()->from( - ['gro' => "icinga_${type}group_${type}_resolved"], + ['gro' => "icinga_{$type}group_{$type}_resolved"], $columns )->join( - ['o' => "icinga_${type}"], - "o.id = gro.${type}_id", + ['o' => "icinga_{$type}"], + "o.id = gro.{$type}_id", [] )->join( - ['g' => "icinga_${type}group"], - "gro.${type}group_id = g.id", + ['g' => "icinga_{$type}group"], + "gro.{$type}group_id = g.id", [] )->joinLeft( - ['go' => "icinga_${type}group_${type}"], - "go.${type}_id = o.id AND go.${type}group_id = g.id", + ['go' => "icinga_{$type}group_{$type}"], + "go.{$type}_id = o.id AND go.{$type}group_id = g.id", [] )->order('o.object_name'); diff --git a/library/Director/Web/Table/HostTemplateUsageTable.php b/library/Director/Web/Table/HostTemplateUsageTable.php index 2d1ee2f..672691f 100644 --- a/library/Director/Web/Table/HostTemplateUsageTable.php +++ b/library/Director/Web/Table/HostTemplateUsageTable.php @@ -11,12 +11,4 @@ class HostTemplateUsageTable extends TemplateUsageTable 'objects' => $this->translate('Objects'), ]; } - - protected function getTypeSummaryDefinitions() - { - return [ - 'templates' => $this->getSummaryLine('template'), - 'objects' => $this->getSummaryLine('object'), - ]; - } } diff --git a/library/Director/Web/Table/IcingaServiceSetServiceTable.php b/library/Director/Web/Table/IcingaServiceSetServiceTable.php index c205e66..2c3dbc4 100644 --- a/library/Director/Web/Table/IcingaServiceSetServiceTable.php +++ b/library/Director/Web/Table/IcingaServiceSetServiceTable.php @@ -12,6 +12,7 @@ use Icinga\Module\Director\Objects\IcingaServiceSet; use gipfl\IcingaWeb2\Link; use gipfl\IcingaWeb2\Table\ZfQueryBasedTable; use gipfl\IcingaWeb2\Url; +use Ramsey\Uuid\Uuid; class IcingaServiceSetServiceTable extends ZfQueryBasedTable { @@ -122,9 +123,12 @@ class IcingaServiceSetServiceTable extends ZfQueryBasedTable ]; $url = 'director/host/servicesetservice'; } else { + if (is_resource($row->uuid)) { + $row->uuid =stream_get_contents($row->uuid); + } + $params = [ - 'name' => $row->service, - 'set' => $row->service_set + 'uuid' => Uuid::fromBytes($row->uuid)->toString(), ]; $url = 'director/service'; } @@ -194,14 +198,28 @@ class IcingaServiceSetServiceTable extends ZfQueryBasedTable $connection = $this->connection(); assert($connection instanceof Db); $builder = new ServiceSetQueryBuilder($connection, $this->branchUuid); - return $builder->selectServicesForSet($this->set)->limit(100); + $query = $builder->selectServicesForSet($this->set); + $alias = $this->branchUuid ? 'u' : 'o'; + + if ($this->affectedHost) { + if ($hostId = $this->affectedHost->get('id')) { + $query->joinLeft( + ['hsb' => 'icinga_host_service_blacklist'], + $this->db()->quoteInto("$alias.id = hsb.service_id AND hsb.host_id = ?", $hostId), + [] + )->columns([ + 'blacklisted' => "CASE WHEN hsb.service_id IS NULL THEN 'n' ELSE 'y' END" + ]); + } + } + + return $query->limit(100); } protected function createFakeRemoveLinkForReadonlyView() { return Html::tag('span', [ - 'class' => 'icon-paste', - 'style' => 'float: right; font-weight: normal', + 'class' => ['icon-paste', 'seviceset-obj-link'], ], $this->host->getObjectName()); } @@ -209,8 +227,7 @@ class IcingaServiceSetServiceTable extends ZfQueryBasedTable { $hostname = $host->getObjectName(); return Link::create($hostname, 'director/host/services', ['name' => $hostname], [ - 'class' => 'icon-paste', - 'style' => 'float: right; font-weight: normal', + 'class' => ['icon-paste', 'seviceset-obj-link'], 'data-base-target' => '_next', 'title' => sprintf( $this->translate('This set has been inherited from %s'), diff --git a/library/Director/Web/Table/IntlZfQueryBasedTable.php b/library/Director/Web/Table/IntlZfQueryBasedTable.php new file mode 100644 index 0000000..81ef14c --- /dev/null +++ b/library/Director/Web/Table/IntlZfQueryBasedTable.php @@ -0,0 +1,51 @@ +getDateFormatter()->format((new DateTime())->setTimestamp($timestamp)); + + if ($this->lastDay !== $day) { + $this->nextHeader()->add( + $this::th($day, [ + 'colspan' => 2, + 'class' => 'table-header-day' + ]) + ); + + $this->lastDay = $day; + $this->nextBody(); + } + } + + protected function getTime(int $timeStamp) + { + $timeFormatter = $this->getDateFormatter(); + + $timeFormatter->setPattern( + in_array(Locale::getDefault(), ['en_US', 'en_US.UTF-8']) ? 'h:mm:ss a': 'H:mm:ss' + ); + + return $timeFormatter->format((new DateTime())->setTimestamp($timeStamp)); + } +} diff --git a/library/Director/Web/Table/NotificationTemplateUsageTable.php b/library/Director/Web/Table/NotificationTemplateUsageTable.php index da411a3..d8cd3d8 100644 --- a/library/Director/Web/Table/NotificationTemplateUsageTable.php +++ b/library/Director/Web/Table/NotificationTemplateUsageTable.php @@ -2,6 +2,8 @@ namespace Icinga\Module\Director\Web\Table; +use Icinga\Module\Director\Db; + class NotificationTemplateUsageTable extends TemplateUsageTable { public function getTypes() @@ -12,11 +14,15 @@ class NotificationTemplateUsageTable extends TemplateUsageTable ]; } - protected function getTypeSummaryDefinitions() + protected function getSummaryTables(string $templateType, Db $connection) { return [ - 'templates' => $this->getSummaryLine('template'), - 'applyrules' => $this->getSummaryLine('apply', 'o.host_id IS NULL'), + 'templates' => TemplatesTable::create( + $templateType, + $connection + ), + 'applyrules' => ApplyRulesTable::create($templateType, $connection) + ->setBranchUuid($this->branchUuid) ]; } } diff --git a/library/Director/Web/Table/ObjectSetTable.php b/library/Director/Web/Table/ObjectSetTable.php index 2773841..4df9bdd 100644 --- a/library/Director/Web/Table/ObjectSetTable.php +++ b/library/Director/Web/Table/ObjectSetTable.php @@ -2,7 +2,9 @@ namespace Icinga\Module\Director\Web\Table; +use gipfl\IcingaWeb2\Zf1\Db\FilterRenderer; use Icinga\Authentication\Auth; +use Icinga\Data\Filter\Filter; use Icinga\Module\Director\Db; use gipfl\IcingaWeb2\Link; use gipfl\IcingaWeb2\Table\ZfQueryBasedTable; @@ -28,6 +30,8 @@ class ObjectSetTable extends ZfQueryBasedTable /** @var Auth */ private $auth; + protected $queries = []; + public static function create($type, Db $db, Auth $auth) { $table = new static($db); @@ -53,7 +57,7 @@ class ObjectSetTable extends ZfQueryBasedTable 'uuid' => Uuid::fromBytes(Db\DbUtil::binaryResult($row->uuid))->toString(), ]; - $url = Url::fromPath("director/${type}set", $params); + $url = Url::fromPath("director/{$type}set", $params); $classes = $this->getRowClasses($row); $tr = static::tr([ @@ -85,7 +89,7 @@ class ObjectSetTable extends ZfQueryBasedTable { $type = $this->getType(); - $table = "icinga_${type}_set"; + $table = "icinga_{$type}_set"; $columns = [ 'id' => 'os.id', 'uuid' => 'os.uuid', @@ -106,15 +110,15 @@ class ObjectSetTable extends ZfQueryBasedTable ['os' => $table], $columns )->joinLeft( - ['o' => "icinga_${type}"], - "o.${type}_set_id = os.id", + ['o' => "icinga_{$type}"], + "o.{$type}_set_id = os.id", [] ); $nameFilter = new FilterByNameRestriction( $this->connection(), $this->auth, - "${type}_set" + "{$type}_set" ); $nameFilter->applyToQuery($query, 'os'); /** @var Db $conn */ @@ -145,7 +149,20 @@ class ObjectSetTable extends ZfQueryBasedTable $query->group('bos.uuid')->group('os.uuid')->group('os.id')->group('bos.branch_uuid'); $right->group('bos.uuid')->group('os.uuid')->group('os.id')->group('bos.branch_uuid'); } - + $right->joinLeft( + ['bo' => "branched_icinga_{$type}"], + "bo.{$type}_set = bos.object_name", + [] + )->group(['bo.object_name', 'o.object_name']); + $query->joinLeft( + ['bo' => "branched_icinga_{$type}"], + "bo.{$type}_set = bos.object_name", + [] + )->group(['bo.object_name', 'o.object_name']); + $this->queries = [ + $query, + $right + ]; $query = $this->db()->select()->union([ 'l' => new DbSelectParenthesis($query), 'r' => new DbSelectParenthesis($right), @@ -168,16 +185,16 @@ class ObjectSetTable extends ZfQueryBasedTable ->group('assign_filter') ->group('description') ->group('count_services'); - }; + } } else { // Disabled for now, check for correctness: // $query->joinLeft( - // ['osi' => "icinga_${type}_set_inheritance"], - // "osi.parent_${type}_set_id = os.id", + // ['osi' => "icinga_{$type}_set_inheritance"], + // "osi.parent_{$type}_set_id = os.id", // [] // )->joinLeft( - // ['oso' => "icinga_${type}_set"], - // "oso.id = oso.${type}_set_id", + // ['oso' => "icinga_{$type}_set"], + // "oso.id = oso.{$type}_set_id", // [] // ); // 'count_hosts' => 'COUNT(DISTINCT oso.id)', @@ -196,11 +213,40 @@ class ObjectSetTable extends ZfQueryBasedTable ->group('os.assign_filter') ->group('os.description'); }; + $this->queries = [$query]; } return $query; } + public function search($search) + { + if (! empty($search)) { + $columns = $this->getSearchColumns(); + if (strpos($search, ' ') === false) { + $filter = Filter::matchAny(); + foreach ($columns as $column) { + $filter->addFilter(Filter::expression($column, '=', "*$search*")); + } + } else { + $filter = Filter::matchAll(); + foreach (explode(' ', $search) as $s) { + $sub = Filter::matchAny(); + foreach ($columns as $column) { + $sub->addFilter(Filter::expression($column, '=', "*$s*")); + } + $filter->addFilter($sub); + } + } + + foreach ($this->queries as $query) { + FilterRenderer::applyToQuery($filter, $query); + } + } + + return $this; + } + /** * @return Db */ diff --git a/library/Director/Web/Table/ObjectsTable.php b/library/Director/Web/Table/ObjectsTable.php index 792cb6d..4ad1166 100644 --- a/library/Director/Web/Table/ObjectsTable.php +++ b/library/Director/Web/Table/ObjectsTable.php @@ -14,6 +14,7 @@ use gipfl\IcingaWeb2\Link; use gipfl\IcingaWeb2\Table\ZfQueryBasedTable; use gipfl\IcingaWeb2\Url; use Ramsey\Uuid\Uuid; +use Zend_Db_Adapter_Pdo_Pgsql; use Zend_Db_Select as ZfSelect; class ObjectsTable extends ZfQueryBasedTable @@ -50,12 +51,18 @@ class ObjectsTable extends ZfQueryBasedTable /** @var Auth */ private $auth; + public function __construct($db, Auth $auth) + { + $this->auth = $auth; + parent::__construct($db); + } + /** * @param $type * @param Db $db * @return static */ - public static function create($type, Db $db) + public static function create($type, Db $db, Auth $auth) { $class = __NAMESPACE__ . '\\ObjectsTable' . ucfirst($type); if (! class_exists($class)) { @@ -63,7 +70,7 @@ class ObjectsTable extends ZfQueryBasedTable } /** @var static $table */ - $table = new $class($db); + $table = new $class($db, $auth); $table->type = $type; return $table; } @@ -84,20 +91,6 @@ class ObjectsTable extends ZfQueryBasedTable return $this; } - /** - * @return Auth - */ - public function getAuth() - { - return $this->auth; - } - - public function setAuth(Auth $auth) - { - $this->auth = $auth; - return $this; - } - public function filterObjectType($type) { $this->filterObjectType = $type; @@ -124,11 +117,17 @@ class ObjectsTable extends ZfQueryBasedTable IcingaObject $template, $inheritance = Db\IcingaObjectFilterHelper::INHERIT_DIRECT ) { + if ($this->branchUuid) { + $tableAlias = 'u'; + } else { + $tableAlias = 'o'; + } IcingaObjectFilterHelper::filterByTemplate( $this->getQuery(), $template, - 'o', - $inheritance + $tableAlias, + $inheritance, + $this->branchUuid ); return $this; @@ -142,7 +141,7 @@ class ObjectsTable extends ZfQueryBasedTable protected function renderObjectNameColumn($row) { $type = $this->baseObjectUrl; - $url = Url::fromPath("director/${type}", [ + $url = Url::fromPath("director/{$type}", [ 'uuid' => Uuid::fromBytes($row->uuid)->toString() ]); @@ -227,11 +226,10 @@ class ObjectsTable extends ZfQueryBasedTable { /** @var Db $db */ $db = $this->connection(); - $auth = $this->getAuth(); return [ - new HostgroupRestriction($db, $auth), - new FilterByNameRestriction($db, $auth, $this->getDummyObject()->getShortTableName()) + new HostgroupRestriction($db, $this->auth), + new FilterByNameRestriction($db, $this->auth, $this->getDummyObject()->getShortTableName()) ]; } @@ -279,7 +277,39 @@ class ObjectsTable extends ZfQueryBasedTable $conn->quoteBinary($this->branchUuid->getBytes()) ), [] - )->where("(bo.branch_deleted IS NULL OR bo.branch_deleted = 'n')"); + ); + + // keep the imported templates as columns + $leftColumns = $columns; + $rightColumns = $columns; + + if ($this->db() instanceof Zend_Db_Adapter_Pdo_Pgsql) { + $leftColumns['imports'] = 'CONCAT(\'[\', ARRAY_TO_STRING(ARRAY_AGG' + . '(CONCAT(\'"\', sub_o.object_name, \'"\')), \',\'), \']\')'; + } else { + $leftColumns['imports'] = 'CONCAT(\'[\', ' + . 'GROUP_CONCAT(CONCAT(\'"\', sub_o.object_name, \'"\')), \']\')'; + } + + $query->reset('columns'); + + $query->columns($leftColumns) + ->joinLeft( + ['oi' => $table . '_inheritance'], + 'o.id = oi.' . $this->getType() . '_id', + [] + )->joinLeft( + ['sub_o' => $table], + 'sub_o.id = oi.parent_' . $this->getType() . '_id', + [] + )->group(['o.id', 'bo.uuid', 'bo.branch_uuid']); + + $rightColumns['imports'] = 'bo.imports'; + + $right->reset('columns'); + $right->columns($rightColumns); + + $query->where("(bo.branch_deleted IS NULL OR bo.branch_deleted = 'n')"); $this->applyObjectTypeFilter($query, $right); $right->joinRight( ['bo' => "branched_$table"], @@ -298,6 +328,7 @@ class ObjectsTable extends ZfQueryBasedTable $query->order('object_name')->limit(100); } else { $this->applyObjectTypeFilter($query); + $query = $this->applyRestrictions($query); $query->order('o.object_name')->limit(100); } diff --git a/library/Director/Web/Table/ObjectsTableService.php b/library/Director/Web/Table/ObjectsTableService.php index 2d4ad41..c9bce72 100644 --- a/library/Director/Web/Table/ObjectsTableService.php +++ b/library/Director/Web/Table/ObjectsTableService.php @@ -203,8 +203,14 @@ class ObjectsTableService extends ObjectsTable 'hsb.service_id = o.id AND hsb.host_id = o.host_id', [] )->where('o.service_set_id IS NULL') + ->group(['o.id', 'h.id','hsb.service_id', 'hsb.host_id']) ->order('o.object_name')->order('h.object_name'); + if ($this->branchUuid) { + $subQuery->where('bo.service_set IS NULL') + ->group(['bo.uuid', 'bo.branch_uuid']); + } + if ($this->host) { if ($this->branchUuid) { $subQuery->where('COALESCE(h.object_name, bo.host) = ?', $this->host->getObjectName()); diff --git a/library/Director/Web/Table/ObjectsTableSetMembers.php b/library/Director/Web/Table/ObjectsTableSetMembers.php new file mode 100644 index 0000000..6b18ac9 --- /dev/null +++ b/library/Director/Web/Table/ObjectsTableSetMembers.php @@ -0,0 +1,255 @@ +type = $type; + $table->auth = $auth; + return $table; + } + + public function getType() + { + return $this->type; + } + + public function getColumnsToBeRendered() + { + return [ + 'os.object_name' => 'Service Set', + 'o.object_name' => 'Service Name' + ]; + } + + public function setBaseObjectUrl($url) + { + $this->baseObjectUrl = $url; + + return $this; + } + + protected function getRowClasses($row) + { + // TODO: remove isset, to figure out where it is missing + if (isset($row->branch_uuid) && $row->branch_uuid !== null) { + return ['branch_modified']; + } + return []; + } + + /** + * Should be triggered from renderRow, still unused. + * + * @param IcingaObject $template + * @param string $inheritance + * @return $this + * @throws \Icinga\Exception\ProgrammingError + */ + public function filterTemplate( + IcingaObject $template, + $inheritance = IcingaObjectFilterHelper::INHERIT_DIRECT + ) { + IcingaObjectFilterHelper::filterByTemplate( + $this->getQuery(), + $template, + 'o', + $inheritance, + $this->branchUuid + ); + + return $this; + } + + + public function renderRow($row) + { + $url = Url::fromPath('director/service/edit', [ + 'name' => $row->object_name, + 'uuid' => Uuid::fromBytes($row->uuid)->toString(), + ]); + + return static::tr([ + static::td([ + Link::create($row->service_set, $url), + ]), + static::td($row->object_name), + ])->addAttributes(['class' => $this->getRowClasses($row)]); + } + + /** + * @return IcingaObject + */ + protected function getDummyObject() + { + if ($this->dummyObject === null) { + $type = $this->type; + $this->dummyObject = IcingaObject::createByType($type); + } + return $this->dummyObject; + } + + protected function prepareQuery() + { + $table = $this->getDummyObject()->getTableName(); + $type = $this->getType(); + + $columns = [ + 'id' => 'o.id', + 'uuid' => 'o.uuid', + 'service_set' => 'os.object_name', + 'object_name' => 'o.object_name', + 'object_type' => 'os.object_type', + 'assign_filter' => 'os.assign_filter', + 'description' => 'os.description', + ]; + + $query = $this->db()->select()->from( + ['o' => $table], + $columns + )->joinLeft( + ['os' => "icinga_{$type}_set"], + "o.{$type}_set_id = os.id", + [] + )->where('o.host_id IS NULL'); + + $nameFilter = new FilterByNameRestriction( + $this->connection(), + $this->auth, + "{$type}_set" + ); + $nameFilter->applyToQuery($query, 'os'); + + if ($this->branchUuid) { + $columns['branch_uuid'] = 'bos.branch_uuid'; + $conn = $this->connection(); + if ($conn->isPgsql()) { + $columns['imports'] = 'CONCAT(\'[\', ARRAY_TO_STRING(ARRAY_AGG' + . '(CONCAT(\'"\', sub_o.object_name, \'"\')), \',\'), \']\')'; + } else { + $columns['imports'] = 'CONCAT(\'[\', ' + . 'GROUP_CONCAT(CONCAT(\'"\', sub_o.object_name, \'"\')), \']\')'; + } + + $columns = $this->branchifyColumns($columns); + $this->stripSearchColumnAliases(); + + $query->reset('columns'); + $right = clone($query); + $conn = $this->connection(); + + $query->columns($columns)->joinLeft( + ['bos' => "branched_icinga_{$type}_set"], + // TODO: PgHexFunc + $this->db()->quoteInto( + 'bos.uuid = os.uuid AND bos.branch_uuid = ?', + $conn->quoteBinary($this->branchUuid->getBytes()) + ), + [] + )->joinLeft( + ['oi' => $table . '_inheritance'], + 'o.id = oi.' . $this->getType() . '_id', + [] + )->joinLeft( + ['sub_o' => $table], + 'sub_o.id = oi.parent_' . $this->getType() . '_id', + [] + )->where("(bos.branch_deleted IS NULL OR bos.branch_deleted = 'n')"); + + $columns['imports'] = 'bo.imports'; + $right->columns($columns)->joinRight( + ['bos' => "branched_icinga_{$type}_set"], + 'bos.uuid = os.uuid', + [] + ) + ->where('os.uuid IS NULL') + ->where('bos.branch_uuid = ?', $conn->quoteBinary($this->branchUuid->getBytes())); + $query->group('COALESCE(os.uuid, bos.uuid)'); + $right->group('COALESCE(os.uuid, bos.uuid)'); + if ($conn->isPgsql()) { + // This is ugly, might want to modify the query - even a subselect looks better + $query->group('bos.uuid')->group('os.uuid')->group('os.id')->group('bos.branch_uuid')->group('o.id'); + $right->group('bos.uuid')->group('os.uuid')->group('os.id')->group('bos.branch_uuid')->group('o.id'); + } + $right->joinLeft( + ['bo' => "branched_icinga_{$type}"], + "bo.{$type}_set = bos.object_name", + [] + )->group(['bo.object_name', 'o.object_name', 'bo.uuid', 'bo.imports']); + $query->joinLeft( + ['bo' => "branched_icinga_{$type}"], + "bo.{$type}_set = bos.object_name", + [] + )->group(['bo.object_name', 'o.object_name', 'bo.uuid']); + + $query = $this->db()->select()->union([ + 'l' => new DbSelectParenthesis($query), + 'r' => new DbSelectParenthesis($right), + ]); + $query = $this->db()->select()->from(['u' => $query]); + $query->order('object_name')->limit(100); + + $query + ->group('uuid') + ->where('object_type = ?', 'template') + ->order('object_name'); + if ($conn->isPgsql()) { + $query + ->group('uuid') + ->group('id') + ->group('imports') + ->group('branch_uuid') + ->group('object_name') + ->group('object_type') + ->group('assign_filter') + ->group('description') + ->group('service_set'); + } + } else { + $query + ->where('o.object_type = ?', 'object') + ->order('os.object_name'); + } + + return $query; + } + + /** + * @return Db + */ + public function connection() + { + return parent::connection(); + } +} diff --git a/library/Director/Web/Table/ReadOnlyFormAvpTable.php b/library/Director/Web/Table/ReadOnlyFormAvpTable.php deleted file mode 100644 index c3b44f3..0000000 --- a/library/Director/Web/Table/ReadOnlyFormAvpTable.php +++ /dev/null @@ -1,113 +0,0 @@ -form = $form; - } - - protected function renderDisplayGroups(QuickForm $form) - { - $html = ''; - - foreach ($form->getDisplayGroups() as $group) { - $elements = $this->filterGroupElements($group); - - if (empty($elements)) { - continue; - } - - $html .= '' . $group->getLegend() . ''; - $html .= $this->renderElements($elements); - } - - return $html; - } - - /** - * @param ZfDisplayGroup $group - * @return ZfElement[] - */ - protected function filterGroupElements(ZfDisplayGroup $group) - { - $blacklist = array('disabled', 'assign_filter'); - $elements = array(); - /** @var ZfElement $element */ - foreach ($group->getElements() as $element) { - if ($element->getValue() === null) { - continue; - } - - if ($element->getType() === 'Zend_Form_Element_Hidden') { - continue; - } - - if (in_array($element->getName(), $blacklist)) { - continue; - } - - - $elements[] = $element; - } - - return $elements; - } - - protected function renderElements($elements) - { - $html = ''; - foreach ($elements as $element) { - $html .= $this->renderElement($element); - } - - return $html; - } - - /** - * @param ZfElement $element - * - * @return string - */ - protected function renderElement(ZfElement $element) - { - $value = $element->getValue(); - return '' - . $this->escape($element->getLabel()) - . '' - . $this->renderValue($value) - . ''; - } - - protected function renderValue($value) - { - if (is_string($value)) { - return $this->escape($value); - } elseif (is_array($value)) { - return $this->escape(implode(', ', $value)); - } - return $this->escape(PlainObjectRenderer::render($value)); - } - - protected function escape($string) - { - return htmlspecialchars($string); - } - - public function render() - { - $this->form->initializeForObject(); - return '' . "\n" - . $this->renderDisplayGroups($this->form) - . '
'; - } -} diff --git a/library/Director/Web/Table/ServiceTemplateUsageTable.php b/library/Director/Web/Table/ServiceTemplateUsageTable.php index 82f9643..c2806f6 100644 --- a/library/Director/Web/Table/ServiceTemplateUsageTable.php +++ b/library/Director/Web/Table/ServiceTemplateUsageTable.php @@ -2,6 +2,9 @@ namespace Icinga\Module\Director\Web\Table; +use Icinga\Authentication\Auth; +use Icinga\Module\Director\Db; + class ServiceTemplateUsageTable extends TemplateUsageTable { public function getTypes() @@ -10,18 +13,27 @@ class ServiceTemplateUsageTable extends TemplateUsageTable 'templates' => $this->translate('Templates'), 'objects' => $this->translate('Objects'), 'applyrules' => $this->translate('Apply Rules'), - // 'setmembers' => $this->translate('Set Members'), + 'setmembers' => $this->translate('Set Members'), ]; } - protected function getTypeSummaryDefinitions() + protected function getSummaryTables(string $templateType, Db $connection) { + $auth = Auth::getInstance(); return [ - 'templates' => $this->getSummaryLine('template'), - 'objects' => $this->getSummaryLine('object'), - 'applyrules' => $this->getSummaryLine('apply', 'o.service_set_id IS NULL'), - // TODO: re-enable - // 'setmembers' => $this->getSummaryLine('apply', 'o.service_set_id IS NOT NULL'), + 'templates' => TemplatesTable::create( + $templateType, + $connection + ), + 'objects' => ObjectsTable::create($templateType, $connection, $this->auth) + ->setBranchUuid($this->branchUuid), + 'applyrules' => ApplyRulesTable::create($templateType, $connection) + ->setBranchUuid($this->branchUuid), + 'setmembers' => ObjectsTableSetMembers::create( + $templateType, + $connection, + $auth + ) ]; } } diff --git a/library/Director/Web/Table/SyncRunTable.php b/library/Director/Web/Table/SyncRunTable.php index e08aad7..19f2678 100644 --- a/library/Director/Web/Table/SyncRunTable.php +++ b/library/Director/Web/Table/SyncRunTable.php @@ -2,22 +2,17 @@ namespace Icinga\Module\Director\Web\Table; -use gipfl\Format\LocalTimeFormat; use Icinga\Module\Director\Objects\SyncRule; use gipfl\IcingaWeb2\Link; -use gipfl\IcingaWeb2\Table\ZfQueryBasedTable; -class SyncRunTable extends ZfQueryBasedTable +class SyncRunTable extends IntlZfQueryBasedTable { /** @var SyncRule */ protected $rule; - protected $timeFormat; - public function __construct(SyncRule $rule) { parent::__construct($rule->getConnection()); - $this->timeFormat = new LocalTimeFormat(); $this->getAttributes() ->set('data-base-target', '_self') ->add('class', 'history'); @@ -31,7 +26,7 @@ class SyncRunTable extends ZfQueryBasedTable return $this::tr([ $this::td($this->makeSummary($row)), $this::td(new Link( - $this->timeFormat->getTime($time), + $this->getTime($time), 'director/syncrule/history', [ 'id' => $row->rule_id, diff --git a/library/Director/Web/Table/TableWithBranchSupport.php b/library/Director/Web/Table/TableWithBranchSupport.php index 7c5b15c..9e412c3 100644 --- a/library/Director/Web/Table/TableWithBranchSupport.php +++ b/library/Director/Web/Table/TableWithBranchSupport.php @@ -56,6 +56,9 @@ trait TableWithBranchSupport $result[$alias] = $column; } + if (isset($result['count_services'])) { + $result['count_services'] = 'COUNT(DISTINCT COALESCE(o.uuid, bo.uuid))'; + } return $result; } diff --git a/library/Director/Web/Table/TemplateUsageTable.php b/library/Director/Web/Table/TemplateUsageTable.php index 66e56ea..376a499 100644 --- a/library/Director/Web/Table/TemplateUsageTable.php +++ b/library/Director/Web/Table/TemplateUsageTable.php @@ -2,9 +2,12 @@ namespace Icinga\Module\Director\Web\Table; +use Icinga\Authentication\Auth; use Icinga\Exception\ProgrammingError; +use Icinga\Module\Director\Db; +use Icinga\Module\Director\Db\Branch\Branch; +use Icinga\Module\Director\Db\IcingaObjectFilterHelper; use Icinga\Module\Director\Objects\IcingaObject; -use Icinga\Module\Director\Resolver\TemplateTree; use gipfl\IcingaWeb2\Link; use ipl\Html\Table; use gipfl\Translation\TranslationHelper; @@ -13,10 +16,17 @@ class TemplateUsageTable extends Table { use TranslationHelper; + use TableWithBranchSupport; + + /** @var Auth */ + protected $auth; + protected $defaultAttributes = ['class' => 'pivot']; protected $objectType; + protected $searchColumns = []; + public function getTypes() { return [ @@ -25,26 +35,22 @@ class TemplateUsageTable extends Table ]; } - protected function getTypeSummaryDefinitions() - { - return [ - 'templates' => $this->getSummaryLine('template'), - 'objects' => $this->getSummaryLine('object'), - ]; - } - /** * @param IcingaObject $template + * @param Branch|null $branch + * * @return TemplateUsageTable + * + * @throws ProgrammingError */ - public static function forTemplate(IcingaObject $template) + public static function forTemplate(IcingaObject $template, Auth $auth, Branch $branch = null) { $type = ucfirst($template->getShortTableName()); - $class = __NAMESPACE__ . "\\${type}TemplateUsageTable"; + $class = __NAMESPACE__ . "\\{$type}TemplateUsageTable"; if (class_exists($class)) { - return new $class($template); + return new $class($template, $auth, $branch); } else { - return new static($template); + return new static($template, $auth, $branch); } } @@ -58,8 +64,9 @@ class TemplateUsageTable extends Table ]; } - protected function __construct(IcingaObject $template) + protected function __construct(IcingaObject $template, Auth $auth, Branch $branch = null) { + $this->auth = $auth; if ($template->get('object_type') !== 'template') { throw new ProgrammingError( @@ -68,6 +75,7 @@ class TemplateUsageTable extends Table ); } + $this->setBranch($branch); $this->objectType = $objectType = $template->getShortTableName(); $types = $this->getTypes(); $usage = $this->getUsageSummary($template); @@ -85,7 +93,7 @@ class TemplateUsageTable extends Table Table::td( Link::create( $count, - "director/${objectType}template/$type", + "director/{$objectType}template/$type", [ 'name' => $template->getObjectName(), 'inheritance' => $inheritance @@ -98,6 +106,7 @@ class TemplateUsageTable extends Table } if ($used) { + $this->getHeader()->add(Table::row($this->getColumnsToBeRendered(), null, 'th')); $this->add($rows); } else { $this->add($this->translate('This template is not in use')); @@ -106,52 +115,51 @@ class TemplateUsageTable extends Table protected function getUsageSummary(IcingaObject $template) { - $id = $template->getAutoincId(); $connection = $template->getConnection(); $db = $connection->getDbAdapter(); - $oType = $this->objectType; - $tree = new TemplateTree($oType, $connection); - $ids = $tree->listDescendantIdsFor($template); - if (empty($ids)) { - $ids = [0]; + + $types = array_keys($this->getTypes()); + $direct = []; + $indirect = []; + $templateType = $template->getShortTableName(); + + foreach ($this->getSummaryTables($templateType, $connection) as $type => $summaryTable) { + $directTable = clone $summaryTable; + $inDirectTable = clone $summaryTable; + + $direct[$type] = $db->query( + $directTable + ->filterTemplate($template, IcingaObjectFilterHelper::INHERIT_DIRECT) + ->getQuery() + )->rowCount(); + $indirect[$type] = $db->query( + $inDirectTable + ->filterTemplate($template, IcingaObjectFilterHelper::INHERIT_INDIRECT) + ->getQuery() + )->rowCount(); } - $baseQuery = $db->select()->from( - ['o' => 'icinga_' . $oType], - $this->getTypeSummaryDefinitions() - )->joinLeft( - ['oi' => "icinga_${oType}_inheritance"], - "oi.${oType}_id = o.id", - [] - ); - - $query = clone($baseQuery); - $direct = $db->fetchRow( - $query->where("oi.parent_${oType}_id = ?", $id) - ); - $query = clone($baseQuery); - $indirect = $db->fetchRow( - $query->where("oi.parent_${oType}_id IN (?)", $ids) - ); - //$indirect->templates = count($ids) - 1; $total = []; - $types = array_keys($this->getTypes()); foreach ($types as $type) { - $total[$type] = $direct->$type + $indirect->$type; + $total[$type] = $direct[$type] + $indirect[$type]; } return (object) [ - 'direct' => $direct, - 'indirect' => $indirect, + 'direct' => (object) $direct, + 'indirect' => (object) $indirect, 'total' => (object) $total ]; } - protected function getSummaryLine($type, $extra = null) + protected function getSummaryTables(string $templateType, Db $connection) { - if ($extra !== null) { - $extra = " AND $extra"; - } - return "COALESCE(SUM(CASE WHEN o.object_type = '${type}'${extra} THEN 1 ELSE 0 END), 0)"; + return [ + 'templates' => TemplatesTable::create( + $templateType, + $connection + ), + 'objects' => ObjectsTable::create($templateType, $connection, $this->auth) + ->setBranchUuid($this->branchUuid) + ]; } } diff --git a/library/Director/Web/Table/TemplatesTable.php b/library/Director/Web/Table/TemplatesTable.php index be195b2..d6582fd 100644 --- a/library/Director/Web/Table/TemplatesTable.php +++ b/library/Director/Web/Table/TemplatesTable.php @@ -36,8 +36,8 @@ class TemplatesTable extends ZfQueryBasedTable implements FilterableByUsage { $type = $this->type; $this->enableMultiSelect( - "director/${type}s/edittemplates", - "director/${type}template", + "director/{$type}s/edittemplates", + "director/{$type}template", ['name'] ); } @@ -60,12 +60,12 @@ class TemplatesTable extends ZfQueryBasedTable implements FilterableByUsage $name, Html::tag( 'span', - ['style' => 'font-style: italic'], + ['class' => 'font-italic'], $this->translate(' - not in use -') ) ]; - $url = Url::fromPath("director/${type}template/usage", [ + $url = Url::fromPath("director/{$type}template/usage", [ 'name' => $name ]); @@ -101,8 +101,8 @@ class TemplatesTable extends ZfQueryBasedTable implements FilterableByUsage { $type = $this->getType(); $this->getQuery()->where( - "(EXISTS (SELECT ${type}_id FROM icinga_${type}_inheritance" - . " WHERE parent_${type}_id = o.id))" + "(EXISTS (SELECT {$type}_id FROM icinga_{$type}_inheritance" + . " WHERE parent_{$type}_id = o.id))" ); } @@ -110,8 +110,8 @@ class TemplatesTable extends ZfQueryBasedTable implements FilterableByUsage { $type = $this->getType(); $this->getQuery()->where( - "(NOT EXISTS (SELECT ${type}_id FROM icinga_${type}_inheritance" - . " WHERE parent_${type}_id = o.id))" + "(NOT EXISTS (SELECT {$type}_id FROM icinga_{$type}_inheritance" + . " WHERE parent_{$type}_id = o.id))" ); } @@ -135,8 +135,8 @@ class TemplatesTable extends ZfQueryBasedTable implements FilterableByUsage protected function prepareQuery() { $type = $this->getType(); - $used = "CASE WHEN EXISTS(SELECT 1 FROM icinga_${type}_inheritance oi" - . " WHERE oi.parent_${type}_id = o.id) THEN 'y' ELSE 'n' END"; + $used = "CASE WHEN EXISTS(SELECT 1 FROM icinga_{$type}_inheritance oi" + . " WHERE oi.parent_{$type}_id = o.id) THEN 'y' ELSE 'n' END"; $columns = [ 'object_name' => 'o.object_name', @@ -145,7 +145,7 @@ class TemplatesTable extends ZfQueryBasedTable implements FilterableByUsage 'is_used' => $used, ]; $query = $this->db()->select()->from( - ['o' => "icinga_${type}"], + ['o' => "icinga_{$type}"], $columns )->where( "o.object_type = 'template'" diff --git a/library/Director/Web/Tabs/InfraTabs.php b/library/Director/Web/Tabs/InfraTabs.php index 8a65c4e..ea7a0cc 100644 --- a/library/Director/Web/Tabs/InfraTabs.php +++ b/library/Director/Web/Tabs/InfraTabs.php @@ -5,6 +5,7 @@ namespace Icinga\Module\Director\Web\Tabs; use Icinga\Authentication\Auth; use gipfl\Translation\TranslationHelper; use gipfl\IcingaWeb2\Widget\Tabs; +use Icinga\Module\Director\Auth\Permission; class InfraTabs extends Tabs { @@ -24,21 +25,21 @@ class InfraTabs extends Tabs { $auth = $this->auth; - if ($auth->hasPermission('director/audit')) { + if ($auth->hasPermission(Permission::AUDIT)) { $this->add('activitylog', [ 'label' => $this->translate('Activity Log'), 'url' => 'director/config/activities' ]); } - if ($auth->hasPermission('director/deploy')) { + if ($auth->hasPermission(Permission::DEPLOY)) { $this->add('deploymentlog', [ 'label' => $this->translate('Deployments'), 'url' => 'director/config/deployments' ]); } - if ($auth->hasPermission('director/admin')) { + if ($auth->hasPermission(Permission::ADMIN)) { $this->add('infrastructure', [ 'label' => $this->translate('Infrastructure'), 'url' => 'director/dashboard', diff --git a/library/Director/Web/Tabs/MainTabs.php b/library/Director/Web/Tabs/MainTabs.php index 5ea2e9b..48ffa7c 100644 --- a/library/Director/Web/Tabs/MainTabs.php +++ b/library/Director/Web/Tabs/MainTabs.php @@ -5,6 +5,7 @@ namespace Icinga\Module\Director\Web\Tabs; use gipfl\Translation\TranslationHelper; use gipfl\IcingaWeb2\Widget\Tabs; use Icinga\Authentication\Auth; +use Icinga\Module\Director\Auth\Permission; use Icinga\Module\Director\Web\Widget\Daemon\BackgroundDaemonState; use Icinga\Module\Director\Db; use Icinga\Module\Director\Health; @@ -26,7 +27,7 @@ class MainTabs extends Tabs 'label' => $this->translate('Overview'), 'url' => 'director' ]); - if ($this->auth->hasPermission('director/admin')) { + if ($this->auth->hasPermission(Permission::ADMIN)) { $this->add('health', [ 'label' => $this->translate('Health'), 'url' => 'director/health' @@ -39,7 +40,7 @@ class MainTabs extends Tabs public function render() { - if ($this->auth->hasPermission('director/admin')) { + if ($this->auth->hasPermission(Permission::ADMIN)) { if ($this->getActiveName() !== 'health') { $state = $this->getHealthState(); if ($state->isProblem()) { diff --git a/library/Director/Web/Tabs/ObjectTabs.php b/library/Director/Web/Tabs/ObjectTabs.php index cbd3f15..c355304 100644 --- a/library/Director/Web/Tabs/ObjectTabs.php +++ b/library/Director/Web/Tabs/ObjectTabs.php @@ -3,6 +3,7 @@ namespace Icinga\Module\Director\Web\Tabs; use Icinga\Authentication\Auth; +use Icinga\Module\Director\Auth\Permission; use Icinga\Module\Director\Objects\IcingaObject; use gipfl\Translation\TranslationHelper; use gipfl\IcingaWeb2\Widget\Tabs; @@ -46,10 +47,10 @@ class ObjectTabs extends Tabs protected function addTabsForNewObject() { $type = $this->type; - $this->add('add', array( + $this->add('add', [ 'url' => sprintf('director/%s/add', $type), 'label' => sprintf($this->translate('Add %s'), ucfirst($type)), - )); + ]); } protected function addTabsForExistingObject() @@ -59,14 +60,12 @@ class ObjectTabs extends Tabs $object = $this->object; $params = $object->getUrlParams(); - if (! $object->isExternal() - || in_array($object->getShortTableName(), $this->allowedExternals) - ) { - $this->add('modify', array( + if (! $object->isExternal() || in_array($object->getShortTableName(), $this->allowedExternals)) { + $this->add('modify', [ 'url' => sprintf('director/%s', $type), 'urlParams' => $params, 'label' => $this->translate(ucfirst($type)) - )); + ]); } if ($object->getShortTableName() === 'host') { $this->add('services', [ @@ -76,19 +75,17 @@ class ObjectTabs extends Tabs ]); } - if ($auth->hasPermission('director/showconfig')) { - if ($object->getShortTableName() !== 'service' - || $object->get('service_set_id') === null - ) { - $this->add('render', array( + if ($auth->hasPermission(Permission::SHOW_CONFIG)) { + if ($object->getShortTableName() !== 'service' || $object->get('service_set_id') === null) { + $this->add('render', [ 'url' => sprintf('director/%s/render', $type), 'urlParams' => $params, 'label' => $this->translate('Preview'), - )); + ]); } } - if ($auth->hasPermission('director/audit')) { + if ($auth->hasPermission(Permission::AUDIT)) { $this->add('history', array( 'url' => sprintf('director/%s/history', $type), 'urlParams' => $params, @@ -96,7 +93,7 @@ class ObjectTabs extends Tabs )); } - if ($auth->hasPermission('director/admin') && $this->hasFields()) { + if ($auth->hasPermission(Permission::ADMIN) && $this->hasFields()) { $this->add('fields', array( 'url' => sprintf('director/%s/fields', $type), 'urlParams' => $params, @@ -117,7 +114,7 @@ class ObjectTabs extends Tabs if ($object->supportsRanges()) { $this->add('ranges', [ - 'url' => "director/${type}/ranges", + 'url' => "director/{$type}/ranges", 'urlParams' => $params, 'label' => $this->translate('Ranges') ]); @@ -138,11 +135,11 @@ class ObjectTabs extends Tabs ]); } - if ($object->getShortTableName() === 'host' && $auth->hasPermission('director/hosts')) { + if ($object->getShortTableName() === 'host' && $auth->hasPermission(Permission::HOSTS)) { $this->add('agent', [ - 'url' => 'director/host/agent', + 'url' => 'director/host/agent', 'urlParams' => $params, - 'label' => $this->translate('Agent') + 'label' => $this->translate('Agent') ]); } } diff --git a/library/Director/Web/Tabs/ObjectsTabs.php b/library/Director/Web/Tabs/ObjectsTabs.php index 4f9e5a8..9d2a737 100644 --- a/library/Director/Web/Tabs/ObjectsTabs.php +++ b/library/Director/Web/Tabs/ObjectsTabs.php @@ -3,6 +3,7 @@ namespace Icinga\Module\Director\Web\Tabs; use Icinga\Authentication\Auth; +use Icinga\Module\Director\Auth\Permission; use Icinga\Module\Director\Objects\IcingaObject; use gipfl\Translation\TranslationHelper; use gipfl\IcingaWeb2\Widget\Tabs; @@ -21,65 +22,66 @@ class ObjectsTabs extends Tabs $plType = strtolower(preg_replace('/cys$/', 'cies', $shortName . 's')); $plType = str_replace('_', '-', $plType); - if ($auth->hasPermission("director/${plType}")) { - $this->add('index', array( + if ($auth->hasPermission("director/{$plType}")) { + $this->add('index', [ 'url' => sprintf('director/%s', $plType), 'label' => $this->translate(ucfirst($plType)), - )); + ]); } if ($object->getShortTableName() === 'command') { - $this->add('external', array( - 'url' => sprintf('director/%s', strtolower($plType)), + $this->add('external', [ + 'url' => sprintf('director/%s', strtolower($plType)), 'urlParams' => ['type' => 'external_object'], - 'label' => $this->translate('External'), - )); + 'label' => $this->translate('External'), + ]); } - if ($auth->hasPermission('director/admin') || ( + if ($auth->hasPermission(Permission::ADMIN) + || ( $object->getShortTableName() === 'notification' - && $auth->hasPermission('director/notifications') + && $auth->hasPermission(Permission::NOTIFICATIONS) ) || ( $object->getShortTableName() === 'scheduled_downtime' - && $auth->hasPermission('director/scheduled-downtimes') + && $auth->hasPermission(Permission::SCHEDULED_DOWNTIMES) )) { if ($object->supportsApplyRules()) { - $this->add('applyrules', array( - 'url' => sprintf('director/%s/applyrules', $plType), + $this->add('applyrules', [ + 'url' => sprintf('director/%s/applyrules', $plType), 'label' => $this->translate('Apply') - )); + ]); } } - if ($auth->hasPermission('director/admin') && $type !== 'zone') { + if ($auth->hasPermission(Permission::ADMIN) && $type !== 'zone') { if ($object->supportsImports()) { - $this->add('templates', array( - 'url' => sprintf('director/%s/templates', $plType), + $this->add('templates', [ + 'url' => sprintf('director/%s/templates', $plType), 'label' => $this->translate('Templates'), - )); + ]); } if ($object->supportsGroups()) { - $this->add('groups', array( - 'url' => sprintf('director/%sgroups', $typeUrl), + $this->add('groups', [ + 'url' => sprintf('director/%sgroups', $typeUrl), 'label' => $this->translate('Groups') - )); + ]); } } - if ($auth->hasPermission('director/admin')) { + if ($auth->hasPermission(Permission::ADMIN)) { if ($object->supportsChoices()) { - $this->add('choices', array( - 'url' => sprintf('director/templatechoices/%s', $shortName), + $this->add('choices', [ + 'url' => sprintf('director/templatechoices/%s', $shortName), 'label' => $this->translate('Choices') - )); + ]); } } - if ($object->supportsSets() && $auth->hasPermission("director/${typeUrl}sets")) { - $this->add('sets', array( - 'url' => sprintf('director/%s/sets', $plType), + if ($object->supportsSets() && $auth->hasPermission("director/{$typeUrl}sets")) { + $this->add('sets', [ + 'url' => sprintf('director/%s/sets', $plType), 'label' => $this->translate('Sets') - )); + ]); } } } diff --git a/library/Director/Web/Tree/TemplateTreeRenderer.php b/library/Director/Web/Tree/TemplateTreeRenderer.php index e238ded..8b23518 100644 --- a/library/Director/Web/Tree/TemplateTreeRenderer.php +++ b/library/Director/Web/Tree/TemplateTreeRenderer.php @@ -71,7 +71,7 @@ class TemplateTreeRenderer extends BaseHtmlElement } else { $li->add(Link::create( $tree['name'], - "director/${type}template/usage", + "director/{$type}template/usage", array('name' => $tree['name']), array('class' => 'icon-' .$type) )); diff --git a/library/Director/Web/Widget/ActivityLogInfo.php b/library/Director/Web/Widget/ActivityLogInfo.php index 8454b26..2b64fd4 100644 --- a/library/Director/Web/Widget/ActivityLogInfo.php +++ b/library/Director/Web/Widget/ActivityLogInfo.php @@ -3,7 +3,10 @@ namespace Icinga\Module\Director\Web\Widget; use gipfl\Json\JsonString; +use Icinga\Module\Director\Data\FieldReferenceLoader; +use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshotFieldResolver; use Icinga\Module\Director\Objects\DirectorActivityLog; +use Icinga\Module\Director\Web\Form\IcingaObjectFieldLoader; use ipl\Html\HtmlDocument; use ipl\Html\HtmlElement; use Icinga\Date\DateFormatter; @@ -83,8 +86,7 @@ class ActivityLogInfo extends HtmlDocument /** @var Url $url */ $url = $url->without('checksum')->without('show'); $div = Html::tag('div', [ - 'class' => 'pagination-control', - 'style' => 'float: right; width: 5em' + 'class' => ['pagination-control', 'activity-log-control'], ]); $ul = Html::tag('ul', ['class' => 'nav tab-nav']); @@ -434,11 +436,30 @@ class ActivityLogInfo extends HtmlDocument { if ($object instanceof IcingaService) { return $this->previewService($object); + } elseif ($object instanceof IcingaServiceSet) { + return $this->previewServiceSet($object); } else { return $object->toSingleIcingaConfig(); } } + /** + * Render service set to be previewed + * + * @param IcingaServiceSet $object + * + * @return IcingaConfig + */ + protected function previewServiceSet(IcingaServiceSet $object) + { + $config = $object->toSingleIcingaConfig(); + foreach ($object->getCachedServices() as $service) { + $service->renderToConfig($config); + } + + return $config; + } + protected function previewService(IcingaService $service) { if (($set = $service->get('service_set')) !== null) { @@ -625,10 +646,27 @@ class ActivityLogInfo extends HtmlDocument $newProps['object_type'] = $props->object_type; } - return IcingaObject::createByType( + $object = IcingaObject::createByType( $type, $newProps, $this->db - )->setProperties((array) $props); + ); + + if ($type === 'icinga_service_set' && isset($props->services)) { + $services = []; + foreach ($props->services as $service) { + $services[$service->object_name] = IcingaObject::createByType( + 'icinga_service', + (array) $service, + $this->db + ); + } + + /** @var IcingaServiceSet $object */ + $object->setCachedServices($services); + unset($props->services); + } + + return $object->setProperties((array) $props); } } diff --git a/library/Director/Web/Widget/AdditionalTableActions.php b/library/Director/Web/Widget/AdditionalTableActions.php index 978f399..7495189 100644 --- a/library/Director/Web/Widget/AdditionalTableActions.php +++ b/library/Director/Web/Widget/AdditionalTableActions.php @@ -2,6 +2,7 @@ namespace Icinga\Module\Director\Web\Widget; +use Icinga\Module\Director\Auth\Permission; use ipl\Html\Html; use ipl\Html\HtmlDocument; use gipfl\IcingaWeb2\Icon; @@ -35,10 +36,10 @@ class AdditionalTableActions public function appendTo(HtmlDocument $parent) { $links = []; - if ($this->hasPermission('director/admin')) { + if ($this->hasPermission(Permission::ADMIN)) { $links[] = $this->createDownloadJsonLink(); } - if ($this->hasPermission('director/showsql')) { + if ($this->hasPermission(Permission::SHOW_SQL)) { $links[] = $this->createShowSqlToggle(); } diff --git a/library/Director/Web/Widget/BranchedObjectHint.php b/library/Director/Web/Widget/BranchedObjectHint.php index ec16094..c50f923 100644 --- a/library/Director/Web/Widget/BranchedObjectHint.php +++ b/library/Director/Web/Widget/BranchedObjectHint.php @@ -15,33 +15,45 @@ class BranchedObjectHint extends HtmlDocument { use TranslationHelper; - public function __construct(Branch $branch, Auth $auth, BranchedObject $object = null) + public function __construct(Branch $branch, Auth $auth, BranchedObject $object = null, $hasPreferredBranch = false) { if (! $branch->isBranch()) { - return; - } - $hook = Branch::requireHook(); - - $name = $branch->getName(); - if (substr($name, 0, 1) === '/') { - $label = $this->translate('this configuration branch'); + if ($hasPreferredBranch) { + $main = true; + $hintMethod = 'warning'; + $link = $this->translate('the main configuration branch'); + $deployHint = ' ' . $this->translate('This will be part of the next deployment'); + } else { + return; + } } else { - $label = $name; + $main = false; + $hintMethod = 'info'; + $deployHint = ' ' . $this->translate('This will not be part of any deployment, unless being merged'); + $hook = Branch::requireHook(); + $name = $branch->getName(); + if (substr($name, 0, 1) === '/') { + $label = $this->translate('this configuration branch'); + } else { + $label = $name; + } + $link = $hook->linkToBranch($branch, $auth, $label); } - $link = $hook->linkToBranch($branch, $auth, $label); + if ($object === null) { - $this->add(Hint::info(Html::sprintf($this->translate( - 'This object will be created in %s. It will not be part of any deployment' - . ' unless being merged' - ), $link))); + $this->add(Hint::$hintMethod(Html::sprintf($this->translate( + 'This object will be created in %s.' + ) . $deployHint, $link))); return; } if (! $object->hasBeenTouchedByBranch()) { - $this->add(Hint::info(Html::sprintf($this->translate( - 'Your changes will be stored in %s. The\'ll not be part of any deployment' - . ' unless being merged' - ), $link))); + $this->add(Hint::$hintMethod(Html::sprintf($this->translate( + 'Your changes are going to be stored in %s.' + ) . $deployHint, $link))); + return; + } + if ($main) { return; } diff --git a/library/Director/Web/Widget/BranchedObjectsHint.php b/library/Director/Web/Widget/BranchedObjectsHint.php index d689178..3f00f9e 100644 --- a/library/Director/Web/Widget/BranchedObjectsHint.php +++ b/library/Director/Web/Widget/BranchedObjectsHint.php @@ -13,9 +13,14 @@ class BranchedObjectsHint extends HtmlDocument { use TranslationHelper; - public function __construct(Branch $branch, Auth $auth) + public function __construct(Branch $branch, Auth $auth, $hasPreferredBranch = false) { if (! $branch->isBranch()) { + if ($hasPreferredBranch) { + $this->add(Hint::warning($this->translate( + "You're currently in the master branch, your changes will make part of the next Deployment" + ))); + } return; } $hook = Branch::requireHook(); diff --git a/library/Director/Web/Widget/DeploymentInfo.php b/library/Director/Web/Widget/DeploymentInfo.php index 110200f..1f87abc 100644 --- a/library/Director/Web/Widget/DeploymentInfo.php +++ b/library/Director/Web/Widget/DeploymentInfo.php @@ -2,6 +2,7 @@ namespace Icinga\Module\Director\Web\Widget; +use Icinga\Module\Director\Auth\Permission; use ipl\Html\HtmlDocument; use Icinga\Authentication\Auth; use Icinga\Module\Director\IcingaConfig\IcingaConfig; @@ -55,7 +56,7 @@ class DeploymentInfo extends HtmlDocument 'url' => $request->getUrl() ))->activate('deployment'); - if ($dep->config_checksum !== null && $auth->hasPermission('director/showconfig')) { + if ($dep->config_checksum !== null && $auth->hasPermission(Permission::SHOW_CONFIG)) { $tabs->add('config', array( 'label' => $this->translate('Config'), 'url' => 'director/config/files', @@ -72,7 +73,8 @@ class DeploymentInfo extends HtmlDocument protected function createInfoTable() { $dep = $this->deployment; - $table = new NameValueTable(); + $table = (new NameValueTable()) + ->addAttributes(['class' => 'deployment-details']); $table->addNameValuePairs([ $this->translate('Deployment time') => $dep->start_time, $this->translate('Sent to') => $dep->peer_identity, @@ -135,16 +137,21 @@ class DeploymentInfo extends HtmlDocument } else { return [$this->translate('Unknown, failed to collect related information'), new Icon('help')]; } - } elseif ($dep->startup_succeeded === 'y') { - return $this->colored('green', [$this->translate('Succeeded'), new Icon('ok')]); } else { - return $this->colored('red', [$this->translate('Failed'), new Icon('cancel')]); - } - } + $div = Html::tag('div')->setSeparator(' '); - protected function colored($color, array $content) - { - return Html::tag('div', ['style' => "color: $color;"], $content)->setSeparator(' '); + if ($dep->startup_succeeded === 'y') { + $div + ->addAttributes(['class' => 'succeeded']) + ->add([$this->translate('Succeeded'), new Icon('ok')]); + } else { + $div + ->addAttributes(['class' => 'failed']) + ->add([$this->translate('Failed'), new Icon('cancel')]); + } + + return $div; + } } public function render() diff --git a/library/Director/Web/Widget/IcingaObjectInspection.php b/library/Director/Web/Widget/IcingaObjectInspection.php index 61f3567..d9cf69d 100644 --- a/library/Director/Web/Widget/IcingaObjectInspection.php +++ b/library/Director/Web/Widget/IcingaObjectInspection.php @@ -205,7 +205,7 @@ class IcingaObjectInspection extends BaseHtmlElement $this->add(Html::tag('p')->add(Html::sprintf( 'The configuration for this object has been rendered by Icinga' . ' Director %s to %s', - DateFormatter::timeAgo(strtotime($deployment->start_time, false)), + DateFormatter::timeAgo(strtotime($deployment->start_time)), $this->linkToSourceLocation($deployment, $source) ))); } -- cgit v1.2.3