pgadmin4/web/pgadmin/tools/restore/static/js/restore.js

710 lines
23 KiB
JavaScript

// Restore dialog
define('tools.restore', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
'underscore.string', 'pgadmin.alertifyjs', 'pgadmin.browser',
'pgadmin.backgrid', 'pgadmin.backform', 'sources/utils',
], function(
gettext, url_for, $, _, Backbone, S, alertify, pgBrowser, Backgrid, Backform,
commonUtils
) {
// if module is already initialized, refer to that.
if (pgBrowser.Restore) {
return pgBrowser.Restore;
}
Backform.CustomSwitchControl = Backform.SwitchControl.extend({
template: _.template([
'<label class="<%=Backform.controlLabelClassName%> custom_switch_label_class"><%=label%></label>',
'<div class="<%=Backform.controlsClassName%> custom_switch_control_class">',
' <div class="checkbox">',
' <label>',
' <input type="checkbox" class="<%=extraClasses.join(\' \')%>" name="<%=name%>" <%=value ? "checked=\'checked\'" : ""%> <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> />',
' </label>',
' </div>',
'</div>',
'<% if (helpMessage && helpMessage.length) { %>',
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
'<% } %>',
].join('\n')),
className: 'pgadmin-control-group form-group pg-el-xs-4',
});
//Restore Model (Objects like Database/Schema/Table)
var RestoreObjectModel = Backbone.Model.extend({
idAttribute: 'id',
defaults: {
custom: false,
file: undefined,
role: undefined,
format: 'custom',
verbose: true,
blobs: true,
encoding: undefined,
database: undefined,
schemas: undefined,
tables: undefined,
functions: undefined,
triggers: undefined,
trigger_funcs: undefined,
indexes: undefined,
},
// Default values!
initialize: function(attrs) {
// Set default options according to node type selection by user
var node_type = attrs.node_data.type;
if (node_type) {
// Only_Schema option
if (node_type === 'function' || node_type === 'index' ||
node_type === 'trigger') {
this.set({
'only_schema': true,
}, {
silent: true,
});
}
// Only_Data option
if (node_type === 'table') {
this.set({
'only_data': true,
}, {
silent: true,
});
}
// Clean option
if (node_type === 'function' || node_type === 'trigger_function') {
this.set({
'clean': true,
}, {
silent: true,
});
}
}
Backbone.Model.prototype.initialize.apply(this, arguments);
},
schema: [{
id: 'format',
label: gettext('Format'),
type: 'text',
disabled: false,
control: 'select2',
select2: {
allowClear: false,
width: '100%',
},
options: [{
label: gettext('Custom or tar'),
value: 'custom',
},
{
label: gettext('Directory'),
value: 'directory',
},
],
}, {
id: 'file',
label: gettext('Filename'),
type: 'text',
disabled: false,
control: Backform.FileControl,
dialog_type: 'select_file',
supp_types: ['*', 'backup', 'sql', 'patch'],
}, {
id: 'no_of_jobs',
label: gettext('Number of jobs'),
type: 'int',
}, {
id: 'role',
label: gettext('Role name'),
control: 'node-list-by-name',
node: 'role',
select2: {
allowClear: false,
},
}, {
type: 'nested',
control: 'fieldset',
label: gettext('Sections'),
group: gettext('Restore options'),
schema: [{
id: 'pre_data',
label: gettext('Pre-data'),
control: Backform.CustomSwitchControl,
group: gettext('Sections'),
deps: ['only_data', 'only_schema'],
disabled: function(m) {
return this.node.type !== 'function' && this.node.type !== 'table' &&
this.node.type !== 'trigger' &&
this.node.type !== 'trigger_function' &&
(m.get('only_data') || m.get('only_schema'));
},
}, {
id: 'data',
label: gettext('Data'),
control: Backform.CustomSwitchControl,
group: gettext('Sections'),
deps: ['only_data', 'only_schema'],
disabled: function(m) {
return this.node.type !== 'function' && this.node.type !== 'table' &&
this.node.type !== 'trigger' &&
this.node.type !== 'trigger_function' &&
(m.get('only_data') || m.get('only_schema'));
},
}, {
id: 'post_data',
label: gettext('Post-data'),
control: Backform.CustomSwitchControl,
group: gettext('Sections'),
deps: ['only_data', 'only_schema'],
disabled: function(m) {
return this.node.type !== 'function' && this.node.type !== 'table' &&
this.node.type !== 'trigger' &&
this.node.type !== 'trigger_function' &&
(m.get('only_data') || m.get('only_schema'));
},
}],
}, {
type: 'nested',
control: 'fieldset',
label: gettext('Type of objects'),
group: gettext('Restore options'),
schema: [{
id: 'only_data',
label: gettext('Only data'),
control: Backform.CustomSwitchControl,
group: gettext('Type of objects'),
deps: ['pre_data', 'data', 'post_data', 'only_schema'],
disabled: function(m) {
return (this.node.type !== 'database' && this.node.type !== 'schema') ||
(m.get('pre_data') ||
m.get('data') ||
m.get('post_data') ||
m.get('only_schema')
);
},
}, {
id: 'only_schema',
label: gettext('Only schema'),
control: Backform.CustomSwitchControl,
group: gettext('Type of objects'),
deps: ['pre_data', 'data', 'post_data', 'only_data'],
disabled: function(m) {
return (this.node.type !== 'database' && this.node.type !== 'schema') ||
(m.get('pre_data') ||
m.get('data') ||
m.get('post_data') ||
m.get('only_data')
);
},
}],
}, {
type: 'nested',
control: 'fieldset',
label: gettext('Do not save'),
group: gettext('Restore options'),
schema: [{
id: 'dns_owner',
label: gettext('Owner'),
control: Backform.CustomSwitchControl,
disabled: false,
group: gettext('Do not save'),
}, {
id: 'dns_privilege',
label: gettext('Privilege'),
control: Backform.CustomSwitchControl,
disabled: false,
group: gettext('Do not save'),
}, {
id: 'dns_tablespace',
label: gettext('Tablespace'),
control: Backform.CustomSwitchControl,
disabled: false,
group: gettext('Do not save'),
}],
}, {
type: 'nested',
control: 'fieldset',
label: gettext('Queries'),
group: gettext('Restore options'),
schema: [{
id: 'include_create_database',
label: gettext('Include CREATE DATABASE statement'),
control: Backform.CustomSwitchControl,
disabled: false,
group: gettext('Queries'),
}, {
id: 'clean',
label: gettext('Clean before restore'),
control: Backform.CustomSwitchControl,
group: gettext('Queries'),
disabled: function() {
return this.node.type === 'function' ||
this.node.type === 'trigger_function';
},
}, {
id: 'single_transaction',
label: gettext('Single transaction'),
control: Backform.CustomSwitchControl,
disabled: false,
group: gettext('Queries'),
}],
}, {
type: 'nested',
control: 'fieldset',
label: gettext('Disable'),
group: gettext('Restore options'),
schema: [{
id: 'disable_trigger',
label: gettext('Trigger'),
control: Backform.CustomSwitchControl,
group: gettext('Disable'),
}, {
id: 'no_data_fail_table',
label: gettext('No data for Failed Tables'),
control: Backform.CustomSwitchControl,
disabled: false,
group: gettext('Disable'),
}],
}, {
type: 'nested',
control: 'fieldset',
label: gettext('Miscellaneous / Behavior'),
group: gettext('Restore options'),
schema: [{
id: 'verbose',
label: gettext('Verbose messages'),
control: Backform.CustomSwitchControl,
disabled: false,
group: gettext('Miscellaneous / Behavior'),
}, {
id: 'use_set_session_auth',
label: gettext('Use SET SESSION AUTHORIZATION'),
control: Backform.CustomSwitchControl,
disabled: false,
group: gettext('Miscellaneous / Behavior'),
}, {
id: 'exit_on_error',
label: gettext('Exit on error'),
control: Backform.CustomSwitchControl,
disabled: false,
group: gettext('Miscellaneous / Behavior'),
}],
}],
validate: function() {
return null;
},
});
// Create an Object Restore of pgBrowser class
pgBrowser.Restore = {
init: function() {
if (this.initialized)
return;
this.initialized = true;
// Define list of nodes on which restore context menu option appears
var restore_supported_nodes = [
'database', 'schema',
'table', 'function',
'trigger', 'index',
'partition',
];
/**
Enable/disable restore menu in tools based
on node selected
if selected node is present in supported_nodes,
menu will be enabled otherwise disabled.
Also, hide it for system view in catalogs
*/
var menu_enabled = function(itemData, item, data) {
var t = pgBrowser.tree,
i = item,
d = itemData;
var parent_item = t.hasParent(i) ? t.parent(i) : null,
parent_data = parent_item ? t.itemData(parent_item) : null;
if (!_.isUndefined(d) && !_.isNull(d) && !_.isNull(parent_data)) {
if (_.indexOf(restore_supported_nodes, d._type) !== -1 &&
is_parent_catalog(itemData, item, data)) {
if (d._type == 'database' && d.allowConn)
return true;
else if (d._type != 'database')
return true;
else
return false;
} else
return false;
} else
return false;
};
var is_parent_catalog = function(itemData, item) {
var t = pgBrowser.tree,
i = item,
d = itemData;
// To iterate over tree to check parent node
while (i) {
// If it is schema then allow user to restore
if (_.indexOf(['catalog'], d._type) > -1)
return false;
i = t.hasParent(i) ? t.parent(i) : null;
d = i ? t.itemData(i) : null;
}
// by default we do not want to allow create menu
return true;
};
// Define the nodes on which the menus to be appear
var menus = [{
name: 'restore_object',
module: this,
applies: ['tools'],
callback: 'restore_objects',
priority: 13,
label: gettext('Restore...'),
icon: 'fa fa-upload',
enable: menu_enabled,
}];
for (var idx = 0; idx < restore_supported_nodes.length; idx++) {
menus.push({
name: 'restore_' + restore_supported_nodes[idx],
node: restore_supported_nodes[idx],
module: this,
applies: ['context'],
callback: 'restore_objects',
priority: 13,
label: gettext('Restore...'),
icon: 'fa fa-upload',
enable: menu_enabled,
});
}
pgBrowser.add_menus(menus);
return this;
},
// Callback to draw Backup Dialog for objects
restore_objects: function(action, treeItem) {
var i = treeItem || 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('Restore Error'),
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('Restore Error'),
S(gettext('Failed to load preference %s of module %s')).sprintf(preference_name, module).value()
);
return;
}
var title = S(gettext('Restore (%s: %s)')),
tree = pgBrowser.tree,
item = treeItem || tree.selected(),
data = item && item.length == 1 && tree.itemData(item),
node = data && data._type && pgBrowser.Nodes[data._type];
if (!node)
return;
var treeInfo = node.getTreeNodeHierarchy.apply(node, [item]);
if (treeInfo.database._label.indexOf('=') >= 0) {
alertify.alert(
gettext('Restore error'),
gettext('Restore job creation failed. '+
'Databases with = symbols in the name cannot be restored using this utility.')
);
return;
}
title = title.sprintf(node.label, data.label).value();
if (!alertify.pg_restore) {
// Create Dialog title on the fly with node details
alertify.dialog('pg_restore', function factory() {
return {
main: function(title, item, data, node) {
this.set('title', title);
this.setting('pg_node', node);
this.setting('pg_item', item);
this.setting('pg_item_data', data);
},
build: function() {
alertify.pgDialogBuild.apply(this);
},
setup: function() {
return {
buttons: [{
text: '',
className: 'btn btn-default pull-left fa fa-lg fa-info',
attrs: {
name: 'object_help',
type: 'button',
url: 'backup.html',
label: gettext('Restore'),
},
}, {
text: '',
key: 112,
className: 'btn btn-default pull-left fa fa-lg fa-question',
attrs: {
name: 'dialog_help',
type: 'button',
label: gettext('Restore'),
url: url_for('help.static', {
'filename': 'restore_dialog.html',
}),
},
}, {
text: gettext('Restore'),
key: 13,
className: 'btn btn-primary fa fa-upload pg-alertify-button',
restore: true,
'data-btn-name': 'restore',
}, {
text: gettext('Cancel'),
key: 27,
className: 'btn btn-danger fa fa-lg fa-times pg-alertify-button',
restore: false,
'data-btn-name': 'cancel',
}],
// Set options for dialog
options: {
title: title,
//disable both padding and overflow control.
padding: !1,
overflow: !1,
model: 0,
resizable: true,
maximizable: true,
pinnable: false,
closableByDimmer: false,
modal: false,
},
};
},
hooks: {
// triggered when the dialog is closed
onclose: function() {
if (this.view) {
this.view.remove({
data: true,
internal: true,
silent: true,
});
}
},
},
settings: {
pg_node: null,
pg_item: null,
pg_item_data: null,
},
prepare: function() {
var self = this;
// Disable Backup button until user provides Filename
this.__internal.buttons[2].element.disabled = true;
var $container = $('<div class=\'restore_dialog\'></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 RestoreObjectModel({
node_data: node,
}, {
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();
this.elements.content.appendChild($container.get(0));
view.$el.attr('tabindex', -1);
// var dialogTabNavigator = pgBrowser.keyboardNavigation.getDialogTabNavigator(view);
pgBrowser.keyboardNavigation.getDialogTabNavigator(view);
var container = view.$el.find('.tab-content:first > .tab-pane.active:first');
commonUtils.findAndSetFocus(container);
// Listen to model & if filename is provided then enable Backup button
this.view.model.on('change', function() {
if (!_.isUndefined(this.get('file')) && this.get('file') !== '') {
this.errorModel.clear();
self.__internal.buttons[2].element.disabled = false;
} else {
self.__internal.buttons[2].element.disabled = true;
this.errorModel.set('file', gettext('Please provide filename'));
}
});
},
// Callback functions when click on the buttons of the Alertify dialogs
callback: function(e) {
// Fetch current server id
var t = pgBrowser.tree,
i = this.settings['pg_item'] || t.selected(),
d = this.settings['pg_item_data'] || (
i && i.length == 1 ? t.itemData(i) : undefined
),
node = this.settings['pg_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, e.button.element.getAttribute('label'));
return;
}
if (e.button['data-btn-name'] === 'restore') {
if (!d)
return;
var info = node.getTreeNodeHierarchy.apply(node, [i]),
m = this.view.model;
// Set current node info into model
m.set('database', info.database._label);
if (!m.get('custom')) {
switch (d._type) {
case 'schema':
m.set('schemas', [d._label]);
break;
case 'table':
m.set('schemas', [info.schema._label]);
m.set('tables', [d._label]);
break;
case 'function':
m.set('schemas', [info.schema._label]);
m.set('functions', [d._label]);
break;
case 'index':
m.set('schemas', [info.schema._label]);
m.set('indexes', [d._label]);
break;
case 'trigger':
m.set('schemas', [info.schema._label]);
m.set('triggers', [d._label]);
break;
case 'trigger_func':
m.set('schemas', [info.schema._label]);
m.set('trigger_funcs', [d._label]);
break;
}
} else {
// TODO::
// When we will implement the object selection in the
// import dialog, we will need to select the objects from
// the tree selection tab.
}
var self = this,
baseUrl = url_for('restore.create_job', {
'sid': info.server._id,
}),
args = this.view.model.toJSON();
$.ajax({
url: baseUrl,
method: 'POST',
data: {
'data': JSON.stringify(args),
},
success: function(res) {
if (res.success) {
alertify.success(
gettext('Restore job created.'), 5
);
pgBrowser.Events.trigger('pgadmin-bgprocess:created', self);
} else {
console.warn(res);
}
},
error: function(xhr) {
try {
var err = $.parseJSON(xhr.responseText);
alertify.alert(
gettext('Restore failed.'),
err.errormsg
);
} catch (e) {
console.warn(e.stack || e);
}
},
});
}
},
};
});
}
alertify.pg_restore(title, item, data, node).resizeTo('65%', '60%');
},
};
return pgBrowser.Restore;
});