diff --git a/doc/82-Changelog.md b/doc/82-Changelog.md index a988cabd..d126b55b 100644 --- a/doc/82-Changelog.md +++ b/doc/82-Changelog.md @@ -19,6 +19,7 @@ before switching to a new version. * FEATURE: Admins have now access to JSON download links in many places * FEATURE: Users equipped with related permissions can toggle "Show SQL" in the GUI * FEATURE: A Service Set can now be assigned to multiple hosts at once #1281 +* FEATURE: Commands can now be filtered by usage (#1480) ### CLI * FEATURE: Director Health Check Plugin (#1278) diff --git a/library/Director/Web/Controller/ObjectsController.php b/library/Director/Web/Controller/ObjectsController.php index 1ad7bf3b..e13df9fe 100644 --- a/library/Director/Web/Controller/ObjectsController.php +++ b/library/Director/Web/Controller/ObjectsController.php @@ -37,6 +37,7 @@ abstract class ObjectsController extends ActionController /** * @return $this + * @throws \Icinga\Exception\Http\HttpNotFoundException */ protected function addObjectsTabs() { @@ -50,6 +51,11 @@ abstract class ObjectsController extends ActionController return $this; } + /** + * @return IcingaObjectsHandler + * @throws \Icinga\Exception\ConfigurationError + * @throws \Icinga\Exception\IcingaException + */ protected function apiRequestHandler() { $request = $this->getRequest(); @@ -72,6 +78,11 @@ abstract class ObjectsController extends ActionController ))->setTable($table); } + /** + * @throws \Icinga\Exception\ConfigurationError + * @throws \Icinga\Exception\Http\HttpNotFoundException + * @throws \Icinga\Exception\IcingaException + */ public function indexAction() { if ($this->getRequest()->isApiRequest()) { @@ -103,16 +114,24 @@ abstract class ObjectsController extends ActionController // Hint: might be used in controllers extending this $this->table = $this->getTable(); $this->table->renderTo($this); - (new AdditionalTableActions($this->getAuth(), $this->url())) + (new AdditionalTableActions($this->getAuth(), $this->url(), $this->table)) ->appendTo($this->actions()); } + /** + * @return ObjectsTable + * @throws \Icinga\Exception\ConfigurationError + */ protected function getTable() { return ObjectsTable::create($this->getType(), $this->db()) ->setAuth($this->getAuth()); } + /** + * @throws NotFoundError + * @throws \Icinga\Exception\ConfigurationError + */ public function editAction() { $type = ucfirst($this->getType()); @@ -146,6 +165,9 @@ abstract class ObjectsController extends ActionController * Loads the TemplatesTable or the TemplateTreeRenderer * * Passing render=tree switches to the tree view. + * @throws \Icinga\Exception\ConfigurationError + * @throws \Icinga\Exception\Http\HttpNotFoundException + * @throws \Icinga\Exception\IcingaException */ public function templatesAction() { @@ -170,11 +192,21 @@ abstract class ObjectsController extends ActionController : TemplatesTable::create($shortType, $this->db())->renderTo($this); } + /** + * @return $this + * @throws \Icinga\Security\SecurityException + */ protected function assertApplyRulePermission() { return $this->assertPermission('director/admin'); } + /** + * @throws \Icinga\Exception\ConfigurationError + * @throws \Icinga\Exception\Http\HttpNotFoundException + * @throws \Icinga\Exception\ProgrammingError + * @throws \Icinga\Security\SecurityException + */ public function applyrulesAction() { $type = $this->getType(); @@ -210,6 +242,11 @@ abstract class ObjectsController extends ActionController $table->renderTo($this); } + /** + * @throws NotFoundError + * @throws \Icinga\Exception\ConfigurationError + * @throws \Icinga\Exception\Http\HttpNotFoundException + */ public function setsAction() { $type = $this->getType(); @@ -243,6 +280,10 @@ abstract class ObjectsController extends ActionController ObjectSetTable::create($type, $this->db(), $this->getAuth())->renderTo($this); } + /** + * @return array + * @throws \Icinga\Exception\ConfigurationError + */ protected function loadMultiObjectsFromParams() { $filter = Filter::fromQueryString($this->params->toString()); @@ -288,12 +329,19 @@ abstract class ObjectsController extends ActionController return $this; } + /** + * @param $feature + * @return bool + */ protected function supports($feature) { $func = "supports$feature"; return IcingaObject::createByType($this->getType())->$func(); } + /** + * @return string + */ protected function getBaseType() { $type = $this->getType(); @@ -304,6 +352,9 @@ abstract class ObjectsController extends ActionController } } + /** + * @return string + */ protected function getType() { // Strip final 's' and upcase an eventual 'group' @@ -318,11 +369,17 @@ abstract class ObjectsController extends ActionController ); } + /** + * @return string + */ protected function getPluralType() { return preg_replace('/cys$/', 'cies', $this->getType() . 's'); } + /** + * @return string + */ protected function getPluralBaseType() { return preg_replace('/cys$/', 'cies', $this->getBaseType() . 's'); diff --git a/library/Director/Web/Table/FilterableByUsage.php b/library/Director/Web/Table/FilterableByUsage.php new file mode 100644 index 00000000..5e8695f3 --- /dev/null +++ b/library/Director/Web/Table/FilterableByUsage.php @@ -0,0 +1,10 @@ +getQuery()->where('object_type = ?', $type); + return $this; } + public function showOnlyUsed() + { + $this->getQuery()->where('(' + . 'EXISTS (SELECT check_command_id FROM icinga_host WHERE check_command_id = o.id)' + . ' OR EXISTS (SELECT check_command_id FROM icinga_service WHERE check_command_id = o.id)' + . ' OR EXISTS (SELECT event_command_id FROM icinga_host WHERE event_command_id = o.id)' + . ' OR EXISTS (SELECT event_command_id FROM icinga_service WHERE event_command_id = o.id)' + . ' OR EXISTS (SELECT command_id FROM icinga_notification WHERE command_id = o.id)' + . ')' + ); + } + + public function showOnlyUnUsed() + { + $this->getQuery()->where('(' + . 'NOT EXISTS (SELECT check_command_id FROM icinga_host WHERE check_command_id = o.id)' + . ' AND NOT EXISTS (SELECT check_command_id FROM icinga_service WHERE check_command_id = o.id)' + . ' AND NOT EXISTS (SELECT event_command_id FROM icinga_host WHERE event_command_id = o.id)' + . ' AND NOT EXISTS (SELECT event_command_id FROM icinga_service WHERE event_command_id = o.id)' + . ' AND NOT EXISTS (SELECT command_id FROM icinga_notification WHERE command_id = o.id)' + . ')' + ); + } + protected function applyObjectTypeFilter(ZfSelect $query) { return $query; diff --git a/library/Director/Web/Widget/AdditionalTableActions.php b/library/Director/Web/Widget/AdditionalTableActions.php index 668e8e1f..b1719f1c 100644 --- a/library/Director/Web/Widget/AdditionalTableActions.php +++ b/library/Director/Web/Widget/AdditionalTableActions.php @@ -6,8 +6,10 @@ use dipl\Html\Html; use dipl\Html\Icon; use dipl\Html\Link; use dipl\Translation\TranslationHelper; +use dipl\Web\Table\ZfQueryBasedTable; use dipl\Web\Url; use Icinga\Authentication\Auth; +use Icinga\Module\Director\Web\Table\FilterableByUsage; class AdditionalTableActions { @@ -19,10 +21,14 @@ class AdditionalTableActions /** @var Url */ protected $url; - public function __construct(Auth $auth, Url $url) + /** @var ZfQueryBasedTable */ + protected $table; + + public function __construct(Auth $auth, Url $url, ZfQueryBasedTable $table) { $this->auth = $auth; $this->url = $url; + $this->table = $table; } public function appendTo(Html $parent) @@ -35,6 +41,10 @@ class AdditionalTableActions $links[] = $this->createShowSqlToggle(); } + if ($this->table instanceof FilterableByUsage) { + $parent->add($this->showUsageFilter($this->table)); + } + if (! empty($links)) { $parent->add($this->moreOptions($links)); } @@ -69,6 +79,43 @@ class AdditionalTableActions return $link; } + protected function showUsageFilter(FilterableByUsage $table) + { + $active = $this->url->getParam('usage', 'all'); + $links = [ + Link::create($this->translate('all'), $this->url->without('usage')), + Link::create($this->translate('used'), $this->url->with('usage', 'used')), + Link::create($this->translate('unused'), $this->url->with('usage', 'unused')), + ]; + + if ($active === 'used') { + $table->showOnlyUsed(); + } elseif ($active === 'unused') { + $table->showOnlyUnUsed(); + } + + $options = $this->ul( + $this->li([ + Link::create( + sprintf($this->translate('Usage (%s)'), $active), + '#', + null, + [ + 'class' => 'icon-sitemap' + ] + ), + $subUl = Html::tag('ul') + ]), + ['class' => 'nav'] + ); + + foreach ($links as $link) { + $subUl->add($this->li($link)); + } + + return $options; + } + protected function moreOptions($links) { $options = $this->ul(