diff --git a/application/controllers/MigrateController.php b/application/controllers/MigrateController.php
index 899f1f8f..014c3328 100644
--- a/application/controllers/MigrateController.php
+++ b/application/controllers/MigrateController.php
@@ -7,12 +7,15 @@ namespace Icinga\Module\Icingadb\Controllers;
use Exception;
use GuzzleHttp\Psr7\ServerRequest;
use Icinga\Application\Hook;
+use Icinga\Application\Icinga;
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\Stdlib\Filter;
+use ipl\Web\Filter\QueryString;
use ipl\Web\Url;
class MigrateController extends Controller
@@ -65,6 +68,55 @@ class MigrateController extends Controller
$response->sendResponse();
}
+ public function searchUrlAction()
+ {
+ $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');
+ }
+
+ $traverseFilter = function ($filter) use (&$traverseFilter) {
+ if ($filter instanceof Filter\Chain) {
+ foreach ($filter as $child) {
+ $newChild = $traverseFilter($child);
+ if ($newChild !== null) {
+ $filter->replace($child, $newChild);
+ }
+ }
+ } elseif ($filter instanceof Filter\Equal) {
+ if (strpos($filter->getValue(), '*') !== false) {
+ return Filter::like($filter->getColumn(), $filter->getValue());
+ }
+ } elseif ($filter instanceof Filter\Unequal) {
+ if (strpos($filter->getValue(), '*') !== false) {
+ return Filter::unlike($filter->getColumn(), $filter->getValue());
+ }
+ }
+ };
+
+ $urls = $this->getRequest()->getPost();
+
+ $result = [];
+ foreach ($urls as $urlString) {
+ $url = Url::fromPath($urlString);
+ $filter = QueryString::parse($url->getQueryString());
+ $filter = $traverseFilter($filter) ?? $filter;
+ $result[] = $url->setQueryString(QueryString::render($filter))->getAbsoluteUrl();
+ }
+
+ $response = $this->getResponse()->json();
+ $response->setSuccessData($result);
+
+ $response->sendResponse();
+ }
+
public function checkboxStateAction()
{
$this->assertHttpMethod('get');
@@ -98,7 +150,10 @@ class MigrateController extends Controller
}
$moduleSupportStates = [];
- if ($this->Auth()->hasPermission('module/monitoring')) {
+ if (
+ Icinga::app()->getModuleManager()->hasEnabled('monitoring')
+ && $this->Auth()->hasPermission('module/monitoring')
+ ) {
$supportList = [];
foreach (Hook::all('Icingadb/IcingadbSupport') as $hook) {
/** @var IcingadbSupportHook $hook */
diff --git a/configuration.php b/configuration.php
index 416d37f7..2c83acc1 100644
--- a/configuration.php
+++ b/configuration.php
@@ -565,9 +565,5 @@ namespace Icinga\Module\Icingadb {
$this->provideJsFile('action-list.js');
$this->provideJsFile('loadmore.js');
-
- $mg = Icinga::app()->getModuleManager();
- if ($mg->hasEnabled('monitoring')) {
- $this->provideJsFile('migrate.js');
- }
+ $this->provideJsFile('migrate.js');
}
diff --git a/public/css/widget/migrate-popup.less b/public/css/widget/migrate-popup.less
index f7adb22f..8f9586b9 100644
--- a/public/css/widget/migrate-popup.less
+++ b/public/css/widget/migrate-popup.less
@@ -76,6 +76,9 @@
}
.suggestion-area {
+ display: flex;
+ flex-direction: column-reverse;
+
padding: .75em;
flex-grow: 1;
pointer-events: auto;
@@ -90,12 +93,19 @@
}
p {
+ display: none;
margin-bottom: .5em;
color: @text-color-light;
}
+ form ~ .monitoring-migration-hint,
+ .search-migration-suggestions:not(:empty) + .search-migration-hint,
+ .monitoring-migration-suggestions:not(:empty) + .monitoring-migration-hint {
+ display: block;
+ }
+
& > button.close {
- float: right;
+ margin-left: auto;
margin-top: 1em;
&:hover {
@@ -105,7 +115,7 @@
ul {
padding: 0;
- margin: .5em 0 0;
+ margin: 0;
list-style-type: none;
}
@@ -116,6 +126,10 @@
&:last-of-type {
margin-bottom: 0;
}
+
+ &:first-of-type {
+ margin-top: 0;
+ }
}
li {
@@ -143,7 +157,6 @@
}
form {
- margin-top: 0.5em;
width: 100%;
.control-group {
@@ -159,5 +172,10 @@
}
}
}
+
+ .search-migration-suggestions:not(:empty) ~ form,
+ .search-migration-suggestions:not(:empty) ~ .monitoring-migration-suggestions:not(:empty) {
+ margin-bottom: .5em;
+ }
}
}
diff --git a/public/js/migrate.js b/public/js/migrate.js
index 9984d3a3..5a00dbee 100644
--- a/public/js/migrate.js
+++ b/public/js/migrate.js
@@ -11,9 +11,11 @@
const POPUP_HTML = '
\n' +
' \n' +
@@ -37,6 +39,7 @@
this.knownBackendSupport = {};
this.urlMigrationReadyState = null;
this.backendSupportReadyState = null;
+ this.searchMigrationReadyState = null;
this.backendSupportRelated = {};
this.$popup = null;
@@ -48,6 +51,7 @@
// We don't want to ask the server to migrate non-monitoring urls
this.isMonitoringUrl = new RegExp('^' + icinga.config.baseUrl + '/monitoring/');
+
this.on('rendered', this.onRendered, this);
this.on('close-column', this.onColumnClose, this);
this.on('click', '#migrate-popup button.close', this.onClose, this);
@@ -107,7 +111,8 @@
};
Migrate.prototype.prepareMigration = function($target) {
- let urls = {};
+ let monitoringUrls = {};
+ let searchUrls = {};
let modules = {}
$target.each((_, container) => {
@@ -115,14 +120,18 @@
let href = $container.data('icingaUrl');
let containerId = $container.attr('id');
- if (typeof href !== 'undefined' && href.match(this.isMonitoringUrl)) {
+ if (!! href) {
if (
typeof this.previousMigrations[containerId] !== 'undefined'
&& this.previousMigrations[containerId] === href
) {
delete this.previousMigrations[containerId];
} else {
- urls[containerId] = href;
+ if (href.match(this.isMonitoringUrl)) {
+ monitoringUrls[containerId] = href;
+ } else if ($container.find('[data-enrichment-type="search-bar"]').length) {
+ searchUrls[containerId] = href;
+ }
}
}
@@ -132,13 +141,20 @@
}
});
- if (Object.keys(urls).length) {
+ if (Object.keys(monitoringUrls).length) {
this.setUrlMigrationReadyState(false);
- this.migrateMonitoringUrls(urls);
+ this.migrateUrls(monitoringUrls, 'monitoring');
} else {
this.setUrlMigrationReadyState(null);
}
+ if (Object.keys(searchUrls).length) {
+ this.setSearchMigrationReadyState(false);
+ this.migrateUrls(searchUrls, 'search');
+ } else {
+ this.setSearchMigrationReadyState(null);
+ }
+
if (Object.keys(modules).length) {
this.setBackendSupportReadyState(false);
this.prepareBackendCheckboxForm(modules);
@@ -146,7 +162,11 @@
this.setBackendSupportReadyState(null);
}
- if (this.urlMigrationReadyState === null && this.backendSupportReadyState === null) {
+ if (
+ this.urlMigrationReadyState === null
+ && this.backendSupportReadyState === null
+ && this.searchMigrationReadyState === null
+ ) {
this.cleanupPopup();
}
};
@@ -221,7 +241,7 @@
_this.knownMigrations[containerUrl] = false;
}
- if (_this.Popup().find('li').length === 1) {
+ if (_this.Popup().find('li').length === 1 && ! _this.Popup().find('#setAsBackendForm').length) {
_this.hidePopup(function () {
// Let the transition finish first, looks cleaner
$suggestion.remove();
@@ -246,7 +266,7 @@
}
};
- Migrate.prototype.migrateMonitoringUrls = function(urls) {
+ Migrate.prototype.migrateUrls = function(urls, type) {
var _this = this,
containerIds = [],
containerUrls = [];
@@ -258,24 +278,34 @@
}
});
+ let endpoint, changeCallback;
+ if (type === 'monitoring') {
+ endpoint = 'monitoring-url';
+ changeCallback = this.changeUrlMigrationReadyState.bind(this);
+ } else {
+ endpoint = 'search-url';
+ changeCallback = this.changeSearchMigrationReadyState.bind(this);
+ }
+
if (containerUrls.length) {
var req = $.ajax({
context : this,
type : 'post',
- url : this.icinga.config.baseUrl + '/icingadb/migrate/monitoring-url',
+ url : this.icinga.config.baseUrl + '/icingadb/migrate/' + endpoint,
headers : { 'Accept': 'application/json' },
contentType : 'application/json',
data : JSON.stringify(containerUrls)
});
req.urls = urls;
+ req.suggestionType = type;
req.urlIndexToContainerId = containerIds;
req.done(this.processUrlMigrationResults);
- req.always(() => this.changeUrlMigrationReadyState(true));
+ req.always(() => changeCallback(true));
} else {
// All urls have already been migrated once, show popup immediately
- this.addSuggestions(urls);
- this.changeUrlMigrationReadyState(true);
+ this.addSuggestions(urls, type);
+ changeCallback(true);
}
};
@@ -298,7 +328,7 @@
_this.knownMigrations[req.urls[containerId]] = migratedUrl;
});
- this.addSuggestions(req.urls);
+ this.addSuggestions(req.urls, req.suggestionType);
};
Migrate.prototype.prepareBackendCheckboxForm = function(modules) {
@@ -373,7 +403,7 @@
$form.attr('data-base-target', 'migrate-popup-backend-submit-blackhole');
$form.append('');
- this.Popup().find('.suggestion-area > ul').after($form);
+ this.Popup().find('.monitoring-migration-suggestions').before($form);
} else {
let $newForm = $(html);
$form.find('[name=backend]').prop('checked', $newForm.find('[name=backend]').is(':checked'));
@@ -382,10 +412,17 @@
this.showPopup();
}
- Migrate.prototype.addSuggestions = function(urls) {
+ Migrate.prototype.addSuggestions = function(urls, type) {
+ var where;
+ if (type === 'monitoring') {
+ where = '.monitoring-migration-suggestions';
+ } else {
+ where = '.search-migration-suggestions';
+ }
+
var _this = this,
hasSuggestions = false,
- $ul = this.Popup().find('.suggestion-area > ul');
+ $ul = this.Popup().find('.suggestion-area > ul' + where);
$.each(urls, function (containerId, containerUrl) {
// No urls for which the user clicked "No" or an error occurred and only migrated urls please
if (_this.knownMigrations[containerUrl] !== false && _this.knownMigrations[containerUrl] !== containerUrl) {
@@ -425,6 +462,9 @@
if (hasSuggestions) {
this.showPopup();
+ if (type === 'search') {
+ this.maximizePopup();
+ }
}
};
@@ -442,6 +482,8 @@
|| _this.knownMigrations[containerUrl] === false
// Already migrated or no migration necessary
|| containerUrl === _this.knownMigrations[containerUrl]
+ // The container URL changed
+ || containerUrl !== $suggestion.data('containerUrl')
) {
toBeRemoved.push($suggestion);
}
@@ -478,7 +520,7 @@
let hasBackendForm = this.cleanupBackendForm();
if (hasBackendForm !== true && this.Popup().find('li').length === toBeRemoved.length) {
- this.hidePopup(function () {
+ this.hidePopup(() => {
// Let the transition finish first, looks cleaner
$.each(toBeRemoved, function (_, $suggestion) {
$suggestion.remove();
@@ -496,13 +538,20 @@
if (typeof hasBackendForm === 'object') {
hasBackendForm.remove();
}
+
+ // Let showPopup() handle the automatic minimization in case all search suggestions have been removed
+ this.showPopup();
}
};
Migrate.prototype.showPopup = function() {
var $popup = this.Popup();
- if (this.storage.get('minimized')) {
- $popup.addClass('active minimized hidden');
+ if (this.storage.get('minimized') && ! this.forceFullyMaximized()) {
+ if (this.isShown()) {
+ this.minimizePopup();
+ } else {
+ $popup.addClass('active minimized hidden');
+ }
} else {
$popup.addClass('active');
}
@@ -532,6 +581,10 @@
this.Popup().removeClass('minimized hidden');
};
+ Migrate.prototype.forceFullyMaximized = function() {
+ return this.Popup().find('.search-migration-suggestions:not(:empty)').length > 0;
+ };
+
Migrate.prototype.togglePopup = function() {
if (this.Popup().is('.minimized')) {
this.maximizePopup();
@@ -549,7 +602,23 @@
Migrate.prototype.changeUrlMigrationReadyState = function (state) {
this.setUrlMigrationReadyState(state);
- if (this.backendSupportReadyState !== false) {
+ if (this.backendSupportReadyState !== false && this.searchMigrationReadyState !== false) {
+ this.searchMigrationReadyState = null;
+ this.backendSupportReadyState = null;
+ this.urlMigrationReadyState = null;
+ this.cleanupPopup();
+ }
+ };
+
+ Migrate.prototype.setSearchMigrationReadyState = function (state) {
+ this.searchMigrationReadyState = state;
+ };
+
+ Migrate.prototype.changeSearchMigrationReadyState = function (state) {
+ this.setSearchMigrationReadyState(state);
+
+ if (this.backendSupportReadyState !== false && this.urlMigrationReadyState !== false) {
+ this.searchMigrationReadyState = null;
this.backendSupportReadyState = null;
this.urlMigrationReadyState = null;
this.cleanupPopup();
@@ -563,7 +632,8 @@
Migrate.prototype.changeBackendSupportReadyState = function (state) {
this.setBackendSupportReadyState(state);
- if (this.urlMigrationReadyState !== false) {
+ if (this.urlMigrationReadyState !== false && this.searchMigrationReadyState !== false) {
+ this.searchMigrationReadyState = null;
this.backendSupportReadyState = null;
this.urlMigrationReadyState = null;
this.cleanupPopup();