diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php index 41d8940a..e5dfed36 100644 --- a/application/controllers/ConfigController.php +++ b/application/controllers/ConfigController.php @@ -8,7 +8,6 @@ use Icinga\Application\Config; use Icinga\Module\Icingadb\Forms\SetAsBackendConfigForm; use Icinga\Module\Icingadb\Forms\DatabaseConfigForm; use Icinga\Module\Icingadb\Forms\RedisConfigForm; -use Icinga\Module\Icingadb\Forms\SetAsBackendForm; use Icinga\Module\Icingadb\Web\Controller; use Icinga\Web\Form; use Icinga\Web\Widget\Tab; @@ -50,11 +49,6 @@ class ConfigController extends Controller public function backendAction() { - // migration.js - (new SetAsBackendForm()) - ->setRedirectUrl('__BACK__') - ->handleRequest(); - $form = new SetAsBackendConfigForm(); $form->handleRequest(); diff --git a/application/controllers/MigrateController.php b/application/controllers/MigrateController.php index 6ddc9446..b032efff 100644 --- a/application/controllers/MigrateController.php +++ b/application/controllers/MigrateController.php @@ -7,8 +7,11 @@ namespace Icinga\Module\Icingadb\Controllers; use Exception; 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 Icinga\Web\Hook; +use ipl\Html\HtmlString; use ipl\Web\Url; class MigrateController extends Controller @@ -70,4 +73,52 @@ class MigrateController extends Controller ->sendResponse(); exit; } + + public function checkboxSubmitAction() + { + $this->assertHttpMethod('post'); + + $form = (new SetAsBackendForm()) + ->setOnSuccess(function () use (&$form) { + $this->addPart(HtmlString::create('"bogus"'), 'Behavior:Migrate'); + $form->save($form->getElement('backend')->isChecked()); + return false; + }); + $form->handleRequest(); + } + + 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/forms/SetAsBackendForm.php b/application/forms/SetAsBackendForm.php index 0de5f782..2c003138 100644 --- a/application/forms/SetAsBackendForm.php +++ b/application/forms/SetAsBackendForm.php @@ -20,9 +20,4 @@ class SetAsBackendForm extends SetAsBackendConfigForm $this->removeElement('btn_submit'); $this->removeElement('btn_submit_session'); } - - public function onSuccess() - { - $this->save($this->getElement('backend')->isChecked()); - } } diff --git a/public/js/migrate.js b/public/js/migrate.js index 1d6d1969..101892db 100644 --- a/public/js/migrate.js +++ b/public/js/migrate.js @@ -24,7 +24,9 @@ ' \n' + ''; - const BACKEND_FORM = '
\n' + + const BACKEND_FORM = '\n' + '
\n' + '
\n' + ' \n' + @@ -35,6 +37,7 @@ ' \n' + '
\n' + ' \n' + + '
\n' + ''; /** @@ -47,13 +50,11 @@ this.icinga = icinga; this.knownMigrations = {}; + this.knownBackendSupport = {}; + this.urlMigrationReadyState = null; + this.backendSupportReadyState = null; + this.backendSupportRelated = {}; this.$popup = null; - this.supportedModules = [ - '/director', - '/businessprocess', - '/jira/', - '/cube/' - ]; // Some persistence, we don't want to annoy our users too much this.storage = Icinga.Storage.BehaviorStorage('icingadb.migrate'); @@ -63,7 +64,6 @@ // We don't want to ask the server to migrate non-monitoring urls this.isMonitoringUrl = new RegExp('^' + icinga.config.baseUrl + '/monitoring/'); - this.isSupportedModule = new RegExp('^' + icinga.config.baseUrl + '(' + this.supportedModules.join('|') +')'); this.on('rendered', this.onRendered, this); this.on('close-column', this.onColumnClose, this); this.on('click', '#migrate-popup button.close', this.onClose, this); @@ -74,17 +74,32 @@ Migrate.prototype = new Icinga.EventListener(); + Migrate.prototype.update = function (data) { + if (data !== 'bogus') { + return; + } + + $.each(this.backendSupportRelated, (id, _) => { + let $container = $('#' + id); + let req = this.icinga.loader.loadUrl($container.data('icingaUrl'), $container); + req.addToHistory = false; + req.scripted = true; + }); + }; + Migrate.prototype.onRendered = function(event) { var _this = event.data.self; var $target = $(event.target); if (! $target.is('#main > .container')) { - var attrUrl = $target.attr('data-icinga-url'); - var dataUrl = $target.data('icingaUrl'); - if (!! attrUrl && attrUrl !== dataUrl) { - // Search urls are redirected, update any migration suggestions - _this.prepareMigration($target); - return; + if ($target.is('#main .container')) { + var attrUrl = $target.attr('data-icinga-url'); + var dataUrl = $target.data('icingaUrl'); + if (!! attrUrl && attrUrl !== dataUrl) { + // Search urls are redirected, update any migration suggestions + _this.prepareMigration($target); + return; + } } // We are else really only interested in top-level containers @@ -108,41 +123,47 @@ }; Migrate.prototype.prepareMigration = function($target) { - var urls = {}; - var href; - var wantBackendForm = false; + let urls = {}; + let modules = {} - var _this = this; - $target.each(function () { - var $container = $(this); - href = $container.data('icingaUrl'); + $target.each((_, container) => { + let $container = $(container); + let href = $container.data('icingaUrl'); + let containerId = $container.attr('id'); - if (typeof href !== 'undefined') { - if (href.match(_this.isMonitoringUrl)) { - var containerId = $container.attr('id'); - - if ( - typeof _this.previousMigrations[containerId] !== 'undefined' - && _this.previousMigrations[containerId] === href - ) { - delete _this.previousMigrations[containerId]; - } else { - urls[containerId] = href; - } - } else if (href.match(_this.isSupportedModule)) { - wantBackendForm = true; + if (typeof href !== 'undefined' && href.match(this.isMonitoringUrl)) { + if ( + typeof this.previousMigrations[containerId] !== 'undefined' + && this.previousMigrations[containerId] === href + ) { + delete this.previousMigrations[containerId]; } else { - _this.removeBackendCheckboxForm(); + urls[containerId] = href; } } + + let moduleName = $container.data('icingaModule'); + if (!! moduleName && moduleName !== 'default' && moduleName !== 'monitoring' && moduleName !== 'icingadb') { + modules[containerId] = moduleName; + } }); - if (this.icinga.utils.objectKeys(urls).length) { + if (Object.keys(urls).length) { + this.setUrlMigrationReadyState(false); this.migrateMonitoringUrls(urls); - } else if (wantBackendForm) { - this.prepareBackendCheckboxForm(); } else { - this.cleanupSuggestions(); + this.setUrlMigrationReadyState(null); + } + + if (Object.keys(modules).length) { + this.setBackendSupportReadyState(false); + this.prepareBackendCheckboxForm(modules); + } else { + this.setBackendSupportReadyState(null); + } + + if (this.urlMigrationReadyState === null && this.backendSupportReadyState === null) { + this.cleanupPopup(); } }; @@ -166,12 +187,26 @@ // Container moved $suggestion.attr('id', 'suggest-' + $newContainer.attr('id')); $suggestion.data('containerId', $newContainer.attr('id')); - } else { - // Container closed - $suggestion.remove(); } } }); + + let backendSupportRelated = { ..._this.backendSupportRelated }; + $.each(backendSupportRelated, (id, module) => { + let $container = $('#' + id); + if (! $container.length || $container.data('icingaModule') !== module) { + let $newContainer = $('#main > .container').filter(function () { + return $(this).data('icingaModule') === module; + }); + if ($newContainer.length) { + _this.backendSupportRelated[$newContainer.attr('id')] = module; + } + + delete _this.backendSupportRelated[id]; + } + }); + + _this.cleanupPopup(); }; Migrate.prototype.onClose = function(event) { @@ -232,7 +267,6 @@ containerIds = [], containerUrls = []; - this.removeBackendCheckboxForm(); $.each(urls, function (containerId, containerUrl) { if (typeof _this.knownMigrations[containerUrl] === 'undefined') { containerUrls.push(containerUrl); @@ -253,11 +287,11 @@ req.urls = urls; req.urlIndexToContainerId = containerIds; req.done(this.processUrlMigrationResults); - req.always(_this.cleanupSuggestions); + req.always(() => this.changeUrlMigrationReadyState(true)); } else { // All urls have already been migrated once, show popup immediately this.addSuggestions(urls); - this.cleanupSuggestions(); + this.changeUrlMigrationReadyState(true); } }; @@ -283,43 +317,85 @@ this.addSuggestions(req.urls); }; - Migrate.prototype.prepareBackendCheckboxForm = function() { - var $popup = this.Popup(); - var checkboxForm = $popup.find('.suggestion-area > #setAsBackendForm'); + Migrate.prototype.prepareBackendCheckboxForm = function(modules) { + let containerIds = []; + let moduleNames = []; - this.cleanupSuggestions(); // remove links - if (! checkboxForm.length) { - $popup.find('.suggestion-area > ul').after($(BACKEND_FORM)); - } - - var req = $.ajax({ - context : this, - type : 'get', - url : this.icinga.config.baseUrl + '/icingadb/migrate/checkbox-state' + $.each(modules, (id, module) => { + if (typeof this.knownBackendSupport[module] === 'undefined') { + containerIds.push(id); + moduleNames.push(module); + } }); - req.done(this.setCheckboxState); + if (moduleNames.length) { + let req = $.ajax({ + context : this, + type : 'post', + url : this.icinga.config.baseUrl + '/icingadb/migrate/backend-support', + headers : { 'Accept': 'application/json' }, + contentType : 'application/json', + data : JSON.stringify(moduleNames) + }); + + req.modules = modules; + req.moduleIndexToContainerId = containerIds; + req.done(this.processBackendSupportResults); + req.always(() => this.changeBackendSupportReadyState(true)); + } else { + // All modules have already been checked once, show popup immediately + this.setupBackendCheckboxForm(modules); + this.changeBackendSupportReadyState(true); + } + }; + + Migrate.prototype.processBackendSupportResults = function(data, textStatus, req) { + let result = data.data; + + $.each(result, (i, state) => { + let containerId = req.moduleIndexToContainerId[i]; + this.knownBackendSupport[req.modules[containerId]] = state; + }); + + this.setupBackendCheckboxForm(req.modules); + }; + + Migrate.prototype.setupBackendCheckboxForm = function(modules) { + let supportedModules = {}; + + $.each(modules, (id, module) => { + if (this.knownBackendSupport[module]) { + supportedModules[id] = module; + } + }); + + if (Object.keys(supportedModules).length) { + let $form = this.Popup().find('.suggestion-area > #setAsBackendForm'); + if (! $form.length) { + $form = $(BACKEND_FORM); + this.Popup().find('.suggestion-area > ul').after($form); + } + + this.backendSupportRelated = { ...this.backendSupportRelated, ...supportedModules }; + + let req = $.ajax({ + context : this, + type : 'get', + url : this.icinga.config.baseUrl + '/icingadb/migrate/checkbox-state' + }); + + req.done(this.setCheckboxState); + } }; Migrate.prototype.setCheckboxState = function(isChecked, textStatus, req) { var $form = this.Popup().find('.suggestion-area > #setAsBackendForm'); var $checkbox = $form.find('input#setAsBackendForm-checkbox'); - this.Popup().find('p').hide(); - $checkbox.prop('checked', isChecked); this.showPopup(); } - Migrate.prototype.removeBackendCheckboxForm = function () { - var $backendCheckboxForm = this.Popup().find('.suggestion-area > #setAsBackendForm'); - if ($backendCheckboxForm.length) { - $backendCheckboxForm.remove(); - } - - this.Popup().find('p').show(); - } - Migrate.prototype.addSuggestions = function(urls) { var _this = this, hasSuggestions = false, @@ -368,9 +444,7 @@ Migrate.prototype.cleanupSuggestions = function() { var _this = this, - toBeRemoved = [], - willBeEmpty = true, - $hasBackendForm = this.Popup().find('.suggestion-area > #setAsBackendForm').length > 0; + toBeRemoved = []; this.Popup().find('li').each(function () { var $suggestion = $(this); var $container = $('#' + $suggestion.data('containerId')); @@ -384,26 +458,58 @@ || containerUrl === _this.knownMigrations[containerUrl] ) { toBeRemoved.push($suggestion); - } else { - willBeEmpty = false; } }); - if ($hasBackendForm) { - willBeEmpty = false; + return toBeRemoved; + }; + + Migrate.prototype.cleanupBackendForm = function () { + let $form = this.Popup().find('#setAsBackendForm'); + if (! $form.length) { + return false; } - if (willBeEmpty) { + let stillRelated = {}; + $.each(this.backendSupportRelated, (id, module) => { + let $container = $('#' + id); + if ($container.length && $container.data('icingaModule') === module) { + stillRelated[id] = module; + } + }); + + this.backendSupportRelated = stillRelated; + + if (Object.keys(stillRelated).length) { + return true; + } + + return $form; + }; + + Migrate.prototype.cleanupPopup = function () { + let toBeRemoved = this.cleanupSuggestions(); + let hasBackendForm = this.cleanupBackendForm(); + + if (hasBackendForm !== true && this.Popup().find('li').length === toBeRemoved.length) { this.hidePopup(function () { // Let the transition finish first, looks cleaner $.each(toBeRemoved, function (_, $suggestion) { $suggestion.remove(); }); + + if (typeof hasBackendForm === 'object') { + hasBackendForm.remove(); + } }); } else { $.each(toBeRemoved, function (_, $suggestion) { $suggestion.remove(); }); + + if (typeof hasBackendForm === 'object') { + hasBackendForm.remove(); + } } }; @@ -450,6 +556,34 @@ } }; + Migrate.prototype.setUrlMigrationReadyState = function (state) { + this.urlMigrationReadyState = state; + }; + + Migrate.prototype.changeUrlMigrationReadyState = function (state) { + this.setUrlMigrationReadyState(state); + + if (this.backendSupportReadyState !== false) { + this.backendSupportReadyState = null; + this.urlMigrationReadyState = null; + this.cleanupPopup(); + } + }; + + Migrate.prototype.setBackendSupportReadyState = function (state) { + this.backendSupportReadyState = state; + }; + + Migrate.prototype.changeBackendSupportReadyState = function (state) { + this.setBackendSupportReadyState(state); + + if (this.urlMigrationReadyState !== false) { + this.backendSupportReadyState = null; + this.urlMigrationReadyState = null; + this.cleanupPopup(); + } + }; + Migrate.prototype.Popup = function() { // Node.contains() is used due to `?renderLayout` if (this.$popup === null || ! document.body.contains(this.$popup[0])) {