diff --git a/TODO.txt b/TODO.txt index 17c473703..ed5109e98 100644 --- a/TODO.txt +++ b/TODO.txt @@ -31,3 +31,9 @@ Backup Object ------------- Allow to select/deselect objects under the object backup operation. + +Restore Object +------------- + +List down the objects within the backup file, and allow the user to +select/deselect the only objects, which user may want to restore. diff --git a/web/pgadmin/tools/backup/__init__.py b/web/pgadmin/tools/backup/__init__.py index 38df62afe..6b97552d7 100644 --- a/web/pgadmin/tools/backup/__init__.py +++ b/web/pgadmin/tools/backup/__init__.py @@ -9,17 +9,20 @@ """Implements Backup Utility""" +import cgi import json import os + from flask import render_template, request, current_app, \ url_for, Response from flask.ext.babel import gettext as _ -from pgadmin.utils.ajax import make_json_response, bad_request -from pgadmin.utils import PgAdminModule, get_storage_directory from flask.ext.security import login_required, current_user -from pgadmin.model import Server + from config import PG_DEFAULT_DRIVER from pgadmin.misc.bgprocess.processes import BatchProcess, IProcessDesc +from pgadmin.model import Server +from pgadmin.utils.ajax import make_json_response, bad_request +from pgadmin.utils import PgAdminModule, get_storage_directory # set template path for sql scripts @@ -77,9 +80,10 @@ class BackupMessage(IProcessDesc): Defines the message shown for the backup operation. """ - def __init__(self, _type, _sid, **kwargs): + def __init__(self, _type, _sid, _bfile, **kwargs): self.backup_type = _type self.sid = _sid + self.bfile = _bfile self.database = None if 'database' in kwargs: @@ -120,28 +124,36 @@ class BackupMessage(IProcessDesc): res = '
' if self.backup_type == BACKUP.OBJECT: - res += _( - "Backing up an object on the server - '{0}' on database '{1}'" - ).format( - "{0} ({1}:{2})".format(s.name, s.host, s.port), - self.database + res += cgi.escape( + _( + "Backing up an object on the server - '{0}' on database '{1}'" + ).format( + "{0} ({1}:{2})".format(s.name, s.host, s.port), + self.database + ) ).encode('ascii', 'xmlcharrefreplace') if self.backup_type == BACKUP.GLOBALS: - res += _("Backing up the globals for the server - '{0}'!").format( - "{0} ({1}:{2})".format(s.name, s.host, s.port) + res += cgi.escape( + _("Backing up the globals for the server - '{0}'").format( + "{0} ({1}:{2})".format(s.name, s.host, s.port) + ) ).encode('ascii', 'xmlcharrefreplace') elif self.backup_type == BACKUP.SERVER: - res += _("Backing up the server - '{0}'!").format( - "{0} ({1}:{2})".format(s.name, s.host, s.port) + res += cgi.escape( + _("Backing up the server - '{0}'").format( + "{0} ({1}:{2})".format(s.name, s.host, s.port) + ) ).encode('ascii', 'xmlcharrefreplace') else: # It should never reach here. res += "Backup" res += '
' - res += _("Running command:").encode('ascii', 'xmlcharrefreplace') - res += '
' - res += cmd.encode('ascii', 'xmlcharrefreplace') + res += cgi.escape( + _("Running command:") + ).encode('ascii', 'xmlcharrefreplace') + res += '
' + res += cgi.escape(cmd).encode('ascii', 'xmlcharrefreplace') replace_next = False @@ -151,7 +163,9 @@ class BackupMessage(IProcessDesc): x = x.replace('"', '\\"') x = x.replace('""', '\\"') - return ' "' + x.encode('ascii', 'xmlcharrefreplace') + '"' + return ' "' + cgi.escape(x).encode( + 'ascii', 'xmlcharrefreplace' + ) + '"' return '' @@ -159,12 +173,14 @@ class BackupMessage(IProcessDesc): if arg and len(arg) >= 2 and arg[:2] == '--': res += ' ' + arg elif replace_next: - res += ' XXX' + res += ' "' + cgi.escape( + os.path.join('', self.bfile) + ).encode('ascii', 'xmlcharrefreplace') + '"' else: if arg == '--file': replace_next = True res += cmdArg(arg) - res += '
' + res += '' return res @@ -197,8 +213,12 @@ def filename_with_file_manager_path(file): Filename to use for backup with full path taken from preference """ # Set file manager directory from preference - file_manager_dir = get_storage_directory() - return os.path.join(file_manager_dir, file) + storage_dir = get_storage_directory() + + if storage_dir: + return os.path.join(storage_dir, file) + + return file @blueprint.route('/create_job/', methods=['POST']) @@ -220,7 +240,7 @@ def create_backup_job(sid): else: data = json.loads(request.data.decode()) - data['file'] = filename_with_file_manager_path(data['file']) + backup_file = filename_with_file_manager_path(data['file']) # Fetch the server details like hostname, port, roles etc server = Server.query.filter_by( @@ -250,7 +270,7 @@ def create_backup_job(sid): args = [ '--file', - data['file'], + backup_file, '--host', server.host, '--port', @@ -275,7 +295,7 @@ def create_backup_job(sid): p = BatchProcess( desc=BackupMessage( BACKUP.SERVER if data['type'] != 'global' else BACKUP.GLOBALS, - sid + sid, data['file'] ), cmd=utility, args=args ) @@ -313,7 +333,7 @@ def create_backup_objects_job(sid): else: data = json.loads(request.data.decode()) - data['file'] = filename_with_file_manager_path(data['file']) + backup_file = filename_with_file_manager_path(data['file']) # Fetch the server details like hostname, port, roles etc server = Server.query.filter_by( @@ -342,7 +362,7 @@ def create_backup_objects_job(sid): utility = manager.utility('backup') args = [ '--file', - data['file'], + backup_file, '--host', server.host, '--port', @@ -353,7 +373,7 @@ def create_backup_objects_job(sid): ] def set_param(key, param): - if key in data: + if key in data and data[key]: args.append(param) def set_value(key, param, value): @@ -420,8 +440,7 @@ def create_backup_objects_job(sid): try: p = BatchProcess( desc=BackupMessage( - BACKUP.OBJECT, - sid, database=data['database'] + BACKUP.OBJECT, sid, data['file'], database=data['database'] ), cmd=utility, args=args) p.start() diff --git a/web/pgadmin/tools/backup/templates/backup/js/backup.js b/web/pgadmin/tools/backup/templates/backup/js/backup.js index ce70d53e5..082a6dab0 100644 --- a/web/pgadmin/tools/backup/templates/backup/js/backup.js +++ b/web/pgadmin/tools/backup/templates/backup/js/backup.js @@ -483,8 +483,13 @@ TODO LIST FOR BACKUP: data:{ 'data': JSON.stringify(args) }, success: function(res) { if (res.success) { - alertify.message('{{ _('Background process for taking backup has been created!') }}', 1); + alertify.message( + '{{ _('Background process for taking backup has been created!') }}', + 5 + ); pgBrowser.Events.trigger('pgadmin-bgprocess:created', self); + } else { + console.log(res); } }, error: function(xhr, status, error) { diff --git a/web/pgadmin/tools/restore/__init__.py b/web/pgadmin/tools/restore/__init__.py new file mode 100644 index 000000000..ab04107ec --- /dev/null +++ b/web/pgadmin/tools/restore/__init__.py @@ -0,0 +1,320 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2016, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""Implements Restore Utility""" + +import cgi +import json +import os + +from flask import render_template, request, current_app, \ + url_for, Response +from flask.ext.security import login_required, current_user +from flask.ext.babel import gettext as _ + +from config import PG_DEFAULT_DRIVER +from pgadmin.model import Server +from pgadmin.misc.bgprocess.processes import BatchProcess, IProcessDesc +from pgadmin.utils.ajax import make_json_response, bad_request +from pgadmin.utils import PgAdminModule, get_storage_directory + +# set template path for sql scripts +MODULE_NAME = 'restore' +server_info = {} + + +class RestoreModule(PgAdminModule): + """ + class RestoreModule(Object): + + It is a utility which inherits PgAdminModule + class and define methods to load its own + javascript file. + """ + + LABEL = _('Restore') + + def get_own_javascripts(self): + """" + Returns: + list: js files used by this module + """ + return [{ + 'name': 'pgadmin.tools.restore', + 'path': url_for('restore.index') + 'restore', + 'when': None + }] + +# Create blueprint for RestoreModule class +blueprint = RestoreModule( + MODULE_NAME, __name__, static_url_path='' +) + + +class RestoreMessage(IProcessDesc): + + def __init__(self, _sid, _bfile): + self.sid = _sid + self.bfile = _bfile + + @property + def message(self): + # Fetch the server details like hostname, port, roles etc + s = Server.query.filter_by( + id=self.sid, user_id=current_user.id + ).first() + + return _("Restoring backup on the server - '{0}'").format( + "{0} ({1}:{2})".format(s.name, s.host, s.port), + ) + + def details(self, cmd, args): + # Fetch the server details like hostname, port, roles etc + s = Server.query.filter_by( + id=self.sid, user_id=current_user.id + ).first() + + res = '
' + + res += cgi.escape( + _( + "Restoring the backup on the server - '{0}'" + ).format( + "{0} ({1}:{2})".format(s.name, s.host, s.port) + ) + ).encode('ascii', 'xmlcharrefreplace') + + res += '
' + res += cgi.escape( + _("Running command:") + ).encode('ascii', 'xmlcharrefreplace') + res += '
' + res += cgi.escape(cmd).encode('ascii', 'xmlcharrefreplace') + + def cmdArg(x): + if x: + x = x.replace('\\', '\\\\') + x = x.replace('"', '\\"') + x = x.replace('""', '\\"') + + return ' "' + cgi.escape(x).encode( + 'ascii', 'xmlcharrefreplace' + ) + '"' + + return '' + + idx = 0 + no_args = len(args) + for arg in args: + if idx < no_args - 1: + if arg[:2] == '--': + res += ' ' + arg + else: + res += cmdArg(arg) + idx += 1 + + if no_args > 1: + res += ' "' + cgi.escape( + os.path.join('', self.bfile) + '"' + ).encode('ascii', 'xmlcharrefreplace') + + res += '
' + + return res + + +@blueprint.route("/") +@login_required +def index(): + return bad_request(errormsg=_("This URL can not be called directly!")) + + +@blueprint.route("/restore.js") +@login_required +def script(): + """render own javascript""" + return Response( + response=render_template( + "restore/js/restore.js", _=_ + ), + status=200, + mimetype="application/javascript" + ) + + +def filename_with_file_manager_path(file): + """ + Args: + file: File name returned from client file manager + + Returns: + Filename to use for backup with full path taken from preference + """ + # Set file manager directory from preference + storage_dir = get_storage_directory() + + if storage_dir: + return os.path.join(storage_dir, file) + + return file + + +@blueprint.route('/create_job/', methods=['POST']) +@login_required +def create_restore_job(sid): + """ + Args: + sid: Server ID + + Creates a new job for restore task + + Returns: + None + """ + if request.form: + # Convert ImmutableDict to dict + data = dict(request.form) + data = json.loads(data['data'][0]) + else: + data = json.loads(request.data.decode()) + + backup_file = filename_with_file_manager_path(data['file']) + + # Fetch the server details like hostname, port, roles etc + server = Server.query.filter_by( + id=sid + ).first() + + if server is None: + return make_json_response( + success=0, + errormsg=_("Couldn't find the given server") + ) + + # To fetch MetaData for the server + from pgadmin.utils.driver import get_driver + + driver = get_driver(PG_DEFAULT_DRIVER) + manager = driver.connection_manager(server.id) + conn = manager.connection() + connected = conn.connected() + + if not connected: + return make_json_response( + success=0, + errormsg=_("Please connect to the server first...") + ) + + utility = manager.utility('restore') + + args = [] + + if 'list' in data: + args.append('--list') + else: + def set_param(key, param): + if key in data and data[key]: + args.append(param) + return True + return False + + def set_value(key, param, value): + if key in data: + args.append(param) + if value: + if value is True: + args.append(data[key]) + else: + args.append(value) + return True + return False + + def set_multiple(key, param, with_schema=True): + if key in data: + data[key] = json.loads(data[key]) + if len(data[key]) > 0: + if with_schema: + for s, o in data[key]: + args.extend([ + param, + driver.qtIdent( + conn, s + ) + '.' + driver.qtIdent(conn, o) + ]) + else: + for o in data[key]: + args.extend([param, o]) + return True + return False + + args.extend([ + '--host', server.host, '--port', server.port, + '--username', server.username, '--no-password' + ]) + + set_value('role', '--role', True) + set_value('database', '--dbname', True) + + if data['format'] == 'directory': + args.extend(['--format', 'directory']) + + set_value('pre_data', '--section', 'pre-data') + set_value('data', '--section', 'data') + set_value('post_data', '--section', 'post-data') + + if not set_param('only_data', '--data-only'): + set_param('dns_owner', '--no-owner') + set_param('dns_privilege ', '--no-privileges') + set_param('dns_tablespace', '--no-tablespaces') + + if not set_param('only_schema', '--schema-only'): + set_param('disable_trigger', '--disable-triggers') + + set_param('include_create_database', '--create') + set_param('clean', '--clean') + set_param('single_transaction', '--single-transaction') + set_param('no_data_fail_table ', '--no-data-for-failed-tables') + set_param('use_set_session_auth ', '--use-set-session-authorization') + set_param('exit_on_error', '--exit-on-error') + + set_value('no_of_jobs', '--jobs', True) + set_param('verbose', '--verbose') + + set_multiple('schemas', '--schema', False) + set_multiple('tables', '--table') + set_multiple('functions', '--function') + set_multiple('triggers', '--trigger') + set_multiple('trigger_funcs', '--function') + set_multiple('indexes', '--index') + + args.append(backup_file) + + try: + p = BatchProcess( + desc=RestoreMessage(sid, data['file']), + cmd=utility, args=args + ) + p.start() + jid = p.id + except Exception as e: + current_app.logger.exception(e) + return make_json_response( + status=410, + success=0, + errormsg=str(e) + ) + # Return response + return make_json_response( + data={'job_id': jid, 'Success': 1} + ) + +""" +TODO:// + Add browser tree +""" diff --git a/web/pgadmin/tools/restore/templates/restore/js/restore.js b/web/pgadmin/tools/restore/templates/restore/js/restore.js new file mode 100644 index 000000000..787f14bb6 --- /dev/null +++ b/web/pgadmin/tools/restore/templates/restore/js/restore.js @@ -0,0 +1,477 @@ +define([ + 'jquery', 'underscore', 'underscore.string', 'alertify', + 'pgadmin.browser', 'backbone', 'backgrid', 'backform', + 'pgadmin.browser.node' + ], + + // This defines Restore dialog + function($, _, S, alertify, pgBrowser, Backbone, Backgrid, Backform, pgNode) { + + // if module is already initialized, refer to that. + if (pgBrowser.Restore) { + return pgBrowser.Restore; + } + + var CustomSwitchControl = Backform.CustomSwitchControl = Backform.SwitchControl.extend({ + template: _.template([ + '', + '
', + '
', + ' ', + '
', + '
', + '<% if (helpMessage && helpMessage.length) { %>', + ' <%=helpMessage%>', + '<% } %>' + ].join("\n")), + className: 'pgadmin-control-group form-group col-xs-6' + }); + + //Restore Model (Objects like Database/Schema/Table) + var RestoreObjectModel = Backbone.Model.extend({ + idAttribute: 'id', + defaults: { + custom: false, + file: undefined, + role: null, + format: 'Custom or tar', + 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, args) { + // 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: '{{ _('Format') }}', + type: 'text', disabled: false, + control: 'select2', select2: { + allowClear: false, + width: "100%" + }, + options: [ + {label: "Custom or tar", value: "custom"}, + {label: "Directory", value: "directory"} + ] + },{ + id: 'file', label: '{{ _('Filename') }}', + type: 'text', disabled: false, control: Backform.FileControl, + dialog_type: 'select_file', supp_types: ['*', 'backup','sql', 'patch'] + },{ + id: 'no_of_jobs', label: '{{ _('Number of jobs') }}', + type: 'int' + },{ + id: 'role', label: '{{ _('Role name') }}', + control: 'node-list-by-name', node: 'role', + select2: { allowClear: false } + },{ + type: 'nested', control: 'fieldset', label: '{{ _('Sections') }}', + group: '{{ _('Restore options') }}', + schema:[{ + id: 'pre_data', label: '{{ _('Pre-data') }}', + control: Backform.CustomSwitchControl, group: '{{ _('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: '{{ _('Data') }}', + control: Backform.CustomSwitchControl, group: '{{ _('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: '{{ _('Post-data') }}', + control: Backform.CustomSwitchControl, group: '{{ _('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: '{{ _('Type of objects') }}', + group: '{{ _('Restore options') }}', + schema:[{ + id: 'only_data', label: '{{ _('Only data') }}', + control: Backform.CustomSwitchControl, group: '{{ _('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: '{{ _('Only schema') }}', + control: Backform.CustomSwitchControl, group: '{{ _('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: '{{ _('Do not save') }}', + group: '{{ _('Restore options') }}', + schema:[{ + id: 'dns_owner', label: '{{ _('Owner') }}', + control: Backform.CustomSwitchControl, disabled: false, group: '{{ _('Do not save') }}' + },{ + id: 'dns_privilege', label: '{{ _('Privilege') }}', + control: Backform.CustomSwitchControl, disabled: false, group: '{{ _('Do not save') }}' + },{ + id: 'dns_tablespace', label: '{{ _('Tablespace') }}', + control: Backform.CustomSwitchControl, disabled: false, group: '{{ _('Do not save') }}' + }] + },{ + type: 'nested', control: 'fieldset', label: '{{ _('Queries') }}', + group: '{{ _('Restore options') }}', + schema:[{ + id: 'include_create_database', label: '{{ _('Include CREATE DATABASE statement') }}', + control: Backform.CustomSwitchControl, disabled: false, group: '{{ _('Queries') }}' + },{ + id: 'clean', label: '{{ _('Clean before restore') }}', + control: Backform.CustomSwitchControl, group: '{{ _('Queries') }}', + disabled: function(m) { + return this.node.type === 'function' || + this.node.type === 'trigger_function'; + } + },{ + id: 'single_transaction', label: '{{ _('Single transaction') }}', + control: Backform.CustomSwitchControl, disabled: false, group: '{{ _('Queries') }}' + }] + },{ + type: 'nested', control: 'fieldset', label: '{{ _('Disable') }}', + group: '{{ _('Restore options') }}', + schema:[{ + id: 'disable_trigger', label: '{{ _('Trigger') }}', + control: Backform.CustomSwitchControl, group: '{{ _('Disable') }}' + },{ + id: 'no_data_fail_table', label: '{{ _('No data for Failed Tables') }}', + control: Backform.CustomSwitchControl, disabled: false, group: '{{ _('Disable') }}' + }] + },{ + type: 'nested', control: 'fieldset', label: '{{ _('Miscellaneous / Behavior') }}', + group: '{{ _('Restore options') }}', + schema:[{ + id: 'verbose', label: '{{ _('Verbose messages') }}', + control: Backform.CustomSwitchControl, disabled: false, + group: '{{ _('Miscellaneous / Behavior') }}' + },{ + id: 'use_set_session_auth', label: '{{ _('Use SET SESSION AUTHORIZATION') }}', + control: Backform.CustomSwitchControl, disabled: false, + group: '{{ _('Miscellaneous / Behavior') }}' + },{ + id: 'exit_on_error', label: '{{ _('Exit on error') }}', + control: Backform.CustomSwitchControl, disabled: false, + group: '{{ _('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' + ]; + + /** + 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 + */ + 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)) + return ( + (_.indexOf(restore_supported_nodes, d._type) !== -1 && + is_parent_catalog(itemData, item, data) ) ? true: false + ); + else + return false; + }; + + is_parent_catalog = function(itemData, item, data) { + 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: 9, label: '{{_("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: 9, label: '{{_("Restore...") }}', + icon: 'fa fa-upload', enable: menu_enabled + }); + } + + pgAdmin.Browser.add_menus(menus); + return this; + }, + // Callback to draw Backup Dialog for objects + restore_objects: function(action, treeItem) { + var title = '{{ _('Restore') }}', + 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; + + 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); + }, + setup:function() { + return { + buttons: [{ + text: '{{ _('Restore') }}', key: 27, + className: 'btn btn-primary', restore: true + },{ + text: '{{ _('Cancel') }}', key: 27, + className: 'btn btn-danger', restore: false + }], + // 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 + } + }; + }, + 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[0].element.disabled = true; + 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 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)); + + // 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[0].element.disabled = false; + } else { + self.__internal.buttons[0].element.disabled = true; + this.errorModel.set('file', '{{ _('Please provide filename') }}') + } + }); + + }, + // Callback functions when click on the buttons of the Alertify dialogs + callback: function(e) { + if (e.button.restore) { + // 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 (!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('tables', [info.schema.label, d.label]); + break; + case 'function': + m.set('functions', [info.schema.label, d.label]); + break; + case 'index': + m.set('indexes', [info.schema.label, d.label]); + break; + case 'trigger': + m.set('triggers', [info.schema.label, d.label]); + break; + case 'trigger_func': + m.set('trigger_funcs', [info.schema.label, 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.index') }}create_job/" + + 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.message( + '{{ _('Restore job created!') }}', 5 + ); + pgBrowser.Events.trigger('pgadmin-bgprocess:created', self); + } else { + console.log(res); + } + }, + error: function(xhr, status, error) { + try { + var err = $.parseJSON(xhr.responseText); + alertify.alert( + '{{ _('Backup failed...') }}', + err.errormsg + ); + } catch (e) {} + } + }); + } + } + }; + }); + } + + alertify.pg_restore(title, item, data, node).resizeTo('65%','60%'); + } + }; + return pgBrowser.Restore; + });