diff options
Diffstat (limited to '')
14 files changed, 2009 insertions, 0 deletions
diff --git a/application/forms/Command/CommandForm.php b/application/forms/Command/CommandForm.php new file mode 100644 index 0000000..a535c6d --- /dev/null +++ b/application/forms/Command/CommandForm.php @@ -0,0 +1,179 @@ +<?php + +/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Forms\Command; + +use ArrayIterator; +use Exception; +use Generator; +use Icinga\Application\Logger; +use Icinga\Module\Icingadb\Command\IcingaCommand; +use Icinga\Module\Icingadb\Command\Transport\CommandTransport; +use Icinga\Module\Icingadb\Common\Auth; +use Icinga\Web\Notification; +use Icinga\Web\Session; +use ipl\Html\Form; +use ipl\Orm\Model; +use ipl\Web\Common\CsrfCounterMeasure; +use Traversable; + +abstract class CommandForm extends Form +{ + use Auth; + use CsrfCounterMeasure; + + protected $defaultAttributes = ['class' => 'icinga-form icinga-controls']; + + /** @var mixed */ + protected $objects; + + /** @var bool */ + protected $isApiTarget = false; + + /** + * Whether an error occurred while sending the command + * + * Prevents the success message from being rendered simultaneously + * + * @var bool + */ + protected $errorOccurred = false; + + /** + * Set the objects to issue the command for + * + * @param mixed $objects A traversable that is also countable + * + * @return $this + */ + public function setObjects($objects): self + { + $this->objects = $objects; + + return $this; + } + + /** + * Get the objects to issue the command for + * + * @return mixed + */ + public function getObjects() + { + return $this->objects; + } + + /** + * Set whether this form is an API target + * + * @param bool $state + * + * @return $this + */ + public function setIsApiTarget(bool $state = true): self + { + $this->isApiTarget = $state; + + return $this; + } + + /** + * Get whether this form is an API target + * + * @return bool + */ + public function isApiTarget(): bool + { + return $this->isApiTarget; + } + + /** + * Create and add form elements representing the command's options + * + * @return void + */ + abstract protected function assembleElements(); + + /** + * Create and add a submit button to the form + * + * @return void + */ + abstract protected function assembleSubmitButton(); + + /** + * Get the commands to issue for the given objects + * + * @param Traversable<Model> $objects + * + * @return Traversable<IcingaCommand> + */ + abstract protected function getCommands(Traversable $objects): Traversable; + + protected function assemble() + { + $this->assembleElements(); + + if (! $this->isApiTarget()) { + $this->assembleSubmitButton(); + $this->addElement($this->createCsrfCounterMeasure(Session::getSession()->getId())); + } + } + + protected function onSuccess() + { + $errors = []; + $objects = $this->getObjects(); + + foreach ($this->getCommands(is_array($objects) ? new ArrayIterator($objects) : $objects) as $command) { + try { + $this->sendCommand($command); + } catch (Exception $e) { + Logger::error($e->getMessage()); + $errors[] = $e->getMessage(); + } + } + + if (! empty($errors)) { + if (count($errors) > 1) { + Notification::warning( + t('Some commands were not transmitted. Please check the log. The first error follows.') + ); + } + + $this->errorOccurred = true; + + Notification::error($errors[0]); + } + } + + /** + * Transmit the given command + * + * @param IcingaCommand $command + * + * @return void + */ + protected function sendCommand(IcingaCommand $command) + { + (new CommandTransport())->send($command); + } + + /** + * Yield the $objects the currently logged in user has the permission $permission for + * + * @param string $permission + * @param Traversable $objects + * + * @return Generator + */ + protected function filterGrantedOn(string $permission, Traversable $objects): Generator + { + foreach ($objects as $object) { + if ($this->isGrantedOn($permission, $object)) { + yield $object; + } + } + } +} diff --git a/application/forms/Command/Instance/ToggleInstanceFeaturesForm.php b/application/forms/Command/Instance/ToggleInstanceFeaturesForm.php new file mode 100644 index 0000000..cf14db8 --- /dev/null +++ b/application/forms/Command/Instance/ToggleInstanceFeaturesForm.php @@ -0,0 +1,154 @@ +<?php + +/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Forms\Command\Instance; + +use Icinga\Module\Icingadb\Command\Instance\ToggleInstanceFeatureCommand; +use Icinga\Module\Icingadb\Forms\Command\CommandForm; +use Icinga\Web\Notification; +use ipl\Web\FormDecorator\IcingaFormDecorator; +use Traversable; + +class ToggleInstanceFeaturesForm extends CommandForm +{ + protected $features; + + protected $featureStatus; + + /** + * ToggleFeature(s) being used to submit this form + * + * @var ToggleInstanceFeatureCommand[] + */ + protected $submittedFeatures = []; + + public function __construct(array $featureStatus) + { + $this->featureStatus = $featureStatus; + $this->features = [ + ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS => + t('Active Host Checks'), + ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS => + t('Active Service Checks'), + ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS => + t('Event Handlers'), + ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION => + t('Flap Detection'), + ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS => + t('Notifications'), + ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA => + t('Performance Data') + ]; + + $this->getAttributes()->add('class', 'instance-features'); + + $this->on(self::ON_SUCCESS, function () { + if ($this->errorOccurred) { + return; + } + + foreach ($this->submittedFeatures as $feature) { + $enabled = $feature->getEnabled(); + switch ($feature->getFeature()) { + case ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS: + if ($enabled) { + $message = t('Enabled active host checks successfully'); + } else { + $message = t('Disabled active host checks successfully'); + } + + break; + case ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS: + if ($enabled) { + $message = t('Enabled active service checks successfully'); + } else { + $message = t('Disabled active service checks successfully'); + } + + break; + case ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS: + if ($enabled) { + $message = t('Enabled event handlers successfully'); + } else { + $message = t('Disabled event handlers checks successfully'); + } + + break; + case ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION: + if ($enabled) { + $message = t('Enabled flap detection successfully'); + } else { + $message = t('Disabled flap detection successfully'); + } + + break; + case ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS: + if ($enabled) { + $message = t('Enabled notifications successfully'); + } else { + $message = t('Disabled notifications successfully'); + } + + break; + case ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA: + if ($enabled) { + $message = t('Enabled performance data successfully'); + } else { + $message = t('Disabled performance data successfully'); + } + + break; + default: + $message = t('Invalid feature option'); + break; + } + + Notification::success($message); + } + }); + } + + protected function assembleElements() + { + $disabled = ! $this->getAuth()->hasPermission('icingadb/command/feature/instance'); + $decorator = new IcingaFormDecorator(); + + foreach ($this->features as $feature => $label) { + $this->addElement( + 'checkbox', + $feature, + [ + 'class' => 'autosubmit', + 'label' => $label, + 'disabled' => $disabled, + 'value' => (bool) $this->featureStatus[$feature] + ] + ); + $decorator->decorate($this->getElement($feature)); + } + } + + protected function assembleSubmitButton() + { + } + + protected function getCommands(Traversable $objects): Traversable + { + foreach ($this->features as $feature => $spec) { + $featureState = $this->getElement($feature)->isChecked(); + + if ((int) $featureState === (int) $this->featureStatus[$feature]) { + continue; + } + + $command = new ToggleInstanceFeatureCommand(); + $command->setFeature($feature); + $command->setEnabled($featureState); + + $this->submittedFeatures[] = $command; + + yield $command; + } + } +} diff --git a/application/forms/Command/Object/AcknowledgeProblemForm.php b/application/forms/Command/Object/AcknowledgeProblemForm.php new file mode 100644 index 0000000..81b93e2 --- /dev/null +++ b/application/forms/Command/Object/AcknowledgeProblemForm.php @@ -0,0 +1,210 @@ +<?php + +/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Forms\Command\Object; + +use DateInterval; +use DateTime; +use Icinga\Application\Config; +use Icinga\Module\Icingadb\Command\Object\AcknowledgeProblemCommand; +use Icinga\Module\Icingadb\Forms\Command\CommandForm; +use Icinga\Module\Icingadb\Model\Host; +use Icinga\Web\Notification; +use ipl\Html\Attributes; +use ipl\Html\HtmlElement; +use ipl\Html\Text; +use ipl\Validator\CallbackValidator; +use ipl\Web\FormDecorator\IcingaFormDecorator; +use ipl\Web\Widget\Icon; +use Traversable; + +use function ipl\Stdlib\iterable_value_first; + +class AcknowledgeProblemForm extends CommandForm +{ + public function __construct() + { + $this->on(self::ON_SUCCESS, function () { + if ($this->errorOccurred) { + return; + } + + $countObjects = count($this->getObjects()); + if (iterable_value_first($this->getObjects()) instanceof Host) { + $message = sprintf(tp( + 'Acknowledged problem successfully', + 'Acknowledged problem on %d hosts successfully', + $countObjects + ), $countObjects); + } else { + $message = sprintf(tp( + 'Acknowledged problem successfully', + 'Acknowledged problem on %d services successfully', + $countObjects + ), $countObjects); + } + + Notification::success($message); + }); + } + + protected function assembleElements() + { + $this->addHtml(new HtmlElement( + 'div', + Attributes::create(['class' => 'form-description']), + new Icon('info-circle', ['class' => 'form-description-icon']), + new HtmlElement( + 'ul', + null, + new HtmlElement('li', null, Text::create(t( + 'This command is used to acknowledge host or service problems. When a problem is acknowledged,' + . ' future notifications about problems are temporarily disabled until the host or service' + . ' recovers.' + ))) + ) + )); + + $config = Config::module('icingadb'); + $decorator = new IcingaFormDecorator(); + + $this->addElement( + 'textarea', + 'comment', + [ + 'required' => true, + 'label' => t('Comment'), + 'description' => t( + 'If you work with other administrators, you may find it useful to share information about' + . ' the host or service that is having problems. Make sure you enter a brief description of' + . ' what you are doing.' + ) + ] + ); + $decorator->decorate($this->getElement('comment')); + + $this->addElement( + 'checkbox', + 'persistent', + [ + 'label' => t('Persistent Comment'), + 'value' => (bool) $config->get('settings', 'acknowledge_persistent', false), + 'description' => t( + 'If you want the comment to remain even when the acknowledgement is removed, check this' + . ' option.' + ) + ] + ); + $decorator->decorate($this->getElement('persistent')); + + $this->addElement( + 'checkbox', + 'notify', + [ + 'label' => t('Send Notification'), + 'value' => (bool) $config->get('settings', 'acknowledge_notify', true), + 'description' => t( + 'If you want an acknowledgement notification to be sent out to the appropriate contacts,' + . ' check this option.' + ) + ] + ); + $decorator->decorate($this->getElement('notify')); + + $this->addElement( + 'checkbox', + 'sticky', + [ + 'label' => t('Sticky Acknowledgement'), + 'value' => (bool) $config->get('settings', 'acknowledge_sticky', false), + 'description' => t( + 'If you want the acknowledgement to remain until the host or service recovers even if the host' + . ' or service changes state, check this option.' + ) + ] + ); + $decorator->decorate($this->getElement('sticky')); + + $this->addElement( + 'checkbox', + 'expire', + [ + 'ignore' => true, + 'class' => 'autosubmit', + 'value' => (bool) $config->get('settings', 'acknowledge_expire', false), + 'label' => t('Use Expire Time'), + 'description' => t('If the acknowledgement should expire, check this option.') + ] + ); + $decorator->decorate($this->getElement('expire')); + + if ($this->getElement('expire')->isChecked()) { + $expireTime = new DateTime(); + $expireTime->add(new DateInterval($config->get('settings', 'acknowledge_expire_time', 'PT1H'))); + + $this->addElement( + 'localDateTime', + 'expire_time', + [ + 'data-use-datetime-picker' => true, + 'required' => true, + 'value' => $expireTime, + 'label' => t('Expire Time'), + 'description' => t( + 'Choose the date and time when Icinga should delete the acknowledgement.' + ), + 'validators' => [ + 'DateTime' => ['break_chain_on_failure' => true], + 'Callback' => function ($value, $validator) { + /** @var CallbackValidator $validator */ + if ($value <= (new DateTime())) { + $validator->addMessage(t('The expire time must not be in the past')); + return false; + } + + return true; + } + ] + ] + ); + $decorator->decorate($this->getElement('expire_time')); + } + } + + protected function assembleSubmitButton() + { + $this->addElement( + 'submit', + 'btn_submit', + [ + 'required' => true, + 'label' => tp('Acknowledge problem', 'Acknowledge problems', count($this->getObjects())) + ] + ); + + (new IcingaFormDecorator())->decorate($this->getElement('btn_submit')); + } + + protected function getCommands(Traversable $objects): Traversable + { + $granted = $this->filterGrantedOn('icingadb/command/acknowledge-problem', $objects); + + if ($granted->valid()) { + $command = new AcknowledgeProblemCommand(); + $command->setObjects($granted); + $command->setComment($this->getValue('comment')); + $command->setAuthor($this->getAuth()->getUser()->getUsername()); + $command->setNotify($this->getElement('notify')->isChecked()); + $command->setSticky($this->getElement('sticky')->isChecked()); + $command->setPersistent($this->getElement('persistent')->isChecked()); + + if (($expireTime = $this->getValue('expire_time')) !== null) { + /** @var DateTime $expireTime */ + $command->setExpireTime($expireTime->getTimestamp()); + } + + yield $command; + } + } +} diff --git a/application/forms/Command/Object/AddCommentForm.php b/application/forms/Command/Object/AddCommentForm.php new file mode 100644 index 0000000..9cd0754 --- /dev/null +++ b/application/forms/Command/Object/AddCommentForm.php @@ -0,0 +1,162 @@ +<?php + +/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Forms\Command\Object; + +use DateInterval; +use DateTime; +use Icinga\Application\Config; +use Icinga\Module\Icingadb\Command\Object\AddCommentCommand; +use Icinga\Module\Icingadb\Forms\Command\CommandForm; +use Icinga\Module\Icingadb\Model\Host; +use Icinga\Web\Notification; +use ipl\Html\Attributes; +use ipl\Html\HtmlElement; +use ipl\Html\Text; +use ipl\Validator\CallbackValidator; +use ipl\Web\FormDecorator\IcingaFormDecorator; +use ipl\Web\Widget\Icon; +use Traversable; + +use function ipl\Stdlib\iterable_value_first; + +class AddCommentForm extends CommandForm +{ + public function __construct() + { + $this->on(self::ON_SUCCESS, function () { + if ($this->errorOccurred) { + return; + } + + $countObjects = count($this->getObjects()); + if (iterable_value_first($this->getObjects()) instanceof Host) { + $message = sprintf( + tp('Added comment successfully', 'Added comment to %d hosts successfully', $countObjects), + $countObjects + ); + } else { + $message = sprintf( + tp('Added comment successfully', 'Added comment to %d services successfully', $countObjects), + $countObjects + ); + } + + Notification::success($message); + }); + } + + protected function assembleElements() + { + $this->addHtml(new HtmlElement( + 'div', + Attributes::create(['class' => 'form-description']), + new Icon('info-circle', ['class' => 'form-description-icon']), + new HtmlElement( + 'ul', + null, + new HtmlElement( + 'li', + null, + Text::create(t('This command is used to add host or service comments.')) + ) + ) + )); + + $decorator = new IcingaFormDecorator(); + + $this->addElement( + 'textarea', + 'comment', + [ + 'required' => true, + 'label' => t('Comment'), + 'description' => t( + 'If you work with other administrators, you may find it useful to share information about' + . ' the host or service that is having problems. Make sure you enter a brief description of' + . ' what you are doing.' + ) + ] + ); + $decorator->decorate($this->getElement('comment')); + + $config = Config::module('icingadb'); + + $this->addElement( + 'checkbox', + 'expire', + [ + 'ignore' => true, + 'class' => 'autosubmit', + 'value' => (bool) $config->get('settings', 'comment_expire', false), + 'label' => t('Use Expire Time'), + 'description' => t('If the comment should expire, check this option.') + ] + ); + $decorator->decorate($this->getElement('expire')); + + if ($this->getElement('expire')->isChecked()) { + $expireTime = new DateTime(); + $expireTime->add(new DateInterval($config->get('settings', 'comment_expire_time', 'PT1H'))); + + $this->addElement( + 'localDateTime', + 'expire_time', + [ + 'data-use-datetime-picker' => true, + 'required' => true, + 'value' => $expireTime, + 'label' => t('Expire Time'), + 'description' => t('Choose the date and time when Icinga should delete the comment.'), + 'validators' => [ + 'DateTime' => ['break_chain_on_failure' => true], + 'Callback' => function ($value, $validator) { + /** @var CallbackValidator $validator */ + if ($value <= (new DateTime())) { + $validator->addMessage(t('The expire time must not be in the past')); + return false; + } + + return true; + } + ] + ] + ); + $decorator->decorate($this->getElement('expire_time')); + } + } + + protected function assembleSubmitButton() + { + $this->addElement( + 'submit', + 'btn_submit', + [ + 'required' => true, + 'label' => tp('Add comment', 'Add comments', count($this->getObjects())) + ] + ); + + (new IcingaFormDecorator())->decorate($this->getElement('btn_submit')); + } + + protected function getCommands(Traversable $objects): Traversable + { + $granted = $this->filterGrantedOn('icingadb/command/comment/add', $objects); + + if ($granted->valid()) { + $command = new AddCommentCommand(); + $command->setObjects($granted); + $command->setComment($this->getValue('comment')); + $command->setAuthor($this->getAuth()->getUser()->getUsername()); + + if (($expireTime = $this->getValue('expire_time'))) { + /** @var DateTime $expireTime */ + $command->setExpireTime($expireTime->getTimestamp()); + } + + yield $command; + } + } +} diff --git a/application/forms/Command/Object/CheckNowForm.php b/application/forms/Command/Object/CheckNowForm.php new file mode 100644 index 0000000..b7a506c --- /dev/null +++ b/application/forms/Command/Object/CheckNowForm.php @@ -0,0 +1,72 @@ +<?php + +/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Forms\Command\Object; + +use Generator; +use Icinga\Module\Icingadb\Command\Object\ScheduleCheckCommand; +use Icinga\Module\Icingadb\Forms\Command\CommandForm; +use Icinga\Web\Notification; +use ipl\Web\Widget\Icon; +use Traversable; + +class CheckNowForm extends CommandForm +{ + protected $defaultAttributes = ['class' => 'inline']; + + public function __construct() + { + $this->on(self::ON_SUCCESS, function () { + if (! $this->errorOccurred) { + Notification::success(tp('Scheduling check..', 'Scheduling checks..', count($this->getObjects()))); + } + }); + } + + protected function assembleElements() + { + } + + protected function assembleSubmitButton() + { + $this->addElement( + 'submitButton', + 'btn_submit', + [ + 'class' => ['link-button', 'spinner'], + 'label' => [ + new Icon('sync-alt'), + t('Check Now') + ], + 'title' => t('Schedule the next active check to run immediately') + ] + ); + } + + protected function getCommands(Traversable $objects): Traversable + { + $granted = (function () use ($objects): Generator { + foreach ($objects as $object) { + if ( + $this->isGrantedOn('icingadb/command/schedule-check', $object) + || ( + $object->active_checks_enabled + && $this->isGrantedOn('icingadb/command/schedule-check/active-only', $object) + ) + ) { + yield $object; + } + } + })(); + + if ($granted->valid()) { + $command = new ScheduleCheckCommand(); + $command->setObjects($granted); + $command->setCheckTime(time()); + $command->setForced(); + + yield $command; + } + } +} diff --git a/application/forms/Command/Object/DeleteCommentForm.php b/application/forms/Command/Object/DeleteCommentForm.php new file mode 100644 index 0000000..25275ba --- /dev/null +++ b/application/forms/Command/Object/DeleteCommentForm.php @@ -0,0 +1,75 @@ +<?php + +/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Forms\Command\Object; + +use Generator; +use Icinga\Module\Icingadb\Command\Object\DeleteCommentCommand; +use Icinga\Module\Icingadb\Forms\Command\CommandForm; +use Icinga\Web\Notification; +use ipl\Web\Common\RedirectOption; +use ipl\Web\Widget\Icon; +use Traversable; + +class DeleteCommentForm extends CommandForm +{ + use RedirectOption; + + protected $defaultAttributes = ['class' => 'inline']; + + public function __construct() + { + $this->on(self::ON_SUCCESS, function () { + if ($this->errorOccurred) { + return; + } + + $countObjects = count($this->getObjects()); + + Notification::success(sprintf( + tp('Removed comment successfully', 'Removed comment from %d objects successfully', $countObjects), + $countObjects + )); + }); + } + + protected function assembleElements() + { + $this->addElement($this->createRedirectOption()); + } + + protected function assembleSubmitButton() + { + $this->addElement( + 'submitButton', + 'btn_submit', + [ + 'class' => ['cancel-button', 'spinner'], + 'label' => [ + new Icon('trash'), + tp('Remove Comment', 'Remove Comments', count($this->getObjects())) + ] + ] + ); + } + + protected function getCommands(Traversable $objects): Traversable + { + $granted = (function () use ($objects): Generator { + foreach ($objects as $object) { + if ($this->isGrantedOn('icingadb/command/comment/delete', $object->{$object->object_type})) { + yield $object; + } + } + })(); + + if ($granted->valid()) { + $command = new DeleteCommentCommand(); + $command->setObjects($granted); + $command->setAuthor($this->getAuth()->getUser()->getUsername()); + + yield $command; + } + } +} diff --git a/application/forms/Command/Object/DeleteDowntimeForm.php b/application/forms/Command/Object/DeleteDowntimeForm.php new file mode 100644 index 0000000..5f695b9 --- /dev/null +++ b/application/forms/Command/Object/DeleteDowntimeForm.php @@ -0,0 +1,90 @@ +<?php + +/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Forms\Command\Object; + +use Generator; +use Icinga\Module\Icingadb\Command\Object\DeleteDowntimeCommand; +use Icinga\Module\Icingadb\Forms\Command\CommandForm; +use Icinga\Web\Notification; +use ipl\Web\Common\RedirectOption; +use ipl\Web\Widget\Icon; +use Traversable; + +class DeleteDowntimeForm extends CommandForm +{ + use RedirectOption; + + protected $defaultAttributes = ['class' => 'inline']; + + public function __construct() + { + $this->on(self::ON_SUCCESS, function () { + if ($this->errorOccurred) { + return; + } + + $countObjects = count($this->getObjects()); + + Notification::success(sprintf( + tp('Removed downtime successfully', 'Removed downtime from %d objects successfully', $countObjects), + $countObjects + )); + }); + } + + protected function assembleElements() + { + $this->addElement($this->createRedirectOption()); + } + + protected function assembleSubmitButton() + { + $isDisabled = true; + foreach ($this->getObjects() as $downtime) { + if ($downtime->scheduled_by === null) { + $isDisabled = false; + break; + } + } + + $this->addElement( + 'submitButton', + 'btn_submit', + [ + 'class' => ['cancel-button', 'spinner'], + 'disabled' => $isDisabled ?: null, + 'title' => $isDisabled + ? t('Downtime cannot be removed at runtime because it is based on a configured scheduled downtime.') + : null, + 'label' => [ + new Icon('trash'), + tp('Delete downtime', 'Delete downtimes', count($this->getObjects())) + ] + ] + ); + } + + protected function getCommands(Traversable $objects): Traversable + { + $granted = (function () use ($objects): Generator { + foreach ($objects as $object) { + if ( + $this->isGrantedOn('icingadb/command/downtime/delete', $object->{$object->object_type}) + && $object->scheduled_by === null + ) { + yield $object; + } + } + })(); + + if ($granted->valid()) { + $command = new DeleteDowntimeCommand(); + $command->setObjects($granted); + $command->setAuthor($this->getAuth()->getUser()->getUsername()); + + yield $command; + } + } +} diff --git a/application/forms/Command/Object/ProcessCheckResultForm.php b/application/forms/Command/Object/ProcessCheckResultForm.php new file mode 100644 index 0000000..5764bf8 --- /dev/null +++ b/application/forms/Command/Object/ProcessCheckResultForm.php @@ -0,0 +1,156 @@ +<?php + +/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Forms\Command\Object; + +use Generator; +use Icinga\Module\Icingadb\Command\Object\ProcessCheckResultCommand; +use Icinga\Module\Icingadb\Forms\Command\CommandForm; +use Icinga\Module\Icingadb\Model\Host; +use Icinga\Web\Notification; +use ipl\Html\Attributes; +use ipl\Html\HtmlElement; +use ipl\Html\Text; +use ipl\Orm\Model; +use ipl\Web\FormDecorator\IcingaFormDecorator; +use ipl\Web\Widget\Icon; +use Traversable; + +use function ipl\Stdlib\iterable_value_first; + +class ProcessCheckResultForm extends CommandForm +{ + public function __construct() + { + $this->on(self::ON_SUCCESS, function () { + if ($this->errorOccurred) { + return; + } + + $countObjects = count($this->getObjects()); + if (iterable_value_first($this->getObjects()) instanceof Host) { + $message = sprintf(tp( + 'Submitted passive check result successfully', + 'Submitted passive check result for %d hosts successfully', + $countObjects + ), $countObjects); + } else { + $message = sprintf(tp( + 'Submitted passive check result successfully', + 'Submitted passive check result for %d services successfully', + $countObjects + ), $countObjects); + } + + Notification::success($message); + }); + } + + protected function assembleElements() + { + $this->addHtml(new HtmlElement( + 'div', + Attributes::create(['class' => 'form-description']), + new Icon('info-circle', ['class' => 'form-description-icon']), + new HtmlElement( + 'ul', + null, + new HtmlElement( + 'li', + null, + Text::create(t('This command is used to submit passive host or service check results.')) + ) + ) + )); + + $decorator = new IcingaFormDecorator(); + + /** @var Model $object */ + $object = iterable_value_first($this->getObjects()); + + $this->addElement( + 'select', + 'status', + [ + 'required' => true, + 'label' => t('Status'), + 'description' => t('The state this check result should report'), + 'options' => $object instanceof Host ? [ + ProcessCheckResultCommand::HOST_UP => t('UP', 'icinga.state'), + ProcessCheckResultCommand::HOST_DOWN => t('DOWN', 'icinga.state') + ] : [ + ProcessCheckResultCommand::SERVICE_OK => t('OK', 'icinga.state'), + ProcessCheckResultCommand::SERVICE_WARNING => t('WARNING', 'icinga.state'), + ProcessCheckResultCommand::SERVICE_CRITICAL => t('CRITICAL', 'icinga.state'), + ProcessCheckResultCommand::SERVICE_UNKNOWN => t('UNKNOWN', 'icinga.state') + ] + ] + ); + $decorator->decorate($this->getElement('status')); + + $this->addElement( + 'text', + 'output', + [ + 'required' => true, + 'label' => t('Output'), + 'description' => t('The plugin output of this check result') + ] + ); + $decorator->decorate($this->getElement('output')); + + $this->addElement( + 'text', + 'perfdata', + [ + 'allowEmpty' => true, + 'label' => t('Performance Data'), + 'description' => t( + 'The performance data of this check result. Leave empty' + . ' if this check result has no performance data' + ) + ] + ); + $decorator->decorate($this->getElement('perfdata')); + } + + protected function assembleSubmitButton() + { + $this->addElement( + 'submit', + 'btn_submit', + [ + 'required' => true, + 'label' => tp( + 'Submit Passive Check Result', + 'Submit Passive Check Results', + count($this->getObjects()) + ) + ] + ); + + (new IcingaFormDecorator())->decorate($this->getElement('btn_submit')); + } + + protected function getCommands(Traversable $objects): Traversable + { + $granted = (function () use ($objects): Generator { + foreach ($this->filterGrantedOn('icingadb/command/process-check-result', $objects) as $object) { + if ($object->passive_checks_enabled) { + yield $object; + } + } + })(); + + if ($granted->valid()) { + $command = new ProcessCheckResultCommand(); + $command->setObjects($granted); + $command->setStatus($this->getValue('status')); + $command->setOutput($this->getValue('output')); + $command->setPerformanceData($this->getValue('perfdata')); + + yield $command; + } + } +} diff --git a/application/forms/Command/Object/RemoveAcknowledgementForm.php b/application/forms/Command/Object/RemoveAcknowledgementForm.php new file mode 100644 index 0000000..8697985 --- /dev/null +++ b/application/forms/Command/Object/RemoveAcknowledgementForm.php @@ -0,0 +1,77 @@ +<?php + +/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Forms\Command\Object; + +use Icinga\Module\Icingadb\Command\Object\RemoveAcknowledgementCommand; +use Icinga\Module\Icingadb\Forms\Command\CommandForm; +use Icinga\Module\Icingadb\Model\Host; +use Icinga\Web\Notification; +use ipl\Web\Widget\Icon; +use Traversable; + +use function ipl\Stdlib\iterable_value_first; + +class RemoveAcknowledgementForm extends CommandForm +{ + public function __construct() + { + $this->on(self::ON_SUCCESS, function () { + if ($this->errorOccurred) { + return; + } + + $countObjects = count($this->getObjects()); + if (iterable_value_first($this->getObjects()) instanceof Host) { + $message = sprintf(tp( + 'Removed acknowledgment successfully', + 'Removed acknowledgment from %d hosts successfully', + $countObjects + ), $countObjects); + } else { + $message = sprintf(tp( + 'Removed acknowledgment successfully', + 'Removed acknowledgment from %d services successfully', + $countObjects + ), $countObjects); + } + + Notification::success($message); + }); + } + + protected $defaultAttributes = ['class' => 'inline']; + + protected function assembleElements() + { + } + + protected function assembleSubmitButton() + { + $this->addElement( + 'submitButton', + 'btn_submit', + [ + 'class' => ['link-button', 'spinner'], + 'label' => [ + new Icon('trash'), + tp('Remove acknowledgement', 'Remove acknowledgements', count($this->getObjects())) + ] + ] + ); + } + + protected function getCommands(Traversable $objects): Traversable + { + $granted = $this->filterGrantedOn('icingadb/command/remove-acknowledgement', $objects); + + if ($granted->valid()) { + $command = new RemoveAcknowledgementCommand(); + $command->setObjects($granted); + $command->setAuthor($this->getAuth()->getUser()->getUsername()); + + yield $command; + } + } +} diff --git a/application/forms/Command/Object/ScheduleCheckForm.php b/application/forms/Command/Object/ScheduleCheckForm.php new file mode 100644 index 0000000..9b32ea1 --- /dev/null +++ b/application/forms/Command/Object/ScheduleCheckForm.php @@ -0,0 +1,137 @@ +<?php + +/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Forms\Command\Object; + +use DateInterval; +use DateTime; +use Generator; +use Icinga\Module\Icingadb\Command\Object\ScheduleCheckCommand; +use Icinga\Module\Icingadb\Forms\Command\CommandForm; +use Icinga\Module\Icingadb\Model\Host; +use Icinga\Web\Notification; +use ipl\Html\Attributes; +use ipl\Html\HtmlElement; +use ipl\Html\Text; +use ipl\Web\FormDecorator\IcingaFormDecorator; +use ipl\Web\Widget\Icon; +use Traversable; + +use function ipl\Stdlib\iterable_value_first; + +class ScheduleCheckForm extends CommandForm +{ + public function __construct() + { + $this->on(self::ON_SUCCESS, function () { + if ($this->errorOccurred) { + return; + } + + $countObjects = count($this->getObjects()); + if (iterable_value_first($this->getObjects()) instanceof Host) { + $message = sprintf( + tp('Scheduled check successfully', 'Scheduled check for %d hosts successfully', $countObjects), + $countObjects + ); + } else { + $message = sprintf( + tp('Scheduled check successfully', 'Scheduled check for %d services successfully', $countObjects), + $countObjects + ); + } + + Notification::success($message); + }); + } + + protected function assembleElements() + { + $this->addHtml(new HtmlElement( + 'div', + Attributes::create(['class' => 'form-description']), + new Icon('info-circle', ['class' => 'form-description-icon']), + new HtmlElement( + 'ul', + null, + new HtmlElement( + 'li', + null, + Text::create(t( + 'This command is used to schedule the next check of hosts or services. Icinga' + . ' will re-queue the hosts or services to be checked at the time you specify.' + )) + ) + ) + )); + + $decorator = new IcingaFormDecorator(); + + $this->addElement( + 'localDateTime', + 'check_time', + [ + 'data-use-datetime-picker' => true, + 'required' => true, + 'label' => t('Check Time'), + 'description' => t('Set the date and time when the check should be scheduled.'), + 'value' => (new DateTime())->add(new DateInterval('PT1H')) + ] + ); + $decorator->decorate($this->getElement('check_time')); + + $this->addElement( + 'checkbox', + 'force_check', + [ + 'label' => t('Force Check'), + 'description' => t( + 'If you select this option, Icinga will force a check regardless of both what time the' + . ' scheduled check occurs and whether or not checks are enabled.' + ) + ] + ); + $decorator->decorate($this->getElement('force_check')); + } + + protected function assembleSubmitButton() + { + $this->addElement( + 'submit', + 'btn_submit', + [ + 'required' => true, + 'label' => tp('Schedule check', 'Schedule checks', count($this->getObjects())) + ] + ); + + (new IcingaFormDecorator())->decorate($this->getElement('btn_submit')); + } + + protected function getCommands(Traversable $objects): Traversable + { + $granted = (function () use ($objects): Generator { + foreach ($objects as $object) { + if ( + $this->isGrantedOn('icingadb/command/schedule-check', $object) + || ( + $object->active_checks_enabled + && $this->isGrantedOn('icingadb/command/schedule-check/active-only', $object) + ) + ) { + yield $object; + } + } + })(); + + if ($granted->valid()) { + $command = new ScheduleCheckCommand(); + $command->setObjects($granted); + $command->setForced($this->getElement('force_check')->isChecked()); + $command->setCheckTime($this->getValue('check_time')->getTimestamp()); + + yield $command; + } + } +} diff --git a/application/forms/Command/Object/ScheduleHostDowntimeForm.php b/application/forms/Command/Object/ScheduleHostDowntimeForm.php new file mode 100644 index 0000000..bc21114 --- /dev/null +++ b/application/forms/Command/Object/ScheduleHostDowntimeForm.php @@ -0,0 +1,119 @@ +<?php + +/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Forms\Command\Object; + +use DateInterval; +use DateTime; +use Icinga\Application\Config; +use Icinga\Module\Icingadb\Command\Object\PropagateHostDowntimeCommand; +use Icinga\Module\Icingadb\Command\Object\ScheduleHostDowntimeCommand; +use Icinga\Web\Notification; +use ipl\Web\FormDecorator\IcingaFormDecorator; +use Traversable; + +class ScheduleHostDowntimeForm extends ScheduleServiceDowntimeForm +{ + /** @var bool */ + protected $hostDowntimeAllServices; + + public function __construct() + { + $this->start = new DateTime(); + $config = Config::module('icingadb'); + $this->commentText = $config->get('settings', 'hostdowntime_comment_text'); + + $this->hostDowntimeAllServices = (bool) $config->get('settings', 'hostdowntime_all_services', false); + + $fixedEnd = clone $this->start; + $fixed = $config->get('settings', 'hostdowntime_end_fixed', 'PT1H'); + $this->fixedEnd = $fixedEnd->add(new DateInterval($fixed)); + + $flexibleEnd = clone $this->start; + $flexible = $config->get('settings', 'hostdowntime_end_flexible', 'PT2H'); + $this->flexibleEnd = $flexibleEnd->add(new DateInterval($flexible)); + + $flexibleDuration = $config->get('settings', 'hostdowntime_flexible_duration', 'PT2H'); + $this->flexibleDuration = new DateInterval($flexibleDuration); + + $this->on(self::ON_SUCCESS, function () { + if ($this->errorOccurred) { + return; + } + + $countObjects = count($this->getObjects()); + + Notification::success(sprintf( + tp('Scheduled downtime successfully', 'Scheduled downtime for %d hosts successfully', $countObjects), + $countObjects + )); + }); + } + + protected function assembleElements() + { + parent::assembleElements(); + + $decorator = new IcingaFormDecorator(); + + $this->addElement( + 'checkbox', + 'all_services', + [ + 'label' => t('All Services'), + 'description' => t( + 'Sets downtime for all services for the matched host objects. If child options are set,' + . ' all child hosts and their services will schedule a downtime too.' + ), + 'value' => $this->hostDowntimeAllServices + ] + ); + $decorator->decorate($this->getElement('all_services')); + + $this->addElement( + 'select', + 'child_options', + array( + 'description' => t('Schedule child downtimes.'), + 'label' => t('Child Options'), + 'options' => [ + 0 => t('Do nothing with child hosts'), + 1 => t('Schedule triggered downtime for all child hosts'), + 2 => t('Schedule non-triggered downtime for all child hosts') + ] + ) + ); + $decorator->decorate($this->getElement('child_options')); + } + + protected function getCommands(Traversable $objects): Traversable + { + $granted = $this->filterGrantedOn('icingadb/command/downtime/schedule', $objects); + + if ($granted->valid()) { + if (($childOptions = (int) $this->getValue('child_options'))) { + $command = new PropagateHostDowntimeCommand(); + $command->setTriggered($childOptions === 1); + } else { + $command = new ScheduleHostDowntimeCommand(); + } + + $command->setObjects($granted); + $command->setComment($this->getValue('comment')); + $command->setAuthor($this->getAuth()->getUser()->getUsername()); + $command->setStart($this->getValue('start')->getTimestamp()); + $command->setEnd($this->getValue('end')->getTimestamp()); + $command->setForAllServices($this->getElement('all_services')->isChecked()); + + if ($this->getElement('flexible')->isChecked()) { + $command->setFixed(false); + $command->setDuration( + $this->getValue('hours') * 3600 + $this->getValue('minutes') * 60 + ); + } + + yield $command; + } + } +} diff --git a/application/forms/Command/Object/ScheduleServiceDowntimeForm.php b/application/forms/Command/Object/ScheduleServiceDowntimeForm.php new file mode 100644 index 0000000..184a4e8 --- /dev/null +++ b/application/forms/Command/Object/ScheduleServiceDowntimeForm.php @@ -0,0 +1,267 @@ +<?php + +/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Forms\Command\Object; + +use DateInterval; +use DateTime; +use Icinga\Application\Config; +use Icinga\Module\Icingadb\Command\Object\ScheduleServiceDowntimeCommand; +use Icinga\Module\Icingadb\Forms\Command\CommandForm; +use Icinga\Web\Notification; +use ipl\Html\Attributes; +use ipl\Html\HtmlElement; +use ipl\Html\Text; +use ipl\Validator\CallbackValidator; +use ipl\Web\FormDecorator\IcingaFormDecorator; +use ipl\Web\Widget\Icon; +use Traversable; + +class ScheduleServiceDowntimeForm extends CommandForm +{ + /** @var DateTime downtime start */ + protected $start; + + /** @var DateTime fixed downtime end */ + protected $fixedEnd; + + /**@var DateTime flexible downtime end */ + protected $flexibleEnd; + + /** @var DateInterval flexible downtime duration */ + protected $flexibleDuration; + + /** @var mixed Comment Text */ + protected $commentText; + + /** + * Initialize this form + */ + public function __construct() + { + $this->start = new DateTime(); + + $config = Config::module('icingadb'); + + $this->commentText = $config->get('settings', 'hostdowntime_comment_text'); + $fixedEnd = clone $this->start; + $fixed = $config->get('settings', 'servicedowntime_end_fixed', 'PT1H'); + $this->fixedEnd = $fixedEnd->add(new DateInterval($fixed)); + + $flexibleEnd = clone $this->start; + $flexible = $config->get('settings', 'servicedowntime_end_flexible', 'PT2H'); + $this->flexibleEnd = $flexibleEnd->add(new DateInterval($flexible)); + + $flexibleDuration = $config->get('settings', 'servicedowntime_flexible_duration', 'PT2H'); + $this->flexibleDuration = new DateInterval($flexibleDuration); + + $this->on(self::ON_SUCCESS, function () { + if ($this->errorOccurred) { + return; + } + + $countObjects = count($this->getObjects()); + + Notification::success(sprintf( + tp('Scheduled downtime successfully', 'Scheduled downtime for %d services successfully', $countObjects), + $countObjects + )); + }); + } + + protected function assembleElements() + { + $isFlexible = $this->getPopulatedValue('flexible') === 'y'; + + $this->addHtml(new HtmlElement( + 'div', + Attributes::create(['class' => 'form-description']), + new Icon('info-circle', ['class' => 'form-description-icon']), + new HtmlElement( + 'ul', + null, + new HtmlElement( + 'li', + null, + Text::create(t( + 'This command is used to schedule host and service downtimes. During the downtime specified' + . ' by the start and end time, Icinga will not send notifications out about the hosts and' + . ' services. When the scheduled downtime expires, Icinga will send out notifications for' + . ' the hosts and services as it normally would.' + )) + ) + ) + )); + + $decorator = new IcingaFormDecorator(); + + $this->addElement( + 'textarea', + 'comment', + [ + 'required' => true, + 'label' => t('Comment'), + 'description' => t( + 'If you work with other administrators, you may find it useful to share information about' + . ' the host or service that is having problems. Make sure you enter a brief description of' + . ' what you are doing.' + ), + 'value' => $this->commentText + ] + ); + $decorator->decorate($this->getElement('comment')); + + $this->addElement( + 'localDateTime', + 'start', + [ + 'data-use-datetime-picker' => true, + 'required' => true, + 'value' => $this->start, + 'label' => t('Start Time'), + 'description' => t('Set the start date and time for the downtime.') + ] + ); + $decorator->decorate($this->getElement('start')); + + $this->addElement( + 'localDateTime', + 'end', + [ + 'data-use-datetime-picker' => true, + 'required' => true, + 'label' => t('End Time'), + 'description' => t('Set the end date and time for the downtime.'), + 'value' => $isFlexible ? $this->flexibleEnd : $this->fixedEnd, + 'validators' => [ + 'DateTime' => ['break_chain_on_failure' => true], + 'Callback' => function ($value, $validator) { + /** @var CallbackValidator $validator */ + + if ($value <= $this->getValue('start')) { + $validator->addMessage(t('The end time must be greater than the start time')); + return false; + } + + if ($value <= (new DateTime())) { + $validator->addMessage(t('A downtime must not be in the past')); + return false; + } + + return true; + } + ] + ] + ); + $decorator->decorate($this->getElement('end')); + + $this->addElement( + 'checkbox', + 'flexible', + [ + 'class' => 'autosubmit', + 'label' => t('Flexible'), + 'description' => t( + 'To make this a flexible downtime, check this option. A flexible downtime starts when the host' + . ' or service enters a problem state sometime between the start and end times you specified.' + . ' It then lasts as long as the duration time you enter.' + ) + ] + ); + $decorator->decorate($this->getElement('flexible')); + + if ($isFlexible) { + $hoursInput = $this->createElement( + 'number', + 'hours', + [ + 'required' => true, + 'label' => t('Duration'), + 'value' => $this->flexibleDuration->h, + 'min' => 0, + 'validators' => [ + 'Callback' => function ($value, $validator) { + /** @var CallbackValidator $validator */ + + if ($this->getValue('minutes') == 0 && $value == 0) { + $validator->addMessage(t('The duration must not be zero')); + return false; + } + + return true; + } + ] + ] + ); + $this->registerElement($hoursInput); + $decorator->decorate($hoursInput); + + $minutesInput = $this->createElement( + 'number', + 'minutes', + [ + 'required' => true, + 'value' => $this->flexibleDuration->m, + 'min' => 0 + ] + ); + $this->registerElement($minutesInput); + $minutesInput->addWrapper( + new HtmlElement('label', null, new HtmlElement('span', null, Text::create(t('Minutes')))) + ); + + $hoursInput->getWrapper()->on( + IcingaFormDecorator::ON_ASSEMBLED, + function ($hoursInputWrapper) use ($minutesInput, $hoursInput) { + $hoursInputWrapper + ->insertAfter($minutesInput, $hoursInput) + ->getAttributes()->add('class', 'downtime-duration'); + } + ); + + $hoursInput->prependWrapper( + new HtmlElement('label', null, new HtmlElement('span', null, Text::create(t('Hours')))) + ); + + $this->add($hoursInput); + } + } + + protected function assembleSubmitButton() + { + $this->addElement( + 'submit', + 'btn_submit', + [ + 'required' => true, + 'label' => tp('Schedule downtime', 'Schedule downtimes', count($this->getObjects())) + ] + ); + + (new IcingaFormDecorator())->decorate($this->getElement('btn_submit')); + } + + protected function getCommands(Traversable $objects): Traversable + { + $granted = $this->filterGrantedOn('icingadb/command/downtime/schedule', $objects); + + if ($granted->valid()) { + $command = new ScheduleServiceDowntimeCommand(); + $command->setObjects($granted); + $command->setComment($this->getValue('comment')); + $command->setAuthor($this->getAuth()->getUser()->getUsername()); + $command->setStart($this->getValue('start')->getTimestamp()); + $command->setEnd($this->getValue('end')->getTimestamp()); + + if ($this->getElement('flexible')->isChecked()) { + $command->setFixed(false); + $command->setDuration( + $this->getValue('hours') * 3600 + $this->getValue('minutes') * 60 + ); + } + + yield $command; + } + } +} diff --git a/application/forms/Command/Object/SendCustomNotificationForm.php b/application/forms/Command/Object/SendCustomNotificationForm.php new file mode 100644 index 0000000..dfb1e96 --- /dev/null +++ b/application/forms/Command/Object/SendCustomNotificationForm.php @@ -0,0 +1,125 @@ +<?php + +/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Forms\Command\Object; + +use Icinga\Application\Config; +use Icinga\Module\Icingadb\Command\Object\SendCustomNotificationCommand; +use Icinga\Module\Icingadb\Forms\Command\CommandForm; +use Icinga\Module\Icingadb\Model\Host; +use Icinga\Web\Notification; +use ipl\Html\Attributes; +use ipl\Html\HtmlElement; +use ipl\Html\Text; +use ipl\Web\FormDecorator\IcingaFormDecorator; +use ipl\Web\Widget\Icon; +use Traversable; + +use function ipl\Stdlib\iterable_value_first; + +class SendCustomNotificationForm extends CommandForm +{ + public function __construct() + { + $this->on(self::ON_SUCCESS, function () { + if ($this->errorOccurred) { + return; + } + + $countObjects = count($this->getObjects()); + if (iterable_value_first($this->getObjects()) instanceof Host) { + $message = sprintf(tp( + 'Sent custom notification successfully', + 'Sent custom notification for %d hosts successfully', + $countObjects + ), $countObjects); + } else { + $message = sprintf(tp( + 'Sent custom notification successfully', + 'Sent custom notification for %d services successfully', + $countObjects + ), $countObjects); + } + + Notification::success($message); + }); + } + + protected function assembleElements() + { + $this->addHtml(new HtmlElement( + 'div', + Attributes::create(['class' => 'form-description']), + new Icon('info-circle', ['class' => 'form-description-icon']), + new HtmlElement( + 'ul', + null, + new HtmlElement( + 'li', + null, + Text::create(t('This command is used to send custom notifications about hosts or services.')) + ) + ) + )); + + $config = Config::module('icingadb'); + $decorator = new IcingaFormDecorator(); + + $this->addElement( + 'textarea', + 'comment', + [ + 'required' => true, + 'label' => t('Comment'), + 'description' => t( + 'Enter a brief description on why you\'re sending this notification. It will be sent with it.' + ) + ] + ); + $decorator->decorate($this->getElement('comment')); + + $this->addElement( + 'checkbox', + 'forced', + [ + 'label' => t('Forced'), + 'value' => (bool) $config->get('settings', 'custom_notification_forced', false), + 'description' => t( + 'If you check this option, the notification is sent regardless' + . ' of downtimes or whether notifications are enabled or not.' + ) + ] + ); + $decorator->decorate($this->getElement('forced')); + } + + protected function assembleSubmitButton() + { + $this->addElement( + 'submit', + 'btn_submit', + [ + 'required' => true, + 'label' => tp('Send custom notification', 'Send custom notifications', count($this->getObjects())) + ] + ); + + (new IcingaFormDecorator())->decorate($this->getElement('btn_submit')); + } + + protected function getCommands(Traversable $objects): Traversable + { + $granted = $this->filterGrantedOn('icingadb/command/send-custom-notification', $objects); + + if ($granted->valid()) { + $command = new SendCustomNotificationCommand(); + $command->setObjects($granted); + $command->setComment($this->getValue('comment')); + $command->setForced($this->getElement('forced')->isChecked()); + $command->setAuthor($this->getAuth()->getUser()->getUsername()); + + yield $command; + } + } +} diff --git a/application/forms/Command/Object/ToggleObjectFeaturesForm.php b/application/forms/Command/Object/ToggleObjectFeaturesForm.php new file mode 100644 index 0000000..50767da --- /dev/null +++ b/application/forms/Command/Object/ToggleObjectFeaturesForm.php @@ -0,0 +1,186 @@ +<?php + +/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Forms\Command\Object; + +use Icinga\Module\Icingadb\Command\Object\ToggleObjectFeatureCommand; +use Icinga\Module\Icingadb\Forms\Command\CommandForm; +use Icinga\Web\Notification; +use ipl\Html\FormElement\CheckboxElement; +use ipl\Orm\Model; +use ipl\Web\FormDecorator\IcingaFormDecorator; +use Traversable; + +class ToggleObjectFeaturesForm extends CommandForm +{ + const LEAVE_UNCHANGED = 'noop'; + + protected $features; + + protected $featureStatus; + + /** + * ToggleFeature(s) being used to submit this form + * + * @var ToggleObjectFeatureCommand[] + */ + protected $submittedFeatures = []; + + public function __construct($featureStatus) + { + $this->featureStatus = $featureStatus; + $this->features = [ + ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS => [ + 'label' => t('Active Checks'), + 'permission' => 'icingadb/command/feature/object/active-checks' + ], + ToggleObjectFeatureCommand::FEATURE_PASSIVE_CHECKS => [ + 'label' => t('Passive Checks'), + 'permission' => 'icingadb/command/feature/object/passive-checks' + ], + ToggleObjectFeatureCommand::FEATURE_NOTIFICATIONS => [ + 'label' => t('Notifications'), + 'permission' => 'icingadb/command/feature/object/notifications' + ], + ToggleObjectFeatureCommand::FEATURE_EVENT_HANDLER => [ + 'label' => t('Event Handler'), + 'permission' => 'icingadb/command/feature/object/event-handler' + ], + ToggleObjectFeatureCommand::FEATURE_FLAP_DETECTION => [ + 'label' => t('Flap Detection'), + 'permission' => 'icingadb/command/feature/object/flap-detection' + ] + ]; + + $this->getAttributes()->add('class', 'object-features'); + + $this->on(self::ON_SUCCESS, function () { + if ($this->errorOccurred) { + return; + } + + foreach ($this->submittedFeatures as $feature) { + $enabled = $feature->getEnabled(); + switch ($feature->getFeature()) { + case ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS: + if ($enabled) { + $message = t('Enabled active checks successfully'); + } else { + $message = t('Disabled active checks successfully'); + } + + break; + case ToggleObjectFeatureCommand::FEATURE_PASSIVE_CHECKS: + if ($enabled) { + $message = t('Enabled passive checks successfully'); + } else { + $message = t('Disabled passive checks successfully'); + } + + break; + case ToggleObjectFeatureCommand::FEATURE_EVENT_HANDLER: + if ($enabled) { + $message = t('Enabled event handler successfully'); + } else { + $message = t('Disabled event handler checks successfully'); + } + + break; + case ToggleObjectFeatureCommand::FEATURE_FLAP_DETECTION: + if ($enabled) { + $message = t('Enabled flap detection successfully'); + } else { + $message = t('Disabled flap detection successfully'); + } + + break; + case ToggleObjectFeatureCommand::FEATURE_NOTIFICATIONS: + if ($enabled) { + $message = t('Enabled notifications successfully'); + } else { + $message = t('Disabled notifications successfully'); + } + + break; + default: + $message = t('Invalid feature option'); + break; + } + + Notification::success($message); + } + }); + } + + protected function assembleElements() + { + $decorator = new IcingaFormDecorator(); + foreach ($this->features as $feature => $spec) { + $options = [ + 'class' => 'autosubmit', + 'disabled' => $this->featureStatus instanceof Model + ? ! $this->isGrantedOn($spec['permission'], $this->featureStatus) + : false, + 'label' => $spec['label'] + ]; + if ($this->featureStatus[$feature] === 2) { + $this->addElement( + 'select', + $feature, + $options + [ + 'description' => t('Multiple Values'), + 'options' => [ + self::LEAVE_UNCHANGED => t('Leave Unchanged'), + t('Disable All'), + t('Enable All') + ], + 'value' => self::LEAVE_UNCHANGED + ] + ); + $decorator->decorate($this->getElement($feature)); + + $this->getElement($feature) + ->getWrapper() + ->getAttributes() + ->add('class', 'indeterminate'); + } else { + $options['value'] = (bool) $this->featureStatus[$feature]; + $this->addElement('checkbox', $feature, $options); + $decorator->decorate($this->getElement($feature)); + } + } + } + + protected function assembleSubmitButton() + { + } + + protected function getCommands(Traversable $objects): Traversable + { + foreach ($this->features as $feature => $spec) { + if ($this->getElement($feature) instanceof CheckboxElement) { + $state = $this->getElement($feature)->isChecked(); + } else { + $state = $this->getElement($feature)->getValue(); + } + + if ($state === self::LEAVE_UNCHANGED || (int) $state === (int) $this->featureStatus[$feature]) { + continue; + } + + $granted = $this->filterGrantedOn($spec['permission'], $objects); + + if ($granted->valid()) { + $command = new ToggleObjectFeatureCommand(); + $command->setObjects($granted); + $command->setFeature($feature); + $command->setEnabled((int) $state); + + $this->submittedFeatures[] = $command; + + yield $command; + } + } + } +} |