Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions application/controllers/HistoryController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php

// SPDX-FileCopyrightText: 2026 Icinga GmbH <https://icinga.com>
// SPDX-License-Identifier: GPL-3.0-or-later

namespace Icinga\Module\Notifications\Controllers;

use Icinga\Module\Notifications\Common\Auth;
use Icinga\Module\Notifications\Common\Database;
use Icinga\Module\Notifications\Model\NotificationHistory;
use Icinga\Module\Notifications\View\NotificationRenderer;
use Icinga\Module\Notifications\Web\Control\SearchBar\ObjectSuggestions;
use Icinga\Module\Notifications\Widget\ItemList\ObjectList;
use ipl\Web\Common\Controls;
use ipl\Web\Compat\CompatController;
use ipl\Web\Compat\SearchControls;
use ipl\Web\Control\LimitControl;
use ipl\Web\Control\SortControl;
use ipl\Web\Control\ViewModeSwitcher;
use ipl\Web\Filter\QueryString;
use ipl\Web\Layout\DetailedItemLayout;
use ipl\Web\Layout\ItemLayout;
use ipl\Web\Layout\MinimalItemLayout;

class HistoryController extends CompatController
{
use Auth;
use SearchControls;
use Controls;

public function indexAction(): void
{
$this->addTitleTab(t('Notification History'));

$notificationHistory = NotificationHistory::on(Database::get())
->with(['channel.available_channel_type', 'contact', 'contactgroup', 'schedule']);

$limitControl = $this->createLimitControl();
$sortControl = $this->createSortControl(
$notificationHistory,
[
'notification_history.triggered_at desc' => t('Triggered At'),
'notification_history.state, notification_history.triggered_at desc' => t('State')
]
);

$paginationControl = $this->createPaginationControl($notificationHistory);
$viewModeSwitcher = $this->createViewModeSwitcher();
$searchBar = $this->createSearchBar($notificationHistory, [
$limitControl->getLimitParam(),
$sortControl->getSortParam(),
$viewModeSwitcher->getViewModeParam()
]);

$this->handleControls($this->getServerRequest());

if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) {
if ($searchBar->hasBeenSubmitted()) {
$filter = QueryString::parse((string) $this->params);
} else {
$this->addControl($searchBar);
$this->sendMultipartUpdate();
return;
}
} else {
$filter = $searchBar->getFilter();
}

$this->applyRestrictions($notificationHistory);
$notificationHistory->filter($filter);

$this->addControl($paginationControl);
$this->addControl($sortControl);
$this->addControl($limitControl);
$this->addControl($viewModeSwitcher);
$this->addControl($searchBar);

$this->addContent(
(new ObjectList($notificationHistory, new NotificationRenderer()))
->setItemLayoutClass(match ($viewModeSwitcher->getViewMode()->getName()) {
'minimal' => MinimalItemLayout::class,
'detailed' => DetailedItemLayout::class,
'common' => ItemLayout::class
})
);

if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) {
$this->sendMultipartUpdate();
}

$this->setAutorefreshInterval(10);
}

public function completeAction(): void
{
$suggestions = new ObjectSuggestions();
$suggestions->setModel(NotificationHistory::class);
$suggestions->forRequest($this->getServerRequest());
$this->getDocument()->add($suggestions);
}

public function searchEditorAction(): void
{
$editor = $this->createSearchEditor(NotificationHistory::on(Database::get()), [
LimitControl::DEFAULT_LIMIT_PARAM,
SortControl::DEFAULT_SORT_PARAM,
ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM
]);

$this->getDocument()->add($editor);
$this->setTitle(t('Adjust Filter'));
}
}
137 changes: 136 additions & 1 deletion application/controllers/IncidentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,42 @@

namespace Icinga\Module\Notifications\Controllers;

use ipl\Web\Common\Controls;
use ipl\Web\Compat\SearchControls;
use Icinga\Module\Notifications\Common\Auth;
use Icinga\Module\Notifications\Common\Database;
use Icinga\Module\Notifications\Common\Links;
use Icinga\Module\Notifications\Model\Contact;
use Icinga\Module\Notifications\Model\Incident;
use Icinga\Module\Notifications\Model\NotificationHistory;
use Icinga\Module\Notifications\View\NotificationRenderer;
use Icinga\Module\Notifications\Web\Control\SearchBar\ObjectSuggestions;
use Icinga\Module\Notifications\Widget\Detail\IncidentDetail;
use Icinga\Module\Notifications\Widget\Detail\IncidentQuickActions;
use Icinga\Module\Notifications\Widget\Detail\ObjectHeader;
use Icinga\Module\Notifications\Widget\ItemList\ObjectList;
use ipl\Html\Attributes;
use ipl\Html\Contract\Form;
use ipl\Stdlib\Filter;
use ipl\Web\Compat\CompatController;
use ipl\Web\Control\LimitControl;
use ipl\Web\Control\SortControl;
use ipl\Web\Filter\QueryString;
use ipl\Web\Layout\DetailedItemLayout;
use ipl\Web\Layout\ItemLayout;
use ipl\Web\Layout\MinimalItemLayout;
use ipl\Web\Url;

class IncidentController extends CompatController
{
use Auth;
use SearchControls;
use Controls;

public function indexAction(): void
{
$this->addTitleTab(t('Incident'));
$this->setTitle(t('Incident'));
$this->setTitleTab($this->getRequest()->getActionName());

$id = $this->params->getRequired('id');

Expand Down Expand Up @@ -63,4 +79,123 @@ public function indexAction(): void

$this->addContent(new IncidentDetail($incident));
}

public function notificationHistoryAction(): void
{
$this->setTitle(t('Notification History'));
$this->setTitleTab($this->getRequest()->getActionName());

$notificationHistory = NotificationHistory::on(Database::get())
->with(['channel', 'contact', 'contactgroup', 'schedule'])
->filter(Filter::equal('notification_history.incident_id', $this->params->shiftRequired('id')));
$this->applyRestrictions($notificationHistory);

$limitControl = $this->createLimitControl();
$sortControl = $this->createSortControl(
$notificationHistory,
[
'notification_history.triggered_at desc' => t('Triggered At'),
'notification_history.state, notification_history.triggered_at desc' => t('State')
]
);

$paginationControl = $this->createPaginationControl($notificationHistory);
$viewModeSwitcher = $this->createViewModeSwitcher();

$searchBar = $this->createSearchBar(
$notificationHistory,
[
$limitControl->getLimitParam(),
$sortControl->getSortParam(),
$viewModeSwitcher->getViewModeParam(),
'id'
]
);

$this->handleControls($this->getServerRequest());

if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) {
if ($searchBar->hasBeenSubmitted()) {
$filter = QueryString::parse((string) $this->params);
} else {
$this->addControl($searchBar);
$this->sendMultipartUpdate();
return;
}
} else {
$filter = $searchBar->getFilter();
}

$notificationHistory->filter($filter);

$this->addControl($paginationControl);
$this->addControl($sortControl);
$this->addControl($limitControl);
$this->addControl($viewModeSwitcher);
$this->addControl($searchBar);

$this->addContent(
(new ObjectList($notificationHistory, new NotificationRenderer()))
->setItemLayoutClass(match ($viewModeSwitcher->getViewMode()->getName()) {
'minimal' => MinimalItemLayout::class,
'detailed' => DetailedItemLayout::class,
'common' => ItemLayout::class
})
);

if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) {
$this->sendMultipartUpdate();
}

$this->setAutorefreshInterval(30);
}

public function completeAction(): void
{
$suggestions = (new ObjectSuggestions())
->setModel(NotificationHistory::class)
->setBaseFilter(
Filter::equal('notification_history.incident_id', $this->params->getRequired('id'))
)
->forRequest($this->getServerRequest());

$this->getDocument()->addHtml($suggestions);
}

public function searchEditorAction(): void
{
$preserveParams = [
LimitControl::DEFAULT_LIMIT_PARAM,
SortControl::DEFAULT_SORT_PARAM,
'id'
];

$editor = $this->createSearchEditor(
NotificationHistory::on(Database::get()),
Url::fromPath('notifications/incident/notification-history', ['id' => $this->params->getRequired('id')]),
$preserveParams
);
$editor->setSuggestionUrl(
Url::fromPath('notifications/incident/complete')
->setParams(Url::fromRequest()->onlyWith($preserveParams)->getParams())
);

$this->getDocument()->addHtml($editor);
$this->setTitle($this->translate('Adjust Filter'));
}

protected function setTitleTab(string $name): void
{
$id = $this->params->getRequired('id');
$this->getTabs()
->add('index', [
'label' => $this->translate('Incident'),
'url' => Links::incident($id)
])
->add('notification-history', [
'label' => $this->translate('Notification History'),
'url' => Url::fromPath('notifications/incident/notification-history', ['id' => $id])
])
->activate($name);
}
}
51 changes: 51 additions & 0 deletions application/controllers/NotificationController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

// SPDX-FileCopyrightText: 2026 Icinga GmbH <https://icinga.com>
// SPDX-License-Identifier: GPL-3.0-or-later

namespace Icinga\Module\Notifications\Controllers;

use Icinga\Module\Notifications\Common\Auth;
use Icinga\Module\Notifications\Common\Database;
use Icinga\Module\Notifications\Model\NotificationHistory;
use Icinga\Module\Notifications\Widget\Detail\ObjectHeader;
use Icinga\Module\Notifications\Widget\Detail\NotificationDetail;
use ipl\Html\Attributes;
use ipl\Stdlib\Filter;
use ipl\Web\Compat\CompatController;

class NotificationController extends CompatController
{
use Auth;

public function indexAction(): void
{
$this->addTitleTab(t('Notification'));

$id = $this->params->getRequired('id');

$query = NotificationHistory::on(Database::get())
->with([
'contact',
'contactgroup',
'schedule',
'channel',
'incident',
'incident.object',
])
->withColumns(['incident.object.sources'])
->filter(Filter::equal('id', $id));

$this->applyRestrictions($query);

/** @var NotificationHistory $notificationHistory */
$notificationHistory = $query->first();
if ($notificationHistory === null) {
$this->httpNotFound(t('Notification not found'));
}

$this->controls->addAttributes(Attributes::create(['class' => 'notification-history-detail']));
$this->addControl(new ObjectHeader($notificationHistory));
$this->addContent(new NotificationDetail($notificationHistory));
}
}
10 changes: 10 additions & 0 deletions configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@
]
);

$section->add(
N_('History'),
[
'icon' => 'paper-plane',
'description' => $this->translate('History'),
'url' => 'notifications/history',
'priority' => 20
]
);

if ($configLandingPage !== null) {
$section->add(
N_('Configuration'),
Expand Down
30 changes: 30 additions & 0 deletions library/Notifications/Common/Icons.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@

namespace Icinga\Module\Notifications\Common;

use Icinga\Module\Notifications\Widget\IconBall;
use UnhandledMatchError;
use UnitEnum;

final class Icons
{
private function __construct()
Expand Down Expand Up @@ -58,4 +62,30 @@ private function __construct()
public const MUTE = 'volume-xmark';

public const UNMUTE = 'volume-high';

public const CONTACTGROUP = 'users';

public const SCHEDULE = 'calendar';

/**
* Get the icon for the given enum
*
* @param UnitEnum $enum
*
* @return IconBall
*
* @throws UnhandledMatchError If no icon is known for the given enum
*/
public static function forEnum(UnitEnum $enum): IconBall
{
return new IconBall(match ($enum) {
IncidentHistoryType::OPENED => Icons::OPENED,
IncidentHistoryType::MUTED => Icons::MUTE,
IncidentHistoryType::UNMUTED => Icons::UNMUTE,
IncidentHistoryType::CLOSED => Icons::CLOSED,
IncidentHistoryType::RULE_MATCHED => Icons::RULE_MATCHED,
IncidentHistoryType::ESCALATION_TRIGGERED => Icons::TRIGGERED,
IncidentHistoryType::NOTIFIED => Icons::NOTIFIED
});
}
}
Loading
Loading