Skip to content

Commit b1a6f3a

Browse files
Allow to manage and subscribe incidents
Add a subscribe/unsubscribe option to the `QuickActions` and manage/unamnage related incidents with the existing acknowledge
1 parent b7da98f commit b1a6f3a

14 files changed

Lines changed: 331 additions & 2 deletions

application/controllers/HostController.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Icinga\Module\Icingadb\Model\Host;
2121
use Icinga\Module\Icingadb\Model\Service;
2222
use Icinga\Module\Icingadb\Model\ServicestateSummary;
23+
use Icinga\Module\Icingadb\Notifications\SubscribeIncidents;
2324
use Icinga\Module\Icingadb\Redis\VolatileStateResults;
2425
use Icinga\Module\Icingadb\Util\OptimizerHints;
2526
use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions;
@@ -45,6 +46,7 @@ class HostController extends Controller
4546
{
4647
use CommandActions;
4748
use HookActions;
49+
use SubscribeIncidents;
4850

4951
/** @var Host The host object */
5052
protected $host;
@@ -590,6 +592,16 @@ public function servicesSearchEditorAction(): void
590592
$this->setTitle($this->translate('Adjust Filter'));
591593
}
592594

595+
public function subscribeAction(): void
596+
{
597+
$this->handleSubscription(true, $this->host, $this->getCommandTargetsUrl());
598+
}
599+
600+
public function unsubscribeAction(): void
601+
{
602+
$this->handleSubscription(false, $this->host, $this->getCommandTargetsUrl());
603+
}
604+
593605
/**
594606
* Fetch the dependency nodes of the current host
595607
*

application/controllers/ServiceController.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Icinga\Module\Icingadb\Model\DependencyNode;
1919
use Icinga\Module\Icingadb\Model\History;
2020
use Icinga\Module\Icingadb\Model\Service;
21+
use Icinga\Module\Icingadb\Notifications\SubscribeIncidents;
2122
use Icinga\Module\Icingadb\Redis\VolatileStateResults;
2223
use Icinga\Module\Icingadb\Util\OptimizerHints;
2324
use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions;
@@ -43,6 +44,7 @@ class ServiceController extends Controller
4344
{
4445
use CommandActions;
4546
use HookActions;
47+
use SubscribeIncidents;
4648

4749
/** @var Service The service object */
4850
protected $service;
@@ -600,6 +602,16 @@ protected function getCommandTargetsUrl(): Url
600602
return Links::service($this->service, $this->service->host);
601603
}
602604

605+
public function subscribeAction(): void
606+
{
607+
$this->handleSubscription(true, $this->service, $this->getCommandTargetsUrl());
608+
}
609+
610+
public function unsubscribeAction(): void
611+
{
612+
$this->handleSubscription(false, $this->service, $this->getCommandTargetsUrl());
613+
}
614+
603615
protected function getDefaultTabControls(): array
604616
{
605617
return [new ObjectHeader($this->service)];

application/forms/Command/Object/AcknowledgeProblemForm.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Icinga\Module\Icingadb\Command\Object\AcknowledgeProblemCommand;
1313
use Icinga\Module\Icingadb\Forms\Command\CommandForm;
1414
use Icinga\Module\Icingadb\Model\Host;
15+
use Icinga\Module\Icingadb\Notifications\ManagesIncidents;
1516
use Icinga\Web\Notification;
1617
use ipl\Html\Attributes;
1718
use ipl\Html\HtmlElement;
@@ -27,13 +28,16 @@
2728

2829
class AcknowledgeProblemForm extends CommandForm
2930
{
31+
use ManagesIncidents;
32+
3033
public function __construct()
3134
{
3235
$this->on(self::ON_SUCCESS, function () {
3336
if ($this->errorOccurred) {
3437
return;
3538
}
3639

40+
$this->manageIncidents(true);
3741
$countObjects = count($this->getObjects());
3842
if (iterable_value_first($this->getObjects()) instanceof Host) {
3943
$message = sprintf(tp(

application/forms/Command/Object/RemoveAcknowledgementForm.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Icinga\Module\Icingadb\Command\Object\RemoveAcknowledgementCommand;
1010
use Icinga\Module\Icingadb\Forms\Command\CommandForm;
1111
use Icinga\Module\Icingadb\Model\Host;
12+
use Icinga\Module\Icingadb\Notifications\ManagesIncidents;
1213
use Icinga\Web\Notification;
1314
use ipl\Orm\Model;
1415
use ipl\Web\Widget\Icon;
@@ -19,13 +20,16 @@
1920

2021
class RemoveAcknowledgementForm extends CommandForm
2122
{
23+
use ManagesIncidents;
24+
2225
public function __construct()
2326
{
2427
$this->on(self::ON_SUCCESS, function () {
2528
if ($this->errorOccurred) {
2629
return;
2730
}
2831

32+
$this->manageIncidents(false);
2933
$countObjects = count($this->getObjects());
3034
if (iterable_value_first($this->getObjects()) instanceof Host) {
3135
$message = sprintf(tp(

configuration.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@
100100
'icingadb/command/send-custom-notification',
101101
$this->translate('Allow to send custom notifications for hosts and services')
102102
);
103+
$this->providePermission(
104+
'icingadb/notifications/subscribe',
105+
$this->translate('Allow to subscribe and manage incidents related to hosts and services')
106+
);
103107

104108
$this->providePermission(
105109
'icingadb/object/show-source',

library/Icingadb/Common/HostLinks.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,14 @@ public static function services(Host $host): Url
7474
{
7575
return Url::fromPath('icingadb/host/services', ['name' => $host->name]);
7676
}
77+
78+
public static function subscribe(Host $host): Url
79+
{
80+
return Url::fromPath('icingadb/host/subscribe', ['name' => $host->name]);
81+
}
82+
83+
public static function unsubscribe(Host $host): Url
84+
{
85+
return Url::fromPath('icingadb/host/unsubscribe', ['name' => $host->name]);
86+
}
7787
}

library/Icingadb/Common/ServiceLinks.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,14 @@ public static function toggleFeatures(Service $service, Host $host): Url
106106
['name' => $service->name, 'host.name' => $host->name]
107107
);
108108
}
109+
110+
public static function subscribe(Service $service, Host $host): Url
111+
{
112+
return Url::fromPath('icingadb/service/subscribe', ['name' => $service->name, 'host.name' => $host->name]);
113+
}
114+
115+
public static function unsubscribe(Service $service, Host $host): Url
116+
{
117+
return Url::fromPath('icingadb/service/unsubscribe', ['name' => $service->name, 'host.name' => $host->name]);
118+
}
109119
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
// SPDX-FileCopyrightText: 2026 Icinga GmbH <https://icinga.com>
4+
// SPDX-License-Identifier: GPL-3.0-or-later
5+
6+
namespace Icinga\Module\Icingadb\Notifications;
7+
8+
use Icinga\Module\Icingadb\Model\Host;
9+
use Icinga\Module\Icingadb\Model\Service;
10+
use Icinga\Module\Notifications\Integrations\Incidents;
11+
12+
class IncidentFinder
13+
{
14+
/**
15+
* Find the open incidents related to the given object
16+
*
17+
* @param Host|Service $object
18+
*
19+
* @return Incidents
20+
*/
21+
public static function forObject(Host|Service $object): Incidents
22+
{
23+
return Incidents::find(self::buildTags($object));
24+
}
25+
26+
/**
27+
* Build the tags to match the given host/service
28+
*
29+
* @param Host|Service $object
30+
*
31+
* @return array<string, ?string>
32+
*/
33+
private static function buildTags(Host|Service $object): array
34+
{
35+
if ($object instanceof Host) {
36+
return [
37+
'host' => $object->name,
38+
'service' => null,
39+
'environment' => bin2hex($object->environment_id)
40+
];
41+
}
42+
43+
return [
44+
'host' => $object->host->name,
45+
'service' => $object->name,
46+
'environment' => bin2hex($object->environment_id)
47+
];
48+
}
49+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
// SPDX-FileCopyrightText: 2026 Icinga GmbH <https://icinga.com>
4+
// SPDX-License-Identifier: GPL-3.0-or-later
5+
6+
namespace Icinga\Module\Icingadb\Notifications;
7+
8+
use Icinga\Application\Icinga;
9+
use Icinga\Module\Icingadb\Forms\Command\CommandForm;
10+
use Icinga\Web\Notification;
11+
use InvalidArgumentException;
12+
13+
/**
14+
* @phpstan-require-extends CommandForm
15+
*/
16+
trait ManagesIncidents
17+
{
18+
/**
19+
* Add or remove the current user as manager of all incidents related to the form's objects
20+
*
21+
* @param bool $manage
22+
*
23+
* @return void
24+
*/
25+
protected function manageIncidents(bool $manage): void
26+
{
27+
if (! Icinga::app()->getModuleManager()->hasEnabled('notifications')) {
28+
return;
29+
}
30+
31+
$username = $this->getAuth()->getUser()->getUsername();
32+
try {
33+
foreach ($this->getObjects() as $object) {
34+
foreach (IncidentFinder::forObject($object) as $incident) {
35+
if ($manage) {
36+
$incident->addManager($username);
37+
} else {
38+
$incident->removeManager($username);
39+
}
40+
}
41+
}
42+
} catch (InvalidArgumentException) {
43+
if ($manage) {
44+
Notification::warning(
45+
t('Cannot manage matching incident, no notification contact configured for your account.')
46+
);
47+
}
48+
}
49+
}
50+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
// SPDX-FileCopyrightText: 2026 Icinga GmbH <https://icinga.com>
4+
// SPDX-License-Identifier: GPL-3.0-or-later
5+
6+
namespace Icinga\Module\Icingadb\Notifications;
7+
8+
use Icinga\Application\Icinga;
9+
use Icinga\Module\Icingadb\Model\Host;
10+
use Icinga\Module\Icingadb\Model\Service;
11+
use Icinga\Module\Icingadb\Web\Controller;
12+
use ipl\Html\Form;
13+
use ipl\Web\Url;
14+
15+
/**
16+
* @phpstan-require-extends Controller
17+
*/
18+
trait SubscribeIncidents
19+
{
20+
/**
21+
* Subscribe or unsubscribe all incidents matching the given tags
22+
*
23+
* @param bool $subscribe
24+
* @param Host|Service $object
25+
* @param Url $redirectUrl The URL to redirect to after the form has been handled
26+
* @return void
27+
*/
28+
protected function handleSubscription(bool $subscribe, Host|Service $object, Url $redirectUrl): void
29+
{
30+
if (
31+
Icinga::app()->getModuleManager()->hasEnabled('notifications')
32+
&& $this->isGrantedOn('icingadb/notifications/subscribe', $object)
33+
) {
34+
$form = (new SubscriptionForm($subscribe, $object))
35+
->setAction((string) Url::fromRequest())
36+
->on(Form::ON_SUBMIT, function () use ($redirectUrl) {
37+
$this->getResponse()->setAutoRefreshInterval(1);
38+
$this->redirectNow($redirectUrl);
39+
});
40+
41+
$form->handleRequest($this->getServerRequest());
42+
43+
$this->addContent($form);
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)