diff options
Diffstat (limited to 'library/Cube/Web')
-rw-r--r-- | library/Cube/Web/ActionLink.php | 103 | ||||
-rw-r--r-- | library/Cube/Web/ActionLinks.php | 115 | ||||
-rw-r--r-- | library/Cube/Web/Controller.php | 297 | ||||
-rw-r--r-- | library/Cube/Web/IdoController.php | 198 |
4 files changed, 713 insertions, 0 deletions
diff --git a/library/Cube/Web/ActionLink.php b/library/Cube/Web/ActionLink.php new file mode 100644 index 0000000..c9ad87b --- /dev/null +++ b/library/Cube/Web/ActionLink.php @@ -0,0 +1,103 @@ +<?php + +// Icinga Web 2 Cube Module | (c) 2016 Icinga GmbH | GPLv2 + +namespace Icinga\Module\Cube\Web; + +use Icinga\Web\Url; +use Icinga\Web\View; + +/** + * ActionLink + * + * ActionLinksHook implementations return instances of this class + * + * @package Icinga\Module\Cube\Web + */ +class ActionLink +{ + /** @var Url */ + protected $url; + + /** @var string */ + protected $title; + + /** @var string */ + protected $description; + + /** @var string */ + protected $icon; + + /** + * ActionLink constructor. + * @param Url $url + * @param string $title + * @param string $description + * @param string $icon + */ + public function __construct(Url $url, $title, $description, $icon) + { + $this->url = $url; + $this->title = $title; + $this->description = $description; + $this->icon = $icon; + } + + /** + * @return Url + */ + public function getUrl() + { + return $this->url; + } + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * @return string + */ + public function getIcon() + { + return $this->icon; + } + + /** + * Render our icon + * + * @param View $view + * @return string + */ + protected function renderIcon(View $view) + { + return $view->icon($this->getIcon()); + } + + /** + * @param View $view + * @return string + */ + public function render(View $view) + { + return sprintf( + '<a href="%s">%s<span class="title">%s</span><p>%s</p></a>', + $this->getUrl(), + $this->renderIcon($view), + $view->escape($this->getTitle()), + $view->escape($this->getDescription()) + ); + } +} diff --git a/library/Cube/Web/ActionLinks.php b/library/Cube/Web/ActionLinks.php new file mode 100644 index 0000000..4b84fac --- /dev/null +++ b/library/Cube/Web/ActionLinks.php @@ -0,0 +1,115 @@ +<?php + +// Icinga Web 2 Cube Module | (c) 2016 Icinga GmbH | GPLv2 + +namespace Icinga\Module\Cube\Web; + +use Exception; +use Icinga\Application\Hook; +use Icinga\Module\Cube\Cube; +use Icinga\Module\Cube\Hook\ActionsHook; +use Icinga\Web\View; + +/** + * ActionLink + * + * ActionsHook implementations return instances of this class + * + * @package Icinga\Module\Cube\Web + */ +class ActionLinks +{ + /** @var ActionLink[] */ + protected $links = array(); + + /** + * Get all links for all Hook implementations + * + * This is what the Cube calls when rendering details + * + * @param Cube $cube + * @param View $view + * + * @return string + */ + public static function renderAll(Cube $cube, View $view) + { + $html = array(); + + /** @var ActionsHook $hook */ + foreach (Hook::all('Cube/Actions') as $hook) { + try { + $hook->prepareActionLinks($cube, $view); + } catch (Exception $e) { + $html[] = self::renderErrorItem($e, $view); + } + + foreach ($hook->getActionLinks()->getLinks() as $link) { + $html[] = '<li>' . $link->render($view) . '</li>'; + } + } + + if (empty($html)) { + $html[] = self::renderErrorItem( + $view->translate('No action links have been provided for this cube'), + $view + ); + } + + return implode("\n", $html) . "\n"; + } + + /** + * @param Exception|string $error + * @param View $view + * @return string + */ + private static function renderErrorItem($error, View $view) + { + if ($error instanceof Exception) { + $error = $error->getMessage(); + } + return '<li class="error">' . $view->escape($error) . '</li>'; + } + + /** + * Add an ActionLink to this set of actions + * + * @param ActionLink $link + * @return $this + */ + public function add(ActionLink $link) + { + $this->links[] = $link; + return $this; + } + + /** + * @return ActionLink[] + */ + public function getLinks() + { + return $this->links; + } + + /** + * @param View $view + * + * @return string + */ + public function render(View $view) + { + $links = $this->getLinks(); + if (empty($links)) { + return ''; + } + + $html = '<ul class="action-links">'; + foreach ($links as $link) { + $html .= $link->render($view); + } + $html .= '</ul>'; + + return $html; + } +} diff --git a/library/Cube/Web/Controller.php b/library/Cube/Web/Controller.php new file mode 100644 index 0000000..028a744 --- /dev/null +++ b/library/Cube/Web/Controller.php @@ -0,0 +1,297 @@ +<?php + +// Icinga Web 2 Cube Module | (c) 2016 Icinga GmbH | GPLv2 + +namespace Icinga\Module\Cube\Web; + +use Icinga\Module\Cube\DimensionParams; +use Icinga\Module\Cube\Forms\DimensionsForm; +use Icinga\Module\Cube\Hook\IcingaDbActionsHook; +use Icinga\Module\Cube\IcingaDb\CustomVariableDimension; +use Icinga\Module\Cube\IcingaDb\IcingaDbCube; +use Icinga\Module\Icingadb\Common\Auth; +use Icinga\Module\Icingadb\Common\Database; +use Icinga\Module\Icingadb\Web\Control\ProblemToggle; +use ipl\Html\FormElement\CheckboxElement; +use ipl\Html\HtmlString; +use ipl\Stdlib\Filter; +use ipl\Stdlib\Str; +use ipl\Web\Compat\CompatController; +use ipl\Web\Compat\SearchControls; +use ipl\Web\Control\SortControl; +use ipl\Web\Filter\QueryString; +use ipl\Web\Url; +use ipl\Web\Widget\Tabs; + +abstract class Controller extends CompatController +{ + use SearchControls; + use Database; + use Auth; + + /** @var string[] Preserved params for searchbar and search editor controls */ + protected $preserveParams = [ + 'dimensions', + 'showSettings', + 'wantNull', + 'problems', + 'sort' + ]; + + /** @var Filter\Rule Filter from query string parameters */ + private $filter; + + /** + * Return this controllers' cube + * + * @return IcingaDbCube + */ + abstract protected function getCube(): IcingaDbCube; + + /** + * Get the filter created from query string parameters + * + * @return Filter\Rule + */ + public function getFilter(): Filter\Rule + { + if ($this->filter === null) { + $this->filter = QueryString::parse((string) $this->params); + } + + return $this->filter; + } + + public function detailsAction(): void + { + $cube = $this->prepareCube(); + $this->getTabs()->add('details', [ + 'label' => $this->translate('Cube details'), + 'url' => $this->getRequest()->getUrl() + ])->activate('details'); + + $cube->setBaseFilter($this->getFilter()); + + $this->setTitle($cube->getSlicesLabel()); + $this->view->links = IcingaDbActionsHook::renderAll($cube); + + $this->addContent( + HtmlString::create($this->view->render('/cube-details.phtml')) + ); + } + + protected function renderCube(): void + { + $cube = $this->prepareCube(); + $this->setTitle(sprintf( + $this->translate('Cube: %s'), + $cube->getPathLabel() + )); + + $this->params->shift('format'); + $showSettings = $this->params->shift('showSettings'); + + $query = $cube->innerQuery(); + $problemsOnly = (bool) $this->params->shift('problems', false); + $problemToggle = (new ProblemToggle($problemsOnly ?: null)) + ->setIdProtector([$this->getRequest(), 'protectId']) + ->on(ProblemToggle::ON_SUCCESS, function (ProblemToggle $form) { + /** @var CheckboxElement $problems */ + $problems = $form->getElement('problems'); + if (! $problems->isChecked()) { + $this->redirectNow(Url::fromRequest()->remove('problems')); + } else { + $this->redirectNow(Url::fromRequest()->setParam('problems')); + } + })->handleRequest($this->getServerRequest()); + + $this->addControl($problemToggle); + + $sortControl = SortControl::create([ + IcingaDbCube::DIMENSION_VALUE_SORT_PARAM => t('Value'), + IcingaDbCube::DIMENSION_SEVERITY_SORT_PARAM . ' desc' => t('Severity'), + ]); + + $this->params->shift($sortControl->getSortParam()); + $cube->sortBy($sortControl->getSort()); + $this->addControl($sortControl); + + $searchBar = $this->createSearchBar( + $query, + $this->preserveParams + ); + + if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) { + if ($searchBar->hasBeenSubmitted()) { + $filter = $this->getFilter(); + } else { + $this->addControl($searchBar); + $this->sendMultipartUpdate(); + return; + } + } else { + $filter = $searchBar->getFilter(); + } + + if ($problemsOnly) { + $filter = Filter::all($filter, Filter::equal('state.is_problem', true)); + } + + $cube->setBaseFilter($filter); + $cube->problemsOnly($problemsOnly); + + $this->addControl($searchBar); + + if (count($cube->listDimensions()) > 0) { + $this->view->cube = $cube; + } else { + $showSettings = true; + } + + $this->view->url = Url::fromRequest() + ->onlyWith($this->preserveParams) + ->setFilter($searchBar->getFilter()); + + if ($showSettings) { + $form = (new DimensionsForm()) + ->setUrl($this->view->url) + ->setCube($cube) + ->on(DimensionsForm::ON_SUCCESS, function ($form) { + $this->redirectNow($form->getRedirectUrl()); + }) + ->handleRequest($this->getServerRequest()); + + $this->view->form = $form; + } else { + $this->setAutorefreshInterval(15); + } + + $this->addContent( + HtmlString::create($this->view->render('/cube-index.phtml')) + ); + + if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) { + $this->sendMultipartUpdate(); + } + } + + private function prepareCube(): IcingaDbCube + { + $cube = $this->getCube(); + $cube->chooseFacts(array_keys($cube->getAvailableFactColumns())); + + $dimensions = DimensionParams::fromString( + $this->params->shift('dimensions', '') + )->getDimensions(); + + if ($this->hasLegacyDimensionParams($dimensions)) { + $this->transformLegacyDimensionParamsAndRedirect($dimensions); + } + + $wantNull = $this->params->shift('wantNull'); + foreach ($dimensions as $dimension) { + $cube->addDimensionByName($dimension); + if ($wantNull) { + $cube->getDimension($dimension)->wantNull(); + } + + $sliceParamWithPrefix = rawurlencode($cube::SLICE_PREFIX . $dimension); + + if ($this->params->has($sliceParamWithPrefix)) { + $this->preserveParams[] = $sliceParamWithPrefix; + $cube->slice($dimension, $this->params->shift($sliceParamWithPrefix)); + } + } + + return $cube; + } + + /** + * Get whether the given dimension param is legacy dimension param + * + * @param string $dimensionParam + * + * @return bool + */ + private function isLegacyDimensionParam(string $dimensionParam): bool + { + return ! Str::startsWith($dimensionParam, CustomVariableDimension::HOST_PREFIX) + && ! Str::startsWith($dimensionParam, CustomVariableDimension::SERVICE_PREFIX); + } + + /** + * Get whether the dimensions contain legacy dimension + * + * @param array $dimensions + * + * @return bool + */ + private function hasLegacyDimensionParams(array $dimensions): bool + { + foreach ($dimensions as $dimension) { + if ($this->isLegacyDimensionParam($dimension)) { + return true; + } + } + + return false; + } + + /** + * Transform legacy dimension and slice params and redirect + * + * This adds the new prefix to params and then redirects so that the new URL contains the prefixed params + * Slices are prefixed to differ filter and slice params + * + * @param array $legacyDimensions + */ + private function transformLegacyDimensionParamsAndRedirect(array $legacyDimensions): void + { + $dimensions = []; + $slices = []; + + $dimensionPrefix = CustomVariableDimension::HOST_PREFIX; + if ($this->getRequest()->getControllerName() === 'services') { + $dimensionPrefix = CustomVariableDimension::SERVICE_PREFIX; + } + + foreach ($legacyDimensions as $param) { + $newParam = $param; + if ($this->isLegacyDimensionParam($param)) { + $newParam = $dimensionPrefix . $param; + } + + $slice = $this->params->shift($param); + if ($slice) { + $slices[IcingaDbCube::SLICE_PREFIX . $newParam] = $slice; + } + + $dimensions[] = $newParam; + } + + $this->redirectNow( + Url::fromRequest() + ->setParam('dimensions', DimensionParams::fromArray($dimensions)->getParams()) + ->addParams($slices) + ->without($legacyDimensions) + ); + } + + public function createTabs(): Tabs + { + $params = Url::fromRequest() + ->onlyWith($this->preserveParams) + ->getParams() + ->toString(); + + return $this->getTabs() + ->add('cube/hosts', [ + 'label' => $this->translate('Hosts'), + 'url' => 'cube/hosts' . ($params === '' ? '' : '?' . $params) + ]) + ->add('cube/services', [ + 'label' => $this->translate('Services'), + 'url' => 'cube/services' . ($params === '' ? '' : '?' . $params) + ]); + } +} diff --git a/library/Cube/Web/IdoController.php b/library/Cube/Web/IdoController.php new file mode 100644 index 0000000..a9feec9 --- /dev/null +++ b/library/Cube/Web/IdoController.php @@ -0,0 +1,198 @@ +<?php + +// Icinga Web 2 Cube Module | (c) 2019 Icinga GmbH | GPLv2 + +namespace Icinga\Module\Cube\Web; + +use Icinga\Application\Modules\Module; +use Icinga\Module\Cube\DimensionParams; +use Icinga\Module\Cube\Forms\DimensionsForm; +use Icinga\Module\Cube\IcingaDb\CustomVariableDimension; +use Icinga\Module\Cube\IcingaDb\IcingaDbCube; +use Icinga\Module\Cube\Ido\IdoCube; +use ipl\Stdlib\Str; +use ipl\Web\Compat\CompatController; +use ipl\Web\Url; +use ipl\Web\Widget\Tabs; + +abstract class IdoController extends CompatController +{ + /** + * Return this controllers' cube + * + * @return IdoCube + */ + abstract protected function getCube(): IdoCube; + + public function detailsAction(): void + { + $cube = $this->prepareCube(); + + $this->getTabs()->add('details', [ + 'label' => $this->translate('Cube details'), + 'url' => $this->getRequest()->getUrl() + ])->activate('details'); + + $this->view->title = $cube->getSlicesLabel(); + + $this->view->links = ActionLinks::renderAll($cube, $this->view); + + $this->render('cube-details', null, true); + } + + protected function renderCube(): void + { + $this->params->shift('format'); + $showSettings = $this->params->shift('showSettings'); + + $cube = $this->prepareCube(); + + $this->view->title = sprintf( + $this->translate('Cube: %s'), + $cube->getPathLabel() + ); + + if (count($cube->listDimensions()) > 0) { + $this->view->cube = $cube; + } else { + $showSettings = true; + } + + $this->view->url = Url::fromRequest(); + if ($showSettings) { + $form = (new DimensionsForm()) + ->setUrl($this->view->url) + ->setCube($cube) + ->setUrl(Url::fromRequest()) + ->on(DimensionsForm::ON_SUCCESS, function ($form) { + $this->redirectNow($form->getRedirectUrl()); + }) + ->handleRequest($this->getServerRequest()); + + $this->view->form = $form; + } else { + $this->setAutorefreshInterval(15); + } + + $this->render('cube-index', null, true); + } + + private function prepareCube(): IdoCube + { + $cube = $this->getCube(); + $cube->chooseFacts(array_keys($cube->getAvailableFactColumns())); + + $vars = DimensionParams::fromString($this->params->shift('dimensions', ''))->getDimensions(); + + $resolved = $this->params->shift('resolved', false); + + if ( + ! $resolved + && Module::exists('icingadb') + && $this->hasIcingadbDimensionParams($vars) + ) { + $this->transformIcingadbDimensionParamsAndRedirect($vars); + } elseif ($resolved) { + $this->redirectNow(Url::fromRequest()->without('resolved')); + } + + $wantNull = $this->params->shift('wantNull'); + + foreach ($vars as $var) { + $cube->addDimensionByName($var); + if ($wantNull) { + $cube->getDimension($var)->wantNull(); + } + } + + foreach ($this->params->toArray() as $param) { + $cube->slice(rawurldecode($param[0]), rawurldecode($param[1])); + } + + return $cube; + } + + /** + * Get whether the dimensions contain icingadb dimension + * + * @param array $dimensions + * + * @return bool + */ + private function hasIcingadbDimensionParams(array $dimensions): bool + { + foreach ($dimensions as $dimension) { + if ( + Str::startsWith($dimension, CustomVariableDimension::HOST_PREFIX) + || Str::startsWith($dimension, CustomVariableDimension::SERVICE_PREFIX) + ) { + return true; + } + } + + return false; + } + + /** + * Transform icingadb dimension and slice params and redirect + * + * This remove the new icingadb prefix from params and remove sort, problems-only, filter params + * + * @param array $icingadbDimensions + */ + private function transformIcingadbDimensionParamsAndRedirect(array $icingadbDimensions): void + { + $dimensions = []; + $slices = []; + $toRemoveSlices = []; + + $prefix = CustomVariableDimension::HOST_PREFIX; + if ($this->getRequest()->getControllerName() === 'ido-services') { + $prefix = CustomVariableDimension::SERVICE_PREFIX; + } + + foreach ($icingadbDimensions as $param) { + $newParam = $param; + if (strpos($param, $prefix) !== false) { + $newParam = substr($param, strlen($prefix)); + } + + $slice = $this->params->shift(IcingaDbCube::SLICE_PREFIX . $param); + if ($slice) { + $slices[$newParam] = $slice; + $toRemoveSlices[] = IcingaDbCube::SLICE_PREFIX . $param; + } + + $dimensions[] = $newParam; + } + + $icingadbParams = array_merge( + $icingadbDimensions, + $toRemoveSlices, + array_keys($this->params->toArray(false)) + ); + + $this->redirectNow( + Url::fromRequest() + ->setParam('dimensions', DimensionParams::fromArray($dimensions)->getParams()) + ->addParams($slices) + ->addParams(['resolved' => true]) + ->without($icingadbParams) + ); + } + + public function createTabs(): Tabs + { + $params = Url::fromRequest()->getParams()->toString(); + + return $this->getTabs() + ->add('cube/hosts', [ + 'label' => $this->translate('Hosts'), + 'url' => 'cube/hosts' . ($params === '' ? '' : '?' . $params) + ]) + ->add('cube/services', [ + 'label' => $this->translate('Services'), + 'url' => 'cube/services' . ($params === '' ? '' : '?' . $params) + ]); + } +} |