public inbox for [email protected]
help / color / mirror / Atom feedPATCH: Initiale for "Restore Utility" [pgAdmin4]
3+ messages / 2 participants
[nested] [flat]
* PATCH: Initiale for "Restore Utility" [pgAdmin4]
@ 2016-05-06 13:26 Murtuza Zabuawala <[email protected]>
0 siblings, 0 replies; 3+ messages in thread
From: Murtuza Zabuawala @ 2016-05-06 13:26 UTC (permalink / raw)
To: pgadmin-hackers
Hi,
PFA initial patch for restore utility.
*Note:* This patch depends on File manager patch.
--
Regards,
Murtuza Zabuawala
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers
Attachments:
[application/octet-stream] Restore_tool_v1.patch (26.7K, 3-Restore_tool_v1.patch)
download | inline diff:
diff --git a/web/pgadmin/tools/restore/__init__.py b/web/pgadmin/tools/restore/__init__.py
new file mode 100644
index 0000000..7596da9
--- /dev/null
+++ b/web/pgadmin/tools/restore/__init__.py
@@ -0,0 +1,207 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Implements Restore Utility"""
+
+import json
+import os
+from random import randint
+from flask import render_template, request, current_app, \
+ url_for, Response
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_json_response, \
+ internal_server_error, bad_request
+from pgadmin.utils import PgAdminModule
+from flask.ext.security import login_required
+from pgadmin.model import db, Jobs, Server
+from config import PG_DEFAULT_DRIVER, UTILITIES
+from pgadmin.utils.preferences import Preferences
+from pgadmin.tools.backup import _format_qtIdents
+
+# 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 = gettext('Binary Paths')
+ """
+
+ LABEL = gettext('Utilities')
+
+ 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
+ }]
+
+ def register_preferences(self):
+ """
+ Get storage directory preference
+ """
+ self.storage_directory = Preferences.module('file_manager')
+ self.storage_dir = self.storage_directory.preference(
+ 'storage_dir'
+ )
+
+ self.bin_path = Preferences.module('backup')
+ # Register 'EDB specific utility binary directory' preference
+ self.edb_utility_dir = self.bin_path.preference(
+ 'edb_utilities_bin_dir'
+ )
+
+ # Register 'PG specific utility binary directory' preference
+ self.pg_utility_dir = self.bin_path.preference(
+ 'pg_utilities_bin_dir'
+ )
+
+# Create blueprint for RestoreModule class
+blueprint = RestoreModule(
+ MODULE_NAME, __name__, static_url_path='')
+
+
[email protected]("/")
+@login_required
+def index():
+ return bad_request(errormsg=gettext("This URL can not be called directly!"))
+
+
[email protected]("/restore.js")
+@login_required
+def script():
+ """render own javascript"""
+ return Response(response=render_template(
+ "restore/js/restore.js", _=gettext),
+ status=200,
+ mimetype="application/javascript")
+
+
+def utility_with_bin_path(server_type):
+ """
+ Args:
+ server_type: Server type (PG/PPAS)
+
+ Returns:
+ Utility to use for restore with full path taken from preference
+ """
+ if server_type == 'ppas':
+ return os.path.join(blueprint.edb_utility_dir.get(),
+ UTILITIES['EDB_RESTORE'])
+ elif server_type == 'pg':
+ return os.path.join(blueprint.pg_utility_dir.get(),
+ UTILITIES['PG_RESTORE'])
+
+
+def filename_with_file_manager_path(file):
+ """
+ Args:
+ file: File name returned from client file manager
+
+ Returns:
+ Filename to use for restore with full path taken from preference
+ """
+ # Set file manager directory from preference
+ file_manager_dir = blueprint.storage_dir.get()
+ return os.path.join(file_manager_dir, file)
+
+
[email protected]('/create_job/<int:sid>', 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())
+
+ data['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=gettext("Couldn't find the given server")
+ )
+
+ # To fetch MetaData for the server
+ from pgadmin.utils.driver import get_driver
+ manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(server.id)
+ conn = manager.connection()
+ connected = conn.connected()
+
+ if not connected:
+ return make_json_response(
+ success=0,
+ errormsg=gettext("Please connect to the server first...")
+ )
+
+ utility = utility_with_bin_path(manager.server_type)
+
+ arguments = render_template(
+ 'arguments/restore_objects.args',
+ server=server,
+ data=data,
+ first_time=False
+ )
+
+ arguments = _format_qtIdents(arguments)
+
+ try:
+ # Generate random job id
+ jid = randint(2001, 3000)
+ create_job = Jobs(
+ job_id=jid,
+ command=utility,
+ arguments=arguments
+ )
+ # Save it
+ db.session.add(create_job)
+ db.session.commit()
+
+ 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
+"""
\ No newline at end of file
diff --git a/web/pgadmin/tools/restore/templates/arguments/restore_objects.args b/web/pgadmin/tools/restore/templates/arguments/restore_objects.args
new file mode 100644
index 0000000..3e57750
--- /dev/null
+++ b/web/pgadmin/tools/restore/templates/arguments/restore_objects.args
@@ -0,0 +1,33 @@
+--host {{server.host}} --port {{server.port}} --username "{{conn|qtIdent(server.username)}}" --role {% if data.role %}
+"{{ conn|qtIdent(data.role) }}" {% else %}"{{ conn|qtIdent(server.role) }}" {% endif %}--no-password --dbname "{{
+conn|qtIdent(data.database) }}" {% if data.format == 'Directory' %}--format directory
+ {% endif %}{% if data.pre_data %}
+--section pre-data {% endif %}{% if data.data %}
+--section data {% endif %}{% if data.post_data %}
+--section post-data {% endif %}{% if data.only_data %}
+--data-only {% else %}
+{% if data.dns_owner %}
+--no-owner {% endif %}{% if data.dns_privilege %}
+--no-privileges {% endif %}{% if data.dns_tablespace %}
+--no-tablespaces {% endif %}
+{% endif %}{% if data.only_schema %}
+--schema-only {% else %}
+{% if data.disable_trigger %}--disable-triggers {% endif %}
+{% endif %}{% if data.include_create_database %}
+--create {% endif %}{% if data.clean %}
+--clean {% endif %}{% if data.single_transaction %}
+--single-transaction {% endif %}{% if data.no_data_fail_table %}
+--no-data-for-failed-tables {% endif %}{% if data.use_set_session_auth %}
+--use-set-session-authorization {% endif %}{% if data.exit_on_error %}
+--exit-on-error {% endif %}{% if data.no_of_jobs %}
+--jobs {{ data.no_of_jobs }} {% endif %}{% if data.verbose %}
+--verbose {% endif %}{% if data.schemas %}
+--schema {{ conn|qtIdent(schemas) }} {% endif %}{% if data.tables %}
+--table {{ conn|qtIdent(tables) }} {% endif %}{% if data.function_node %}
+--function {{ conn|qtIdent(function_node) }} {% endif %}{% if data.trigger_node %}
+--trigger {{ conn|qtIdent(trigger_node) }} {% endif %}{% if data.trigger_function_node %}
+--function {{ conn|qtIdent(trigger_function_node) }} {% endif %}{% if data.index_node %}
+--index {{ conn|qtIdent(index_node) }} {% endif %}{% if data.file %}
+"{{data.file}}"
+{% endif %}{% if first_time %}
+--list {% endif %}
\ No newline at end of file
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 0000000..0d68f4f
--- /dev/null
+++ b/web/pgadmin/tools/restore/templates/restore/js/restore.js
@@ -0,0 +1,442 @@
+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([
+ '<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 col-xs-6'
+ });
+
+ //Restore Model (Objects like Database/Schema/Table)
+ var RestoreObjectModel = Backbone.Model.extend({
+ idAttribute: 'id',
+ defaults: {
+ file: undefined,
+ role: 'postgres',
+ format: 'Custom or tar',
+ verbose: true,
+ blobs: true,
+ encoding: undefined,
+ database: undefined,
+ schemas: undefined,
+ tables: undefined,
+ function_node: undefined,
+ trigger_node: undefined,
+ trigger_function_node: undefined,
+ index_node: 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 or tar"},
+ {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') }}'
+ }]
+ },{
+ id: 'todo', label: '{{ _('TODO') }}', text: '{{ _('Add Objects selection tree here') }}',
+ type: 'note', group: '{{ _('Objects') }}'
+ }],
+ 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.restore_objects) {
+ // Create Dialog title on the fly with node details
+ alertify.dialog('restore_objects' ,function factory() {
+ return {
+ main: function(title) {
+ this.set('title', title);
+ },
+ setup:function() {
+ return {
+ buttons: [{
+ text: '{{ _('Restore') }}', key: 27, className: 'btn btn-primary'
+ },{
+ text: '{{ _('Cancel') }}', key: 27, className: 'btn btn-danger'
+ }],
+ // 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});
+ }
+ }
+ },
+ prepare: function() {
+ var self = this;
+ // Disable Backup button until user provides Filename
+ this.__internal.buttons[0].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));
+
+ // 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.text === "Restore") {
+ // Fetch current server id
+ 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]);
+ // Set current node info into model
+ this.view.model.set('database', treeInfo.database.label);
+ if (treeInfo.schema)
+ this.view.model.set('schema', treeInfo.schema.label);
+ if (treeInfo.table)
+ this.view.model.set('table', treeInfo.table.label);
+ if (treeInfo.function)
+ this.view.model.set('function', treeInfo.function.label);
+ if (treeInfo.index)
+ this.view.model.set('index', treeInfo.index.label);
+ if (treeInfo.trigger)
+ this.view.model.set('trigger', treeInfo.trigger.label);
+ if (treeInfo.trigger_function)
+ this.view.model.set('trigger_function', treeInfo.trigger_function.label);
+
+ var self = this,
+ baseUrl = "{{ url_for('restore.index') }}" +
+ "create_job/" + treeInfo.server._id,
+ args = this.view.model.toJSON();
+
+ $.ajax({
+ url: baseUrl,
+ method: 'POST',
+ data:{ 'data': JSON.stringify(args) },
+ success: function(res) {
+ var msg = alertify.message('{{ _('Restore job created (Click for more details)') }}', 10);
+ msg.callback = function (isClicked) {
+ if(isClicked)
+ console.log('Show detailed logs >>' + res);
+ }
+ },
+ error: function(xhr, status, error) {
+ try {
+ var err = $.parseJSON(xhr.responseText);
+ alertify.alert(
+ '{{ _('Backup failed...') }}',
+ err.errormsg
+ );
+ } catch (e) {}
+ }
+ });
+ }
+ }
+ };
+ });
+ }
+ alertify.restore_objects(title).resizeTo('65%','60%');
+ }
+ };
+ return pgBrowser.Restore;
+ });
\ No newline at end of file
^ permalink raw reply [nested|flat] 3+ messages in thread
* Re: PATCH: Initiale for "Restore Utility" [pgAdmin4]
@ 2016-05-06 14:30 Murtuza Zabuawala <[email protected]>
2016-05-15 17:03 ` Re: PATCH: Initiale for "Restore Utility" [pgAdmin4] Ashesh Vashi <[email protected]>
0 siblings, 1 reply; 3+ messages in thread
From: Murtuza Zabuawala @ 2016-05-06 14:30 UTC (permalink / raw)
To: pgadmin-hackers
Hi,
PFA updated patch, I have fixed typo in argument template.
--
Regards,
Murtuza Zabuawala
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Fri, May 6, 2016 at 6:56 PM, Murtuza Zabuawala <
[email protected]> wrote:
> Hi,
>
> PFA initial patch for restore utility.
>
> *Note:* This patch depends on File manager patch.
>
>
> --
> Regards,
> Murtuza Zabuawala
> EnterpriseDB: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>
--
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers
Attachments:
[application/octet-stream] Restore_tool_v2.patch (26.7K, 3-Restore_tool_v2.patch)
download | inline diff:
diff --git a/web/pgadmin/tools/restore/__init__.py b/web/pgadmin/tools/restore/__init__.py
new file mode 100644
index 0000000..7596da9
--- /dev/null
+++ b/web/pgadmin/tools/restore/__init__.py
@@ -0,0 +1,207 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Implements Restore Utility"""
+
+import json
+import os
+from random import randint
+from flask import render_template, request, current_app, \
+ url_for, Response
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_json_response, \
+ internal_server_error, bad_request
+from pgadmin.utils import PgAdminModule
+from flask.ext.security import login_required
+from pgadmin.model import db, Jobs, Server
+from config import PG_DEFAULT_DRIVER, UTILITIES
+from pgadmin.utils.preferences import Preferences
+from pgadmin.tools.backup import _format_qtIdents
+
+# 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 = gettext('Binary Paths')
+ """
+
+ LABEL = gettext('Utilities')
+
+ 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
+ }]
+
+ def register_preferences(self):
+ """
+ Get storage directory preference
+ """
+ self.storage_directory = Preferences.module('file_manager')
+ self.storage_dir = self.storage_directory.preference(
+ 'storage_dir'
+ )
+
+ self.bin_path = Preferences.module('backup')
+ # Register 'EDB specific utility binary directory' preference
+ self.edb_utility_dir = self.bin_path.preference(
+ 'edb_utilities_bin_dir'
+ )
+
+ # Register 'PG specific utility binary directory' preference
+ self.pg_utility_dir = self.bin_path.preference(
+ 'pg_utilities_bin_dir'
+ )
+
+# Create blueprint for RestoreModule class
+blueprint = RestoreModule(
+ MODULE_NAME, __name__, static_url_path='')
+
+
[email protected]("/")
+@login_required
+def index():
+ return bad_request(errormsg=gettext("This URL can not be called directly!"))
+
+
[email protected]("/restore.js")
+@login_required
+def script():
+ """render own javascript"""
+ return Response(response=render_template(
+ "restore/js/restore.js", _=gettext),
+ status=200,
+ mimetype="application/javascript")
+
+
+def utility_with_bin_path(server_type):
+ """
+ Args:
+ server_type: Server type (PG/PPAS)
+
+ Returns:
+ Utility to use for restore with full path taken from preference
+ """
+ if server_type == 'ppas':
+ return os.path.join(blueprint.edb_utility_dir.get(),
+ UTILITIES['EDB_RESTORE'])
+ elif server_type == 'pg':
+ return os.path.join(blueprint.pg_utility_dir.get(),
+ UTILITIES['PG_RESTORE'])
+
+
+def filename_with_file_manager_path(file):
+ """
+ Args:
+ file: File name returned from client file manager
+
+ Returns:
+ Filename to use for restore with full path taken from preference
+ """
+ # Set file manager directory from preference
+ file_manager_dir = blueprint.storage_dir.get()
+ return os.path.join(file_manager_dir, file)
+
+
[email protected]('/create_job/<int:sid>', 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())
+
+ data['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=gettext("Couldn't find the given server")
+ )
+
+ # To fetch MetaData for the server
+ from pgadmin.utils.driver import get_driver
+ manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(server.id)
+ conn = manager.connection()
+ connected = conn.connected()
+
+ if not connected:
+ return make_json_response(
+ success=0,
+ errormsg=gettext("Please connect to the server first...")
+ )
+
+ utility = utility_with_bin_path(manager.server_type)
+
+ arguments = render_template(
+ 'arguments/restore_objects.args',
+ server=server,
+ data=data,
+ first_time=False
+ )
+
+ arguments = _format_qtIdents(arguments)
+
+ try:
+ # Generate random job id
+ jid = randint(2001, 3000)
+ create_job = Jobs(
+ job_id=jid,
+ command=utility,
+ arguments=arguments
+ )
+ # Save it
+ db.session.add(create_job)
+ db.session.commit()
+
+ 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
+"""
\ No newline at end of file
diff --git a/web/pgadmin/tools/restore/templates/arguments/restore_objects.args b/web/pgadmin/tools/restore/templates/arguments/restore_objects.args
new file mode 100644
index 0000000..1920f98
--- /dev/null
+++ b/web/pgadmin/tools/restore/templates/arguments/restore_objects.args
@@ -0,0 +1,33 @@
+--host {{server.host}} --port {{server.port}} --username "{{conn|qtIdent(server.username)}}" --role {% if data.role %}
+"{{ conn|qtIdent(data.role) }}" {% else %}"{{ conn|qtIdent(server.role) }}" {% endif %}--no-password --dbname "{{
+conn|qtIdent(data.database) }}" {% if data.format == 'Directory' %}--format directory
+ {% endif %}{% if data.pre_data %}
+--section pre-data {% endif %}{% if data.data %}
+--section data {% endif %}{% if data.post_data %}
+--section post-data {% endif %}{% if data.only_data %}
+--data-only {% else %}
+{% if data.dns_owner %}
+--no-owner {% endif %}{% if data.dns_privilege %}
+--no-privileges {% endif %}{% if data.dns_tablespace %}
+--no-tablespaces {% endif %}
+{% endif %}{% if data.only_schema %}
+--schema-only {% else %}
+{% if data.disable_trigger %}--disable-triggers {% endif %}
+{% endif %}{% if data.include_create_database %}
+--create {% endif %}{% if data.clean %}
+--clean {% endif %}{% if data.single_transaction %}
+--single-transaction {% endif %}{% if data.no_data_fail_table %}
+--no-data-for-failed-tables {% endif %}{% if data.use_set_session_auth %}
+--use-set-session-authorization {% endif %}{% if data.exit_on_error %}
+--exit-on-error {% endif %}{% if data.no_of_jobs %}
+--jobs {{ data.no_of_jobs }} {% endif %}{% if data.verbose %}
+--verbose {% endif %}{% if data.schemas %}
+--schema {{ conn|qtIdent(data.schemas) }} {% endif %}{% if data.tables %}
+--table {{ conn|qtIdent(data.tables) }} {% endif %}{% if data.function_node %}
+--function {{ conn|qtIdent(data.function_node) }} {% endif %}{% if data.trigger_node %}
+--trigger {{ conn|qtIdent(data.trigger_node) }} {% endif %}{% if data.trigger_function_node %}
+--function {{ conn|qtIdent(data.trigger_function_node) }} {% endif %}{% if data.index_node %}
+--index {{ conn|qtIdent(data.index_node) }} {% endif %}{% if data.file %}
+"{{data.file}}"
+{% endif %}{% if first_time %}
+--list {% endif %}
\ No newline at end of file
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 0000000..0d68f4f
--- /dev/null
+++ b/web/pgadmin/tools/restore/templates/restore/js/restore.js
@@ -0,0 +1,442 @@
+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([
+ '<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 col-xs-6'
+ });
+
+ //Restore Model (Objects like Database/Schema/Table)
+ var RestoreObjectModel = Backbone.Model.extend({
+ idAttribute: 'id',
+ defaults: {
+ file: undefined,
+ role: 'postgres',
+ format: 'Custom or tar',
+ verbose: true,
+ blobs: true,
+ encoding: undefined,
+ database: undefined,
+ schemas: undefined,
+ tables: undefined,
+ function_node: undefined,
+ trigger_node: undefined,
+ trigger_function_node: undefined,
+ index_node: 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 or tar"},
+ {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') }}'
+ }]
+ },{
+ id: 'todo', label: '{{ _('TODO') }}', text: '{{ _('Add Objects selection tree here') }}',
+ type: 'note', group: '{{ _('Objects') }}'
+ }],
+ 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.restore_objects) {
+ // Create Dialog title on the fly with node details
+ alertify.dialog('restore_objects' ,function factory() {
+ return {
+ main: function(title) {
+ this.set('title', title);
+ },
+ setup:function() {
+ return {
+ buttons: [{
+ text: '{{ _('Restore') }}', key: 27, className: 'btn btn-primary'
+ },{
+ text: '{{ _('Cancel') }}', key: 27, className: 'btn btn-danger'
+ }],
+ // 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});
+ }
+ }
+ },
+ prepare: function() {
+ var self = this;
+ // Disable Backup button until user provides Filename
+ this.__internal.buttons[0].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));
+
+ // 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.text === "Restore") {
+ // Fetch current server id
+ 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]);
+ // Set current node info into model
+ this.view.model.set('database', treeInfo.database.label);
+ if (treeInfo.schema)
+ this.view.model.set('schema', treeInfo.schema.label);
+ if (treeInfo.table)
+ this.view.model.set('table', treeInfo.table.label);
+ if (treeInfo.function)
+ this.view.model.set('function', treeInfo.function.label);
+ if (treeInfo.index)
+ this.view.model.set('index', treeInfo.index.label);
+ if (treeInfo.trigger)
+ this.view.model.set('trigger', treeInfo.trigger.label);
+ if (treeInfo.trigger_function)
+ this.view.model.set('trigger_function', treeInfo.trigger_function.label);
+
+ var self = this,
+ baseUrl = "{{ url_for('restore.index') }}" +
+ "create_job/" + treeInfo.server._id,
+ args = this.view.model.toJSON();
+
+ $.ajax({
+ url: baseUrl,
+ method: 'POST',
+ data:{ 'data': JSON.stringify(args) },
+ success: function(res) {
+ var msg = alertify.message('{{ _('Restore job created (Click for more details)') }}', 10);
+ msg.callback = function (isClicked) {
+ if(isClicked)
+ console.log('Show detailed logs >>' + res);
+ }
+ },
+ error: function(xhr, status, error) {
+ try {
+ var err = $.parseJSON(xhr.responseText);
+ alertify.alert(
+ '{{ _('Backup failed...') }}',
+ err.errormsg
+ );
+ } catch (e) {}
+ }
+ });
+ }
+ }
+ };
+ });
+ }
+ alertify.restore_objects(title).resizeTo('65%','60%');
+ }
+ };
+ return pgBrowser.Restore;
+ });
\ No newline at end of file
^ permalink raw reply [nested|flat] 3+ messages in thread
* Re: PATCH: Initiale for "Restore Utility" [pgAdmin4]
2016-05-06 14:30 Re: PATCH: Initiale for "Restore Utility" [pgAdmin4] Murtuza Zabuawala <[email protected]>
@ 2016-05-15 17:03 ` Ashesh Vashi <[email protected]>
0 siblings, 0 replies; 3+ messages in thread
From: Ashesh Vashi @ 2016-05-15 17:03 UTC (permalink / raw)
To: Murtuza Zabuawala <[email protected]>; +Cc: pgadmin-hackers
Thanks - committed after integrating it with background process executor,
and observer.
Added loading, and selection of the objects within the backup file in the
TODO list.
--
Thanks & Regards,
Ashesh Vashi
EnterpriseDB INDIA: Enterprise PostgreSQL Company
<http://www.enterprisedb.com;
*http://www.linkedin.com/in/asheshvashi*
<http://www.linkedin.com/in/asheshvashi;
On Fri, May 6, 2016 at 8:00 PM, Murtuza Zabuawala <
[email protected]> wrote:
> Hi,
>
> PFA updated patch, I have fixed typo in argument template.
>
> --
> Regards,
> Murtuza Zabuawala
> EnterpriseDB: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>
> On Fri, May 6, 2016 at 6:56 PM, Murtuza Zabuawala <
> [email protected]> wrote:
>
>> Hi,
>>
>> PFA initial patch for restore utility.
>>
>> *Note:* This patch depends on File manager patch.
>>
>>
>> --
>> Regards,
>> Murtuza Zabuawala
>> EnterpriseDB: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>>
>
>
>
> --
> Sent via pgadmin-hackers mailing list ([email protected])
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgadmin-hackers
>
>
^ permalink raw reply [nested|flat] 3+ messages in thread
end of thread, other threads:[~2016-05-15 17:03 UTC | newest]
Thread overview: 3+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2016-05-06 13:26 PATCH: Initiale for "Restore Utility" [pgAdmin4] Murtuza Zabuawala <[email protected]>
2016-05-06 14:30 Re: PATCH: Initiale for "Restore Utility" [pgAdmin4] Murtuza Zabuawala <[email protected]>
2016-05-15 17:03 ` Ashesh Vashi <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox