mirror of
https://github.com/Icinga/icingadb-web.git
synced 2026-06-22 23:28:54 -04:00
Adjust HostsController and ServicesController to use the new ColumnChooser form and the TabularViewModeSwitcher. The Controller class is adjusted to work with the TabularViewModeSwitcher The HostGroupsController and ServiceGroupsController are adjusted, to ensure they use with the GridViewModeSwitcher.
483 lines
17 KiB
PHP
483 lines
17 KiB
PHP
<?php
|
|
|
|
// SPDX-FileCopyrightText: 2019 Icinga GmbH <https://icinga.com>
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
namespace Icinga\Module\Icingadb\Controllers;
|
|
|
|
use GuzzleHttp\Psr7\ServerRequest;
|
|
use GuzzleHttp\Psr7\Utils;
|
|
use Icinga\Module\Icingadb\Common\CommandActions;
|
|
use Icinga\Module\Icingadb\Common\Links;
|
|
use Icinga\Module\Icingadb\Data\PivotTable;
|
|
use Icinga\Module\Icingadb\Model\Service;
|
|
use Icinga\Module\Icingadb\Model\ServicestateSummary;
|
|
use Icinga\Module\Icingadb\Redis\VolatileStateResults;
|
|
use Icinga\Module\Icingadb\Util\FeatureStatus;
|
|
use Icinga\Module\Icingadb\Web\Control\ColumnChooser;
|
|
use Icinga\Module\Icingadb\Web\Control\ProblemToggle;
|
|
use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions;
|
|
use Icinga\Module\Icingadb\Web\Control\TabularViewModeSwitcher;
|
|
use Icinga\Module\Icingadb\Web\Controller;
|
|
use Icinga\Module\Icingadb\Widget\Detail\MultiselectQuickActions;
|
|
use Icinga\Module\Icingadb\Widget\Detail\ObjectsDetail;
|
|
use Icinga\Module\Icingadb\Widget\ItemList\ObjectList;
|
|
use Icinga\Module\Icingadb\Widget\ItemTable\ServiceItemTable;
|
|
use Icinga\Module\Icingadb\Widget\ServiceStatusBar;
|
|
use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher;
|
|
use Icinga\Module\Icingadb\Widget\ShowMore;
|
|
use Icinga\Util\Environment;
|
|
use ipl\Html\HtmlString;
|
|
use ipl\Orm\Query;
|
|
use ipl\Stdlib\Filter;
|
|
use ipl\Web\Control\LimitControl;
|
|
use ipl\Web\Control\SortControl;
|
|
use ipl\Web\Url;
|
|
|
|
class ServicesController extends Controller
|
|
{
|
|
use CommandActions;
|
|
|
|
public function indexAction()
|
|
{
|
|
$this->addTitleTab(t('Services'));
|
|
$compact = $this->view->compact;
|
|
|
|
$db = $this->getDb();
|
|
|
|
$services = Service::on($db)->with([
|
|
'state',
|
|
'state.last_comment',
|
|
'host',
|
|
'host.state',
|
|
'icon_image'
|
|
]);
|
|
$services->getWith()['service.state']->setJoinType('INNER');
|
|
$services->setResultSetClass(VolatileStateResults::class);
|
|
|
|
$this->handleSearchRequest($services);
|
|
|
|
$summary = null;
|
|
if (! $compact) {
|
|
$summary = ServicestateSummary::on($db);
|
|
}
|
|
|
|
$limitControl = $this->createLimitControl();
|
|
$paginationControl = $this->createPaginationControl($services);
|
|
$sortControl = $this->createSortControl(
|
|
$services,
|
|
[
|
|
'service.display_name' => t('Name'),
|
|
'service.state.severity desc,service.state.last_state_change desc' => t('Severity'),
|
|
'service.state.soft_state' => t('Current State'),
|
|
'service.state.last_state_change desc' => t('Last State Change'),
|
|
'host.display_name' => t('Host')
|
|
],
|
|
['service.state.severity DESC', 'service.state.last_state_change DESC']
|
|
);
|
|
$viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl);
|
|
$columns = $this->createColumnControl($services, $viewModeSwitcher);
|
|
|
|
$searchBar = $this->createSearchBar($services, [
|
|
$limitControl->getLimitParam(),
|
|
$sortControl->getSortParam(),
|
|
$viewModeSwitcher->getViewModeParam(),
|
|
'columns'
|
|
]);
|
|
|
|
if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) {
|
|
if ($searchBar->hasBeenSubmitted()) {
|
|
$filter = $this->getFilter();
|
|
} else {
|
|
$this->addControl($searchBar);
|
|
$this->sendMultipartUpdate();
|
|
return;
|
|
}
|
|
} else {
|
|
$filter = $searchBar->getFilter();
|
|
}
|
|
|
|
$services->peekAhead($compact);
|
|
|
|
$this->filter($services, $filter);
|
|
if (! $compact) {
|
|
$this->filter($summary, $filter);
|
|
yield $this->export($services, $summary);
|
|
} else {
|
|
yield $this->export($services);
|
|
}
|
|
|
|
$this->addControl($paginationControl);
|
|
$this->addControl($sortControl);
|
|
$this->addControl($limitControl);
|
|
$this->addControl($viewModeSwitcher);
|
|
$this->addControl($searchBar);
|
|
|
|
$results = $services->execute();
|
|
|
|
$continueWith = $this->createContinueWith(Links::servicesDetails(), $searchBar, $results->hasResult());
|
|
|
|
if ($viewModeSwitcher->getViewMode() === 'tabular') {
|
|
$serviceList = (new ServiceItemTable($results, ServiceItemTable::applyColumnMetaData($services, $columns)))
|
|
->setSort($sortControl->getSort());
|
|
} else {
|
|
$serviceList = (new ObjectList($results))
|
|
->setViewMode($viewModeSwitcher->getViewMode());
|
|
}
|
|
|
|
$serviceList->setEmptyStateMessage($paginationControl->getEmptyStateMessage());
|
|
|
|
$this->addContent($serviceList);
|
|
|
|
if ($compact) {
|
|
$this->addContent(
|
|
(new ShowMore($results, Url::fromRequest()->without(['showCompact', 'limit', 'view'])))
|
|
->setBaseTarget('_next')
|
|
->setAttribute('title', sprintf(
|
|
t('Show all %d services'),
|
|
$services->count()
|
|
))
|
|
);
|
|
} else {
|
|
/** @var ServicestateSummary $servicesSummary */
|
|
$servicesSummary = $summary->first();
|
|
$this->addFooter((new ServiceStatusBar($servicesSummary))->setBaseFilter($filter));
|
|
}
|
|
|
|
if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) {
|
|
$this->sendMultipartUpdate($continueWith);
|
|
}
|
|
|
|
$this->setAutorefreshInterval(10);
|
|
}
|
|
|
|
public function detailsAction()
|
|
{
|
|
$this->addTitleTab(t('Services'));
|
|
|
|
$db = $this->getDb();
|
|
|
|
$services = Service::on($db)->with([
|
|
'state',
|
|
'icon_image',
|
|
'host',
|
|
'host.state'
|
|
]);
|
|
$services->setResultSetClass(VolatileStateResults::class);
|
|
$summary = ServicestateSummary::on($db)->with(['state']);
|
|
|
|
$this->filter($services);
|
|
$this->filter($summary);
|
|
|
|
$services->limit(3);
|
|
$services->peekAhead();
|
|
|
|
yield $this->export($services, $summary);
|
|
|
|
$results = $services->execute();
|
|
$summary = $summary->first();
|
|
|
|
$downtimes = Service::on($db)->with(['downtime']);
|
|
$downtimes->getWith()['service.downtime']->setJoinType('INNER');
|
|
$this->filter($downtimes);
|
|
$summary->downtimes_total = $downtimes->count();
|
|
|
|
$comments = Service::on($db)->with(['comment']);
|
|
$comments->getWith()['service.comment']->setJoinType('INNER');
|
|
// TODO: This should be automatically done by the model/resolver and added as ON condition
|
|
$comments->filter(Filter::equal('comment.object_type', 'service'));
|
|
$this->filter($comments);
|
|
$summary->comments_total = $comments->count();
|
|
|
|
$this->addControl(
|
|
(new ObjectList($results))
|
|
->setViewMode('minimal')
|
|
->setDetailActionsDisabled()
|
|
);
|
|
$this->addControl(new ShowMore(
|
|
$results,
|
|
Links::services()->setFilter($this->getFilter()),
|
|
sprintf(t('Show all %d services'), $services->count())
|
|
));
|
|
$this->addControl(
|
|
(new MultiselectQuickActions('service', $summary))
|
|
->setBaseFilter($this->getFilter())
|
|
);
|
|
|
|
$this->addContent(
|
|
(new ObjectsDetail('service', $summary, $services))
|
|
->setBaseFilter($this->getFilter())
|
|
);
|
|
}
|
|
|
|
public function completeAction()
|
|
{
|
|
$suggestions = new ObjectSuggestions();
|
|
$suggestions->setModel(Service::class);
|
|
$request = clone ServerRequest::fromGlobals();
|
|
$requestData = json_decode($request->getBody()->read(8192), true);
|
|
if (! array_key_exists('type', $requestData['term'])) {
|
|
$requestData['term']['type'] = 'column';
|
|
}
|
|
|
|
$request = $request->withBody(Utils::streamFor(json_encode($requestData)));
|
|
$suggestions->forRequest($request);
|
|
$this->getDocument()->add($suggestions);
|
|
}
|
|
|
|
public function searchEditorAction()
|
|
{
|
|
$editor = $this->createSearchEditor(Service::on($this->getDb()), [
|
|
LimitControl::DEFAULT_LIMIT_PARAM,
|
|
SortControl::DEFAULT_SORT_PARAM,
|
|
ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM,
|
|
'columns'
|
|
]);
|
|
|
|
$this->getDocument()->add($editor);
|
|
$this->setTitle(t('Adjust Filter'));
|
|
}
|
|
|
|
public function gridAction()
|
|
{
|
|
Environment::raiseExecutionTime();
|
|
|
|
$db = $this->getDb();
|
|
$this->addTitleTab(t('Service Grid'));
|
|
|
|
$query = Service::on($db)->with([
|
|
'state',
|
|
'host',
|
|
'host.state'
|
|
]);
|
|
$query->setResultSetClass(VolatileStateResults::class);
|
|
|
|
$this->handleSearchRequest($query);
|
|
|
|
// Create problem toggle before shifting params. Otherwise, the toggle
|
|
// will redirect to the grid without the shifted params.
|
|
$problemToggle = $this->createProblemToggle();
|
|
|
|
$this->params->shift('page'); // Handled by PivotTable internally
|
|
$this->params->shift('limit'); // Handled by PivotTable internally
|
|
$flipped = $this->params->shift('flipped', false);
|
|
|
|
$sortControl = $this->createSortControl($query, [
|
|
'service.display_name' => t('Service Name'),
|
|
'host.display_name' => t('Host Name'),
|
|
])->setDefault('service.display_name');
|
|
$searchBar = $this->createSearchBar($query, [
|
|
LimitControl::DEFAULT_LIMIT_PARAM,
|
|
$sortControl->getSortParam(),
|
|
'flipped',
|
|
'page',
|
|
'problems'
|
|
]);
|
|
|
|
if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) {
|
|
if ($searchBar->hasBeenSubmitted()) {
|
|
$filter = $this->getFilter();
|
|
} else {
|
|
$this->addControl($searchBar);
|
|
$this->sendMultipartUpdate();
|
|
return;
|
|
}
|
|
} else {
|
|
$filter = $searchBar->getFilter();
|
|
}
|
|
|
|
$this->filter($query, $filter);
|
|
|
|
$this->addControl($problemToggle);
|
|
$this->addControl($sortControl);
|
|
$this->addControl($searchBar);
|
|
|
|
$pivotFilter = $problemToggle->isChecked() ?
|
|
Filter::equal('service.state.is_problem', 'y') : null;
|
|
|
|
$columns = [
|
|
'id',
|
|
'host.id',
|
|
'host_name' => 'host.name',
|
|
'host_display_name' => 'host.display_name',
|
|
'name' => 'service.name',
|
|
'display_name' => 'service.display_name',
|
|
'service.state.is_handled',
|
|
'service.state.output',
|
|
'service.state.soft_state'
|
|
];
|
|
|
|
if ($flipped) {
|
|
$pivot = (new PivotTable($query, 'host_name', 'name', $columns))
|
|
->setXAxisFilter($pivotFilter)
|
|
->setYAxisFilter($pivotFilter ? clone $pivotFilter : null)
|
|
->setXAxisHeader('host_display_name')
|
|
->setYAxisHeader('display_name');
|
|
} else {
|
|
$pivot = (new PivotTable($query, 'name', 'host_name', $columns))
|
|
->setXAxisFilter($pivotFilter)
|
|
->setYAxisFilter($pivotFilter ? clone $pivotFilter : null)
|
|
->setXAxisHeader('display_name')
|
|
->setYAxisHeader('host_display_name');
|
|
}
|
|
|
|
$this->view->horizontalPaginator = $pivot->paginateXAxis();
|
|
$this->view->verticalPaginator = $pivot->paginateYAxis();
|
|
list($pivotData, $pivotHeader) = $pivot->toArray();
|
|
$this->view->pivotData = $pivotData;
|
|
$this->view->pivotHeader = $pivotHeader;
|
|
|
|
$continueWith = $this->createContinueWith(Links::servicesDetails(), $searchBar, ! empty($pivotData));
|
|
/** Preserve filter and params in view links (the `BaseFilter` implementation for view scripts -.-) */
|
|
$this->view->baseUrl = Url::fromRequest()
|
|
->onlyWith([
|
|
LimitControl::DEFAULT_LIMIT_PARAM,
|
|
$sortControl->getSortParam(),
|
|
'flipped',
|
|
'page',
|
|
'problems'
|
|
]);
|
|
$preservedParams = $this->view->baseUrl->getParams();
|
|
$this->view->baseUrl->setFilter($filter);
|
|
|
|
$searchBar->setEditorUrl(Url::fromPath(
|
|
"icingadb/services/grid-search-editor"
|
|
)->setParams($preservedParams));
|
|
|
|
$this->view->controls = $this->controls;
|
|
|
|
if ($flipped) {
|
|
$this->getHelper('viewRenderer')->setScriptAction('grid-flipped');
|
|
}
|
|
|
|
if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) {
|
|
// TODO: Everything up to addContent() (inclusive) can be removed once the grid is a widget
|
|
$this->view->controls = ''; // Relevant controls are transmitted separately
|
|
$viewRenderer = $this->getHelper('viewRenderer');
|
|
$viewRenderer->postDispatch();
|
|
$viewRenderer->setNoRender(false);
|
|
|
|
$content = trim($this->getResponse());
|
|
$this->getResponse()->clearBody($viewRenderer->getResponseSegment());
|
|
|
|
$this->addContent(HtmlString::create(substr($content, strpos($content, '>') + 1, -6)));
|
|
|
|
$this->sendMultipartUpdate($continueWith);
|
|
}
|
|
|
|
$this->setAutorefreshInterval(30);
|
|
}
|
|
|
|
public function gridSearchEditorAction()
|
|
{
|
|
$editor = $this->createSearchEditor(
|
|
Service::on($this->getDb()),
|
|
Url::fromPath('icingadb/services/grid'),
|
|
[
|
|
LimitControl::DEFAULT_LIMIT_PARAM,
|
|
SortControl::DEFAULT_SORT_PARAM,
|
|
'flipped',
|
|
'page',
|
|
'problems'
|
|
]
|
|
);
|
|
|
|
$this->getDocument()->add($editor);
|
|
$this->setTitle(t('Adjust Filter'));
|
|
}
|
|
|
|
public function columnControlAction()
|
|
{
|
|
$this->addTitleTab($this->translate('Select Columns'));
|
|
$this->addContent(
|
|
(new ColumnChooser(Url::fromPath('icingadb/services/complete'), Service::on($this->getDb())->getResolver()))
|
|
->setAction((string) Url::fromRequest())
|
|
->on(ColumnChooser::ON_SENT, function (ColumnChooser $form) {
|
|
if ($form->hasBeenSubmitted()) {
|
|
$url = Url::fromPath('icingadb/services');
|
|
$url->setParam('columns', $form->getValue('columns', ''));
|
|
$this->redirectNow($url);
|
|
} else {
|
|
foreach ($form->getPartUpdates() as $update) {
|
|
if (! is_array($update)) {
|
|
$update = [$update];
|
|
}
|
|
|
|
$this->addPart(...$update);
|
|
}
|
|
}
|
|
})->handleRequest($this->getServerRequest())
|
|
);
|
|
}
|
|
|
|
protected function fetchCommandTargets(): Query
|
|
{
|
|
$db = $this->getDb();
|
|
|
|
$services = Service::on($db)->with([
|
|
'state',
|
|
'host',
|
|
'host.state'
|
|
]);
|
|
$services->setResultSetClass(VolatileStateResults::class);
|
|
|
|
switch ($this->getRequest()->getActionName()) {
|
|
case 'acknowledge':
|
|
$services->filter(Filter::equal('state.is_problem', 'y'))
|
|
->filter(Filter::equal('state.is_acknowledged', 'n'));
|
|
|
|
break;
|
|
}
|
|
|
|
$this->filter($services);
|
|
|
|
return $services;
|
|
}
|
|
|
|
protected function getCommandTargetsUrl(): Url
|
|
{
|
|
return Links::servicesDetails()->setFilter($this->getFilter());
|
|
}
|
|
|
|
protected function getFeatureStatus()
|
|
{
|
|
$summary = ServicestateSummary::on($this->getDb());
|
|
$this->filter($summary);
|
|
|
|
return new FeatureStatus('service', $summary->first());
|
|
}
|
|
|
|
protected function getViewModeSwitcherInstance(): ViewModeSwitcher
|
|
{
|
|
return new TabularViewModeSwitcher();
|
|
}
|
|
|
|
protected function prepareSearchFilter(Query $query, string $search, Filter\Any $filter, array $additionalColumns)
|
|
{
|
|
if ($this->params->shift('_hostFilterOnly', false)) {
|
|
foreach (['host.name_ci', 'host.display_name', 'host.address', 'host.address6'] as $column) {
|
|
$filter->add(Filter::like($column, "*$search*"));
|
|
}
|
|
} else {
|
|
parent::prepareSearchFilter($query, $search, $filter, $additionalColumns);
|
|
}
|
|
}
|
|
|
|
public function createProblemToggle(): ProblemToggle
|
|
{
|
|
$filter = $this->params->shift('problems');
|
|
|
|
$problemToggle = new ProblemToggle($filter);
|
|
$problemToggle->setIdProtector([$this->getRequest(), 'protectId']);
|
|
|
|
$problemToggle->on(ProblemToggle::ON_SUCCESS, function (ProblemToggle $form) {
|
|
if (! $form->getElement('problems')->isChecked()) {
|
|
$this->redirectNow(Url::fromRequest()->remove('problems'));
|
|
} else {
|
|
$this->redirectNow(Url::fromRequest()->setParams($this->params->add('problems')));
|
|
}
|
|
})->handleRequest(ServerRequest::fromGlobals());
|
|
|
|
return $problemToggle;
|
|
}
|
|
}
|