summaryrefslogtreecommitdiffstats
path: root/application/forms/Command
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--application/forms/Command/CommandForm.php179
-rw-r--r--application/forms/Command/Instance/ToggleInstanceFeaturesForm.php154
-rw-r--r--application/forms/Command/Object/AcknowledgeProblemForm.php210
-rw-r--r--application/forms/Command/Object/AddCommentForm.php162
-rw-r--r--application/forms/Command/Object/CheckNowForm.php72
-rw-r--r--application/forms/Command/Object/DeleteCommentForm.php75
-rw-r--r--application/forms/Command/Object/DeleteDowntimeForm.php90
-rw-r--r--application/forms/Command/Object/ProcessCheckResultForm.php156
-rw-r--r--application/forms/Command/Object/RemoveAcknowledgementForm.php77
-rw-r--r--application/forms/Command/Object/ScheduleCheckForm.php137
-rw-r--r--application/forms/Command/Object/ScheduleHostDowntimeForm.php119
-rw-r--r--application/forms/Command/Object/ScheduleServiceDowntimeForm.php267
-rw-r--r--application/forms/Command/Object/SendCustomNotificationForm.php125
-rw-r--r--application/forms/Command/Object/ToggleObjectFeaturesForm.php186
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;
+ }
+ }
+ }
+}