Enhancement/improve column opening behaviour (#1293)
Some checks failed
L10n Update / update (push) Has been cancelled
PHP Tests / Static analysis for php 8.2 on ubuntu-latest (push) Has been cancelled
PHP Tests / Static analysis for php 8.3 on ubuntu-latest (push) Has been cancelled
PHP Tests / Static analysis for php 8.4 on ubuntu-latest (push) Has been cancelled
PHP Tests / Unit tests with php 8.2 on ubuntu-latest (push) Has been cancelled
PHP Tests / Unit tests with php 8.3 on ubuntu-latest (push) Has been cancelled
PHP Tests / Unit tests with php 8.4 on ubuntu-latest (push) Has been cancelled

This PR improves the UX in the IcingaDB Web interface by:
- Enhancing the page opening behavior in column views for smoother
navigation.
- Adding a search bar to the tabs Host Services, Host History and
Service History

## Resolves
- #981
- #1288
This commit is contained in:
Jan Schuppik 2025-11-14 12:36:11 +01:00 committed by GitHub
parent 6f6c5700a9
commit c7cc328bf2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 253 additions and 8 deletions

View file

@ -150,6 +150,35 @@ class HostController extends Controller
);
$viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl, true);
$preserveParams = [
$limitControl->getLimitParam(),
$sortControl->getSortParam(),
$viewModeSwitcher->getViewModeParam(),
'name'
];
$requestParams = Url::fromRequest()->onlyWith($preserveParams)->getParams();
$searchBar = $this->createSearchBar($history, $preserveParams)
->setEditorUrl(
Url::fromPath('icingadb/host/history-search-editor')
->setParams($requestParams)
)->setSuggestionUrl(
Url::fromPath('icingadb/host/history-complete')
->setParams(clone $requestParams)
);
if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) {
if ($searchBar->hasBeenSubmitted()) {
$filter = $this->getFilter();
} else {
$this->addControl($searchBar);
$this->sendMultipartUpdate();
return;
}
} else {
$filter = $searchBar->getFilter();
}
$history->peekAhead();
$page = $paginationControl->getCurrentPageNumber();
@ -160,17 +189,19 @@ class HostController extends Controller
}
$history->filter(Filter::lessThanOrEqual('event_time', $before));
$this->filter($history, $filter);
yield $this->export($history);
$this->addControl($sortControl);
$this->addControl($limitControl);
$this->addControl($viewModeSwitcher);
$this->addControl($searchBar);
$historyList = (new LoadMoreObjectList($history->execute()))
->setViewMode($viewModeSwitcher->getViewMode())
->setPageSize($limitControl->getLimit())
->setLoadMoreUrl($url->setParam('before', $before));
->setLoadMoreUrl($url->setParam('before', $before)->setFilter($filter));
if ($compact) {
$historyList->setPageNumber($page);
@ -181,6 +212,10 @@ class HostController extends Controller
} else {
$this->addContent($historyList);
}
if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) {
$this->sendMultipartUpdate();
}
}
public function servicesAction(): Generator
@ -218,6 +253,37 @@ class HostController extends Controller
['service.state.severity DESC', 'service.state.last_state_change DESC']
);
$preserveParams = [
$limitControl->getLimitParam(),
$sortControl->getSortParam(),
$viewModeSwitcher->getViewModeParam(),
'name'
];
$requestParams = Url::fromRequest()->onlyWith($preserveParams)->getParams();
$searchBar = $this->createSearchBar($services, $preserveParams)
->setEditorUrl(
Url::fromPath('icingadb/host/services-search-editor')
->setParams($requestParams)
)->setSuggestionUrl(
Url::fromPath('icingadb/host/services-complete')
->setParams(clone $requestParams)
);
if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) {
if ($searchBar->hasBeenSubmitted()) {
$filter = $this->getFilter();
} else {
$this->addControl($searchBar);
$this->sendMultipartUpdate();
return;
}
} else {
$filter = $searchBar->getFilter();
}
$services->filter($filter);
yield $this->export($services);
$serviceList = (new ObjectList($services))
@ -228,9 +294,18 @@ class HostController extends Controller
$this->addControl($sortControl);
$this->addControl($limitControl);
$this->addControl($viewModeSwitcher);
$this->addControl($searchBar);
$continueWith = $this->createContinueWith(
Links::servicesDetails()->setFilter(Filter::equal('host.name', $this->host->name)),
$searchBar
);
$this->addContent($serviceList);
if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) {
$this->sendMultipartUpdate($continueWith);
}
$this->setAutorefreshInterval(10);
}
@ -391,6 +466,26 @@ class HostController extends Controller
$this->getDocument()->add($suggestions);
}
public function historyCompleteAction(): void
{
$suggestions = (new ObjectSuggestions())
->setModel(History::class)
->setBaseFilter(Filter::equal('host.id', $this->host->id))
->forRequest($this->getServerRequest());
$this->getDocument()->addHtml($suggestions);
}
public function servicesCompleteAction(): void
{
$suggestions = (new ObjectSuggestions())
->setModel(Service::class)
->setBaseFilter(Filter::equal('host.id', $this->host->id))
->forRequest($this->getServerRequest());
$this->getDocument()->addHtml($suggestions);
}
public function searchEditorAction(): void
{
$editor = $this->createSearchEditor(
@ -432,6 +527,50 @@ class HostController extends Controller
$this->setTitle($this->translate('Adjust Filter'));
}
public function historySearchEditorAction(): void
{
$preserveParams = [
LimitControl::DEFAULT_LIMIT_PARAM,
SortControl::DEFAULT_SORT_PARAM,
ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM,
'name'
];
$editor = $this->createSearchEditor(
History::on($this->getDb()),
Url::fromPath('icingadb/host/history', ['name' => $this->host->name]),
$preserveParams
);
$editor->setSuggestionUrl(
Url::fromPath('icingadb/host/history-complete')
->setParams(Url::fromRequest()->onlyWith($preserveParams)->getParams())
);
$this->getDocument()->addHtml($editor);
$this->setTitle($this->translate('Adjust Filter'));
}
public function servicesSearchEditorAction(): void
{
$preserveParams = [
LimitControl::DEFAULT_LIMIT_PARAM,
SortControl::DEFAULT_SORT_PARAM,
ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM,
'name'
];
$editor = $this->createSearchEditor(
Service::on($this->getDb()),
Url::fromPath('icingadb/host/services', ['name' => $this->host->name]),
$preserveParams
);
$editor->setSuggestionUrl(
Url::fromPath('icingadb/host/services-complete')
->setParams(Url::fromRequest()->onlyWith($preserveParams)->getParams())
);
$this->getDocument()->addHtml($editor);
$this->setTitle($this->translate('Adjust Filter'));
}
/**
* Fetch the dependency nodes of the current host
*

View file

@ -300,6 +300,36 @@ class ServiceController extends Controller
);
$viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl, true);
$preserveParams = [
$limitControl->getLimitParam(),
$sortControl->getSortParam(),
$viewModeSwitcher->getViewModeParam(),
'name',
'host.name'
];
$requestParams = Url::fromRequest()->onlyWith($preserveParams)->getParams();
$searchBar = $this->createSearchBar($history, $preserveParams)
->setEditorUrl(
Url::fromPath('icingadb/service/history-search-editor')
->setParams($requestParams)
)->setSuggestionUrl(
Url::fromPath('icingadb/service/history-complete')
->setParams(clone $requestParams)
);
if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) {
if ($searchBar->hasBeenSubmitted()) {
$filter = $this->getFilter();
} else {
$this->addControl($searchBar);
$this->sendMultipartUpdate();
return;
}
} else {
$filter = $searchBar->getFilter();
}
$history->peekAhead();
$page = $paginationControl->getCurrentPageNumber();
@ -310,17 +340,19 @@ class ServiceController extends Controller
}
$history->filter(Filter::lessThanOrEqual('event_time', $before));
$this->filter($history, $filter);
yield $this->export($history);
$this->addControl($sortControl);
$this->addControl($limitControl);
$this->addControl($viewModeSwitcher);
$this->addControl($searchBar);
$historyList = (new LoadMoreObjectList($history->execute()))
->setViewMode($viewModeSwitcher->getViewMode())
->setPageSize($limitControl->getLimit())
->setLoadMoreUrl($url->setParam('before', $before));
->setLoadMoreUrl($url->setParam('before', $before)->setFilter($filter));
if ($compact) {
$historyList->setPageNumber($page);
@ -331,6 +363,10 @@ class ServiceController extends Controller
} else {
$this->addContent($historyList);
}
if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) {
$this->sendMultipartUpdate();
}
}
public function completeAction(): void
@ -355,6 +391,16 @@ class ServiceController extends Controller
$this->getDocument()->add($suggestions);
}
public function historyCompleteAction(): void
{
$suggestions = (new ObjectSuggestions())
->setModel(History::class)
->setBaseFilter(Filter::equal('service.id', $this->service->id))
->forRequest($this->getServerRequest());
$this->getDocument()->addHtml($suggestions);
}
public function searchEditorAction(): void
{
$editor = $this->createSearchEditor(
@ -404,6 +450,29 @@ class ServiceController extends Controller
$this->setTitle($this->translate('Adjust Filter'));
}
public function historySearchEditorAction(): void
{
$preserveParams = [
LimitControl::DEFAULT_LIMIT_PARAM,
SortControl::DEFAULT_SORT_PARAM,
ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM,
'name',
'host.name'
];
$editor = $this->createSearchEditor(
History::on($this->getDb()),
Url::fromPath('icingadb/service/history', ['name' => $this->service->name]),
$preserveParams
);
$editor->setSuggestionUrl(
Url::fromPath('icingadb/service/history-complete')
->setParams(Url::fromRequest()->onlyWith($preserveParams)->getParams())
);
$this->getDocument()->addHtml($editor);
$this->setTitle($this->translate('Adjust Filter'));
}
/**
* Fetch the dependency nodes of the current service
*

View file

@ -61,6 +61,10 @@ abstract class ObjectActionsHook
continue;
}
if ($link->getBaseTarget() === null && ! $link->hasAttribute('target')) {
$link->setBaseTarget('_next');
}
// It may be ValidHtml, but modules shouldn't be able to break our views.
// That's why it needs to be rendered instantly, as any error will then
// be caught here.

View file

@ -4,6 +4,7 @@
namespace Icinga\Module\Icingadb\Widget\Detail;
use Icinga\Module\Icingadb\Common\HostLinks;
use Icinga\Module\Icingadb\Hook\ExtensionHook\ObjectDetailExtensionHook;
use Icinga\Module\Icingadb\Model\Host;
use Icinga\Module\Icingadb\Model\ServicestateSummary;
@ -25,8 +26,8 @@ class HostDetail extends ObjectDetail
protected function createServiceStatistics(): array
{
if ($this->serviceSummary->services_total > 0) {
$services = new ServiceStatistics($this->serviceSummary);
$services->setBaseFilter(Filter::equal('host.name', $this->object->name));
$services = (new ServiceStatistics($this->serviceSummary))
->setUrl(HostLinks::services($this->object));
} else {
$services = new EmptyState(t('This host has no services'));
}

View file

@ -43,7 +43,6 @@ use Icinga\Module\Icingadb\Widget\ItemList\DowntimeList;
use Icinga\Module\Icingadb\Widget\ShowMore;
use ipl\Sql\Expression;
use ipl\Sql\Filter\Exists;
use ipl\Web\Url;
use ipl\Web\Widget\CopyToClipboard;
use ipl\Web\Widget\EmptyState;
use ipl\Web\Widget\EmptyStateBar;

View file

@ -42,6 +42,8 @@ use ipl\Web\Layout\ItemLayout;
*/
class ObjectHeader extends BaseHtmlElement
{
protected $defaultAttributes = ['data-base-target' => '_next'];
/** @var Item */
protected $object;

View file

@ -5,9 +5,11 @@
namespace Icinga\Module\Icingadb\Widget\Detail;
use Icinga\Chart\Donut;
use Icinga\Module\Icingadb\Common\HostLinks;
use Icinga\Module\Icingadb\Common\Links;
use Icinga\Module\Icingadb\Widget\ServiceStateBadges;
use ipl\Html\ValidHtml;
use ipl\Web\Url;
use ipl\Web\Widget\VerticalKeyValue;
use ipl\Html\HtmlString;
use ipl\Web\Widget\Link;
@ -16,11 +18,40 @@ class ServiceStatistics extends ObjectStatistics
{
protected $summary;
/** @var ?Url */
protected ?Url $url;
public function __construct($summary)
{
$this->summary = $summary;
}
/**
* Return the URL pointing to all matching services.
*
* If not set, the URL of the services overview is returned as fallback.
*
* @return Url
*/
public function getUrl(): Url
{
return $this->url ?? Links::services();
}
/**
* Set the URL pointing to all matching services.
*
* @param Url $url The URL to set.
*
* @return $this
*/
public function setUrl(Url $url): self
{
$this->url = $url;
return $this;
}
protected function createDonut(): ValidHtml
{
$donut = (new Donut())
@ -38,7 +69,7 @@ class ServiceStatistics extends ObjectStatistics
protected function createTotal(): ValidHtml
{
$url = Links::services();
$url = $this->getUrl();
if ($this->hasBaseFilter()) {
$url->setFilter($this->getBaseFilter());
}
@ -59,6 +90,6 @@ class ServiceStatistics extends ObjectStatistics
$badges->setBaseFilter($this->getBaseFilter());
}
return $badges;
return $badges->setUrl($this->getUrl());
}
}

View file

@ -12,7 +12,7 @@ class TagList extends BaseHtmlElement
{
protected $content = [];
protected $defaultAttributes = ['class' => 'tag-list'];
protected $defaultAttributes = ['class' => 'tag-list', 'data-base-target' => '_next'];
protected $tag = 'div';