From 84373cdf34e469abdcc3aa1337ba0f8d150daef4 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 25 Apr 2022 12:00:44 +0200 Subject: [PATCH] Support `_dashlet` parameter * It's bound to the `Window` object * If there's a request header, it's set * If there's a request param, it's set * It's preserved trough redirects not changing the path * If no redirect happens, a response header is set * JS accepts the response header always, maintains a data-attribute * JS transmits it in a request header, if it's not GET and not changing the path --- .../Web/Controller/ActionController.php | 5 +++ library/Icinga/Web/Response.php | 18 ++++++++++- library/Icinga/Web/Window.php | 32 ++++++++++++++++++- public/js/icinga/loader.js | 17 ++++++++++ public/js/icinga/ui.js | 9 +++++- 5 files changed, 78 insertions(+), 3 deletions(-) diff --git a/library/Icinga/Web/Controller/ActionController.php b/library/Icinga/Web/Controller/ActionController.php index 608159971..6852caaf8 100644 --- a/library/Icinga/Web/Controller/ActionController.php +++ b/library/Icinga/Web/Controller/ActionController.php @@ -157,6 +157,11 @@ class ActionController extends Zend_Controller_Action $this->_helper->layout()->disableLayout(); } + if (($dashletId = $this->params->shift('_dashlet')) !== null) { + // Not removed from the request's url because GET requests for the same url should still be tracked + $this->Window()->setDashletId(hex2bin($dashletId)); + } + // $auth->authenticate($request, $response, $this->requiresLogin()); if ($this->requiresLogin()) { if (! $request->isXmlHttpRequest() && $request->isApiRequest()) { diff --git a/library/Icinga/Web/Response.php b/library/Icinga/Web/Response.php index df0b842d5..e59697ed0 100644 --- a/library/Icinga/Web/Response.php +++ b/library/Icinga/Web/Response.php @@ -317,28 +317,44 @@ class Response extends Zend_Controller_Response_Http { $redirectUrl = $this->getRedirectUrl(); if ($this->getRequest()->isXmlHttpRequest()) { + $window = Window::getInstance(); + $dashletId = $window->getDashletId(); + if ($redirectUrl !== null) { if ($this->getRequest()->isGet() && Icinga::app()->getViewRenderer()->view->compact) { $redirectUrl->getParams()->set('showCompact', true); } + if ($dashletId !== null && $this->getRequest()->getUrl()->getPath() === $redirectUrl->getPath()) { + $redirectUrl->getParams()->set('_dashlet', bin2hex($dashletId)); + } + $this->setHeader('X-Icinga-Redirect', rawurlencode($redirectUrl->getAbsoluteUrl()), true); if ($this->getRerenderLayout()) { $this->setHeader('X-Icinga-Rerender-Layout', 'yes', true); } + } else { + if ($dashletId !== null) { + $this->setHeader('X-Icinga-DashletId', bin2hex($dashletId)); + } } + if ($this->getOverrideWindowId()) { - $this->setHeader('X-Icinga-WindowId', Window::getInstance()->getId(), true); + $this->setHeader('X-Icinga-WindowId', $window->getId(), true); } + if ($this->getRerenderLayout()) { $this->setHeader('X-Icinga-Container', 'layout', true); } + if ($this->isWindowReloaded()) { $this->setHeader('X-Icinga-Reload-Window', 'yes', true); } + if ($this->isReloadCss()) { $this->setHeader('X-Icinga-Reload-Css', 'now', true); } + if (($autoRefreshInterval = $this->getAutoRefreshInterval()) !== null) { $this->setHeader('X-Icinga-Refresh', $autoRefreshInterval, true); } diff --git a/library/Icinga/Web/Window.php b/library/Icinga/Web/Window.php index 158483a06..89555ad5c 100644 --- a/library/Icinga/Web/Window.php +++ b/library/Icinga/Web/Window.php @@ -19,6 +19,9 @@ class Window /** @var string */ protected $containerId; + /** @var ?string A dashlet's UUID which the current request is associated with */ + protected $dashletId; + public function __construct($id) { $parts = explode('_', $id, 2); @@ -60,6 +63,28 @@ class Window return $this->containerId ?: $this->id; } + /** + * Get the dashlet's UUID which the current request is associated with + * + * @return ?string + */ + public function getDashletId(): ?string + { + return $this->dashletId; + } + + /** + * Associate the current request with a specific dashlet + * + * @param ?string $dashletId The dashlet's UUID + * + * @return void + */ + public function setDashletId(?string $dashletId): void + { + $this->dashletId = $dashletId; + } + /** * Return a window-aware session by using the given prefix * @@ -111,13 +136,18 @@ class Window public static function getInstance() { if (! isset(static::$window)) { - $id = Icinga::app()->getRequest()->getHeader('X-Icinga-WindowId'); + $request = Icinga::app()->getRequest(); + $id = $request->getHeader('X-Icinga-WindowId'); if (empty($id) || $id === static::UNDEFINED) { Icinga::app()->getResponse()->setOverrideWindowId(); $id = static::generateId(); } static::$window = new Window($id); + + if (($dashletId = $request->getHeader('X-Icinga-DashletId'))) { + static::$window->setDashletId(hex2bin($dashletId)); + } } return static::$window; diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js index 8d69fe826..d8c8a9945 100644 --- a/public/js/icinga/loader.js +++ b/public/js/icinga/loader.js @@ -266,6 +266,15 @@ headers['X-Icinga-Container'] = id; } + if (method !== 'GET' && $target[0].dataset.icingaDashletId) { + let currentUrlPath = this.icinga.utils.parseUrl($target.data('icingaUrl')).path; + let newUrlPath = this.icinga.utils.parseUrl(url).path; + + if (newUrlPath === currentUrlPath) { + headers['X-Icinga-DashletId'] = $target[0].dataset.icingaDashletId; + } + } + if (autorefresh) { headers['X-Icinga-Autorefresh'] = '1'; } @@ -776,6 +785,14 @@ this.icinga.ui.setWindowId(windowId); } + // Preserve the dashlet identifier if available, clean up if not + let dashletId = req.getResponseHeader('X-Icinga-DashletId'); + if (dashletId) { + req.$target[0].dataset.icingaDashletId = decodeURIComponent(dashletId); + } else { + delete req.$target[0].dataset.icingaDashletId; + } + // Handle search requests, still hardcoded. if (req.url.match(/^\/search/) && req.$target.data('icingaUrl').match(/^\/search/)) { var $resp = $('
' + req.responseText + '
'); // div helps getting an XML tree diff --git a/public/js/icinga/ui.js b/public/js/icinga/ui.js index 584f5ed10..cad552dac 100644 --- a/public/js/icinga/ui.js +++ b/public/js/icinga/ui.js @@ -231,7 +231,8 @@ 'data-icinga-refresh': $col.data('icingaRefresh'), 'data-last-update': $col.data('lastUpdate'), 'data-icinga-module': $col.data('icingaModule'), - 'data-icinga-container-id': $col[0].dataset.icingaContainerId + 'data-icinga-container-id': $col[0].dataset.icingaContainerId, + 'data-icinga-dashlet-id': $col[0].dataset.icingaDashletId }, 'class': $col.attr('class') }; @@ -242,6 +243,7 @@ $col.removeData('lastUpdate'); $col.removeData('icingaModule'); delete $col[0].dataset.icingaContainerId; + delete $col[0].dataset.icingaDashletId; $col.removeAttr('class').attr('class', 'container'); return props; }, @@ -255,6 +257,10 @@ $col.data('lastUpdate', backup['data']['data-last-update']); $col.data('icingaModule', backup['data']['data-icinga-module']); $col[0].dataset.icingaContainerId = backup['data']['data-icinga-container-id']; + + if (backup['data']['data-icinga-dashlet-id']) { + $col[0].dataset.icingaDashletId = backup['data']['data-icinga-dashlet-id']; + } }, triggerWindowResize: function () { @@ -342,6 +348,7 @@ $c.removeData('lastUpdate'); $c.removeData('icingaModule'); delete $c[0].dataset.icingaContainerId; + delete $c[0].dataset.icingaDashletId; $c.removeAttr('class').attr('class', 'container'); this.icinga.loader.stopPendingRequestsFor($c); $c.trigger('close-column');