diff options
Diffstat (limited to '')
25 files changed, 3473 insertions, 0 deletions
diff --git a/application/controllers/CommandTransportController.php b/application/controllers/CommandTransportController.php new file mode 100644 index 0000000..b0b4c32 --- /dev/null +++ b/application/controllers/CommandTransportController.php @@ -0,0 +1,155 @@ +<?php + +/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use GuzzleHttp\Psr7\ServerRequest; +use Icinga\Application\Config; +use Icinga\Data\Filter\Filter; +use Icinga\Forms\ConfirmRemovalForm; +use Icinga\Module\Icingadb\Command\Transport\CommandTransportConfig; +use Icinga\Module\Icingadb\Forms\ApiTransportForm; +use Icinga\Module\Icingadb\Widget\ItemList\CommandTransportList; +use Icinga\Web\Notification; +use ipl\Html\HtmlString; +use ipl\Web\Widget\ButtonLink; + +class CommandTransportController extends ConfigController +{ + public function init() + { + $this->assertPermission('config/modules'); + } + + public function indexAction() + { + $list = new CommandTransportList((new CommandTransportConfig())->select()); + + $this->addControl( + (new ButtonLink( + t('Create Command Transport'), + 'icingadb/command-transport/add', + 'plus' + ))->setBaseTarget('_next') + ); + + $this->addContent($list); + + $this->mergeTabs($this->Module()->getConfigTabs()); + $this->getTabs()->disableLegacyExtensions(); + $this->setTitle($this->getTabs() + ->activate('command-transports') + ->getActiveTab() + ->getLabel()); + } + + public function showAction() + { + $transportName = $this->params->getRequired('name'); + + $transportConfig = (new CommandTransportConfig()) + ->select() + ->where('name', $transportName) + ->fetchRow(); + if ($transportConfig === false) { + $this->httpNotFound(t('Unknown transport')); + } + + $form = new ApiTransportForm(); + $form->populate((array) $transportConfig); + $form->on(ApiTransportForm::ON_SUCCESS, function (ApiTransportForm $form) use ($transportName) { + (new CommandTransportConfig())->update( + 'transport', + $form->getValues(), + Filter::where('name', $transportName) + ); + + Notification::success(sprintf(t('Updated command transport "%s" successfully'), $transportName)); + + $this->redirectNow('icingadb/command-transport'); + }); + + $form->handleRequest(ServerRequest::fromGlobals()); + + $this->addContent($form); + + $this->addTitleTab($this->translate('Command Transport: %s'), $transportName); + $this->getTabs()->disableLegacyExtensions(); + } + + public function addAction() + { + $form = new ApiTransportForm(); + $form->on(ApiTransportForm::ON_SUCCESS, function (ApiTransportForm $form) { + (new CommandTransportConfig())->insert('transport', $form->getValues()); + + Notification::success(t('Created command transport successfully')); + + $this->redirectNow('icingadb/command-transport'); + }); + + $form->handleRequest(ServerRequest::fromGlobals()); + + $this->addContent($form); + + $this->addTitleTab($this->translate('Add Command Transport')); + $this->getTabs()->disableLegacyExtensions(); + } + + public function removeAction() + { + $transportName = $this->params->getRequired('name'); + + $form = new ConfirmRemovalForm(); + $form->setAttrib('style', 'text-align:center;'); + $form->setOnSuccess(function () use ($transportName) { + (new CommandTransportConfig())->delete( + 'transport', + Filter::where('name', $transportName) + ); + + Notification::success(sprintf(t('Removed command transport "%s" successfully'), $transportName)); + + $this->redirectNow('icingadb/command-transport'); + }); + + $form->handleRequest(); + + $this->addContent(HtmlString::create($form->render())); + + $this->setTitle($this->translate('Remove Command Transport: %s'), $transportName); + $this->getTabs()->disableLegacyExtensions(); + } + + public function sortAction() + { + $transportName = $this->params->getRequired('name'); + $newPosition = (int) $this->params->getRequired('pos'); + + $config = $this->Config('commandtransports'); + if (! $config->hasSection($transportName)) { + $this->httpNotFound(t('Unknown transport')); + } + + if ($newPosition < 0 || $newPosition > $config->count()) { + $this->httpBadRequest(t('Position out of bounds')); + } + + $transports = $config->getConfigObject()->toArray(); + $transportNames = array_keys($transports); + + array_splice($transportNames, array_search($transportName, $transportNames, true), 1); + array_splice($transportNames, $newPosition, 0, [$transportName]); + + $sortedTransports = []; + foreach ($transportNames as $name) { + $sortedTransports[$name] = $transports[$name]; + } + + $newConfig = Config::fromArray($sortedTransports); + $newConfig->saveIni($config->getConfigFile()); + + $this->redirectNow('icingadb/command-transport'); + } +} diff --git a/application/controllers/CommentController.php b/application/controllers/CommentController.php new file mode 100644 index 0000000..b184d6b --- /dev/null +++ b/application/controllers/CommentController.php @@ -0,0 +1,72 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use Icinga\Exception\NotFoundError; +use Icinga\Module\Icingadb\Common\CommandActions; +use Icinga\Module\Icingadb\Common\Links; +use Icinga\Module\Icingadb\Model\Comment; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Module\Icingadb\Widget\Detail\CommentDetail; +use Icinga\Module\Icingadb\Widget\ItemList\CommentList; +use ipl\Stdlib\Filter; +use ipl\Web\Url; + +class CommentController extends Controller +{ + use CommandActions; + + /** @var Comment The comment object */ + protected $comment; + + public function init() + { + $this->addTitleTab(t('Comment')); + + $name = $this->params->getRequired('name'); + + $query = Comment::on($this->getDb())->with([ + 'host', + 'host.state', + 'service', + 'service.state', + 'service.host', + 'service.host.state' + ]); + $query->filter(Filter::equal('comment.name', $name)); + + $this->applyRestrictions($query); + + $comment = $query->first(); + if ($comment === null) { + throw new NotFoundError(t('Comment not found')); + } + + $this->comment = $comment; + } + + public function indexAction() + { + $this->addControl((new CommentList([$this->comment])) + ->setViewMode('minimal') + ->setDetailActionsDisabled() + ->setCaptionDisabled() + ->setNoSubjectLink()); + + $this->addContent((new CommentDetail($this->comment))->setTicketLinkEnabled()); + + $this->setAutorefreshInterval(10); + } + + protected function fetchCommandTargets(): array + { + return [$this->comment]; + } + + protected function getCommandTargetsUrl(): Url + { + return Links::comment($this->comment); + } +} diff --git a/application/controllers/CommentsController.php b/application/controllers/CommentsController.php new file mode 100644 index 0000000..298fae5 --- /dev/null +++ b/application/controllers/CommentsController.php @@ -0,0 +1,200 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use GuzzleHttp\Psr7\ServerRequest; +use Icinga\Module\Icingadb\Common\Links; +use Icinga\Module\Icingadb\Forms\Command\Object\DeleteCommentForm; +use Icinga\Module\Icingadb\Model\Comment; +use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Module\Icingadb\Widget\ItemList\CommentList; +use Icinga\Module\Icingadb\Widget\ShowMore; +use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher; +use ipl\Web\Control\LimitControl; +use ipl\Web\Control\SortControl; +use ipl\Web\Filter\QueryString; +use ipl\Web\Url; + +class CommentsController extends Controller +{ + public function indexAction() + { + $this->addTitleTab(t('Comments')); + $compact = $this->view->compact; + + $db = $this->getDb(); + + $comments = Comment::on($db)->with([ + 'host', + 'host.state', + 'service', + 'service.host', + 'service.host.state', + 'service.state' + ]); + + $this->handleSearchRequest($comments); + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($comments); + $sortControl = $this->createSortControl( + $comments, + [ + 'comment.entry_time desc' => t('Entry Time'), + 'host.display_name' => t('Host'), + 'service.display_name' => t('Service'), + 'comment.author' => t('Author'), + 'comment.expire_time desc' => t('Expire Time') + ] + ); + $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl); + $searchBar = $this->createSearchBar($comments, [ + $limitControl->getLimitParam(), + $sortControl->getSortParam(), + $viewModeSwitcher->getViewModeParam() + ]); + + if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) { + if ($searchBar->hasBeenSubmitted()) { + $filter = $this->getFilter(); + } else { + $this->addControl($searchBar); + $this->sendMultipartUpdate(); + return; + } + } else { + $filter = $searchBar->getFilter(); + } + + $this->filter($comments, $filter); + + $comments->peekAhead($compact); + + yield $this->export($comments); + + $this->addControl($paginationControl); + $this->addControl($sortControl); + $this->addControl($limitControl); + $this->addControl($viewModeSwitcher); + $this->addControl($searchBar); + $continueWith = $this->createContinueWith(Links::commentsDetails(), $searchBar); + + $results = $comments->execute(); + + $this->addContent((new CommentList($results))->setViewMode($viewModeSwitcher->getViewMode())); + + if ($compact) { + $this->addContent( + (new ShowMore($results, Url::fromRequest()->without(['showCompact', 'limit', 'view']))) + ->setBaseTarget('_next') + ->setAttribute('title', sprintf( + t('Show all %d comments'), + $comments->count() + )) + ); + } + + if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) { + $this->sendMultipartUpdate($continueWith); + } + + $this->setAutorefreshInterval(10); + } + + public function deleteAction() + { + $this->setTitle(t('Remove Comments')); + + $db = $this->getDb(); + + $comments = Comment::on($db)->with([ + 'host', + 'host.state', + 'service', + 'service.host', + 'service.host.state', + 'service.state' + ]); + + $this->filter($comments); + + $form = (new DeleteCommentForm()) + ->setObjects($comments) + ->setRedirectUrl(Links::comments()->getAbsoluteUrl()) + ->on(DeleteCommentForm::ON_SUCCESS, function ($form) { + // This forces the column to reload nearly instantly after the redirect + // and ensures the effect of the command is visible to the user asap + $this->getResponse()->setAutoRefreshInterval(1); + + $this->redirectNow($form->getRedirectUrl()); + }) + ->handleRequest(ServerRequest::fromGlobals()); + + $this->addContent($form); + } + + public function detailsAction() + { + $this->addTitleTab(t('Comments')); + + $db = $this->getDb(); + + $comments = Comment::on($db)->with([ + 'host', + 'host.state', + 'service', + 'service.host', + 'service.host.state', + 'service.state' + ]); + + $comments->limit(3)->peekAhead(); + + $this->filter($comments); + + yield $this->export($comments); + + $rs = $comments->execute(); + + $this->addControl((new CommentList($rs))->setViewMode('minimal')); + + $this->addControl(new ShowMore( + $rs, + Links::comments()->setQueryString(QueryString::render($this->getFilter())), + sprintf(t('Show all %d comments'), $comments->count()) + )); + + $this->addContent( + (new DeleteCommentForm()) + ->setObjects($comments) + ->setAction( + Links::commentsDelete() + ->setQueryString(QueryString::render($this->getFilter())) + ->getAbsoluteUrl() + ) + ); + } + + public function completeAction() + { + $suggestions = new ObjectSuggestions(); + $suggestions->setModel(Comment::class); + $suggestions->forRequest(ServerRequest::fromGlobals()); + $this->getDocument()->add($suggestions); + } + + public function searchEditorAction() + { + $editor = $this->createSearchEditor(Comment::on($this->getDb()), [ + LimitControl::DEFAULT_LIMIT_PARAM, + SortControl::DEFAULT_SORT_PARAM, + ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM + ]); + + $this->getDocument()->add($editor); + $this->setTitle(t('Adjust Filter')); + } +} diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php new file mode 100644 index 0000000..0d64fbf --- /dev/null +++ b/application/controllers/ConfigController.php @@ -0,0 +1,63 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use Icinga\Application\Config; +use Icinga\Module\Icingadb\Forms\DatabaseConfigForm; +use Icinga\Module\Icingadb\Forms\RedisConfigForm; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Web\Form; +use Icinga\Web\Widget\Tab; +use Icinga\Web\Widget\Tabs; +use ipl\Html\HtmlString; + +class ConfigController extends Controller +{ +// public function init() +// { +// $this->assertPermission('config/modules'); +// +// parent::init(); +// } + + public function databaseAction() + { + $form = (new DatabaseConfigForm()) + ->setIniConfig(Config::module('icingadb')); + + $form->handleRequest(); + + $this->mergeTabs($this->Module()->getConfigTabs()->activate('database')); + + $this->addFormToContent($form); + } + + public function redisAction() + { + $form = (new RedisConfigForm()) + ->setIniConfig($this->Config()); + + $form->handleRequest(); + + $this->mergeTabs($this->Module()->getConfigTabs()->activate('redis')); + + $this->addFormToContent($form); + } + + protected function addFormToContent(Form $form) + { + $this->addContent(new HtmlString($form->render())); + } + + protected function mergeTabs(Tabs $tabs): self + { + /** @var Tab $tab */ + foreach ($tabs->getTabs() as $tab) { + $this->tabs->add($tab->getName(), $tab); + } + + return $this; + } +} diff --git a/application/controllers/DowntimeController.php b/application/controllers/DowntimeController.php new file mode 100644 index 0000000..a0a7fa0 --- /dev/null +++ b/application/controllers/DowntimeController.php @@ -0,0 +1,84 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use Icinga\Exception\NotFoundError; +use Icinga\Module\Icingadb\Common\CommandActions; +use Icinga\Module\Icingadb\Common\Links; +use Icinga\Module\Icingadb\Model\Downtime; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Module\Icingadb\Widget\Detail\DowntimeDetail; +use Icinga\Module\Icingadb\Widget\ItemList\DowntimeList; +use ipl\Stdlib\Filter; +use ipl\Web\Url; + +class DowntimeController extends Controller +{ + use CommandActions; + + /** @var Downtime */ + protected $downtime; + + public function init() + { + $this->addTitleTab(t('Downtime')); + + $name = $this->params->getRequired('name'); + + $query = Downtime::on($this->getDb())->with([ + 'host', + 'host.state', + 'service', + 'service.state', + 'service.host', + 'service.host.state', + 'parent', + 'parent.host', + 'parent.host.state', + 'parent.service', + 'parent.service.state', + 'triggered_by', + 'triggered_by.host', + 'triggered_by.host.state', + 'triggered_by.service', + 'triggered_by.service.state' + ]); + $query->filter(Filter::equal('downtime.name', $name)); + + $this->applyRestrictions($query); + + $downtime = $query->first(); + if ($downtime === null) { + throw new NotFoundError(t('Downtime not found')); + } + + $this->downtime = $downtime; + } + + public function indexAction() + { + $detail = new DowntimeDetail($this->downtime); + + $this->addControl((new DowntimeList([$this->downtime])) + ->setViewMode('minimal') + ->setDetailActionsDisabled() + ->setCaptionDisabled() + ->setNoSubjectLink()); + + $this->addContent($detail); + + $this->setAutorefreshInterval(10); + } + + protected function fetchCommandTargets(): array + { + return [$this->downtime]; + } + + protected function getCommandTargetsUrl(): Url + { + return Links::downtime($this->downtime); + } +} diff --git a/application/controllers/DowntimesController.php b/application/controllers/DowntimesController.php new file mode 100644 index 0000000..89fdfc8 --- /dev/null +++ b/application/controllers/DowntimesController.php @@ -0,0 +1,206 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use GuzzleHttp\Psr7\ServerRequest; +use Icinga\Module\Icingadb\Common\Links; +use Icinga\Module\Icingadb\Forms\Command\Object\DeleteDowntimeForm; +use Icinga\Module\Icingadb\Model\Downtime; +use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Module\Icingadb\Widget\ItemList\DowntimeList; +use Icinga\Module\Icingadb\Widget\ShowMore; +use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher; +use ipl\Web\Control\LimitControl; +use ipl\Web\Control\SortControl; +use ipl\Web\Filter\QueryString; +use ipl\Web\Url; + +class DowntimesController extends Controller +{ + public function indexAction() + { + $this->addTitleTab(t('Downtimes')); + $compact = $this->view->compact; + + $db = $this->getDb(); + + $downtimes = Downtime::on($db)->with([ + 'host', + 'host.state', + 'service', + 'service.host', + 'service.host.state', + 'service.state' + ]); + + $this->handleSearchRequest($downtimes); + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($downtimes); + $sortControl = $this->createSortControl( + $downtimes, + [ + 'downtime.is_in_effect desc, downtime.start_time desc' => t('Is In Effect'), + 'downtime.entry_time' => t('Entry Time'), + 'host.display_name' => t('Host'), + 'service.display_name' => t('Service'), + 'downtime.author' => t('Author'), + 'downtime.start_time desc' => t('Start Time'), + 'downtime.end_time desc' => t('End Time'), + 'downtime.scheduled_start_time desc' => t('Scheduled Start Time'), + 'downtime.scheduled_end_time desc' => t('Scheduled End Time'), + 'downtime.duration desc' => t('Duration') + ] + ); + $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl); + $searchBar = $this->createSearchBar($downtimes, [ + $limitControl->getLimitParam(), + $sortControl->getSortParam(), + $viewModeSwitcher->getViewModeParam() + ]); + + if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) { + if ($searchBar->hasBeenSubmitted()) { + $filter = $this->getFilter(); + } else { + $this->addControl($searchBar); + $this->sendMultipartUpdate(); + + return; + } + } else { + $filter = $searchBar->getFilter(); + } + + $this->filter($downtimes, $filter); + + $downtimes->peekAhead($compact); + + yield $this->export($downtimes); + + $this->addControl($paginationControl); + $this->addControl($sortControl); + $this->addControl($limitControl); + $this->addControl($viewModeSwitcher); + $this->addControl($searchBar); + $continueWith = $this->createContinueWith(Links::downtimesDetails(), $searchBar); + + $results = $downtimes->execute(); + + $this->addContent((new DowntimeList($results))->setViewMode($viewModeSwitcher->getViewMode())); + + if ($compact) { + $this->addContent( + (new ShowMore($results, Url::fromRequest()->without(['showCompact', 'limit', 'view']))) + ->setBaseTarget('_next') + ->setAttribute('title', sprintf( + t('Show all %d downtimes'), + $downtimes->count() + )) + ); + } + + if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) { + $this->sendMultipartUpdate($continueWith); + } + + $this->setAutorefreshInterval(10); + } + + public function deleteAction() + { + $this->setTitle(t('Cancel Downtimes')); + + $db = $this->getDb(); + + $downtimes = Downtime::on($db)->with([ + 'host', + 'host.state', + 'service', + 'service.host', + 'service.host.state', + 'service.state' + ]); + + $this->filter($downtimes); + + $form = (new DeleteDowntimeForm()) + ->setObjects($downtimes) + ->setRedirectUrl(Links::downtimes()->getAbsoluteUrl()) + ->on(DeleteDowntimeForm::ON_SUCCESS, function ($form) { + // This forces the column to reload nearly instantly after the redirect + // and ensures the effect of the command is visible to the user asap + $this->getResponse()->setAutoRefreshInterval(1); + + $this->redirectNow($form->getRedirectUrl()); + }) + ->handleRequest(ServerRequest::fromGlobals()); + + $this->addContent($form); + } + + public function detailsAction() + { + $this->addTitleTab(t('Downtimes')); + + $db = $this->getDb(); + + $downtimes = Downtime::on($db)->with([ + 'host', + 'host.state', + 'service', + 'service.host', + 'service.host.state', + 'service.state' + ]); + + $downtimes->limit(3)->peekAhead(); + + $this->filter($downtimes); + + yield $this->export($downtimes); + + $rs = $downtimes->execute(); + + $this->addControl((new DowntimeList($rs))->setViewMode('minimal')); + + $this->addControl(new ShowMore( + $rs, + Links::downtimes()->setQueryString(QueryString::render($this->getFilter())), + sprintf(t('Show all %d downtimes'), $downtimes->count()) + )); + + $this->addContent( + (new DeleteDowntimeForm()) + ->setObjects($downtimes) + ->setAction( + Links::downtimesDelete() + ->setQueryString(QueryString::render($this->getFilter())) + ->getAbsoluteUrl() + ) + ); + } + + public function completeAction() + { + $suggestions = new ObjectSuggestions(); + $suggestions->setModel(Downtime::class); + $suggestions->forRequest(ServerRequest::fromGlobals()); + $this->getDocument()->add($suggestions); + } + + public function searchEditorAction() + { + $editor = $this->createSearchEditor(Downtime::on($this->getDb()), [ + LimitControl::DEFAULT_LIMIT_PARAM, + SortControl::DEFAULT_SORT_PARAM, + ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM + ]); + + $this->getDocument()->add($editor); + $this->setTitle(t('Adjust Filter')); + } +} diff --git a/application/controllers/ErrorController.php b/application/controllers/ErrorController.php new file mode 100644 index 0000000..38621c0 --- /dev/null +++ b/application/controllers/ErrorController.php @@ -0,0 +1,97 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use Icinga\Controllers\ErrorController as IcingaErrorController; +use ipl\Html\Html; +use ipl\Html\HtmlDocument; +use ipl\Web\Layout\Content; +use ipl\Web\Layout\Controls; +use ipl\Web\Url; +use ipl\Web\Widget\Link; +use ipl\Web\Widget\Tabs; + +class ErrorController extends IcingaErrorController +{ + /** @var HtmlDocument */ + protected $document; + + /** @var Controls */ + protected $controls; + + /** @var Content */ + protected $content; + + /** @var Tabs */ + protected $tabs; + + protected function prepareInit() + { + $this->document = new HtmlDocument(); + $this->document->setSeparator("\n"); + $this->controls = new Controls(); + $this->content = new Content(); + $this->tabs = new Tabs(); + + $this->controls->setTabs($this->tabs); + $this->view->document = $this->document; + } + + public function postDispatch() + { + $this->tabs->add(uniqid(), [ + 'active' => true, + 'label' => $this->view->title, + 'url' => $this->getRequest()->getUrl() + ]); + + if (! $this->content->isEmpty()) { + $this->document->prepend($this->content); + } + + if (! $this->view->compact && ! $this->controls->isEmpty()) { + $this->document->prepend($this->controls); + } + + parent::postDispatch(); + } + + protected function postDispatchXhr() + { + parent::postDispatchXhr(); + $this->getResponse()->setHeader('X-Icinga-Module', $this->getModuleName(), true); + } + + public function errorAction() + { + $error = $this->getParam('error_handler'); + $exception = $error->exception; + /** @var \Exception $exception */ + + $message = $exception->getMessage(); + if (substr($message, 0, 27) !== 'Cannot load resource config') { + $this->forward('error', 'error', 'default'); + return; + } else { + $this->setParam('error_handler', null); + } + + // TODO: Find a native way for ipl-html to support enriching text with html + $heading = Html::tag('h2', t('Database not configured')); + $intro = Html::tag('p', ['data-base-target' => '_next'], Html::sprintf( + 'You seem to not have configured a resource for Icinga DB yet. Please %s and then tell Icinga DB Web %s.', + new Link( + Html::tag('strong', 'create one'), + Url::fromPath('config/resource') + ), + new Link( + Html::tag('strong', 'which one it is'), + Url::fromPath('icingadb/config/database') + ) + )); + + $this->content->add([$heading, $intro]); + } +} diff --git a/application/controllers/EventController.php b/application/controllers/EventController.php new file mode 100644 index 0000000..7108606 --- /dev/null +++ b/application/controllers/EventController.php @@ -0,0 +1,71 @@ +<?php + +/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use ArrayObject; +use Icinga\Module\Icingadb\Model\History; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Module\Icingadb\Widget\Detail\EventDetail; +use Icinga\Module\Icingadb\Widget\ItemList\HistoryList; +use ipl\Orm\ResultSet; +use ipl\Stdlib\Filter; + +class EventController extends Controller +{ + /** @var History */ + protected $event; + + public function init() + { + $this->addTitleTab(t('Event')); + + $id = $this->params->getRequired('id'); + + $query = History::on($this->getDb()) + ->with([ + 'host', + 'host.state', + 'service', + 'service.state', + 'comment', + 'downtime', + 'downtime.parent', + 'downtime.parent.host', + 'downtime.parent.host.state', + 'downtime.parent.service', + 'downtime.parent.service.state', + 'downtime.triggered_by', + 'downtime.triggered_by.host', + 'downtime.triggered_by.host.state', + 'downtime.triggered_by.service', + 'downtime.triggered_by.service.state', + 'flapping', + 'notification', + 'acknowledgement', + 'state' + ]) + ->filter(Filter::equal('id', hex2bin($id))); + + $this->applyRestrictions($query); + + $event = $query->first(); + if ($event === null) { + $this->httpNotFound(t('Event not found')); + } + + $this->event = $event; + } + + public function indexAction() + { + $this->addControl((new HistoryList(new ResultSet(new ArrayObject([$this->event])))) + ->setViewMode('minimal') + ->setPageSize(1) + ->setCaptionDisabled() + ->setNoSubjectLink() + ->setDetailActionsDisabled()); + $this->addContent((new EventDetail($this->event))->setTicketLinkEnabled()); + } +} diff --git a/application/controllers/HealthController.php b/application/controllers/HealthController.php new file mode 100644 index 0000000..52ba220 --- /dev/null +++ b/application/controllers/HealthController.php @@ -0,0 +1,115 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use GuzzleHttp\Psr7\ServerRequest; +use Icinga\Module\Icingadb\Command\Instance\ToggleInstanceFeatureCommand; +use Icinga\Module\Icingadb\Common\Links; +use Icinga\Module\Icingadb\Forms\Command\Instance\ToggleInstanceFeaturesForm; +use Icinga\Module\Icingadb\Model\HoststateSummary; +use Icinga\Module\Icingadb\Model\Instance; +use Icinga\Module\Icingadb\Model\ServicestateSummary; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Module\Icingadb\Widget\Health; +use ipl\Web\Widget\VerticalKeyValue; +use ipl\Html\Html; +use ipl\Web\Url; + +class HealthController extends Controller +{ + public function indexAction() + { + $this->addTitleTab(t('Health')); + + $db = $this->getDb(); + + $instance = Instance::on($db)->with(['endpoint']); + $hoststateSummary = HoststateSummary::on($db); + $servicestateSummary = ServicestateSummary::on($db); + + $this->applyRestrictions($hoststateSummary); + $this->applyRestrictions($servicestateSummary); + + yield $this->export($instance, $hoststateSummary, $servicestateSummary); + + $instance = $instance->first(); + + if ($instance === null) { + $this->addContent(Html::tag('p', t( + 'It seems that Icinga DB is not running.' + . ' Make sure Icinga DB is running and writing into the database.' + ))); + + return; + } + + $hoststateSummary = $hoststateSummary->first(); + $servicestateSummary = $servicestateSummary->first(); + + $this->content->addAttributes(['class' => 'monitoring-health']); + + $this->addContent(new Health($instance)); + $this->addContent(Html::tag('section', ['class' => 'check-summary'], [ + Html::tag('div', ['class' => 'col'], [ + Html::tag('h3', t('Host Checks')), + Html::tag('div', ['class' => 'col-content'], [ + new VerticalKeyValue( + t('Active'), + $hoststateSummary->hosts_active_checks_enabled + ), + new VerticalKeyValue( + t('Passive'), + $hoststateSummary->hosts_passive_checks_enabled + ) + ]) + ]), + Html::tag('div', ['class' => 'col'], [ + Html::tag('h3', t('Service Checks')), + Html::tag('div', ['class' => 'col-content'], [ + new VerticalKeyValue( + t('Active'), + $servicestateSummary->services_active_checks_enabled + ), + new VerticalKeyValue( + t('Passive'), + $servicestateSummary->services_passive_checks_enabled + ) + ]) + ]) + ])); + + $featureCommands = Html::tag( + 'section', + ['class' => 'instance-commands'], + Html::tag('h2', t('Feature Commands')) + ); + $toggleInstanceFeaturesCommandForm = new ToggleInstanceFeaturesForm([ + ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS => + $instance->icinga2_active_host_checks_enabled, + ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS => + $instance->icinga2_active_service_checks_enabled, + ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS => + $instance->icinga2_event_handlers_enabled, + ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION => + $instance->icinga2_flap_detection_enabled, + ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS => + $instance->icinga2_notifications_enabled, + ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA => + $instance->icinga2_performance_data_enabled + ]); + $toggleInstanceFeaturesCommandForm->setObjects([$instance]); + $toggleInstanceFeaturesCommandForm->on(ToggleInstanceFeaturesForm::ON_SUCCESS, function () { + $this->getResponse()->setAutoRefreshInterval(1); + + $this->redirectNow(Url::fromPath('icingadb/health')->getAbsoluteUrl()); + }); + $toggleInstanceFeaturesCommandForm->handleRequest(ServerRequest::fromGlobals()); + + $featureCommands->add($toggleInstanceFeaturesCommandForm); + $this->addContent($featureCommands); + + $this->setAutorefreshInterval(30); + } +} diff --git a/application/controllers/HistoryController.php b/application/controllers/HistoryController.php new file mode 100644 index 0000000..b4fc6df --- /dev/null +++ b/application/controllers/HistoryController.php @@ -0,0 +1,138 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use GuzzleHttp\Psr7\ServerRequest; +use Icinga\Module\Icingadb\Model\History; +use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Module\Icingadb\Widget\ItemList\HistoryList; +use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher; +use ipl\Stdlib\Filter; +use ipl\Web\Control\LimitControl; +use ipl\Web\Control\SortControl; +use ipl\Web\Filter\QueryString; +use ipl\Web\Url; + +class HistoryController extends Controller +{ + public function indexAction() + { + $this->addTitleTab(t('History')); + $compact = $this->view->compact; // TODO: Find a less-legacy way.. + + $preserveParams = [ + LimitControl::DEFAULT_LIMIT_PARAM, + SortControl::DEFAULT_SORT_PARAM, + ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM + ]; + + $db = $this->getDb(); + + $history = History::on($db)->with([ + 'host', + 'host.state', + 'service', + 'service.state', + 'comment', + 'downtime', + 'flapping', + 'notification', + 'acknowledgement', + 'state' + ]); + + $before = $this->params->shift('before', time()); + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($history); + $sortControl = $this->createSortControl( + $history, + [ + 'history.event_time desc' => t('Event Time') + ] + ); + $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl, true); + $searchBar = $this->createSearchBar($history, $preserveParams); + + 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(); + + if ($page > 1 && ! $compact) { + $history->limit($page * $limitControl->getLimit()); + } + + $history->filter(Filter::lessThanOrEqual('event_time', $before)); + $this->filter($history, $filter); + + $history->getWith()['history.host']->setJoinType('LEFT'); + $history->filter(Filter::any( + // Because of LEFT JOINs, make sure we'll fetch history entries only for items which still exist: + Filter::like('host.id', '*'), + Filter::like('service.id', '*') + )); + + yield $this->export($history); + + $this->addControl($sortControl); + $this->addControl($limitControl); + $this->addControl($viewModeSwitcher); + $this->addControl($searchBar); + + $url = Url::fromRequest()->onlyWith($preserveParams); + $url->setQueryString(QueryString::render($filter) . '&' . $url->getParams()->toString()); + + $historyList = (new HistoryList($history->execute())) + ->setPageSize($limitControl->getLimit()) + ->setViewMode($viewModeSwitcher->getViewMode()) + ->setLoadMoreUrl($url->setParam('before', $before)); + if ($compact) { + $historyList->setPageNumber($page); + } + + if ($compact && $page > 1) { + $this->document->addFrom($historyList); + } else { + $this->addContent($historyList); + } + + if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) { + $this->sendMultipartUpdate(); + } + } + + public function completeAction() + { + $suggestions = new ObjectSuggestions(); + $suggestions->setModel(History::class); + $suggestions->forRequest(ServerRequest::fromGlobals()); + $this->getDocument()->add($suggestions); + } + + public function searchEditorAction() + { + $editor = $this->createSearchEditor(History::on($this->getDb()), [ + LimitControl::DEFAULT_LIMIT_PARAM, + SortControl::DEFAULT_SORT_PARAM, + ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM + ]); + + $this->getDocument()->add($editor); + $this->setTitle(t('Adjust Filter')); + } +} diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php new file mode 100644 index 0000000..ae5944d --- /dev/null +++ b/application/controllers/HostController.php @@ -0,0 +1,288 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use Icinga\Exception\NotFoundError; +use Icinga\Module\Icingadb\Command\Object\GetObjectCommand; +use Icinga\Module\Icingadb\Command\Transport\CommandTransport; +use Icinga\Module\Icingadb\Common\CommandActions; +use Icinga\Module\Icingadb\Common\HostLinks; +use Icinga\Module\Icingadb\Common\Links; +use Icinga\Module\Icingadb\Hook\TabHook\HookActions; +use Icinga\Module\Icingadb\Model\History; +use Icinga\Module\Icingadb\Model\Host; +use Icinga\Module\Icingadb\Model\Service; +use Icinga\Module\Icingadb\Model\ServicestateSummary; +use Icinga\Module\Icingadb\Redis\VolatileStateResults; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Module\Icingadb\Widget\Detail\HostDetail; +use Icinga\Module\Icingadb\Widget\Detail\HostInspectionDetail; +use Icinga\Module\Icingadb\Widget\Detail\HostMetaInfo; +use Icinga\Module\Icingadb\Widget\Detail\QuickActions; +use Icinga\Module\Icingadb\Widget\ItemList\HostList; +use Icinga\Module\Icingadb\Widget\ItemList\HistoryList; +use Icinga\Module\Icingadb\Widget\ItemList\ServiceList; +use ipl\Stdlib\Filter; +use ipl\Web\Url; +use ipl\Web\Widget\Tabs; + +class HostController extends Controller +{ + use CommandActions; + use HookActions; + + /** @var Host The host object */ + protected $host; + + public function init() + { + $name = $this->params->getRequired('name'); + + $query = Host::on($this->getDb())->with(['state', 'icon_image']); + $query + ->setResultSetClass(VolatileStateResults::class) + ->filter(Filter::equal('host.name', $name)); + + $this->applyRestrictions($query); + + /** @var Host $host */ + $host = $query->first(); + if ($host === null) { + throw new NotFoundError(t('Host not found')); + } + + $this->host = $host; + $this->loadTabsForObject($host); + + $this->setTitleTab($this->getRequest()->getActionName()); + $this->setTitle($host->display_name); + } + + public function indexAction() + { + $serviceSummary = ServicestateSummary::on($this->getDb()); + $serviceSummary->filter(Filter::equal('service.host_id', $this->host->id)); + + $this->applyRestrictions($serviceSummary); + + if ($this->host->state->is_overdue) { + $this->controls->addAttributes(['class' => 'overdue']); + } + + $this->addControl((new HostList([$this->host])) + ->setViewMode('objectHeader') + ->setDetailActionsDisabled() + ->setNoSubjectLink()); + $this->addControl(new HostMetaInfo($this->host)); + $this->addControl(new QuickActions($this->host)); + + $this->addContent(new HostDetail($this->host, $serviceSummary->first())); + + $this->setAutorefreshInterval(10); + } + + public function sourceAction() + { + $this->assertPermission('icingadb/object/show-source'); + $apiResult = (new CommandTransport())->send((new GetObjectCommand())->setObject($this->host)); + + if ($this->host->state->is_overdue) { + $this->controls->addAttributes(['class' => 'overdue']); + } + + $this->addControl((new HostList([$this->host])) + ->setViewMode('objectHeader') + ->setDetailActionsDisabled() + ->setNoSubjectLink()); + $this->addContent(new HostInspectionDetail( + $this->host, + reset($apiResult) + )); + } + + public function historyAction() + { + $compact = $this->view->compact; // TODO: Find a less-legacy way.. + + if ($this->host->state->is_overdue) { + $this->controls->addAttributes(['class' => 'overdue']); + } + + $db = $this->getDb(); + + $history = History::on($db)->with([ + 'host', + 'host.state', + 'comment', + 'downtime', + 'flapping', + 'notification', + 'acknowledgement', + 'state' + ]); + + $history->filter(Filter::all( + Filter::equal('history.host_id', $this->host->id), + Filter::unlike('history.service_id', '*') + )); + + $before = $this->params->shift('before', time()); + $url = Url::fromRequest()->setParams(clone $this->params); + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($history); + $sortControl = $this->createSortControl( + $history, + [ + 'history.event_time desc' => t('Event Time') + ] + ); + $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl, true); + + $history->peekAhead(); + + $page = $paginationControl->getCurrentPageNumber(); + + if ($page > 1 && ! $compact) { + $history->limit($page * $limitControl->getLimit()); + } + + $history->filter(Filter::lessThanOrEqual('event_time', $before)); + + yield $this->export($history); + + $this->addControl((new HostList([$this->host])) + ->setViewMode('objectHeader') + ->setDetailActionsDisabled() + ->setNoSubjectLink()); + $this->addControl($sortControl); + $this->addControl($limitControl); + $this->addControl($viewModeSwitcher); + + $historyList = (new HistoryList($history->execute())) + ->setViewMode($viewModeSwitcher->getViewMode()) + ->setPageSize($limitControl->getLimit()) + ->setLoadMoreUrl($url->setParam('before', $before)); + + if ($compact) { + $historyList->setPageNumber($page); + } + + if ($compact && $page > 1) { + $this->document->addFrom($historyList); + } else { + $this->addContent($historyList); + } + } + + public function servicesAction() + { + if ($this->host->state->is_overdue) { + $this->controls->addAttributes(['class' => 'overdue']); + } + + $db = $this->getDb(); + + $services = Service::on($db)->with([ + 'state', + 'state.last_comment', + 'icon_image', + 'host', + 'host.state' + ]); + $services + ->setResultSetClass(VolatileStateResults::class) + ->filter(Filter::equal('host.id', $this->host->id)); + + $this->applyRestrictions($services); + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($services); + $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl); + $sortControl = $this->createSortControl( + $services, + [ + 'service.display_name' => t('Name'), + 'service.state.severity desc' => t('Severity'), + 'service.state.soft_state' => t('Current State'), + 'service.state.last_state_change desc' => t('Last State Change') + ] + ); + + yield $this->export($services); + + $serviceList = (new ServiceList($services)) + ->setViewMode($viewModeSwitcher->getViewMode()); + + $this->addControl((new HostList([$this->host])) + ->setViewMode('objectHeader') + ->setDetailActionsDisabled() + ->setNoSubjectLink()); + $this->addControl($paginationControl); + $this->addControl($sortControl); + $this->addControl($limitControl); + $this->addControl($viewModeSwitcher); + + $this->addContent($serviceList); + + $this->setAutorefreshInterval(10); + } + + protected function createTabs(): Tabs + { + $tabs = $this->getTabs() + ->add('index', [ + 'label' => t('Host'), + 'url' => Links::host($this->host) + ]) + ->add('services', [ + 'label' => t('Services'), + 'url' => HostLinks::services($this->host) + ]) + ->add('history', [ + 'label' => t('History'), + 'url' => HostLinks::history($this->host) + ]); + + if ($this->hasPermission('icingadb/object/show-source')) { + $tabs->add('source', [ + 'label' => t('Source'), + 'url' => Links::hostSource($this->host) + ]); + } + + foreach ($this->loadAdditionalTabs() as $name => $tab) { + $tabs->add($name, $tab + ['urlParams' => ['name' => $this->host->name]]); + } + + return $tabs; + } + + protected function setTitleTab(string $name) + { + $tab = $this->createTabs()->get($name); + + if ($tab !== null) { + $tab->setActive(); + + $this->setTitle($tab->getLabel()); + } + } + + protected function fetchCommandTargets(): array + { + return [$this->host]; + } + + protected function getCommandTargetsUrl(): Url + { + return Links::host($this->host); + } + + protected function getDefaultTabControls(): array + { + return [(new HostList([$this->host]))->setDetailActionsDisabled()->setNoSubjectLink()]; + } +} diff --git a/application/controllers/HostgroupController.php b/application/controllers/HostgroupController.php new file mode 100644 index 0000000..978489d --- /dev/null +++ b/application/controllers/HostgroupController.php @@ -0,0 +1,89 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use Icinga\Exception\NotFoundError; +use Icinga\Module\Icingadb\Model\Host; +use Icinga\Module\Icingadb\Model\Hostgroupsummary; +use Icinga\Module\Icingadb\Redis\VolatileStateResults; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Module\Icingadb\Widget\ItemList\HostList; +use Icinga\Module\Icingadb\Widget\ItemList\HostgroupList; +use ipl\Html\Html; +use ipl\Stdlib\Filter; +use ipl\Web\Widget\HorizontalKeyValue; + +class HostgroupController extends Controller +{ + /** @var Hostgroupsummary The host group object */ + protected $hostgroup; + + public function init() + { + $this->assertRouteAccess('hostgroups'); + + $this->addTitleTab(t('Host Group')); + + $name = $this->params->getRequired('name'); + + $query = Hostgroupsummary::on($this->getDb()); + + foreach ($query->getUnions() as $unionPart) { + $unionPart->filter(Filter::equal('hostgroup.name', $name)); + } + + $this->applyRestrictions($query); + + $hostgroup = $query->first(); + if ($hostgroup === null) { + throw new NotFoundError(t('Host group not found')); + } + + $this->hostgroup = $hostgroup; + $this->setTitle($hostgroup->display_name); + } + + public function indexAction() + { + $db = $this->getDb(); + + $hosts = Host::on($db)->with(['state', 'state.last_comment', 'icon_image']); + $hosts + ->setResultSetClass(VolatileStateResults::class) + ->filter(Filter::equal('hostgroup.id', $this->hostgroup->id)); + $this->applyRestrictions($hosts); + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($hosts); + $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl); + + $hostList = (new HostList($hosts->execute())) + ->setViewMode($viewModeSwitcher->getViewMode()); + + yield $this->export($hosts); + + // ICINGAWEB_EXPORT_FORMAT is not set yet and $this->format is inaccessible, yeah... + if ($this->getRequest()->getParam('format') === 'pdf') { + $this->addContent((new HostgroupList([$this->hostgroup])) + ->setViewMode('minimal') + ->setDetailActionsDisabled() + ->setNoSubjectLink()); + $this->addContent(Html::tag('h2', null, t('Hosts'))); + } else { + $this->addControl((new HostgroupList([$this->hostgroup])) + ->setViewMode('minimal') + ->setDetailActionsDisabled() + ->setNoSubjectLink()); + } + + $this->addControl($paginationControl); + $this->addControl($viewModeSwitcher); + $this->addControl($limitControl); + + $this->addContent($hostList); + + $this->setAutorefreshInterval(10); + } +} diff --git a/application/controllers/HostgroupsController.php b/application/controllers/HostgroupsController.php new file mode 100644 index 0000000..f510cf4 --- /dev/null +++ b/application/controllers/HostgroupsController.php @@ -0,0 +1,121 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use GuzzleHttp\Psr7\ServerRequest; +use Icinga\Module\Icingadb\Model\Hostgroup; +use Icinga\Module\Icingadb\Model\Hostgroupsummary; +use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Module\Icingadb\Widget\ItemList\HostgroupList; +use Icinga\Module\Icingadb\Widget\ShowMore; +use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher; +use ipl\Web\Control\LimitControl; +use ipl\Web\Control\SortControl; +use ipl\Web\Url; + +class HostgroupsController extends Controller +{ + public function init() + { + parent::init(); + + $this->assertRouteAccess(); + } + + public function indexAction() + { + $this->addTitleTab(t('Host Groups')); + $compact = $this->view->compact; + + $db = $this->getDb(); + + $hostgroups = Hostgroupsummary::on($db); + + $this->handleSearchRequest($hostgroups); + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($hostgroups); + $sortControl = $this->createSortControl( + $hostgroups, + [ + 'display_name' => t('Name'), + 'hosts_severity desc' => t('Severity'), + 'hosts_total desc' => t('Total Hosts'), + 'services_total desc' => t('Total Services') + ] + ); + $searchBar = $this->createSearchBar($hostgroups, [ + $limitControl->getLimitParam(), + $sortControl->getSortParam() + ]); + + if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) { + if ($searchBar->hasBeenSubmitted()) { + $filter = $this->getFilter(); + } else { + $this->addControl($searchBar); + $this->sendMultipartUpdate(); + return; + } + } else { + $filter = $searchBar->getFilter(); + } + + $this->filter($hostgroups, $filter); + + $hostgroups->peekAhead($compact); + + yield $this->export($hostgroups); + + $this->addControl($paginationControl); + $this->addControl($sortControl); + $this->addControl($limitControl); + $this->addControl($searchBar); + + $results = $hostgroups->execute(); + + $this->addContent( + (new HostgroupList($results))->setBaseFilter($filter) + ); + + if ($compact) { + $this->addContent( + (new ShowMore($results, Url::fromRequest()->without(['showCompact', 'limit', 'view']))) + ->setBaseTarget('_next') + ->setAttribute('title', sprintf( + t('Show all %d hostgroups'), + $hostgroups->count() + )) + ); + } + + if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) { + $this->sendMultipartUpdate(); + } + + $this->setAutorefreshInterval(30); + } + + public function completeAction() + { + $suggestions = new ObjectSuggestions(); + $suggestions->setModel(Hostgroup::class); + $suggestions->forRequest(ServerRequest::fromGlobals()); + $this->getDocument()->add($suggestions); + } + + public function searchEditorAction() + { + $editor = $this->createSearchEditor(Hostgroupsummary::on($this->getDb()), [ + LimitControl::DEFAULT_LIMIT_PARAM, + SortControl::DEFAULT_SORT_PARAM, + ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM + ]); + + $this->getDocument()->add($editor); + $this->setTitle(t('Adjust Filter')); + } +} diff --git a/application/controllers/HostsController.php b/application/controllers/HostsController.php new file mode 100644 index 0000000..eaf804a --- /dev/null +++ b/application/controllers/HostsController.php @@ -0,0 +1,240 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use GuzzleHttp\Psr7\ServerRequest; +use Icinga\Module\Icingadb\Common\CommandActions; +use Icinga\Module\Icingadb\Common\Links; +use Icinga\Module\Icingadb\Model\Host; +use Icinga\Module\Icingadb\Model\HoststateSummary; +use Icinga\Module\Icingadb\Redis\VolatileStateResults; +use Icinga\Module\Icingadb\Util\FeatureStatus; +use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions; +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\HostList; +use Icinga\Module\Icingadb\Widget\HostStatusBar; +use Icinga\Module\Icingadb\Widget\ItemTable\HostItemTable; +use Icinga\Module\Icingadb\Widget\ShowMore; +use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher; +use ipl\Orm\Query; +use ipl\Stdlib\Filter; +use ipl\Web\Control\LimitControl; +use ipl\Web\Control\SortControl; +use ipl\Web\Filter\QueryString; +use ipl\Web\Url; + +class HostsController extends Controller +{ + use CommandActions; + + public function indexAction() + { + $this->addTitleTab(t('Hosts')); + $compact = $this->view->compact; + + $db = $this->getDb(); + + $hosts = Host::on($db)->with(['state', 'icon_image', 'state.last_comment']); + $hosts->getWith()['host.state']->setJoinType('INNER'); + $hosts->setResultSetClass(VolatileStateResults::class); + + $this->handleSearchRequest($hosts); + + $summary = null; + if (! $compact) { + $summary = HoststateSummary::on($db); + } + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($hosts); + $sortControl = $this->createSortControl( + $hosts, + [ + 'host.display_name' => t('Name'), + 'host.state.severity desc' => t('Severity'), + 'host.state.soft_state' => t('Current State'), + 'host.state.last_state_change desc' => t('Last State Change') + ] + ); + $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl); + $columns = $this->createColumnControl($hosts, $viewModeSwitcher); + + $searchBar = $this->createSearchBar($hosts, [ + $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(); + } + + $hosts->peekAhead($compact); + + $this->filter($hosts, $filter); + if (isset($summary)) { + $this->filter($summary, $filter); + yield $this->export($hosts, $summary); + } else { + yield $this->export($hosts); + } + + $this->addControl($paginationControl); + $this->addControl($sortControl); + $this->addControl($limitControl); + $this->addControl($viewModeSwitcher); + $this->addControl($searchBar); + $continueWith = $this->createContinueWith(Links::hostsDetails(), $searchBar); + + $results = $hosts->execute(); + + if ($viewModeSwitcher->getViewMode() === 'tabular') { + $hostList = (new HostItemTable($results, HostItemTable::applyColumnMetaData($hosts, $columns))) + ->setSort($sortControl->getSort()); + } else { + $hostList = (new HostList($results)) + ->setViewMode($viewModeSwitcher->getViewMode()); + } + + $this->addContent($hostList); + + if ($compact) { + $this->addContent( + (new ShowMore($results, Url::fromRequest()->without(['showCompact', 'limit', 'view']))) + ->setBaseTarget('_next') + ->setAttribute('title', sprintf( + t('Show all %d hosts'), + $hosts->count() + )) + ); + } else { + $this->addFooter((new HostStatusBar($summary->first()))->setBaseFilter($filter)); + } + + if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) { + $this->sendMultipartUpdate($continueWith); + } + + $this->setAutorefreshInterval(10); + } + + public function detailsAction() + { + $this->addTitleTab(t('Hosts')); + + $db = $this->getDb(); + + $hosts = Host::on($db)->with(['state', 'icon_image']); + $hosts->setResultSetClass(VolatileStateResults::class); + $summary = HoststateSummary::on($db)->with(['state']); + + $this->filter($hosts); + $this->filter($summary); + + $hosts->limit(3); + $hosts->peekAhead(); + + yield $this->export($hosts, $summary); + + $results = $hosts->execute(); + $summary = $summary->first(); + + $downtimes = Host::on($db)->with(['downtime']); + $downtimes->getWith()['host.downtime']->setJoinType('INNER'); + $this->filter($downtimes); + $summary->downtimes_total = $downtimes->count(); + + $comments = Host::on($db)->with(['comment']); + $comments->getWith()['host.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', 'host')); + $this->filter($comments); + $summary->comments_total = $comments->count(); + + $this->addControl( + (new HostList($results)) + ->setViewMode('minimal') + ); + $this->addControl(new ShowMore( + $results, + Links::hosts()->setQueryString(QueryString::render($this->getFilter())), + sprintf(t('Show all %d hosts'), $hosts->count()) + )); + $this->addControl( + (new MultiselectQuickActions('host', $summary)) + ->setBaseFilter($this->getFilter()) + ); + + $this->addContent( + (new ObjectsDetail('host', $summary, $hosts)) + ->setBaseFilter($this->getFilter()) + ); + } + + public function completeAction() + { + $suggestions = new ObjectSuggestions(); + $suggestions->setModel(Host::class); + $suggestions->forRequest(ServerRequest::fromGlobals()); + $this->getDocument()->add($suggestions); + } + + public function searchEditorAction() + { + $editor = $this->createSearchEditor(Host::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')); + } + + protected function fetchCommandTargets(): Query + { + $db = $this->getDb(); + + $hosts = Host::on($db)->with('state'); + $hosts->setResultSetClass(VolatileStateResults::class); + + switch ($this->getRequest()->getActionName()) { + case 'acknowledge': + $hosts->filter(Filter::equal('state.is_problem', 'y')) + ->filter(Filter::equal('state.is_acknowledged', 'n')); + + break; + } + + $this->filter($hosts); + + return $hosts; + } + + protected function getCommandTargetsUrl(): Url + { + return Links::hostsDetails()->setQueryString(QueryString::render($this->getFilter())); + } + + protected function getFeatureStatus() + { + $summary = HoststateSummary::on($this->getDb()); + $this->filter($summary); + + return new FeatureStatus('host', $summary->first()); + } +} diff --git a/application/controllers/MigrateController.php b/application/controllers/MigrateController.php new file mode 100644 index 0000000..395319a --- /dev/null +++ b/application/controllers/MigrateController.php @@ -0,0 +1,120 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use Exception; +use GuzzleHttp\Psr7\ServerRequest; +use Icinga\Application\Hook; +use Icinga\Exception\IcingaException; +use Icinga\Module\Icingadb\Compat\UrlMigrator; +use Icinga\Module\Icingadb\Forms\SetAsBackendForm; +use Icinga\Module\Icingadb\Hook\IcingadbSupportHook; +use Icinga\Module\Icingadb\Web\Controller; +use ipl\Html\HtmlString; +use ipl\Web\Url; + +class MigrateController extends Controller +{ + public function monitoringUrlAction() + { + $this->assertHttpMethod('post'); + if (! $this->getRequest()->isApiRequest()) { + $this->httpBadRequest('No API request'); + } + + if ( + ! preg_match('/([^;]*);?/', $this->getRequest()->getHeader('Content-Type'), $matches) + || $matches[1] !== 'application/json' + ) { + $this->httpBadRequest('No JSON content'); + } + + $urls = $this->getRequest()->getPost(); + + $result = []; + $errors = []; + foreach ($urls as $urlString) { + $url = Url::fromPath($urlString); + if (UrlMigrator::isSupportedUrl($url)) { + try { + $urlString = UrlMigrator::transformUrl($url)->getAbsoluteUrl(); + } catch (Exception $e) { + $errors[$urlString] = [ + IcingaException::describe($e), + IcingaException::getConfidentialTraceAsString($e) + ]; + $urlString = false; + } + } + + $result[] = $urlString; + } + + $response = $this->getResponse()->json(); + if (empty($errors)) { + $response->setSuccessData($result); + } else { + $response->setFailData([ + 'result' => $result, + 'errors' => $errors + ]); + } + + $response->sendResponse(); + } + + public function checkboxStateAction() + { + $this->assertHttpMethod('get'); + + $form = new SetAsBackendForm(); + $form->setAction(Url::fromPath('icingadb/migrate/checkbox-submit')->getAbsoluteUrl()); + + $this->getDocument()->addHtml($form); + } + + public function checkboxSubmitAction() + { + $this->assertHttpMethod('post'); + $this->addPart(HtmlString::create('"bogus"'), 'Behavior:Migrate'); + + (new SetAsBackendForm())->handleRequest(ServerRequest::fromGlobals()); + } + + public function backendSupportAction() + { + $this->assertHttpMethod('post'); + if (! $this->getRequest()->isApiRequest()) { + $this->httpBadRequest('No API request'); + } + + if ( + ! preg_match('/([^;]*);?/', $this->getRequest()->getHeader('Content-Type'), $matches) + || $matches[1] !== 'application/json' + ) { + $this->httpBadRequest('No JSON content'); + } + + $supportList = []; + foreach (Hook::all('Icingadb/IcingadbSupport') as $hook) { + /** @var IcingadbSupportHook $hook */ + $supportList[$hook->getModule()->getName()] = $hook->supportsIcingaDb(); + } + + $moduleSupportStates = []; + foreach ($this->getRequest()->getPost() as $moduleName) { + if (isset($supportList[$moduleName])) { + $moduleSupportStates[] = $supportList[$moduleName]; + } else { + $moduleSupportStates[] = false; + } + } + + $this->getResponse() + ->json() + ->setSuccessData($moduleSupportStates) + ->sendResponse(); + } +} diff --git a/application/controllers/NotificationsController.php b/application/controllers/NotificationsController.php new file mode 100644 index 0000000..1c8a5ff --- /dev/null +++ b/application/controllers/NotificationsController.php @@ -0,0 +1,134 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use GuzzleHttp\Psr7\ServerRequest; +use Icinga\Module\Icingadb\Model\NotificationHistory; +use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Module\Icingadb\Widget\ItemList\NotificationList; +use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher; +use ipl\Sql\Sql; +use ipl\Stdlib\Filter; +use ipl\Web\Control\LimitControl; +use ipl\Web\Control\SortControl; +use ipl\Web\Filter\QueryString; +use ipl\Web\Url; + +class NotificationsController extends Controller +{ + public function indexAction() + { + $this->addTitleTab(t('Notifications')); + $compact = $this->view->compact; + + $preserveParams = [ + LimitControl::DEFAULT_LIMIT_PARAM, + SortControl::DEFAULT_SORT_PARAM, + ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM + ]; + + $db = $this->getDb(); + + $notifications = NotificationHistory::on($db)->with([ + 'history', + 'host', + 'host.state', + 'service', + 'service.state' + ]); + + $this->handleSearchRequest($notifications); + $before = $this->params->shift('before', time()); + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($notifications); + $sortControl = $this->createSortControl( + $notifications, + [ + 'notification_history.send_time desc' => t('Send Time') + ] + ); + $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl, true); + $searchBar = $this->createSearchBar($notifications, $preserveParams); + + if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) { + if ($searchBar->hasBeenSubmitted()) { + $filter = $this->getFilter(); + } else { + $this->addControl($searchBar); + $this->sendMultipartUpdate(); + return; + } + } else { + $filter = $searchBar->getFilter(); + } + + $notifications->peekAhead(); + + $page = $paginationControl->getCurrentPageNumber(); + + if ($page > 1 && ! $compact) { + $notifications->limit($page * $limitControl->getLimit()); + } + + $notifications->filter(Filter::lessThanOrEqual('send_time', $before)); + $this->filter($notifications, $filter); + $notifications->filter(Filter::any( + // Make sure we'll fetch service history entries only for services which still exist + Filter::unlike('service_id', '*'), + Filter::like('history.service.id', '*') + )); + + yield $this->export($notifications); + + $this->addControl($sortControl); + $this->addControl($limitControl); + $this->addControl($viewModeSwitcher); + $this->addControl($searchBar); + + $url = Url::fromRequest()->onlyWith($preserveParams); + $url->setQueryString(QueryString::render($filter) . '&' . $url->getParams()->toString()); + + $notificationList = (new NotificationList($notifications->execute())) + ->setPageSize($limitControl->getLimit()) + ->setViewMode($viewModeSwitcher->getViewMode()) + ->setLoadMoreUrl($url->setParam('before', $before)); + + if ($compact) { + $notificationList->setPageNumber($page); + } + + if ($compact && $page > 1) { + $this->document->addFrom($notificationList); + } else { + $this->addContent($notificationList); + } + + if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) { + $this->sendMultipartUpdate(); + } + } + + public function completeAction() + { + $suggestions = new ObjectSuggestions(); + $suggestions->setModel(NotificationHistory::class); + $suggestions->forRequest(ServerRequest::fromGlobals()); + $this->getDocument()->add($suggestions); + } + + public function searchEditorAction() + { + $editor = $this->createSearchEditor(NotificationHistory::on($this->getDb()), [ + LimitControl::DEFAULT_LIMIT_PARAM, + SortControl::DEFAULT_SORT_PARAM, + ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM + ]); + + $this->getDocument()->add($editor); + $this->setTitle(t('Adjust Filter')); + } +} diff --git a/application/controllers/ServiceController.php b/application/controllers/ServiceController.php new file mode 100644 index 0000000..2933d93 --- /dev/null +++ b/application/controllers/ServiceController.php @@ -0,0 +1,239 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use Icinga\Exception\NotFoundError; +use Icinga\Module\Icingadb\Command\Object\GetObjectCommand; +use Icinga\Module\Icingadb\Command\Transport\CommandTransport; +use Icinga\Module\Icingadb\Common\CommandActions; +use Icinga\Module\Icingadb\Common\Links; +use Icinga\Module\Icingadb\Common\ServiceLinks; +use Icinga\Module\Icingadb\Hook\TabHook\HookActions; +use Icinga\Module\Icingadb\Model\History; +use Icinga\Module\Icingadb\Model\Service; +use Icinga\Module\Icingadb\Redis\VolatileStateResults; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Module\Icingadb\Widget\Detail\QuickActions; +use Icinga\Module\Icingadb\Widget\Detail\ServiceDetail; +use Icinga\Module\Icingadb\Widget\Detail\ServiceInspectionDetail; +use Icinga\Module\Icingadb\Widget\Detail\ServiceMetaInfo; +use Icinga\Module\Icingadb\Widget\ItemList\HistoryList; +use Icinga\Module\Icingadb\Widget\ItemList\ServiceList; +use ipl\Stdlib\Filter; +use ipl\Web\Url; + +class ServiceController extends Controller +{ + use CommandActions; + use HookActions; + + /** @var Service The service object */ + protected $service; + + public function init() + { + $name = $this->params->getRequired('name'); + $hostName = $this->params->getRequired('host.name'); + + $query = Service::on($this->getDb())->with([ + 'state', + 'icon_image', + 'host', + 'host.state' + ]); + $query + ->setResultSetClass(VolatileStateResults::class) + ->filter(Filter::all( + Filter::equal('service.name', $name), + Filter::equal('host.name', $hostName) + )); + + $this->applyRestrictions($query); + + /** @var Service $service */ + $service = $query->first(); + if ($service === null) { + throw new NotFoundError(t('Service not found')); + } + + $this->service = $service; + $this->loadTabsForObject($service); + + $this->setTitleTab($this->getRequest()->getActionName()); + $this->setTitle( + t('%s on %s', '<service> on <host>'), + $service->display_name, + $service->host->display_name + ); + } + + public function indexAction() + { + if ($this->service->state->is_overdue) { + $this->controls->addAttributes(['class' => 'overdue']); + } + + $this->addControl((new ServiceList([$this->service])) + ->setViewMode('objectHeader') + ->setDetailActionsDisabled() + ->setNoSubjectLink()); + $this->addControl(new ServiceMetaInfo($this->service)); + $this->addControl(new QuickActions($this->service)); + + $this->addContent(new ServiceDetail($this->service)); + + $this->setAutorefreshInterval(10); + } + + public function sourceAction() + { + $this->assertPermission('icingadb/object/show-source'); + $apiResult = (new CommandTransport())->send((new GetObjectCommand())->setObject($this->service)); + + if ($this->service->state->is_overdue) { + $this->controls->addAttributes(['class' => 'overdue']); + } + + $this->addControl((new ServiceList([$this->service])) + ->setViewMode('objectHeader') + ->setDetailActionsDisabled() + ->setNoSubjectLink()); + $this->addContent(new ServiceInspectionDetail( + $this->service, + reset($apiResult) + )); + } + + public function historyAction() + { + $compact = $this->view->compact; // TODO: Find a less-legacy way.. + + if ($this->service->state->is_overdue) { + $this->controls->addAttributes(['class' => 'overdue']); + } + + $db = $this->getDb(); + + $history = History::on($db)->with([ + 'host', + 'host.state', + 'service', + 'service.state', + 'comment', + 'downtime', + 'flapping', + 'notification', + 'acknowledgement', + 'state' + ]); + $history->filter(Filter::all( + Filter::equal('history.host_id', $this->service->host_id), + Filter::equal('history.service_id', $this->service->id) + )); + + $before = $this->params->shift('before', time()); + $url = Url::fromRequest()->setParams(clone $this->params); + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($history); + $sortControl = $this->createSortControl( + $history, + [ + 'history.event_time desc' => t('Event Time') + ] + ); + $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl, true); + + $history->peekAhead(); + + $page = $paginationControl->getCurrentPageNumber(); + + if ($page > 1 && ! $compact) { + $history->limit($page * $limitControl->getLimit()); + } + + $history->filter(Filter::lessThanOrEqual('event_time', $before)); + + yield $this->export($history); + + $this->addControl((new ServiceList([$this->service])) + ->setViewMode('objectHeader') + ->setDetailActionsDisabled() + ->setNoSubjectLink()); + $this->addControl($sortControl); + $this->addControl($limitControl); + $this->addControl($viewModeSwitcher); + + $historyList = (new HistoryList($history->execute())) + ->setViewMode($viewModeSwitcher->getViewMode()) + ->setPageSize($limitControl->getLimit()) + ->setLoadMoreUrl($url->setParam('before', $before)); + + if ($compact) { + $historyList->setPageNumber($page); + } + + if ($compact && $page > 1) { + $this->document->addFrom($historyList); + } else { + $this->addContent($historyList); + } + } + + protected function createTabs() + { + $tabs = $this->getTabs() + ->add('index', [ + 'label' => t('Service'), + 'url' => Links::service($this->service, $this->service->host) + ]) + ->add('history', [ + 'label' => t('History'), + 'url' => ServiceLinks::history($this->service, $this->service->host) + ]); + + if ($this->hasPermission('icingadb/object/show-source')) { + $tabs->add('source', [ + 'label' => t('Source'), + 'url' => Links::serviceSource($this->service, $this->service->host) + ]); + } + + foreach ($this->loadAdditionalTabs() as $name => $tab) { + $tabs->add($name, $tab + ['urlParams' => [ + 'name' => $this->service->name, + 'host.name' => $this->service->host->name + ]]); + } + + return $tabs; + } + + protected function setTitleTab(string $name) + { + $tab = $this->createTabs()->get($name); + + if ($tab !== null) { + $tab->setActive(); + + $this->setTitle($tab->getLabel()); + } + } + + protected function fetchCommandTargets(): array + { + return [$this->service]; + } + + protected function getCommandTargetsUrl(): Url + { + return Links::service($this->service, $this->service->host); + } + + protected function getDefaultTabControls(): array + { + return [(new ServiceList([$this->service]))->setDetailActionsDisabled()->setNoSubjectLink()]; + } +} diff --git a/application/controllers/ServicegroupController.php b/application/controllers/ServicegroupController.php new file mode 100644 index 0000000..89fb829 --- /dev/null +++ b/application/controllers/ServicegroupController.php @@ -0,0 +1,95 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use Icinga\Exception\NotFoundError; +use Icinga\Module\Icingadb\Model\Service; +use Icinga\Module\Icingadb\Model\ServicegroupSummary; +use Icinga\Module\Icingadb\Redis\VolatileStateResults; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Module\Icingadb\Widget\ItemList\ServiceList; +use Icinga\Module\Icingadb\Widget\ItemList\ServicegroupList; +use ipl\Html\Html; +use ipl\Stdlib\Filter; + +class ServicegroupController extends Controller +{ + /** @var ServicegroupSummary The service group object */ + protected $servicegroup; + + public function init() + { + $this->assertRouteAccess('servicegroups'); + + $this->addTitleTab(t('Service Group')); + + $name = $this->params->getRequired('name'); + + $query = ServicegroupSummary::on($this->getDb()); + + foreach ($query->getUnions() as $unionPart) { + $unionPart->filter(Filter::equal('servicegroup.name', $name)); + } + + $this->applyRestrictions($query); + + $servicegroup = $query->first(); + if ($servicegroup === null) { + throw new NotFoundError(t('Service group not found')); + } + + $this->servicegroup = $servicegroup; + $this->setTitle($servicegroup->display_name); + } + + public function indexAction() + { + $db = $this->getDb(); + + $services = Service::on($db)->with([ + 'state', + 'state.last_comment', + 'icon_image', + 'host', + 'host.state' + ]); + $services + ->setResultSetClass(VolatileStateResults::class) + ->filter(Filter::equal('servicegroup.id', $this->servicegroup->id)); + + $this->applyRestrictions($services); + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($services); + $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl); + + $serviceList = (new ServiceList($services->execute())) + ->setViewMode($viewModeSwitcher->getViewMode()); + + yield $this->export($services); + + // ICINGAWEB_EXPORT_FORMAT is not set yet and $this->format is inaccessible, yeah... + if ($this->getRequest()->getParam('format') === 'pdf') { + $this->addContent((new ServicegroupList([$this->servicegroup])) + ->setViewMode('minimal') + ->setDetailActionsDisabled() + ->setNoSubjectLink()); + $this->addContent(Html::tag('h2', null, t('Services'))); + } else { + $this->addControl((new ServicegroupList([$this->servicegroup])) + ->setViewMode('minimal') + ->setDetailActionsDisabled() + ->setNoSubjectLink()); + } + + $this->addControl($paginationControl); + $this->addControl($viewModeSwitcher); + $this->addControl($limitControl); + + $this->addContent($serviceList); + + $this->setAutorefreshInterval(10); + } +} diff --git a/application/controllers/ServicegroupsController.php b/application/controllers/ServicegroupsController.php new file mode 100644 index 0000000..76c48fb --- /dev/null +++ b/application/controllers/ServicegroupsController.php @@ -0,0 +1,120 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use GuzzleHttp\Psr7\ServerRequest; +use Icinga\Module\Icingadb\Model\Servicegroup; +use Icinga\Module\Icingadb\Model\ServicegroupSummary; +use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Module\Icingadb\Widget\ItemList\ServicegroupList; +use Icinga\Module\Icingadb\Widget\ShowMore; +use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher; +use ipl\Web\Control\LimitControl; +use ipl\Web\Control\SortControl; +use ipl\Web\Url; + +class ServicegroupsController extends Controller +{ + public function init() + { + parent::init(); + + $this->assertRouteAccess(); + } + + public function indexAction() + { + $this->addTitleTab(t('Service Groups')); + $compact = $this->view->compact; + + $db = $this->getDb(); + + $servicegroups = ServicegroupSummary::on($db); + + $this->handleSearchRequest($servicegroups); + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($servicegroups); + $sortControl = $this->createSortControl( + $servicegroups, + [ + 'display_name' => t('Name'), + 'services_severity desc' => t('Severity'), + 'services_total desc' => t('Total Services') + ] + ); + $searchBar = $this->createSearchBar($servicegroups, [ + $limitControl->getLimitParam(), + $sortControl->getSortParam() + ]); + + if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) { + if ($searchBar->hasBeenSubmitted()) { + $filter = $this->getFilter(); + } else { + $this->addControl($searchBar); + $this->sendMultipartUpdate(); + return; + } + } else { + $filter = $searchBar->getFilter(); + } + + $this->filter($servicegroups, $filter); + + $servicegroups->peekAhead($compact); + + yield $this->export($servicegroups); + + $this->addControl($paginationControl); + $this->addControl($sortControl); + $this->addControl($limitControl); + $this->addControl($searchBar); + + $results = $servicegroups->execute(); + + $this->addContent( + (new ServicegroupList($results))->setBaseFilter($filter) + ); + + if ($compact) { + $this->addContent( + (new ShowMore($results, Url::fromRequest()->without(['showCompact', 'limit', 'view']))) + ->setBaseTarget('_next') + ->setAttribute('title', sprintf( + t('Show all %d servicegroups'), + $servicegroups->count() + )) + ); + } + + if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) { + $this->sendMultipartUpdate(); + } + + $this->setAutorefreshInterval(30); + } + + public function completeAction() + { + $suggestions = new ObjectSuggestions(); + $suggestions->setModel(Servicegroup::class); + $suggestions->forRequest(ServerRequest::fromGlobals()); + $this->getDocument()->add($suggestions); + } + + public function searchEditorAction() + { + $editor = $this->createSearchEditor(ServicegroupSummary::on($this->getDb()), [ + LimitControl::DEFAULT_LIMIT_PARAM, + SortControl::DEFAULT_SORT_PARAM, + ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM + ]); + + $this->getDocument()->add($editor); + $this->setTitle(t('Adjust Filter')); + } +} diff --git a/application/controllers/ServicesController.php b/application/controllers/ServicesController.php new file mode 100644 index 0000000..9ee9474 --- /dev/null +++ b/application/controllers/ServicesController.php @@ -0,0 +1,438 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use GuzzleHttp\Psr7\ServerRequest; +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\ProblemToggle; +use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions; +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\ServiceList; +use Icinga\Module\Icingadb\Widget\ItemTable\ServiceItemTable; +use Icinga\Module\Icingadb\Widget\ServiceStatusBar; +use Icinga\Module\Icingadb\Widget\ShowMore; +use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher; +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\Filter\QueryString; +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' => t('Severity'), + 'service.state.soft_state' => t('Current State'), + 'service.state.last_state_change desc' => t('Last State Change'), + 'host.display_name' => t('Host') + ] + ); + $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); + $continueWith = $this->createContinueWith(Links::servicesDetails(), $searchBar); + + $results = $services->execute(); + + if ($viewModeSwitcher->getViewMode() === 'tabular') { + $serviceList = (new ServiceItemTable($results, ServiceItemTable::applyColumnMetaData($services, $columns))) + ->setSort($sortControl->getSort()); + } else { + $serviceList = (new ServiceList($results)) + ->setViewMode($viewModeSwitcher->getViewMode()); + } + + $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 { + $this->addFooter((new ServiceStatusBar($summary->first()))->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 ServiceList($results)) + ->setViewMode('minimal') + ); + $this->addControl(new ShowMore( + $results, + Links::services()->setQueryString(QueryString::render($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); + $suggestions->forRequest(ServerRequest::fromGlobals()); + $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() + { + $db = $this->getDb(); + $this->addTitleTab(t('Service Grid')); + + $query = Service::on($db)->with([ + 'state', + 'host', + 'host.state' + ]); + $query->setResultSetClass(VolatileStateResults::class); + + $this->handleSearchRequest($query); + + $this->params->shift('page'); // Handled by PivotTable internally + $this->params->shift('limit'); // Handled by PivotTable internally + $flipped = $this->params->shift('flipped', false); + + $problemToggle = $this->createProblemToggle(); + $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); + $continueWith = $this->createContinueWith(Links::servicesDetails(), $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; + + /** Preserve filter and params in view links (the `BaseFilter` implementation for view scripts -.-) */ + $this->view->baseUrl = $this->getRequest()->getUrl() + ->onlyWith([ + LimitControl::DEFAULT_LIMIT_PARAM, + $sortControl->getSortParam(), + 'flipped', + 'page', + 'problems' + ]); + $preservedParams = $this->view->baseUrl->getParams(); + $this->view->baseUrl->setQueryString(QueryString::render($filter)); + foreach ($preservedParams->toArray(false) as $name => $value) { + if (is_int($name)) { + $name = $value; + $value = true; + } + + $this->view->baseUrl->getParams()->addEncoded($name, $value); + } + + $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')); + } + + 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()->setQueryString(QueryString::render($this->getFilter())); + } + + protected function getFeatureStatus() + { + $summary = ServicestateSummary::on($this->getDb()); + $this->filter($summary); + + return new FeatureStatus('service', $summary->first()); + } + + protected function prepareSearchFilter(Query $query, string $search, Filter\Any $filter) + { + if ($this->params->shift('_hostFilterOnly', false)) { + $filter->add(Filter::like('host.name_ci', "*$search*")); + } else { + parent::prepareSearchFilter($query, $search, $filter); + } + } + + 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; + } +} diff --git a/application/controllers/TacticalController.php b/application/controllers/TacticalController.php new file mode 100644 index 0000000..582b046 --- /dev/null +++ b/application/controllers/TacticalController.php @@ -0,0 +1,96 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use GuzzleHttp\Psr7\ServerRequest; +use Icinga\Module\Icingadb\Model\HoststateSummary; +use Icinga\Module\Icingadb\Model\ServicestateSummary; +use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Module\Icingadb\Widget\HostSummaryDonut; +use Icinga\Module\Icingadb\Widget\ServiceSummaryDonut; +use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher; +use ipl\Orm\Query; +use ipl\Stdlib\Filter; +use ipl\Web\Control\LimitControl; +use ipl\Web\Control\SortControl; + +class TacticalController extends Controller +{ + public function indexAction() + { + $this->addTitleTab(t('Tactical Overview')); + + $db = $this->getDb(); + + $hoststateSummary = HoststateSummary::on($db); + $servicestateSummary = ServicestateSummary::on($db); + + $this->handleSearchRequest($servicestateSummary); + + $searchBar = $this->createSearchBar($servicestateSummary); + if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) { + if ($searchBar->hasBeenSubmitted()) { + $filter = $this->getFilter(); + } else { + $this->addControl($searchBar); + $this->sendMultipartUpdate(); + return; + } + } else { + $filter = $searchBar->getFilter(); + } + + $this->filter($hoststateSummary, $filter); + $this->filter($servicestateSummary, $filter); + + yield $this->export($hoststateSummary, $servicestateSummary); + + $this->addControl($searchBar); + + $this->addContent( + (new HostSummaryDonut($hoststateSummary->first())) + ->setBaseFilter($filter) + ); + + $this->addContent( + (new ServiceSummaryDonut($servicestateSummary->first())) + ->setBaseFilter($filter) + ); + + if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) { + $this->sendMultipartUpdate(); + } + + $this->setAutorefreshInterval(10); + } + + public function completeAction() + { + $suggestions = new ObjectSuggestions(); + $suggestions->setModel(ServicestateSummary::class); + $suggestions->forRequest(ServerRequest::fromGlobals()); + $this->getDocument()->add($suggestions); + } + + public function searchEditorAction() + { + $editor = $this->createSearchEditor(ServicestateSummary::on($this->getDb()), [ + LimitControl::DEFAULT_LIMIT_PARAM, + SortControl::DEFAULT_SORT_PARAM, + ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM + ]); + + $this->getDocument()->add($editor); + $this->setTitle(t('Adjust Filter')); + } + + protected function prepareSearchFilter(Query $query, string $search, Filter\Any $filter) + { + parent::prepareSearchFilter($query, $search, $filter); + + $filter->add(Filter::like('host.name_ci', "*$search*")); + } +} diff --git a/application/controllers/UserController.php b/application/controllers/UserController.php new file mode 100644 index 0000000..751bb38 --- /dev/null +++ b/application/controllers/UserController.php @@ -0,0 +1,48 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use Icinga\Exception\NotFoundError; +use Icinga\Module\Icingadb\Model\User; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Module\Icingadb\Widget\Detail\UserDetail; +use Icinga\Module\Icingadb\Widget\ItemList\UserList; +use ipl\Stdlib\Filter; + +class UserController extends Controller +{ + /** @var User The user object */ + protected $user; + + public function init() + { + $this->assertRouteAccess('users'); + + $this->addTitleTab(t('User')); + + $name = $this->params->getRequired('name'); + + $query = User::on($this->getDb()); + $query->filter(Filter::equal('user.name', $name)); + + $this->applyRestrictions($query); + + $user = $query->first(); + if ($user === null) { + throw new NotFoundError(t('User not found')); + } + + $this->user = $user; + $this->setTitle($user->display_name); + } + + public function indexAction() + { + $this->addControl((new UserList([$this->user]))->setNoSubjectLink()->setDetailActionsDisabled()); + $this->addContent(new UserDetail($this->user)); + + $this->setAutorefreshInterval(10); + } +} diff --git a/application/controllers/UsergroupController.php b/application/controllers/UsergroupController.php new file mode 100644 index 0000000..e1d4ae2 --- /dev/null +++ b/application/controllers/UsergroupController.php @@ -0,0 +1,48 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use Icinga\Exception\NotFoundError; +use Icinga\Module\Icingadb\Model\Usergroup; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Module\Icingadb\Widget\Detail\UsergroupDetail; +use Icinga\Module\Icingadb\Widget\ItemList\UsergroupList; +use ipl\Stdlib\Filter; + +class UsergroupController extends Controller +{ + /** @var Usergroup The usergroup object */ + protected $usergroup; + + public function init() + { + $this->assertRouteAccess('usergroups'); + + $this->addTitleTab(t('User Group')); + + $name = $this->params->getRequired('name'); + + $query = Usergroup::on($this->getDb()); + $query->filter(Filter::equal('usergroup.name', $name)); + + $this->applyRestrictions($query); + + $usergroup = $query->first(); + if ($usergroup === null) { + throw new NotFoundError(t('User group not found')); + } + + $this->usergroup = $usergroup; + $this->setTitle($usergroup->display_name); + } + + public function indexAction() + { + $this->addControl((new UsergroupList([$this->usergroup]))->setNoSubjectLink()->setDetailActionsDisabled()); + $this->addContent(new UsergroupDetail($this->usergroup)); + + $this->setAutorefreshInterval(10); + } +} diff --git a/application/controllers/UsergroupsController.php b/application/controllers/UsergroupsController.php new file mode 100644 index 0000000..3f0ea11 --- /dev/null +++ b/application/controllers/UsergroupsController.php @@ -0,0 +1,97 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use GuzzleHttp\Psr7\ServerRequest; +use Icinga\Module\Icingadb\Model\Usergroup; +use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Module\Icingadb\Widget\ItemList\UsergroupList; +use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher; +use ipl\Web\Control\LimitControl; +use ipl\Web\Control\SortControl; + +class UsergroupsController extends Controller +{ + public function init() + { + parent::init(); + + $this->assertRouteAccess(); + } + + public function indexAction() + { + $this->addTitleTab(t('User Groups')); + + $db = $this->getDb(); + + $usergroups = Usergroup::on($db); + + $this->handleSearchRequest($usergroups); + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($usergroups); + $sortControl = $this->createSortControl( + $usergroups, + [ + 'usergroup.display_name' => t('Name') + ] + ); + $searchBar = $this->createSearchBar($usergroups, [ + $limitControl->getLimitParam(), + $sortControl->getSortParam() + ]); + + if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) { + if ($searchBar->hasBeenSubmitted()) { + $filter = $this->getFilter(); + } else { + $this->addControl($searchBar); + $this->sendMultipartUpdate(); + return; + } + } else { + $filter = $searchBar->getFilter(); + } + + $this->filter($usergroups, $filter); + + yield $this->export($usergroups); + + $this->addControl($paginationControl); + $this->addControl($sortControl); + $this->addControl($limitControl); + $this->addControl($searchBar); + + $this->addContent(new UsergroupList($usergroups)); + + if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) { + $this->sendMultipartUpdate(); + } + + $this->setAutorefreshInterval(10); + } + + public function completeAction() + { + $suggestions = new ObjectSuggestions(); + $suggestions->setModel(Usergroup::class); + $suggestions->forRequest(ServerRequest::fromGlobals()); + $this->getDocument()->add($suggestions); + } + + public function searchEditorAction() + { + $editor = $this->createSearchEditor(Usergroup::on($this->getDb()), [ + LimitControl::DEFAULT_LIMIT_PARAM, + SortControl::DEFAULT_SORT_PARAM, + ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM + ]); + + $this->getDocument()->add($editor); + $this->setTitle(t('Adjust Filter')); + } +} diff --git a/application/controllers/UsersController.php b/application/controllers/UsersController.php new file mode 100644 index 0000000..ef75e89 --- /dev/null +++ b/application/controllers/UsersController.php @@ -0,0 +1,99 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Controllers; + +use GuzzleHttp\Psr7\ServerRequest; +use Icinga\Module\Icingadb\Model\User; +use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions; +use Icinga\Module\Icingadb\Web\Controller; +use Icinga\Module\Icingadb\Widget\ItemList\UserList; +use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher; +use ipl\Web\Control\LimitControl; +use ipl\Web\Control\SortControl; + +class UsersController extends Controller +{ + public function init() + { + parent::init(); + + $this->assertRouteAccess(); + } + + public function indexAction() + { + $this->addTitleTab(t('Users')); + + $db = $this->getDb(); + + $users = User::on($db); + + $this->handleSearchRequest($users); + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($users); + $sortControl = $this->createSortControl( + $users, + [ + 'user.display_name' => t('Name'), + 'user.email' => t('Email'), + 'user.pager' => t('Pager Address / Number') + ] + ); + $searchBar = $this->createSearchBar($users, [ + $limitControl->getLimitParam(), + $sortControl->getSortParam() + ]); + + if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) { + if ($searchBar->hasBeenSubmitted()) { + $filter = $this->getFilter(); + } else { + $this->addControl($searchBar); + $this->sendMultipartUpdate(); + return; + } + } else { + $filter = $searchBar->getFilter(); + } + + $this->filter($users, $filter); + + yield $this->export($users); + + $this->addControl($paginationControl); + $this->addControl($sortControl); + $this->addControl($limitControl); + $this->addControl($searchBar); + + $this->addContent(new UserList($users)); + + if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) { + $this->sendMultipartUpdate(); + } + + $this->setAutorefreshInterval(10); + } + + public function completeAction() + { + $suggestions = new ObjectSuggestions(); + $suggestions->setModel(User::class); + $suggestions->forRequest(ServerRequest::fromGlobals()); + $this->getDocument()->add($suggestions); + } + + public function searchEditorAction() + { + $editor = $this->createSearchEditor(User::on($this->getDb()), [ + LimitControl::DEFAULT_LIMIT_PARAM, + SortControl::DEFAULT_SORT_PARAM, + ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM + ]); + + $this->getDocument()->add($editor); + $this->setTitle(t('Adjust Filter')); + } +} |