summaryrefslogtreecommitdiffstats
path: root/library/Cube/Web
diff options
context:
space:
mode:
Diffstat (limited to 'library/Cube/Web')
-rw-r--r--library/Cube/Web/ActionLink.php103
-rw-r--r--library/Cube/Web/ActionLinks.php115
-rw-r--r--library/Cube/Web/Controller.php297
-rw-r--r--library/Cube/Web/IdoController.php198
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)
+ ]);
+ }
+}