Addd support for taking backup for the server.
Tweaked by Ashesh Vashi to integrate the backgroud process, and also with some improvements as stated below: * Resolved an issue loading existing preference. * Improved the background process observer/executor for supporting detalied view. * Added the utility path preferences in the ServerType class.pull/3/head
parent
fe0911f285
commit
8ca760ee2b
5
TODO.txt
5
TODO.txt
|
@ -26,3 +26,8 @@ Query Tool updateable recordset support
|
|||
Add smarts to the Query Tool to allow it to recognise if a query produces a
|
||||
data set that would be updateable (e.g. from a single table, all primary key
|
||||
columns present), and if so, allow editing.
|
||||
|
||||
Backup Object
|
||||
-------------
|
||||
|
||||
Allow to select/deselect objects under the object backup operation.
|
||||
|
|
|
@ -170,7 +170,7 @@ class ServerModule(sg.ServerGroupPluginModule):
|
|||
Override it so that - it does not register the show_node preference for
|
||||
server type.
|
||||
"""
|
||||
pass
|
||||
ServerType.register_preferences()
|
||||
|
||||
class ServerMenuItem(MenuItem):
|
||||
def __init__(self, **kwargs):
|
||||
|
|
|
@ -7,8 +7,10 @@
|
|||
#
|
||||
##########################################################################
|
||||
|
||||
import os
|
||||
from flask import render_template
|
||||
from flask.ext.babel import gettext
|
||||
from flask.ext.babel import gettext as _
|
||||
from pgadmin.utils.preferences import Preferences
|
||||
|
||||
|
||||
class ServerType(object):
|
||||
|
@ -26,6 +28,7 @@ class ServerType(object):
|
|||
self.stype = server_type
|
||||
self.desc = description
|
||||
self.spriority = priority
|
||||
self.utility_path = None
|
||||
|
||||
assert(server_type not in ServerType.registry)
|
||||
ServerType.registry[server_type] = self
|
||||
|
@ -38,6 +41,24 @@ class ServerType(object):
|
|||
def description(self):
|
||||
return self.desc
|
||||
|
||||
@classmethod
|
||||
def register_preferences(cls):
|
||||
paths = Preferences('paths', _('Paths'))
|
||||
|
||||
for key in cls.registry:
|
||||
st = cls.registry[key]
|
||||
|
||||
st.utility_path = paths.register(
|
||||
'bin_paths', st.stype + '_bin_dir',
|
||||
_("{0} bin path").format(st.stype.upper()),
|
||||
'text', "", category_label=_('Binary paths'),
|
||||
help_str=_(
|
||||
"Set the PATH where the {0} binary utilities can be found...".format(
|
||||
st.desc
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def priority(self):
|
||||
return self.spriority
|
||||
|
@ -70,17 +91,29 @@ class ServerType(object):
|
|||
reverse=True
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def utility(cls, operation, sverion):
|
||||
if operation == 'backup':
|
||||
return 'pg_dump'
|
||||
if operation == 'backup_server':
|
||||
return 'pg_dumpall'
|
||||
if operation == 'restore':
|
||||
return 'pg_restore'
|
||||
def utility(self, operation, sverion):
|
||||
res = None
|
||||
|
||||
return None
|
||||
if operation == 'backup':
|
||||
res = 'pg_dump'
|
||||
elif operation == 'backup_server':
|
||||
res = 'pg_dumpall'
|
||||
elif operation == 'restore':
|
||||
res = 'pg_restore'
|
||||
elif operation == 'sql':
|
||||
res = 'psql'
|
||||
else:
|
||||
raise Exception(
|
||||
_("Couldn't find the utility for the operation '%s'".format(
|
||||
operation
|
||||
))
|
||||
)
|
||||
|
||||
return os.path.join(
|
||||
self.utility_path.get(),
|
||||
(res if os.name != 'nt' else (res + '.exe'))
|
||||
)
|
||||
|
||||
|
||||
# Default Server Type
|
||||
ServerType('pg', gettext("PostgreSQL"), -1)
|
||||
ServerType('pg', _("PostgreSQL"), -1)
|
||||
|
|
|
@ -9,16 +9,15 @@
|
|||
|
||||
"""A blueprint module providing utility functions for the application."""
|
||||
|
||||
import datetime
|
||||
from flask import session, current_app
|
||||
from pgadmin.utils import PgAdminModule
|
||||
import pgadmin.utils.driver as driver
|
||||
|
||||
MODULE_NAME = 'misc'
|
||||
|
||||
# Initialise the module
|
||||
blueprint = PgAdminModule(MODULE_NAME, __name__,
|
||||
url_prefix='')
|
||||
blueprint = PgAdminModule(
|
||||
MODULE_NAME, __name__, url_prefix=''
|
||||
)
|
||||
|
||||
##########################################################################
|
||||
# A special URL used to "ping" the server
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
Introduce a function to run the process executor in detached mode.
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals
|
||||
from abc import ABCMeta, abstractproperty
|
||||
from abc import ABCMeta, abstractproperty, abstractmethod
|
||||
import csv
|
||||
from datetime import datetime
|
||||
from dateutil import parser
|
||||
|
@ -50,8 +50,8 @@ class IProcessDesc(object):
|
|||
def message(self):
|
||||
pass
|
||||
|
||||
@abstractproperty
|
||||
def details(self):
|
||||
@abstractmethod
|
||||
def details(self, cmd, args):
|
||||
pass
|
||||
|
||||
|
||||
|
@ -322,7 +322,12 @@ class BatchProcess(object):
|
|||
details = desc
|
||||
|
||||
if isinstance(desc, IProcessDesc):
|
||||
details = desc.details
|
||||
args = []
|
||||
args_csv = StringIO(p.arguments)
|
||||
args_reader = csv.reader(args_csv, delimiter=str(','))
|
||||
for arg in args_reader:
|
||||
args = args + arg
|
||||
details = desc.details(p.command, args)
|
||||
desc = desc.message
|
||||
|
||||
res.append({
|
||||
|
|
|
@ -170,11 +170,6 @@ function(_, S, $, pgBrowser, alertify, pgMessages) {
|
|||
self.curr_status = pgMessages['running'];
|
||||
}
|
||||
|
||||
if ('execution_time' in data) {
|
||||
self.execution_time = self.execution_time + ' ' +
|
||||
pgMessages['seconds'];
|
||||
}
|
||||
|
||||
if (!_.isNull(self.exit_code)) {
|
||||
if (self.exit_code == 0) {
|
||||
self.curr_status = pgMessages['successfully_finished'];
|
||||
|
@ -238,12 +233,12 @@ function(_, S, $, pgBrowser, alertify, pgMessages) {
|
|||
if (!self.notifier) {
|
||||
var content = $('<div class="pg-bg-bgprocess row"></div>').append(
|
||||
$('<div></div>', {
|
||||
class: "col-xs-12 h3 pg-bg-notify-header"
|
||||
class: "h5 pg-bg-notify-header"
|
||||
}).text(
|
||||
self.desc
|
||||
)
|
||||
).append(
|
||||
$('<div></div>', {class: 'pg-bg-notify-body' }).append(
|
||||
$('<div></div>', {class: 'pg-bg-notify-body h6' }).append(
|
||||
$('<div></div>', {class: 'pg-bg-start col-xs-12' }).append(
|
||||
$('<div></div>').text(self.stime.toString())
|
||||
).append(
|
||||
|
@ -252,10 +247,10 @@ function(_, S, $, pgBrowser, alertify, pgMessages) {
|
|||
)
|
||||
),
|
||||
for_details = $('<div></div>', {
|
||||
class: "col-xs-12 text-center pg-bg-click"
|
||||
class: "col-xs-12 text-center pg-bg-click h6"
|
||||
}).text(pgMessages.CLICK_FOR_DETAILED_MSG).appendTo(content),
|
||||
status = $('<div></div>', {
|
||||
class: "pg-bg-status col-xs-12 " + ((self.exit_code === 0) ?
|
||||
class: "pg-bg-status col-xs-12 h5 " + ((self.exit_code === 0) ?
|
||||
'bg-success': (self.exit_code == 1) ?
|
||||
'bg-failed' : '')
|
||||
}).appendTo(content);
|
||||
|
@ -287,22 +282,34 @@ function(_, S, $, pgBrowser, alertify, pgMessages) {
|
|||
});
|
||||
}
|
||||
// TODO:: Formatted execution time
|
||||
self.container.find('.pg-bg-etime').empty().text(
|
||||
String(self.execution_time)
|
||||
self.container.find('.pg-bg-etime').empty().append(
|
||||
$('<span></span>', {class: 'blink'}).text(
|
||||
String(self.execution_time)
|
||||
)
|
||||
).append(
|
||||
$('<span></span>').text(' ' + pgMessages['seconds'])
|
||||
);
|
||||
self.container.find('.pg-bg-status').empty().append(
|
||||
self.curr_status
|
||||
)
|
||||
self.curr_status
|
||||
);
|
||||
} else {
|
||||
self.show_detailed_view.apply(self)
|
||||
}
|
||||
},
|
||||
|
||||
show_detailed_view: function() {
|
||||
var self = this,
|
||||
panel = this.panel =
|
||||
panel = this.panel,
|
||||
is_new = false;
|
||||
|
||||
if (!self.panel) {
|
||||
is_new = true;
|
||||
panel = this.panel =
|
||||
pgBrowser.BackgroundProcessObsorver.create_panel();
|
||||
|
||||
panel.title('Process Watcher - ' + self.desc);
|
||||
panel.focus();
|
||||
panel.title('Process Watcher - ' + self.desc);
|
||||
panel.focus();
|
||||
}
|
||||
|
||||
var container = panel.$container,
|
||||
status_class = (
|
||||
|
@ -314,52 +321,61 @@ function(_, S, $, pgBrowser, alertify, pgMessages) {
|
|||
$header = container.find('.bg-process-details'),
|
||||
$footer = container.find('.bg-process-footer');
|
||||
|
||||
if (is_new) {
|
||||
// set logs
|
||||
$logs.html(self.logs);
|
||||
|
||||
// set logs
|
||||
$logs.html(self.logs);
|
||||
|
||||
// set bgprocess detailed description
|
||||
$header.find('.bg-detailed-desc').html(self.detailed_desc);
|
||||
// set bgprocess detailed description
|
||||
$header.find('.bg-detailed-desc').html(self.detailed_desc);
|
||||
}
|
||||
|
||||
// set bgprocess start time
|
||||
$header.find('.bg-process-stats .bgprocess-start-time').html(self.stime);
|
||||
$header.find('.bg-process-stats .bgprocess-start-time').html(
|
||||
self.stime
|
||||
);
|
||||
|
||||
// set status
|
||||
$footer.find('.bg-process-status p').addClass(
|
||||
$footer.find('.bg-process-status p').removeClass().addClass(
|
||||
status_class
|
||||
).html(
|
||||
self.curr_status
|
||||
);
|
||||
).html(self.curr_status);
|
||||
|
||||
// set bgprocess execution time
|
||||
$footer.find('.bg-process-exec-time p').html(self.execution_time);
|
||||
|
||||
self.details = true;
|
||||
setTimeout(
|
||||
function() {
|
||||
self.status.apply(self);
|
||||
}, 1000
|
||||
$footer.find('.bg-process-exec-time p').empty().append(
|
||||
$('<span></span>', {class: 'blink'}).text(
|
||||
String(self.execution_time)
|
||||
)
|
||||
).append(
|
||||
$('<span></span>').text(' ' + pgMessages['seconds'])
|
||||
);
|
||||
|
||||
var resize_log_container = function($logs, $header, $footer) {
|
||||
var h = $header.outerHeight() + $footer.outerHeight();
|
||||
$logs.css('padding-bottom', h);
|
||||
}.bind(panel, $logs, $header, $footer);
|
||||
if (is_new) {
|
||||
self.details = true;
|
||||
setTimeout(
|
||||
function() {
|
||||
self.status.apply(self);
|
||||
}, 1000
|
||||
);
|
||||
|
||||
panel.on(wcDocker.EVENT.RESIZED, resize_log_container);
|
||||
panel.on(wcDocker.EVENT.ATTACHED, resize_log_container);
|
||||
panel.on(wcDocker.EVENT.DETACHED, resize_log_container);
|
||||
var resize_log_container = function($logs, $header, $footer) {
|
||||
var h = $header.outerHeight() + $footer.outerHeight();
|
||||
$logs.css('padding-bottom', h);
|
||||
}.bind(panel, $logs, $header, $footer);
|
||||
|
||||
resize_log_container();
|
||||
panel.on(wcDocker.EVENT.RESIZED, resize_log_container);
|
||||
panel.on(wcDocker.EVENT.ATTACHED, resize_log_container);
|
||||
panel.on(wcDocker.EVENT.DETACHED, resize_log_container);
|
||||
|
||||
panel.on(wcDocker.EVENT.CLOSED, function(process) {
|
||||
process.panel = null;
|
||||
resize_log_container();
|
||||
|
||||
process.details = false;
|
||||
if (process.exit_code != null) {
|
||||
process.acknowledge_server.apply(process);
|
||||
}
|
||||
}.bind(panel, this));
|
||||
panel.on(wcDocker.EVENT.CLOSED, function(process) {
|
||||
process.panel = null;
|
||||
|
||||
process.details = false;
|
||||
if (process.exit_code != null) {
|
||||
process.acknowledge_server.apply(process);
|
||||
}
|
||||
}.bind(panel, this));
|
||||
}
|
||||
},
|
||||
|
||||
acknowledge_server: function() {
|
||||
|
@ -472,9 +488,9 @@ function(_, S, $, pgBrowser, alertify, pgMessages) {
|
|||
content: '<div class="bg-process-details col-xs-12">'+
|
||||
'<p class="bg-detailed-desc"></p>'+
|
||||
'<div class="bg-process-stats">'+
|
||||
'<span><b>' + pgMessages['START_TIME'] + ':</b></span>'+
|
||||
'<p class="bgprocess-start-time"></p>'+
|
||||
'</div>'+
|
||||
'<span><b>' + pgMessages['START_TIME'] + ': </b>'+
|
||||
'<span class="bgprocess-start-time"></span>'+
|
||||
'</span></div>'+
|
||||
'</div>'+
|
||||
'<div class="bg-process-watcher col-xs-12">'+
|
||||
'</div>'+
|
||||
|
|
|
@ -1098,6 +1098,7 @@ button.pg-alertify-button {
|
|||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
div.backform_control_notes label.control-label {
|
||||
min-width: 0px;
|
||||
}
|
||||
|
@ -1106,7 +1107,6 @@ form[name="change_password_form"] .help-block {
|
|||
color: #A94442 !important;
|
||||
}
|
||||
|
||||
|
||||
.file_selection_ctrl .create_input span {
|
||||
padding-right: 10px;
|
||||
font-weight: bold;
|
||||
|
@ -1165,3 +1165,44 @@ form[name="change_password_form"] .help-block {
|
|||
.dropzone .dz-preview .dz-progress .dz-upload {
|
||||
bottom: initial;
|
||||
}
|
||||
|
||||
/* Fix Alertify dialog alignment for Backform controls */
|
||||
.alertify_tools_dialog_properties {
|
||||
bottom: 0 !important;
|
||||
left: 0 !important;
|
||||
position: absolute !important;
|
||||
right: 0 !important;
|
||||
top: 35px !important;
|
||||
}
|
||||
|
||||
/* For Backup & Restore Dialog */
|
||||
.custom_switch_label_class {
|
||||
min-width: 0px !important;
|
||||
padding-bottom: 10px !important;
|
||||
font-size: 13px !important;
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
.custom_switch_control_class {
|
||||
min-width: 0px !important;
|
||||
padding-bottom: 10px !important;
|
||||
}
|
||||
|
||||
/* animate blink */
|
||||
.blink {
|
||||
animation: blink-animation 1s steps(5, start) infinite;
|
||||
-webkit-animation: blink-animation 1s steps(5, start) infinite;
|
||||
|
||||
}
|
||||
|
||||
@keyframes blink-animation {
|
||||
to {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes blink-animation {
|
||||
to {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,440 @@
|
|||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
"""Implements Backup Utility"""
|
||||
|
||||
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
|
||||
|
||||
|
||||
# set template path for sql scripts
|
||||
MODULE_NAME = 'backup'
|
||||
server_info = {}
|
||||
|
||||
|
||||
class BackupModule(PgAdminModule):
|
||||
"""
|
||||
class BackupModule(Object):
|
||||
|
||||
It is a utility which inherits PgAdminModule
|
||||
class and define methods to load its own
|
||||
javascript file.
|
||||
"""
|
||||
|
||||
LABEL = _('Backup')
|
||||
|
||||
def get_own_javascripts(self):
|
||||
""""
|
||||
Returns:
|
||||
list: js files used by this module
|
||||
"""
|
||||
return [{
|
||||
'name': 'pgadmin.tools.backup',
|
||||
'path': url_for('backup.index') + 'backup',
|
||||
'when': None
|
||||
}]
|
||||
|
||||
def show_system_objects(self):
|
||||
"""
|
||||
return system preference objects
|
||||
"""
|
||||
return self.pref_show_system_objects
|
||||
|
||||
|
||||
# Create blueprint for BackupModule class
|
||||
blueprint = BackupModule(
|
||||
MODULE_NAME, __name__, static_url_path=''
|
||||
)
|
||||
|
||||
|
||||
class BACKUP(object):
|
||||
"""
|
||||
Constants defined for Backup utilities
|
||||
"""
|
||||
GLOBALS = 1
|
||||
SERVER = 2
|
||||
OBJECT = 3
|
||||
|
||||
|
||||
class BackupMessage(IProcessDesc):
|
||||
"""
|
||||
BackupMessage(IProcessDesc)
|
||||
|
||||
Defines the message shown for the backup operation.
|
||||
"""
|
||||
def __init__(self, _type, _sid, **kwargs):
|
||||
self.backup_type = _type
|
||||
self.sid = _sid
|
||||
self.database = None
|
||||
|
||||
if 'database' in kwargs:
|
||||
self.database = kwargs['database']
|
||||
|
||||
@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()
|
||||
|
||||
if self.backup_type == BACKUP.OBJECT:
|
||||
return _(
|
||||
"Backing up an object on the server - '{0}' on database '{1}'..."
|
||||
).format(
|
||||
"{0} ({1}:{2})".format(s.name, s.host, s.port),
|
||||
self.database
|
||||
)
|
||||
if self.backup_type == BACKUP.GLOBALS:
|
||||
return _("Backing up the globals for the server - '{0}'...").format(
|
||||
"{0} ({1}:{2})".format(s.name, s.host, s.port)
|
||||
)
|
||||
elif self.backup_type == BACKUP.SERVER:
|
||||
return _("Backing up the server - '{0}'...").format(
|
||||
"{0} ({1}:{2})".format(s.name, s.host, s.port)
|
||||
)
|
||||
else:
|
||||
# It should never reach here.
|
||||
return "Unknown Backup"
|
||||
|
||||
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 = '<div class="h5">'
|
||||
|
||||
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
|
||||
).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)
|
||||
).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)
|
||||
).encode('ascii', 'xmlcharrefreplace')
|
||||
else:
|
||||
# It should never reach here.
|
||||
res += "Backup"
|
||||
|
||||
res += '</div><div class="h5">'
|
||||
res += _("Running command:").encode('ascii', 'xmlcharrefreplace')
|
||||
res += '<br>'
|
||||
res += cmd.encode('ascii', 'xmlcharrefreplace')
|
||||
|
||||
replace_next = False
|
||||
|
||||
def cmdArg(x):
|
||||
if x:
|
||||
x = x.replace('\\', '\\\\')
|
||||
x = x.replace('"', '\\"')
|
||||
x = x.replace('""', '\\"')
|
||||
|
||||
return ' "' + x.encode('ascii', 'xmlcharrefreplace') + '"'
|
||||
|
||||
return ''
|
||||
|
||||
for arg in args:
|
||||
if arg and len(arg) >= 2 and arg[:2] == '--':
|
||||
res += ' ' + arg
|
||||
elif replace_next:
|
||||
res += ' XXX'
|
||||
else:
|
||||
if arg == '--file':
|
||||
replace_next = True
|
||||
res += cmdArg(arg)
|
||||
res += '</div>'
|
||||
|
||||
return res
|
||||
|
||||
|
||||
@blueprint.route("/")
|
||||
@login_required
|
||||
def index():
|
||||
return bad_request(errormsg=_("This URL can not be called directly!"))
|
||||
|
||||
|
||||
@blueprint.route("/backup.js")
|
||||
@login_required
|
||||
def script():
|
||||
"""render own javascript"""
|
||||
return Response(
|
||||
response=render_template(
|
||||
"backup/js/backup.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
|
||||
file_manager_dir = get_storage_directory()
|
||||
return os.path.join(file_manager_dir, file)
|
||||
|
||||
|
||||
@blueprint.route('/create_job/<int:sid>', methods=['POST'])
|
||||
@login_required
|
||||
def create_backup_job(sid):
|
||||
"""
|
||||
Args:
|
||||
sid: Server ID
|
||||
|
||||
Creates a new job for backup task (Backup Server/Globals)
|
||||
|
||||
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, user_id=current_user.id
|
||||
).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('backup_server')
|
||||
|
||||
args = [
|
||||
'--file',
|
||||
data['file'],
|
||||
'--host',
|
||||
server.host,
|
||||
'--port',
|
||||
str(server.port),
|
||||
'--username',
|
||||
server.username,
|
||||
'--no-password',
|
||||
'--database',
|
||||
driver.qtIdent(conn, server.maintenance_db)
|
||||
]
|
||||
if 'role' in data and data['role']:
|
||||
args.append('--role')
|
||||
args.append(data['role'])
|
||||
if 'verbose' in data and data['verbose']:
|
||||
args.append('--verbose')
|
||||
if 'dqoute' in data and data['dqoute']:
|
||||
args.append('--quote-all-identifiers')
|
||||
if data['type'] == 'global':
|
||||
args.append('--globals-only')
|
||||
|
||||
try:
|
||||
p = BatchProcess(
|
||||
desc=BackupMessage(
|
||||
BACKUP.SERVER if data['type'] != 'global' else BACKUP.GLOBALS,
|
||||
sid
|
||||
),
|
||||
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}
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route('/create_job/backup_object/<int:sid>', methods=['POST'])
|
||||
@login_required
|
||||
def create_backup_objects_job(sid):
|
||||
"""
|
||||
Args:
|
||||
sid: Server ID
|
||||
|
||||
Creates a new job for backup task (Backup Database(s)/Schema(s)/Table(s))
|
||||
|
||||
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, user_id=current_user.id
|
||||
).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('backup')
|
||||
args = [
|
||||
'--file',
|
||||
data['file'],
|
||||
'--host',
|
||||
server.host,
|
||||
'--port',
|
||||
str(server.port),
|
||||
'--username',
|
||||
server.username,
|
||||
'--no-password'
|
||||
]
|
||||
|
||||
def set_param(key, param):
|
||||
if key in data:
|
||||
args.append(param)
|
||||
|
||||
def set_value(key, param, value):
|
||||
if key in data:
|
||||
args.append(param)
|
||||
if value:
|
||||
if value is True:
|
||||
args.append(param[key])
|
||||
else:
|
||||
args.append(value)
|
||||
|
||||
set_param('verbose', '--verbose')
|
||||
set_param('dqoute', '--quote-all-identifiers')
|
||||
|
||||
if data['format'] is not None:
|
||||
if data['format'] == 'custom':
|
||||
args.extend(['--format', 'custom'])
|
||||
|
||||
set_param('blobs', '--blobs')
|
||||
set_value('ratio', '--compress', True)
|
||||
|
||||
elif data['format'] == 'tar':
|
||||
args.extend(['--format', 'tar'])
|
||||
|
||||
set_param('blobs', '--blobs')
|
||||
|
||||
elif data['format'] == 'plain':
|
||||
args.extend(['--format', 'plain'])
|
||||
if data['only_data']:
|
||||
args.append('--data-only')
|
||||
set_param('disable_trigger', '--disable-triggers')
|
||||
else:
|
||||
set_param('only_schema', '--schema-only')
|
||||
set_param('dns_owner', '--no-owner')
|
||||
set_param('include_create_database', '--create')
|
||||
set_param('include_drop_database', '--clean')
|
||||
elif data['format'] == 'directory':
|
||||
args.extend(['--format', 'directory'])
|
||||
|
||||
set_param('pre_data', '--section pre-data')
|
||||
set_param('data', '--section data')
|
||||
set_param('post_data', '--section post-data')
|
||||
set_param('dns_privilege', '--no-privileges')
|
||||
set_param('dns_tablespace', '--no-tablespaces')
|
||||
set_param('dns_unlogged_tbl_data', '--no-unlogged-table-data')
|
||||
set_param('use_insert_commands', '--inserts')
|
||||
set_param('use_column_inserts', '--column-inserts')
|
||||
set_param('disable_quoting', '--disable-dollar-quoting')
|
||||
set_param('with_oids', '--oids')
|
||||
set_param('use_set_session_auth', '--use-set-session-authorization')
|
||||
|
||||
set_value('no_of_jobs', '--jobs', True)
|
||||
|
||||
for s in data['schemas']:
|
||||
args.extend(['--schema', driver.qtIdent(conn, s)])
|
||||
|
||||
for s, t in data['tables']:
|
||||
args.extend([
|
||||
'--table', driver.qtIdent(conn, s) + '.' + driver.qtIdent(conn, t)
|
||||
])
|
||||
|
||||
args.append(driver.qtIdent(conn, data['database']))
|
||||
|
||||
try:
|
||||
p = BatchProcess(
|
||||
desc=BackupMessage(
|
||||
BACKUP.OBJECT,
|
||||
sid, database=data['database']
|
||||
),
|
||||
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}
|
||||
)
|
|
@ -0,0 +1,670 @@
|
|||
define([
|
||||
'jquery', 'underscore', 'underscore.string', 'alertify',
|
||||
'pgadmin.browser', 'backbone', 'backgrid', 'backform', 'pgadmin.browser.node'
|
||||
],
|
||||
|
||||
// This defines Backup dialog
|
||||
function($, _, S, alertify, pgBrowser, Backbone, Backgrid, Backform, pgNode) {
|
||||
|
||||
// if module is already initialized, refer to that.
|
||||
if (pgBrowser.Backup) {
|
||||
return pgBrowser.Backup;
|
||||
}
|
||||
|
||||
/*
|
||||
=====================
|
||||
TODO LIST FOR BACKUP:
|
||||
=====================
|
||||
1) Add Object tree on object tab which allows user to select
|
||||
objects which can be backed up
|
||||
2) Allow user to select/deselect objects
|
||||
3) If database is selected in browser
|
||||
show all database children objects selected in Object tree
|
||||
4) If schema is selected in browser
|
||||
show all schema children objects selected in Object tree
|
||||
5) If table is selected then show table/schema/database selected
|
||||
in Object tree
|
||||
6) if root objects like database/schema is not selected and their
|
||||
children are selected then add them separately with in tables attribute
|
||||
with schema.
|
||||
*/
|
||||
|
||||
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'
|
||||
});
|
||||
|
||||
//Backup Model (Server Node)
|
||||
var BackupModel = Backbone.Model.extend({
|
||||
idAttribute: 'id',
|
||||
defaults: {
|
||||
file: undefined,
|
||||
role: undefined,
|
||||
dqoute: false,
|
||||
verbose: true,
|
||||
type: undefined /* global, server */
|
||||
},
|
||||
schema: [{
|
||||
id: 'file', label: '{{ _('Filename') }}',
|
||||
type: 'text', disabled: false, control: Backform.FileControl,
|
||||
dialog_type: 'create_file', supp_types: ['*', 'sql']
|
||||
},{
|
||||
id: 'role', label: '{{ _('Role name') }}',
|
||||
control: 'node-list-by-name', node: 'role',
|
||||
select2: { allowClear: false }
|
||||
},{
|
||||
type: 'nested', control: 'fieldset', label: '{{ _('Miscellaneous') }}',
|
||||
schema:[{
|
||||
id: 'verbose', label: '{{ _('Verbose messages') }}',
|
||||
control: Backform.CustomSwitchControl, disabled: false,
|
||||
group: '{{ _('Miscellaneous') }}'
|
||||
},{
|
||||
id: 'dqoute', label: '{{ _('Force double quote on identifiers') }}',
|
||||
control: Backform.CustomSwitchControl, disabled: false,
|
||||
group: '{{ _('Miscellaneous') }}'
|
||||
}]
|
||||
},{
|
||||
id: 'server_note', label: '{{ _('Note') }}',
|
||||
text: '{{ _('The backup format will be PLAIN') }}',
|
||||
type: 'note', visible: function(m){
|
||||
return m.get('type') === 'server';
|
||||
}
|
||||
},{
|
||||
id: 'globals_note', label: '{{ _('Note') }}',
|
||||
text: '{{ _('Only objects global to the entire database will be backed up in PLAIN format') }}',
|
||||
type: 'note', visible: function(m){
|
||||
return m.get('type') === 'globals';
|
||||
}
|
||||
},{
|
||||
}],
|
||||
validate: function() {
|
||||
// TODO: HOW TO VALIDATE ???
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
//Backup Model (Objects like Database/Schema/Table)
|
||||
var BackupObjectModel = Backbone.Model.extend({
|
||||
idAttribute: 'id',
|
||||
defaults: {
|
||||
file: undefined,
|
||||
role: 'postgres',
|
||||
format: 'custom',
|
||||
verbose: true,
|
||||
blobs: true,
|
||||
encoding: undefined,
|
||||
schemas: [],
|
||||
tables: [],
|
||||
database: undefined
|
||||
},
|
||||
schema: [{
|
||||
id: 'file', label: '{{ _('Filename') }}',
|
||||
type: 'text', disabled: false, control: Backform.FileControl,
|
||||
dialog_type: 'create_file', supp_types: ['*', 'sql']
|
||||
},{
|
||||
id: 'format', label: '{{ _('Format') }}',
|
||||
type: 'text', disabled: false,
|
||||
control: 'select2', select2: {
|
||||
allowClear: false,
|
||||
width: "100%"
|
||||
},
|
||||
options: [
|
||||
{label: "Custom", value: "custom"},
|
||||
{label: "Tar", value: "tar"},
|
||||
{label: "Plain", value: "plain"},
|
||||
{label: "Directory", value: "directory"}
|
||||
]
|
||||
},{
|
||||
id: 'ratio', label: '{{ _('Comprasion ratio') }}',
|
||||
type: 'int', min: 0, max:9, disabled: false
|
||||
},{
|
||||
id: 'encoding', label: '{{ _('Encoding') }}',
|
||||
type: 'text', disabled: false, node: 'database',
|
||||
control: 'node-ajax-options', url: 'get_encodings'
|
||||
},{
|
||||
id: 'no_of_jobs', label: '{{ _('Number of jobs') }}',
|
||||
type: 'int', deps: ['format'], disabled: function(m) {
|
||||
return !(m.get('format') === "Directory");
|
||||
}
|
||||
},{
|
||||
id: 'role', label: '{{ _('Role name') }}',
|
||||
control: 'node-list-by-name', node: 'role',
|
||||
select2: { allowClear: false }
|
||||
},{
|
||||
type: 'nested', control: 'fieldset', label: '{{ _('Sections') }}',
|
||||
group: '{{ _('Dump options') }}',
|
||||
schema:[{
|
||||
id: 'pre_data', label: '{{ _('Pre-data') }}',
|
||||
control: Backform.CustomSwitchControl, group: '{{ _('Sections') }}',
|
||||
deps: ['only_data', 'only_schema'], disabled: function(m) {
|
||||
return 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 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 m.get('only_data')
|
||||
|| m.get('only_schema');
|
||||
}
|
||||
}]
|
||||
},{
|
||||
type: 'nested', control: 'fieldset', label: '{{ _('Type of objects') }}',
|
||||
group: '{{ _('Dump 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 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 m.get('pre_data')
|
||||
|| m.get('data')
|
||||
|| m.get('post_data')
|
||||
|| m.get('only_data');
|
||||
}
|
||||
},{
|
||||
id: 'blobs', label: '{{ _('Blobs') }}',
|
||||
control: Backform.CustomSwitchControl, disabled: false, group: '{{ _('Type of objects') }}'
|
||||
}]
|
||||
},{
|
||||
type: 'nested', control: 'fieldset', label: '{{ _('Do not save') }}',
|
||||
group: '{{ _('Dump 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') }}'
|
||||
},{
|
||||
id: 'dns_unlogged_tbl_data', label: '{{ _('Unlogged table data') }}',
|
||||
control: Backform.CustomSwitchControl, disabled: false, group: '{{ _('Do not save') }}'
|
||||
}]
|
||||
},{
|
||||
type: 'nested', control: 'fieldset', label: '{{ _('Queries') }}',
|
||||
group: '{{ _('Dump options') }}',
|
||||
schema:[{
|
||||
id: 'use_column_inserts', label: '{{ _('Use Column Inserts') }}',
|
||||
control: Backform.CustomSwitchControl, disabled: false, group: '{{ _('Queries') }}'
|
||||
},{
|
||||
id: 'use_insert_commands', label: '{{ _('Use Insert Commands') }}',
|
||||
control: Backform.CustomSwitchControl, disabled: false, group: '{{ _('Queries') }}'
|
||||
},{
|
||||
id: 'include_create_database', label: '{{ _('Include CREATE DATABASE statement') }}',
|
||||
control: Backform.CustomSwitchControl, disabled: false, group: '{{ _('Queries') }}'
|
||||
},{
|
||||
id: 'include_drop_database', label: '{{ _('Include DROP DATABASE statement') }}',
|
||||
control: Backform.CustomSwitchControl, disabled: false, group: '{{ _('Queries') }}'
|
||||
}]
|
||||
},{
|
||||
type: 'nested', control: 'fieldset', label: '{{ _('Disable') }}',
|
||||
group: '{{ _('Dump options') }}',
|
||||
schema:[{
|
||||
id: 'disable_trigger', label: '{{ _('Trigger') }}',
|
||||
control: Backform.CustomSwitchControl, group: '{{ _('Disable') }}',
|
||||
deps: ['only_data'], disabled: function(m) {
|
||||
return !(m.get('only_data'));
|
||||
}
|
||||
},{
|
||||
id: 'disable_quoting', label: '{{ _('$ quoting') }}',
|
||||
control: Backform.CustomSwitchControl, disabled: false, group: '{{ _('Disable') }}'
|
||||
}]
|
||||
},{
|
||||
type: 'nested', control: 'fieldset', label: '{{ _('Miscellaneous') }}',
|
||||
group: '{{ _('Dump options') }}',
|
||||
schema:[{
|
||||
id: 'with_oids', label: '{{ _('With OID(s)') }}',
|
||||
control: Backform.CustomSwitchControl, disabled: false, group: '{{ _('Miscellaneous') }}'
|
||||
},{
|
||||
id: 'verbose', label: '{{ _('Verbose messages') }}',
|
||||
control: Backform.CustomSwitchControl, disabled: false, group: '{{ _('Miscellaneous') }}'
|
||||
},{
|
||||
id: 'dqoute', label: '{{ _('Force double quote on identifiers') }}',
|
||||
control: Backform.CustomSwitchControl, disabled: false, group: '{{ _('Miscellaneous') }}'
|
||||
},{
|
||||
id: 'use_set_session_auth', label: '{{ _('Use SET SESSION AUTHORIZATION') }}',
|
||||
control: Backform.CustomSwitchControl, disabled: false, group: '{{ _('Miscellaneous') }}'
|
||||
}]
|
||||
}],
|
||||
validate: function() {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// Create an Object Backup of pgBrowser class
|
||||
pgBrowser.Backup = {
|
||||
init: function() {
|
||||
if (this.initialized)
|
||||
return;
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
// Define list of nodes on which backup context menu option appears
|
||||
var backup_supported_nodes = [
|
||||
'database', 'schema', 'table'
|
||||
];
|
||||
|
||||
/**
|
||||
Enable/disable backup 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(backup_supported_nodes, d._type) !== -1 &&
|
||||
parent_data._type != 'catalog') ? true: false
|
||||
);
|
||||
else
|
||||
return false;
|
||||
};
|
||||
|
||||
menu_enabled_server = 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 server node selected && connected
|
||||
if(!_.isUndefined(d) && !_.isNull(d))
|
||||
return (('server' === d._type) && d.connected);
|
||||
else
|
||||
false;
|
||||
};
|
||||
|
||||
// Define the nodes on which the menus to be appear
|
||||
var menus = [{
|
||||
name: 'backup_global', module: this,
|
||||
applies: ['tools'], callback: 'start_backup_global',
|
||||
priority: 10, label: '{{_("Backup Globals...") }}',
|
||||
icon: 'fa fa-floppy-o', enable: menu_enabled_server
|
||||
},{
|
||||
name: 'backup_server', module: this,
|
||||
applies: ['tools'], callback: 'start_backup_server',
|
||||
priority: 10, label: '{{_("Backup Server...") }}',
|
||||
icon: 'fa fa-floppy-o', enable: menu_enabled_server
|
||||
},{
|
||||
name: 'backup_global_ctx', module: this, node: 'server',
|
||||
applies: ['context'], callback: 'start_backup_global',
|
||||
priority: 10, label: '{{_("Backup Globals...") }}',
|
||||
icon: 'fa fa-floppy-o', enable: menu_enabled_server
|
||||
},{
|
||||
name: 'backup_server_ctx', module: this, node: 'server',
|
||||
applies: ['context'], callback: 'start_backup_server',
|
||||
priority: 10, label: '{{_("Backup Server...") }}',
|
||||
icon: 'fa fa-floppy-o', enable: menu_enabled_server
|
||||
},{
|
||||
name: 'backup_object', module: this,
|
||||
applies: ['tools'], callback: 'backup_objects',
|
||||
priority: 10, label: '{{_("Backup...") }}',
|
||||
icon: 'fa fa-floppy-o', enable: menu_enabled
|
||||
}];
|
||||
|
||||
for (var idx = 0; idx < backup_supported_nodes.length; idx++) {
|
||||
menus.push({
|
||||
name: 'backup_' + backup_supported_nodes[idx],
|
||||
node: backup_supported_nodes[idx], module: this,
|
||||
applies: ['context'], callback: 'backup_objects',
|
||||
priority: 10, label: '{{_("Backup...") }}',
|
||||
icon: 'fa fa-floppy-o', enable: menu_enabled
|
||||
});
|
||||
}
|
||||
|
||||
pgAdmin.Browser.add_menus(menus);
|
||||
return this;
|
||||
},
|
||||
start_backup_global: function(action, item) {
|
||||
var params = {'globals': true };
|
||||
this.start_backup_global_server.apply(
|
||||
this, [action, item, params]
|
||||
);
|
||||
},
|
||||
start_backup_server: function(action, item) {
|
||||
var params = {'server': true };
|
||||
this.start_backup_global_server.apply(
|
||||
this, [action, item, params]
|
||||
);
|
||||
},
|
||||
|
||||
// Callback to draw Backup Dialog for globals/server
|
||||
start_backup_global_server: function(action, item, params) {
|
||||
|
||||
var of_type = undefined;
|
||||
|
||||
// Set Notes according to type of backup
|
||||
if (!_.isUndefined(params['globals']) && params['globals']) {
|
||||
of_type = 'globals';
|
||||
} else {
|
||||
of_type = 'server';
|
||||
}
|
||||
|
||||
var DialogName = 'BackupDialog_' + of_type,
|
||||
DialogTitle = ((of_type == 'globals') ?
|
||||
'{{ _('Backup Globals...') }}' :
|
||||
'{{ _('Backup Server...') }}');
|
||||
|
||||
if(!alertify[DialogName]) {
|
||||
alertify.dialog(DialogName ,function factory() {
|
||||
return {
|
||||
main: function(title) {
|
||||
this.set('title', title);
|
||||
},
|
||||
setup:function() {
|
||||
return {
|
||||
buttons: [{
|
||||
text: '{{ _('Backup') }}', key: 27, className: 'btn btn-primary fa fa-lg fa-save pg-alertify-button'
|
||||
},{
|
||||
text: '{{ _('Cancel') }}', key: 27, className: 'btn btn-danger fa fa-lg fa-times pg-alertify-button'
|
||||
}],
|
||||
// Set options for dialog
|
||||
options: {
|
||||
title: DialogTitle,
|
||||
//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) {
|
||||
// clear our backform model/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='backup_dialog'></div>");
|
||||
// Find current/selected node
|
||||
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;
|
||||
// Create treeInfo
|
||||
var treeInfo = node.getTreeNodeHierarchy.apply(node, [i]);
|
||||
// Instance of backbone model
|
||||
var newModel = new BackupModel(
|
||||
{type: of_type}, {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
|
||||
});
|
||||
// Add our class to alertify
|
||||
$(this.elements.body.childNodes[0]).addClass(
|
||||
'alertify_tools_dialog_properties obj_properties'
|
||||
);
|
||||
// Render dialog
|
||||
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 === '{{ _('Backup') }}') {
|
||||
// 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]);
|
||||
|
||||
var self = this,
|
||||
baseUrl = "{{ url_for('backup.index') }}" +
|
||||
"create_job/" + treeInfo.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('{{ _('Background process for taking backup has been created!') }}', 1);
|
||||
pgBrowser.Events.trigger('pgadmin-bgprocess:created', self);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
try {
|
||||
var err = $.parseJSON(xhr.responseText);
|
||||
alertify.alert(
|
||||
'{{ _('Backup failed...') }}',
|
||||
err.errormsg
|
||||
);
|
||||
} catch (e) {}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
alertify[DialogName](true).resizeTo('60%','50%');
|
||||
},
|
||||
|
||||
// Callback to draw Backup Dialog for objects
|
||||
backup_objects: function(action, treeItem) {
|
||||
var title = S('{{ 'Backup (%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;
|
||||
|
||||
title = title.sprintf(node.label, data.label).value();
|
||||
|
||||
if(!alertify.backup_objects) {
|
||||
// Create Dialog title on the fly with node details
|
||||
alertify.dialog('backup_objects' ,function factory() {
|
||||
return {
|
||||
main: function(title) {
|
||||
this.set('title', title);
|
||||
},
|
||||
setup:function() {
|
||||
return {
|
||||
buttons: [{
|
||||
text: '{{ _('Backup') }}', key: 27, className: 'btn btn-primary fa fa-lg fa-save pg-alertify-button'
|
||||
},{
|
||||
text: '{{ _('Cancel') }}', key: 27, className: 'btn btn-danger fa fa-lg fa-times pg-alertify-button'
|
||||
}],
|
||||
// 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='backup_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 BackupObjectModel(
|
||||
{}, {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 === "Backup") {
|
||||
// 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 database into model
|
||||
this.view.model.set('database', treeInfo.database.label);
|
||||
|
||||
// We will remove once object tree is implemented
|
||||
// If selected node is Schema then add it in model
|
||||
if(d._type == 'schema') {
|
||||
var schemas = [];
|
||||
schemas.push(d.label);
|
||||
this.view.model.set('schemas', schemas);
|
||||
}
|
||||
// If selected node is Table then add it in model along with
|
||||
// its schema
|
||||
if(d._type == 'table') {
|
||||
var tables = [],
|
||||
selected_table = [];
|
||||
selected_table.push(treeInfo.schema.label)
|
||||
selected_table.push(d.label);
|
||||
this.view.model.set('tables', selected_table);
|
||||
}
|
||||
|
||||
var self = this,
|
||||
baseUrl = "{{ url_for('backup.index') }}" +
|
||||
"create_job/backup_object/" + treeInfo.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('{{ _('Background process for taking backup has been created!') }}', 1);
|
||||
pgBrowser.Events.trigger('pgadmin-bgprocess:created', self);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
try {
|
||||
var err = $.parseJSON(xhr.responseText);
|
||||
alertify.alert(
|
||||
'{{ _('Backup failed...') }}',
|
||||
err.errormsg
|
||||
);
|
||||
} catch (e) {}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
alertify.backup_objects(title).resizeTo('65%','60%');
|
||||
}
|
||||
};
|
||||
return pgBrowser.Backup;
|
||||
});
|
|
@ -264,7 +264,7 @@ class Preferences(object):
|
|||
self.mid = module.id
|
||||
|
||||
if name in Preferences.modules:
|
||||
m = Preferences.modules
|
||||
m = Preferences.modules[name]
|
||||
self.categories = m.categories
|
||||
else:
|
||||
Preferences.modules[name] = self
|
||||
|
|
Loading…
Reference in New Issue