/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ ;(function(Icinga, $) { 'use strict'; Icinga.Behaviors = Icinga.Behaviors || {}; const ANIMATION_LENGTH = 350; const POPUP_HTML = '
\n' + '
\n' + '
\n' + '

Preview this in Icinga DB

\n' + '
    \n' + ' \n' + '
    \n' + '
    \n' + '
    \n' + '
    '; const SUGGESTION_HTML = '
  • \n' + ' \n' + ' \n' + '
  • '; /** * Icinga DB Migration behavior. * * @param icinga {Icinga} The current Icinga Object */ var Migrate = function(icinga) { Icinga.EventListener.call(this, icinga); this.icinga = icinga; this.knownMigrations = {}; this.knownBackendSupport = {}; this.urlMigrationReadyState = null; this.backendSupportReadyState = null; this.backendSupportRelated = {}; this.$popup = null; // Some persistence, we don't want to annoy our users too much this.storage = Icinga.Storage.BehaviorStorage('icingadb.migrate'); this.tempStorage = Icinga.Storage.BehaviorStorage('icingadb.migrate'); this.tempStorage.setBackend(window.sessionStorage); this.previousMigrations = {}; // 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); this.on('click', '#migrate-popup li button', this.onDecision, this); this.on('click', '#migrate-popup .minimizer', this.onHandleClicked, this); this.storage.onChange('minimized', this.onMinimized, this); }; 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')) { 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 return; } if (_this.tempStorage.get('closed') || $('#layout.fullscreen-layout').length) { // Don't bother in case the user closed the popup or we're in fullscreen return; } var $dashboard = $target.children('.dashboard'); if ($dashboard.length) { // After a page load dashlets have no id as `renderContentToContainer()` didn't ran yet _this.icinga.ui.assignUniqueContainerIds(); $target = $dashboard.children('.container'); } _this.prepareMigration($target); }; Migrate.prototype.prepareMigration = function($target) { let urls = {}; let modules = {} $target.each((_, container) => { let $container = $(container); let href = $container.data('icingaUrl'); let containerId = $container.attr('id'); if (typeof href !== 'undefined' && href.match(this.isMonitoringUrl)) { if ( typeof this.previousMigrations[containerId] !== 'undefined' && this.previousMigrations[containerId] === href ) { delete this.previousMigrations[containerId]; } else { urls[containerId] = href; } } let moduleName = $container.data('icingaModule'); if (!! moduleName && moduleName !== 'default' && moduleName !== 'monitoring' && moduleName !== 'icingadb') { modules[containerId] = moduleName; } }); if (Object.keys(urls).length) { this.setUrlMigrationReadyState(false); this.migrateMonitoringUrls(urls); } else { 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(); } }; Migrate.prototype.onColumnClose = function(event) { var _this = event.data.self; _this.Popup().find('.suggestion-area > ul li').each(function () { var $suggestion = $(this); var suggestionUrl = $suggestion.data('containerUrl'); var $container = $('#' + $suggestion.data('containerId')); var containerUrl = ''; if ($container.length) { containerUrl = $container.data('icingaUrl'); } if (suggestionUrl !== containerUrl) { var $newContainer = $('#main > .container').filter(function () { return $(this).data('icingaUrl') === suggestionUrl; }); if ($newContainer.length) { // Container moved $suggestion.attr('id', 'suggest-' + $newContainer.attr('id')); $suggestion.data('containerId', $newContainer.attr('id')); } } }); 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) { var _this = event.data.self; _this.tempStorage.set('closed', true); _this.hidePopup(); }; Migrate.prototype.onDecision = function(event) { var _this = event.data.self; var $button = $(event.target).closest('button'); var $suggestion = $button.parent(); var $container = $('#' + $suggestion.data('containerId')); var containerUrl = $container.data('icingaUrl'); if ($button.attr('value') === '1') { // Yes var newHref = _this.knownMigrations[containerUrl]; _this.icinga.loader.loadUrl(newHref, $container); _this.previousMigrations[$suggestion.data('containerId')] = containerUrl; if ($container.parent().is('.dashboard')) { $container.find('h1 a').attr('href', _this.icinga.utils.removeUrlParams(newHref, ['showCompact'])); } } else { // No _this.knownMigrations[containerUrl] = false; } if (_this.Popup().find('li').length === 1) { _this.hidePopup(function () { // Let the transition finish first, looks cleaner $suggestion.remove(); }); } else { $suggestion.remove(); } }; Migrate.prototype.onHandleClicked = function(event) { var _this = event.data.self; if (_this.togglePopup()) { _this.storage.set('minimized', true); } else { _this.storage.remove('minimized'); } }; Migrate.prototype.onMinimized = function(isMinimized, oldValue) { if (isMinimized && isMinimized !== oldValue && this.isShown()) { this.minimizePopup(); } }; Migrate.prototype.migrateMonitoringUrls = function(urls) { var _this = this, containerIds = [], containerUrls = []; $.each(urls, function (containerId, containerUrl) { if (typeof _this.knownMigrations[containerUrl] === 'undefined') { containerUrls.push(containerUrl); containerIds.push(containerId); } }); if (containerUrls.length) { var req = $.ajax({ context : this, type : 'post', url : this.icinga.config.baseUrl + '/icingadb/migrate/monitoring-url', headers : { 'Accept': 'application/json' }, contentType : 'application/json', data : JSON.stringify(containerUrls) }); req.urls = urls; req.urlIndexToContainerId = containerIds; req.done(this.processUrlMigrationResults); req.always(() => this.changeUrlMigrationReadyState(true)); } else { // All urls have already been migrated once, show popup immediately this.addSuggestions(urls); this.changeUrlMigrationReadyState(true); } }; Migrate.prototype.processUrlMigrationResults = function(data, textStatus, req) { var _this = this; var result, containerId; if (data.status === 'success') { result = data.data; } else { // if (data.status === 'fail') result = data.data.result; $.each(data.data.errors, function (k, error) { _this.icinga.logger.error('[Migrate] Erroneous url "' + k + '": ' + error[0] + '\n' + error[1]); }); } $.each(result, function (i, migratedUrl) { containerId = req.urlIndexToContainerId[i]; _this.knownMigrations[req.urls[containerId]] = migratedUrl; }); this.addSuggestions(req.urls); }; Migrate.prototype.prepareBackendCheckboxForm = function(modules) { let containerIds = []; let moduleNames = []; $.each(modules, (id, module) => { if (typeof this.knownBackendSupport[module] === 'undefined') { containerIds.push(id); moduleNames.push(module); } }); 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) { this.backendSupportRelated = { ...this.backendSupportRelated, ...supportedModules }; let req = $.ajax({ context : this, type : 'get', url : this.icinga.config.baseUrl + '/icingadb/migrate/checkbox-state?showCompact' }); req.done(this.setCheckboxState); } }; Migrate.prototype.setCheckboxState = function(html, textStatus, req) { let $form = this.Popup().find('.suggestion-area > #setAsBackendForm'); if (! $form.length) { $form = $(html); $form.attr('data-base-target', 'migrate-popup-backend-submit-blackhole'); $form.append('
    '); this.Popup().find('.suggestion-area > ul').after($form); } else { let $newForm = $(html); $form.find('[name=backend]').prop('checked', $newForm.find('[name=backend]').is(':checked')); } this.showPopup(); } Migrate.prototype.addSuggestions = function(urls) { var _this = this, hasSuggestions = false, $ul = this.Popup().find('.suggestion-area > ul'); $.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) { var $container = $('#' + containerId); var $suggestion = $ul.find('li#suggest-' + containerId); if ($suggestion.length) { if ($suggestion.data('containerUrl') === containerUrl) { // There's already a suggestion for this exact container and url hasSuggestions = true; return; } $suggestion.data('containerUrl', containerUrl); } else { $suggestion = $(SUGGESTION_HTML); $suggestion.attr('id', 'suggest-' + containerId); $suggestion.data('containerId', containerId); $suggestion.data('containerUrl', containerUrl); $ul.append($suggestion); } hasSuggestions = true; var title; if ($container.data('icingaTitle')) { title = $container.data('icingaTitle').split(' :: ').slice(0, -1).join(' :: '); } else if ($container.parent().is('.dashboard')) { title = $container.find('h1 a').text(); } else { title = $container.find('.tabs li.active a').text(); } $suggestion.find('button:first-of-type').text(title); } }); if (hasSuggestions) { this.showPopup(); } }; Migrate.prototype.cleanupSuggestions = function() { var _this = this, toBeRemoved = []; this.Popup().find('li').each(function () { var $suggestion = $(this); var $container = $('#' + $suggestion.data('containerId')); var containerUrl = $container.data('icingaUrl'); if ( // Unknown url, yet typeof _this.knownMigrations[containerUrl] === 'undefined' // User doesn't want to migrate || _this.knownMigrations[containerUrl] === false // Already migrated or no migration necessary || containerUrl === _this.knownMigrations[containerUrl] ) { toBeRemoved.push($suggestion); } }); return toBeRemoved; }; Migrate.prototype.cleanupBackendForm = function () { let $form = this.Popup().find('#setAsBackendForm'); if (! $form.length) { return false; } 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(); } } }; Migrate.prototype.showPopup = function() { var $popup = this.Popup(); if (this.storage.get('minimized')) { $popup.addClass('active minimized hidden'); } else { $popup.addClass('active'); } }; Migrate.prototype.hidePopup = function (after) { this.Popup().removeClass('active minimized hidden'); if (typeof after === 'function') { setTimeout(after, ANIMATION_LENGTH); } }; Migrate.prototype.isShown = function() { return this.Popup().is('.active'); }; Migrate.prototype.minimizePopup = function() { var $popup = this.Popup(); $popup.addClass('minimized'); setTimeout(function () { $popup.addClass('hidden'); }, ANIMATION_LENGTH); }; Migrate.prototype.maximizePopup = function() { this.Popup().removeClass('minimized hidden'); }; Migrate.prototype.togglePopup = function() { if (this.Popup().is('.minimized')) { this.maximizePopup(); return false; } else { this.minimizePopup(); return true; } }; 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])) { $('#layout').append($(POPUP_HTML)); this.$popup = $('#migrate-popup'); } return this.$popup; }; Icinga.Behaviors.Migrate = Migrate; })(Icinga, jQuery);