///////////////////////////////////////////////////////////// // // pgAdmin 4 - PostgreSQL Tools // // Copyright (C) 2013 - 2020, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // ////////////////////////////////////////////////////////////// define([ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'pgadmin.alertifyjs', 'sources/pgadmin', 'pgadmin.browser', 'backbone', 'backgrid', 'backform', 'sources/utils', 'tools/maintenance/static/js/menu_utils', 'sources/nodes/supported_database_node', 'pgadmin.backform', 'pgadmin.backgrid', 'pgadmin.browser.node.ui', ], function( gettext, url_for, $, _, Alertify, pgAdmin, pgBrowser, Backbone, Backgrid, Backform, commonUtils, menuUtils, supportedNodes ) { pgAdmin = pgAdmin || window.pgAdmin || {}; var pgTools = pgAdmin.Tools = pgAdmin.Tools || {}; // Return back, this has been called more than once if (pgAdmin.Tools.maintenance) return pgAdmin.Tools.maintenance; // Main model for Maintenance functionality var MaintenanceModel = Backbone.Model.extend({ defaults: { op: 'VACUUM', vacuum_full: false, vacuum_freeze: false, vacuum_analyze: false, verbose: true, }, initialize: function() { var node_info = arguments[1]['node_info']; // If node is Unique or Primary key then set op to reindex if ('primary_key' in node_info || 'unique_constraint' in node_info || 'index' in node_info) { this.set('op', 'REINDEX'); this.set('verbose', false); } }, schema: [{ id: 'op', label: gettext('Maintenance operation'), cell: 'string', type: 'radioModern', controlsClassName: 'pgadmin-controls col-12 col-sm-8', controlLabelClassName: 'control-label col-sm-4 col-12', group: gettext('Options'), value: 'VACUUM', options: [{ 'label': 'VACUUM', 'value': 'VACUUM', }, { 'label': 'ANALYZE', 'value': 'ANALYZE', }, { 'label': 'REINDEX', 'value': 'REINDEX', }, { 'label': 'CLUSTER', 'value': 'CLUSTER', }, ], }, { type: 'nested', control: 'fieldset', label: gettext('Vacuum'), group: gettext('Options'), contentClass: 'row', schema: [{ id: 'vacuum_full', group: gettext('Vacuum'), disabled: 'isDisabled', type: 'switch', extraToggleClasses: 'pg-el-sm-4', controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12', controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12', label: gettext('FULL'), deps: ['op'], }, { id: 'vacuum_freeze', deps: ['op'], disabled: 'isDisabled', type: 'switch', extraToggleClasses: 'pg-el-sm-4', controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12', controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12', label: gettext('FREEZE'), group: gettext('Vacuum'), }, { id: 'vacuum_analyze', deps: ['op'], disabled: 'isDisabled', type: 'switch', extraToggleClasses: 'pg-el-sm-4', controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12', controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12', label: gettext('ANALYZE'), group: gettext('Vacuum'), }], }, { id: 'verbose', group: gettext('Options'), deps: ['op'], type: 'switch', label: gettext('Verbose Messages'), disabled: 'isDisabled', }, ], // Enable/Disable the items based on the user maintenance operation // selection. isDisabled: function(m) { var node_info = this.node_info; switch (this.name) { case 'vacuum_full': case 'vacuum_freeze': case 'vacuum_analyze': return (m.get('op') != 'VACUUM'); case 'verbose': if ('primary_key' in node_info || 'unique_constraint' in node_info || 'index' in node_info) { if (m.get('op') == 'REINDEX') { setTimeout(function() { m.set('verbose', false); }, 10); return true; } } return m.get('op') == 'REINDEX'; default: return false; } }, }); pgTools.maintenance = { init: function() { // We do not want to initialize the module multiple times. if (this.initialized) return; this.initialized = true; var menus = [{ name: 'maintenance', module: this, applies: ['tools'], callback: 'callback_maintenance', priority: 10, label: gettext('Maintenance...'), icon: 'fa fa-wrench', enable: supportedNodes.enabled.bind( null, pgBrowser.treeMenu, menuUtils.maintenanceSupportedNodes ), }]; // Add supported menus into the menus list for (var idx = 0; idx < menuUtils.maintenanceSupportedNodes.length; idx++) { menus.push({ name: 'maintenance_context_' + menuUtils.maintenanceSupportedNodes[idx], node: menuUtils.maintenanceSupportedNodes[idx], module: this, applies: ['context'], callback: 'callback_maintenance', priority: 10, label: gettext('Maintenance...'), icon: 'fa fa-wrench', enable: supportedNodes.enabled.bind( null, pgBrowser.treeMenu, menuUtils.maintenanceSupportedNodes ), }); } pgBrowser.add_menus(menus); }, /* Open the dialog for the maintenance functionality */ callback_maintenance: function(args, item) { var i = item || pgBrowser.tree.selected(), server_data = null; while (i) { var node_data = pgBrowser.tree.itemData(i); if (node_data._type == 'server') { server_data = node_data; break; } if (pgBrowser.tree.hasParent(i)) { i = $(pgBrowser.tree.parent(i)); } else { Alertify.alert(gettext('Please select server or child node from tree.')); break; } } if (!server_data) { return; } var module = 'paths', preference_name = 'pg_bin_dir', msg = gettext('Please configure the PostgreSQL Binary Path in the Preferences dialog.'); if ((server_data.type && server_data.type == 'ppas') || server_data.server_type == 'ppas') { preference_name = 'ppas_bin_dir'; msg = gettext('Please configure the EDB Advanced Server Binary Path in the Preferences dialog.'); } var preference = pgBrowser.get_preference(module, preference_name); if (preference) { if (!preference.value) { Alertify.alert(gettext('Configuration required'), msg); return; } } else { Alertify.alert(gettext('Failed to load preference %s of module %s', preference_name, module)); return; } var self = this, t = pgBrowser.tree; i = item || t.selected(); var d = i && i.length == 1 ? t.itemData(i) : undefined; if (!d) return; var node = d && pgBrowser.Nodes[d._type], treeInfo = node.getTreeNodeHierarchy.apply(node, [i]); if (treeInfo.database._label.indexOf('=') >= 0) { Alertify.alert( gettext('Maintenance error'), gettext('Maintenance job creation failed. '+ 'Databases with = symbols in the name cannot be maintained using this utility.') ); return; } if (!Alertify.MaintenanceDialog) { Alertify.dialog('MaintenanceDialog', function factory() { return { main: function(title) { this.set('title', title); }, setup: function() { return { buttons: [{ text: '', className: 'btn btn-secondary pull-left fa fa-info pg-alertify-icon-button', attrs: { name: 'object_help', type: 'button', url: 'maintenance.html', label: gettext('Maintenance'), 'aria-label': gettext('Object Help'), }, }, { text: '', key: 112, className: 'btn btn-secondary pull-left fa fa-question pg-alertify-icon-button', attrs: { name: 'dialog_help', type: 'button', label: gettext('Maintenance'), 'aria-label': gettext('Help'), url: url_for( 'help.static', { 'filename': 'maintenance_dialog.html', } ), }, }, { text: gettext('Cancel'), key: 27, className: 'btn btn-secondary fa fa-lg fa-times pg-alertify-button', 'data-btn-name': 'cancel', }, { text: gettext('OK'), key: 13, className: 'btn btn-primary fa fa-lg fa-check pg-alertify-button', 'data-btn-name': 'ok', }], options: { modal: 0, pinnable: false, //disable both padding and overflow control. padding: !1, overflow: !1, }, }; }, // Callback functions when click on the buttons of the Alertify dialogs callback: function(e) { var i = pgBrowser.tree.selected(), d = i && i.length == 1 ? pgBrowser.tree.itemData(i) : undefined, node = d && pgBrowser.Nodes[d._type]; if (e.button.element.name == 'dialog_help' || e.button.element.name == 'object_help') { e.cancel = true; pgBrowser.showHelp(e.button.element.name, e.button.element.getAttribute('url'), node, i); return; } if (e.button['data-btn-name'] === 'ok') { var schema = undefined, table = undefined, primary_key = undefined, unique_constraint = undefined, index = undefined; if (!d) return; var treeInfo = node.getTreeNodeHierarchy.apply(node, [i]); if (treeInfo.schema != undefined) { schema = treeInfo.schema._label; } if (treeInfo.partition != undefined) { table = treeInfo.partition._label; } else if (treeInfo.table != undefined) { table = treeInfo.table._label; } if (treeInfo.primary_key != undefined) { primary_key = treeInfo.primary_key._label; } else if (treeInfo.unique_constraint != undefined) { unique_constraint = treeInfo.unique_constraint._label; } else if (treeInfo.index != undefined) { index = treeInfo.index._label; } this.view.model.set({ 'database': treeInfo.database._label, 'schema': schema, 'table': table, 'primary_key': primary_key, 'unique_constraint': unique_constraint, 'index': index, }); $.ajax({ url: url_for( 'maintenance.create_job', { 'sid': treeInfo.server._id, 'did': treeInfo.database._id, }), method: 'POST', data: { 'data': JSON.stringify(this.view.model.toJSON()), }, }) .done(function(res) { if (res.data && res.data.status) { //Do nothing as we are creating the job and exiting from the main dialog Alertify.success(res.data.info); pgBrowser.Events.trigger('pgadmin-bgprocess:created', self); } else { Alertify.alert( gettext('Maintenance job creation failed.'), res.errormsg ); } }) .fail(function() { Alertify.alert( gettext('Maintenance job creation failed.') ); }); } }, build: function() { Alertify.pgDialogBuild.apply(this); }, hooks: { onclose: function() { if (this.view) { this.view.remove({ data: true, internal: true, silent: true, }); } }, onshow: function() { var container = $(this.elements.body).find('.tab-content:first > .tab-pane.active:first'); commonUtils.findAndSetFocus(container); }, }, prepare: function() { // Main maintenance tool dialog container var $container = $('
'); var t = pgBrowser.tree, i = t.selected(), d = i && i.length == 1 ? t.itemData(i) : undefined, node = d && pgBrowser.Nodes[d._type]; if (!d) return; var treeInfo = node.getTreeNodeHierarchy.apply(node, [i]); var newModel = new MaintenanceModel({}, { node_info: treeInfo, }), fields = Backform.generateViewSchema( treeInfo, newModel, 'create', node, treeInfo.server, true ); var view = this.view = new Backform.Dialog({ el: $container, model: newModel, schema: fields, }); $(this.elements.body.childNodes[0]).addClass('alertify_tools_dialog_properties obj_properties'); view.render(); // If node is Index, Unique or Primary key then disable vacuum & analyze button if (d._type == 'primary_key' || d._type == 'unique_constraint' || d._type == 'index') { var vacuum_analyze_btns = $container.find( '.btn-group label.btn:lt(2)' ).addClass('disabled'); // Find reindex button element & add active class to it var reindex_btn = vacuum_analyze_btns[1].nextElementSibling; $(reindex_btn).trigger('click'); } view.$el.attr('tabindex', -1); this.elements.content.appendChild($container.get(0)); }, }; }); } const baseUrl = url_for('maintenance.utility_exists', { 'sid': server_data._id, }); // Check psql utility exists or not. $.ajax({ url: baseUrl, type:'GET', }) .done(function(res) { if (!res.success) { Alertify.alert( gettext('Utility not found'), res.errormsg ); return; } // Open the Alertify dialog Alertify.MaintenanceDialog(gettext('Maintenance...')).set('resizable', true) .resizeTo(pgAdmin.Browser.stdW.md,pgAdmin.Browser.stdH.md); }) .fail(function() { Alertify.alert( gettext('Utility not found'), gettext('Failed to fetch Utility information') ); return; }); }, }; return pgAdmin.Tools.maintenance; });