497 lines
16 KiB
JavaScript
497 lines
16 KiB
JavaScript
/////////////////////////////////////////////////////////////
|
|
//
|
|
// 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 = $('<div class=\'maintenance_dlg\'></div>');
|
|
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;
|
|
});
|