Commands: show usage over templates and objects

fixes #335
This commit is contained in:
Thomas Gelf 2018-05-04 18:02:05 +02:00
parent 6add437dce
commit 99ddb9266b
5 changed files with 217 additions and 4 deletions

View File

@ -2,13 +2,18 @@
namespace Icinga\Module\Director\Controllers;
use dipl\Html\Html;
use Icinga\Module\Director\Forms\IcingaCommandArgumentForm;
use Icinga\Module\Director\Objects\IcingaCommand;
use Icinga\Module\Director\Resolver\CommandUsage;
use Icinga\Module\Director\Web\Controller\ObjectController;
use Icinga\Module\Director\Web\Table\IcingaCommandArgumentTable;
class CommandController extends ObjectController
{
/**
* @throws \Icinga\Exception\ProgrammingError
*/
public function init()
{
parent::init();
@ -22,6 +27,56 @@ class CommandController extends ObjectController
}
}
/**
* @throws \Icinga\Exception\ProgrammingError
*/
public function indexAction()
{
$this->showUsage();
parent::indexAction();
}
/**
* @throws \Icinga\Exception\ProgrammingError
*/
public function renderAction()
{
if ($this->object->isExternal()) {
$this->showUsage();
}
parent::renderAction();
}
/**
* @throws \Icinga\Exception\ProgrammingError
*/
protected function showUsage()
{
/** @var IcingaCommand $command */
$command = $this->object;
if ($command->isInUse()) {
$usage = new CommandUsage($command);
$this->content()->add(Html::tag('p', [
'class' => 'information',
'data-base-target' => '_next'
], Html::sprintf(
$this->translate('This Command is currently being used by %s'),
Html::tag('span', null, $usage->getLinks())->setSeparator(', ')
)));
} else {
$this->content()->add(Html::tag(
'p',
['class' => 'warning'],
$this->translate('This Command is currently not in use')
));
}
}
/**
* @throws \Icinga\Exception\Http\HttpNotFoundException
* @throws \Icinga\Exception\ProgrammingError
*/
public function argumentsAction()
{
$p = $this->params;

View File

@ -23,6 +23,7 @@ before switching to a new version.
* 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)
* FEATURE: Show usage of Commands over templates and objects (#335)
* FIX: Don't suggest Command templates where Commands are required (#1414)
* FIX: Do not allow to delete Commands being used by other objects (#1443)

View File

@ -0,0 +1,106 @@
<?php
namespace Icinga\Module\Director\Resolver;
use Icinga\Exception\ProgrammingError;
use Icinga\Module\Director\Objects\IcingaCommand;
use dipl\Html\Link;
use dipl\Translation\TranslationHelper;
class CommandUsage
{
use TranslationHelper;
/** @var IcingaCommand */
protected $command;
/** @var \Zend_Db_Adapter_Abstract */
protected $db;
/**
* CommandUsageTable constructor.
* @param IcingaCommand $command
* @throws ProgrammingError
*/
public function __construct(IcingaCommand $command)
{
if ($command->isTemplate()) {
throw new ProgrammingError(
'CommandUsageTable expects object or external_object, got a template'
);
}
$this->command = $command;
$this->db = $command->getDb();
}
/**
* @return array
* @throws ProgrammingError
*/
public function getLinks()
{
$name = $this->command->getObjectName();
$links = [];
$map = [
'host' => ['check_command', 'event_command'],
'service' => ['check_command', 'event_command'],
'notification' => ['command'],
];
$types = [
'host' => [
'object' => $this->translate('%d Host(s)'),
'template' => $this->translate('%d Host Template(s)'),
],
'service' => [
'object' => $this->translate('%d Service(s)'),
'template' => $this->translate('%d Service Template(s)'),
'apply' => $this->translate('%d Service Apply Rule(s)'),
],
'notification' => [
'object' => $this->translate('%d Notification(s)'),
'template' => $this->translate('%d Notification Template(s)'),
'apply' => $this->translate('%d Notification Apply Rule(s)'),
],
];
$urlSuffix = [
'object' => '',
'template' => '/templates',
'apply' => '/applyrules',
];
foreach ($map as $type => $relations) {
$res = $this->fetchFor($type, $relations, array_keys($types[$type]));
foreach ($types[$type] as $objectType => $caption) {
if ($res->$objectType > 0) {
$suffix = $urlSuffix[$objectType];
$links[] = Link::create(
sprintf($caption, $res->$objectType),
"director/${type}s$suffix",
['command' => $name]
);
}
}
}
return $links;
}
protected function fetchFor($table, $rels, $objectTypes)
{
$id = $this->command->getAutoincId();
$columns = [];
foreach ($objectTypes as $type) {
$columns[$type] = "COALESCE(SUM(CASE WHEN object_type = '$type' THEN 1 ELSE 0 END), 0)";
}
$query = $this->db->select()->from("icinga_$table", $columns);
foreach ($rels as $rel) {
$query->orWhere("${rel}_id = ?", $id);
}
return $this->db->fetchRow($query);
}
}

View File

@ -2,11 +2,13 @@
namespace Icinga\Module\Director\Web\Controller;
use dipl\Web\Table\ZfQueryBasedTable;
use Icinga\Data\Filter\FilterChain;
use Icinga\Data\Filter\FilterExpression;
use Icinga\Exception\NotFoundError;
use Icinga\Data\Filter\Filter;
use Icinga\Module\Director\Forms\IcingaMultiEditForm;
use Icinga\Module\Director\Objects\IcingaCommand;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\RestApi\IcingaObjectsHandler;
@ -112,7 +114,8 @@ abstract class ObjectsController extends ActionController
}
// Hint: might be used in controllers extending this
$this->table = $this->getTable();
$this->table = $this->eventuallyFilterCommand($this->getTable());
$this->table->renderTo($this);
(new AdditionalTableActions($this->getAuth(), $this->url(), $this->table))
->appendTo($this->actions());
@ -187,9 +190,13 @@ abstract class ObjectsController extends ActionController
)
->actions(new TemplateActionBar($shortType, $this->url()));
$this->params->get('render') === 'tree'
? TemplateTreeRenderer::showType($shortType, $this, $this->db())
: TemplatesTable::create($shortType, $this->db())->renderTo($this);
if ($this->params->get('render') === 'tree') {
TemplateTreeRenderer::showType($shortType, $this, $this->db());
} else {
$table = TemplatesTable::create($shortType, $this->db());
$this->eventuallyFilterCommand($table);
$table->renderTo($this);
}
}
/**
@ -239,6 +246,7 @@ abstract class ObjectsController extends ActionController
$table = new ApplyRulesTable($this->db());
$table->setType($this->getType());
$this->eventuallyFilterCommand($table);
$table->renderTo($this);
}
@ -311,6 +319,37 @@ abstract class ObjectsController extends ActionController
return $objects;
}
/**
* @param ZfQueryBasedTable $table
* @return ZfQueryBasedTable
* @throws \Icinga\Exception\ConfigurationError
*/
protected function eventuallyFilterCommand(ZfQueryBasedTable $table)
{
if ($this->params->get('command')) {
$command = IcingaCommand::load($this->params->get('command'), $this->db());
switch ($this->getBaseType()) {
case 'host':
case 'service':
$table->getQuery()->where(
$this->db()->getDbAdapter()->quoteInto(
'(o.check_command_id = ? OR o.event_command_id = ?)',
$command->getAutoincId()
)
);
break;
case 'notification':
$table->getQuery()->where(
'o.command_id = ?',
$command->getAutoincId()
);
break;
}
}
return $table;
}
/**
* @param $feature
* @return $this

View File

@ -1041,6 +1041,18 @@ p.warning {
}
}
p.information {
color: white;
padding: 1em 2em;
background-color: @colorOk;
font-weight: bold;
a {
color: inherit;
text-decoration: underline;
}
}
table.tinystats {
font-size: 0.7em;
float: right;