pgadmin4/web/pgadmin/browser/server_groups/servers/static/js/server.js
Aditya Toshniwal f16498a8a7 Optimize Webpack to improve overall performance.
Changes include:
1) Remove underscore-string and sprintf-js packages as we were using only %s. Instead, added a function to do the same. Also changed gettext to behave like sprintf directly.
2) backgrid.sizeable.columns was not used anywhere, removed. @babel/polyfill is deprecated, replaced it with core-js.
3) Moved few css to make sure they get minified and bundled.
4) Added Flask-Compress to send static files as compressed gzip. This will reduce network traffic and improve initial load time for pgAdmin.
5) Split few JS files to make code reusable.
6) Lazy load few modules like leaflet, wkx is required only if geometry viewer is opened. snapsvg loaded only when explain plan is executed. This will improve sqleditor initial opening time.

Reviewed By: Khushboo Vashi
Fixes #4701
2019-10-10 12:05:28 +05:30

1296 lines
49 KiB
JavaScript

/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2019, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
define('pgadmin.node.server', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
'sources/pgadmin', 'pgadmin.browser',
'pgadmin.server.supported_servers', 'pgadmin.user_management.current_user',
'pgadmin.alertifyjs', 'pgadmin.backform',
'sources/browser/server_groups/servers/model_validation',
'pgadmin.browser.server.privilege',
], function(
gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser,
supported_servers, current_user, Alertify, Backform,
modelValidation
) {
if (!pgBrowser.Nodes['server']) {
var SSL_MODES = ['prefer', 'require', 'verify-ca', 'verify-full'];
pgBrowser.SecLabelModel = pgBrowser.Node.Model.extend({
defaults: {
provider: undefined,
label: undefined,
},
schema: [{
id: 'provider', label: gettext('Provider'),
type: 'text', editable: true,
cellHeaderClasses:'width_percent_50',
},{
id: 'label', label: gettext('Security label'),
type: 'text', editable: true,
cellHeaderClasses:'override_label_class_font_size',
}],
validate: function() {
this.errorModel.clear();
if (_.isUndefined(this.get('label')) ||
_.isNull(this.get('label')) ||
String(this.get('label')).replace(/^\s+|\s+$/g, '') == '') {
var errmsg = gettext('Label must be specified.');
this.errorModel.set('label', errmsg);
return errmsg;
}
return null;
},
});
pgAdmin.Browser.Nodes['server'] = pgAdmin.Browser.Node.extend({
parent_type: 'server_group',
type: 'server',
dialogHelp: url_for('help.static', {'filename': 'server_dialog.html'}),
label: gettext('Server'),
canDrop: true,
hasStatistics: true,
hasCollectiveStatistics: true,
can_expand: function(d) {
return d && d.connected;
},
Init: function() {
/* Avoid multiple registration of same menus */
if (this.initialized)
return;
this.initialized = true;
pgBrowser.add_menus([{
name: 'create_server_on_sg', node: 'server_group', module: this,
applies: ['object', 'context'], callback: 'show_obj_properties',
category: 'create', priority: 1, label: gettext('Server...'),
data: {action: 'create'}, icon: 'wcTabIcon icon-server',
},{
name: 'create_server', node: 'server', module: this,
applies: ['object', 'context'], callback: 'show_obj_properties',
category: 'create', priority: 3, label: gettext('Server...'),
data: {action: 'create'}, icon: 'wcTabIcon icon-server',
},{
name: 'connect_server', node: 'server', module: this,
applies: ['object', 'context'], callback: 'connect_server',
category: 'connect', priority: 4, label: gettext('Connect Server'),
icon: 'fa fa-link', enable : 'is_not_connected',
},{
name: 'disconnect_server', node: 'server', module: this,
applies: ['object', 'context'], callback: 'disconnect_server',
category: 'drop', priority: 5, label: gettext('Disconnect Server'),
icon: 'fa fa-chain-broken', enable : 'is_connected',
},{
name: 'reload_configuration', node: 'server', module: this,
applies: ['tools', 'context'], callback: 'reload_configuration',
category: 'reload', priority: 6, label: gettext('Reload Configuration'),
icon: 'fa fa-repeat', enable : 'enable_reload_config',
},{
name: 'restore_point', node: 'server', module: this,
applies: ['tools', 'context'], callback: 'restore_point',
category: 'restore', priority: 9, label: gettext('Add Named Restore Point...'),
icon: 'fa fa-anchor', enable : 'is_applicable',
},{
name: 'change_password', node: 'server', module: this,
applies: ['object'], callback: 'change_password',
label: gettext('Change Password...'), priority: 10,
icon: 'fa fa-lock', enable : 'is_connected',
},{
name: 'wal_replay_pause', node: 'server', module: this,
applies: ['tools', 'context'], callback: 'pause_wal_replay',
category: 'wal_replay_pause', priority: 7, label: gettext('Pause Replay of WAL'),
icon: 'fa fa-pause-circle', enable : 'wal_pause_enabled',
},{
name: 'wal_replay_resume', node: 'server', module: this,
applies: ['tools', 'context'], callback: 'resume_wal_replay',
category: 'wal_replay_resume', priority: 8, label: gettext('Resume Replay of WAL'),
icon: 'fa fa-play-circle', enable : 'wal_resume_enabled',
},{
name: 'clear_saved_password', node: 'server', module: this,
applies: ['object', 'context'], callback: 'clear_saved_password',
label: gettext('Clear Saved Password'), icon: 'fa fa-eraser',
priority: 11,
enable: function(node) {
if (node && node._type === 'server' &&
node.is_password_saved) {
return true;
}
return false;
},
},{
name: 'clear_sshtunnel_password', node: 'server', module: this,
applies: ['object', 'context'], callback: 'clear_sshtunnel_password',
label: gettext('Clear SSH Tunnel Password'), icon: 'fa fa-eraser',
priority: 12,
enable: function(node) {
if (node && node._type === 'server' &&
node.is_tunnel_password_saved) {
return true;
}
return false;
},
}]);
_.bindAll(this, 'connection_lost');
pgBrowser.Events.on(
'pgadmin:server:connection:lost', this.connection_lost
);
},
is_not_connected: function(node) {
return (node && node.connected != true);
},
is_connected: function(node) {
return (node && node.connected == true);
},
enable_reload_config: function(node) {
// Must be connected & is Super user
if (node && node._type == 'server' &&
node.connected && node.user.is_superuser) {
return true;
}
return false;
},
is_applicable: function(node) {
// Must be connected & super user & not in recovery mode
if (node && node._type == 'server' &&
node.connected && node.user.is_superuser
&& node.in_recovery == false) {
return true;
}
return false;
},
wal_pause_enabled: function(node) {
// Must be connected & is Super user & in Recovery mode
if (node && node._type == 'server' &&
node.connected && node.user.is_superuser
&& node.in_recovery == true
&& node.wal_pause == false) {
return true;
}
return false;
},
wal_resume_enabled: function(node) {
// Must be connected & is Super user & in Recovery mode
if (node && node._type == 'server' &&
node.connected && node.user.is_superuser
&& node.in_recovery == true
&& node.wal_pause == true) {
return true;
}
return false;
},
callbacks: {
/* Connect the server */
connect_server: function(args){
var input = args || {},
obj = this,
t = pgBrowser.tree,
i = input.item || t.selected(),
d = i && i.length == 1 ? t.itemData(i) : undefined;
if (!d)
return false;
connect_to_server(obj, d, t, i, false);
return false;
},
/* Disconnect the server */
disconnect_server: function(args, notify) {
var input = args || {},
obj = this,
t = pgBrowser.tree,
i = 'item' in input ? input.item : t.selected(),
d = i && i.length == 1 ? t.itemData(i) : undefined;
if (!d)
return false;
notify = notify || _.isUndefined(notify) || _.isNull(notify);
var disconnect = function() {
$.ajax({
url: obj.generate_url(i, 'connect', d, true),
type:'DELETE',
})
.done(function(res) {
if (res.success == 1) {
Alertify.success(res.info);
d = t.itemData(i);
t.removeIcon(i);
d.connected = false;
d.icon = 'icon-server-not-connected';
t.addIcon(i, {icon: d.icon});
obj.callbacks.refresh.apply(obj, [null, i]);
if (pgBrowser.serverInfo && d._id in pgBrowser.serverInfo) {
delete pgBrowser.serverInfo[d._id];
}
pgBrowser.enable_disable_menus(i);
// Trigger server disconnect event
pgBrowser.Events.trigger(
'pgadmin:server:disconnect',
{item: i, data: d}, false
);
}
else {
try {
Alertify.error(res.errormsg);
} catch (e) {
console.warn(e.stack || e);
}
t.unload(i);
}
})
.fail(function(xhr, status, error) {
Alertify.pgRespErrorNotify(xhr, error);
t.unload(i);
});
};
if (notify) {
Alertify.confirm(
gettext('Disconnect server'),
gettext('Are you sure you want to disconnect the server %s?', d.label),
function() { disconnect(); },
function() { return true;}
);
} else {
disconnect();
}
return false;
},
/* Connect the server (if not connected), before opening this node */
beforeopen: function(item, data) {
if(!data || data._type != 'server') {
return false;
}
pgBrowser.tree.addIcon(item, {icon: data.icon});
if (!data.connected) {
connect_to_server(this, data, pgBrowser.tree, item, false);
return false;
}
return true;
},
added: function(item, data) {
pgBrowser.serverInfo = pgBrowser.serverInfo || {};
pgBrowser.serverInfo[data._id] = _.extend({}, data);
// Call added method of node.js
pgAdmin.Browser.Node.callbacks.added.apply(this, arguments);
return true;
},
/* Reload configuration */
reload_configuration: function(args){
var input = args || {},
obj = this,
t = pgBrowser.tree,
i = input.item || t.selected(),
d = i && i.length == 1 ? t.itemData(i) : undefined;
if (!d)
return false;
Alertify.confirm(
gettext('Reload server configuration'),
gettext('Are you sure you want to reload the server configuration on %s?', d.label),
function() {
$.ajax({
url: obj.generate_url(i, 'reload', d, true),
method:'GET',
})
.done(function(res) {
if (res.data.status) {
Alertify.success(res.data.result);
}
else {
Alertify.error(res.data.result);
}
})
.fail(function(xhr, status, error) {
Alertify.pgRespErrorNotify(xhr, error);
t.unload(i);
});
},
function() { return true; }
);
return false;
},
/* Add restore point */
restore_point: function(args) {
var input = args || {},
obj = this,
t = pgBrowser.tree,
i = input.item || t.selected(),
d = i && i.length == 1 ? t.itemData(i) : undefined;
if (!d)
return false;
Alertify.prompt(
gettext('Enter the name of the restore point to add'), '',
// We will execute this function when user clicks on the OK button
function(evt, value) {
// If user has provided a value, send it to the server
if(!_.isUndefined(value) && !_.isNull(value) && value !== ''
&& String(value).replace(/^\s+|\s+$/g, '') !== '') {
$.ajax({
url: obj.generate_url(i, 'restore_point', d, true),
method:'POST',
data:{ 'value': JSON.stringify(value) },
})
.done(function(res) {
Alertify.success(res.data.result, 10);
})
.fail(function(xhr, status, error) {
Alertify.pgRespErrorNotify(xhr, error);
t.unload(i);
});
} else {
evt.cancel = true;
Alertify.error( gettext('Please enter a valid name.'), 10);
}
},
// We will execute this function when user clicks on the Cancel
// button. Do nothing just close it.
function(evt) { evt.cancel = false; }
).set({'title': gettext('Restore point name')});
},
/* Change password */
change_password: function(args){
var input = args || {},
obj = this,
t = pgBrowser.tree,
i = input.item || t.selected(),
d = i && i.length == 1 ? t.itemData(i) : undefined,
url = obj.generate_url(i, 'change_password', d, true),
is_pgpass_file_used = false,
check_pgpass_url = obj.generate_url(i, 'check_pgpass', d, true);
if (!d)
return false;
if(!Alertify.changeServerPassword) {
var newPasswordModel = Backbone.Model.extend({
defaults: {
user_name: undefined,
password: undefined,
newPassword: undefined,
confirmPassword: undefined,
},
validate: function() {
return null;
},
}),
passwordChangeFields = [{
name: 'user_name', label: gettext('User'),
type: 'text', disabled: true, control: 'input',
},{
name: 'password', label: gettext('Current Password'),
type: 'password', disabled: function() { return is_pgpass_file_used; },
control: 'input', required: true,
},{
name: 'newPassword', label: gettext('New Password'),
type: 'password', disabled: false, control: 'input',
required: true,
},{
name: 'confirmPassword', label: gettext('Confirm Password'),
type: 'password', disabled: false, control: 'input',
required: true,
}];
Alertify.dialog('changeServerPassword' ,function factory() {
return {
main: function(params) {
var title = gettext('Change Password ');
this.set('title', title);
this.user_name = params.user.name;
},
setup:function() {
return {
buttons: [{
text: gettext('Cancel'), key: 27,
className: 'btn btn-secondary fa fa-times pg-alertify-button', attrs: {name: 'cancel'},
},{
text: gettext('OK'), key: 13, className: 'btn btn-primary fa fa-check pg-alertify-button',
attrs: {name:'submit'},
}],
// Set options for dialog
options: {
padding : !1,
overflow: !1,
modal:false,
resizable: true,
maximizable: true,
pinnable: false,
closableByDimmer: 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 Ok button until user provides input
this.__internal.buttons[1].element.disabled = true;
var $container = $('<div class=\'change_password\'></div>'),
newpasswordmodel = new newPasswordModel(
{'user_name': self.user_name}
),
view = this.view = new Backform.Form({
el: $container,
model: newpasswordmodel,
fields: passwordChangeFields,
});
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() {
var that = this,
password = this.get('password'),
newPassword = this.get('newPassword'),
confirmPassword = this.get('confirmPassword');
// Only check password field if pgpass file is not available
if ((!is_pgpass_file_used &&
(_.isUndefined(password) || _.isNull(password) || password == '')) ||
_.isUndefined(newPassword) || _.isNull(newPassword) || newPassword == '' ||
_.isUndefined(confirmPassword) || _.isNull(confirmPassword) || confirmPassword == '') {
self.__internal.buttons[1].element.disabled = true;
} else if (newPassword != confirmPassword) {
self.__internal.buttons[1].element.disabled = true;
this.errorTimeout && clearTimeout(this.errorTimeout);
this.errorTimeout = setTimeout(function() {
that.errorModel.set('confirmPassword', gettext('Passwords do not match.'));
} ,400);
}else {
that.errorModel.clear();
self.__internal.buttons[1].element.disabled = false;
}
});
},
// Callback functions when click on the buttons of the Alertify dialogs
callback: function(e) {
if (e.button.element.name == 'submit') {
var self = this,
args = this.view.model.toJSON();
e.cancel = true;
$.ajax({
url: url,
method:'POST',
data:{'data': JSON.stringify(args) },
})
.done(function(res) {
if (res.success) {
// Notify user to update pgpass file
if(is_pgpass_file_used) {
Alertify.alert(
gettext('Change Password'),
gettext('Please make sure to disconnect the server'
+ ' and update the new password in the pgpass file'
+ ' before performing any other operation')
);
}
Alertify.success(res.info);
self.close();
} else {
Alertify.error(res.errormsg);
}
})
.fail(function(xhr, status, error) {
Alertify.pgRespErrorNotify(xhr, error);
});
}
},
};
});
}
// Call to check if server is using pgpass file or not
$.ajax({
url: check_pgpass_url,
method:'GET',
})
.done(function(res) {
if (res.success && res.data.is_pgpass) {
is_pgpass_file_used = true;
}
Alertify.changeServerPassword(d).resizeTo('40%','52%');
})
.fail(function(xhr, status, error) {
Alertify.pgRespErrorNotify(xhr, error);
});
return false;
},
/* Pause WAL Replay */
pause_wal_replay: function(args) {
var input = args || {},
obj = this,
t = pgBrowser.tree,
i = input.item || t.selected(),
d = i && i.length == 1 ? t.itemData(i) : undefined;
if (!d)
return false;
$.ajax({
url: obj.generate_url(i, 'wal_replay' , d, true),
type:'DELETE',
dataType: 'json',
})
.done(function(res) {
if (res.success == 1) {
Alertify.success(res.info);
t.itemData(i).wal_pause=res.data.wal_pause;
t.unload(i);
t.setInode(i);
t.deselect(i);
// Fetch updated data from server
setTimeout(function() {
t.select(i);
}, 10);
}
})
.fail(function(xhr, status, error) {
Alertify.pgRespErrorNotify(xhr, error);
t.unload(i);
});
},
/* Resume WAL Replay */
resume_wal_replay: function(args) {
var input = args || {},
obj = this,
t = pgBrowser.tree,
i = input.item || t.selected(),
d = i && i.length == 1 ? t.itemData(i) : undefined;
if (!d)
return false;
$.ajax({
url: obj.generate_url(i, 'wal_replay' , d, true),
type:'PUT',
dataType: 'json',
})
.done(function(res) {
if (res.success == 1) {
Alertify.success(res.info);
t.itemData(i).wal_pause=res.data.wal_pause;
t.unload(i);
t.setInode(i);
t.deselect(i);
// Fetch updated data from server
setTimeout(function() {
t.select(i);
}, 10);
}
})
.fail(function(xhr, status, error) {
Alertify.pgRespErrorNotify(xhr, error);
t.unload(i);
});
},
/* Cleat saved database server password */
clear_saved_password: function(args){
var input = args || {},
obj = this,
t = pgBrowser.tree,
i = input.item || t.selected(),
d = i && i.length == 1 ? t.itemData(i) : undefined;
if (!d)
return false;
Alertify.confirm(
gettext('Clear saved password'),
gettext('Are you sure you want to clear the saved password for server %s?', d.label),
function() {
$.ajax({
url: obj.generate_url(i, 'clear_saved_password', d, true),
method:'PUT',
})
.done(function(res) {
if (res.success == 1) {
Alertify.success(res.info);
t.itemData(i).is_password_saved=res.data.is_password_saved;
}
else {
Alertify.error(res.info);
}
})
.fail(function(xhr, status, error) {
Alertify.pgRespErrorNotify(xhr, error);
});
},
function() { return true; }
);
return false;
},
/* Reset stored ssh tunnel password */
clear_sshtunnel_password: function(args){
var input = args || {},
obj = this,
t = pgBrowser.tree,
i = input.item || t.selected(),
d = i && i.length == 1 ? t.itemData(i) : undefined;
if (!d)
return false;
Alertify.confirm(
gettext('Clear SSH Tunnel password'),
gettext('Are you sure you want to clear the saved password of SSH Tunnel for server %s?', d.label),
function() {
$.ajax({
url: obj.generate_url(i, 'clear_sshtunnel_password', d, true),
method:'PUT',
})
.done(function(res) {
if (res.success == 1) {
Alertify.success(res.info);
t.itemData(i).is_tunnel_password_saved=res.data.is_tunnel_password_saved;
}
else {
Alertify.error(res.info);
}
})
.fail(function(xhr, status, error) {
Alertify.pgRespErrorNotify(xhr, error);
});
},
function() { return true; }
);
return false;
},
},
model: pgAdmin.Browser.Node.Model.extend({
defaults: {
gid: undefined,
id: undefined,
name: '',
sslmode: 'prefer',
host: '',
hostaddr: '',
port: 5432,
db: 'postgres',
username: current_user.name,
role: null,
connect_now: true,
password: undefined,
save_password: false,
db_res: '',
passfile: undefined,
sslcompression: false,
sslcert: undefined,
sslkey: undefined,
sslrootcert: undefined,
sslcrl: undefined,
service: undefined,
use_ssh_tunnel: 0,
tunnel_host: undefined,
tunnel_port: 22,
tunnel_username: undefined,
tunnel_identity_file: undefined,
tunnel_password: undefined,
tunnel_authentication: 0,
save_tunnel_password: false,
connect_timeout: 0,
},
// Default values!
initialize: function(attrs, args) {
var isNew = (_.size(attrs) === 0);
if (isNew) {
this.set({'gid': args.node_info['server_group']._id});
}
pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
},
schema: [{
id: 'id', label: gettext('ID'), type: 'int', mode: ['properties'],
},{
id: 'name', label: gettext('Name'), type: 'text',
mode: ['properties', 'edit', 'create'],
},{
id: 'gid', label: gettext('Server group'), type: 'int',
control: 'node-list-by-id', node: 'server_group',
mode: ['create', 'edit'], select2: {allowClear: false},
},{
id: 'server_type', label: gettext('Server type'), type: 'options',
mode: ['properties'], visible: 'isConnected',
'options': supported_servers,
},{
id: 'connected', label: gettext('Connected?'), type: 'switch',
mode: ['properties'], group: gettext('Connection'), 'options': {
'onText': gettext('True'), 'offText': gettext('False'), 'size': 'mini',
},
},{
id: 'version', label: gettext('Version'), type: 'text', group: null,
mode: ['properties'], visible: 'isConnected',
},{
id: 'bgcolor', label: gettext('Background'), type: 'color',
group: null, mode: ['edit', 'create'], disabled: 'isfgColorSet',
deps: ['fgcolor'],
},{
id: 'fgcolor', label: gettext('Foreground'), type: 'color',
group: null, mode: ['edit', 'create'], disabled: 'isConnected',
},{
id: 'connect_now', controlLabel: gettext('Connect now?'), type: 'checkbox',
group: null, mode: ['create'],
},{
id: 'comment', label: gettext('Comments'), type: 'multiline', group: null,
mode: ['properties', 'edit', 'create'],
},{
id: 'host', label: gettext('Host name/address'), type: 'text', group: gettext('Connection'),
mode: ['properties', 'edit', 'create'], disabled: 'isConnected',
},{
id: 'port', label: gettext('Port'), type: 'int', group: gettext('Connection'),
mode: ['properties', 'edit', 'create'], disabled: 'isConnected', min: 1, max: 65535,
},{
id: 'db', label: gettext('Maintenance database'), type: 'text', group: gettext('Connection'),
mode: ['properties', 'edit', 'create'], disabled: 'isConnected',
},{
id: 'username', label: gettext('Username'), type: 'text', group: gettext('Connection'),
mode: ['properties', 'edit', 'create'], disabled: 'isConnected',
},{
id: 'password', label: gettext('Password'), type: 'password',
group: gettext('Connection'), control: 'input', mode: ['create'], deps: ['connect_now'],
visible: function(model) {
return model.get('connect_now') && model.isNew();
},
},{
id: 'save_password', controlLabel: gettext('Save password?'),
type: 'checkbox', group: gettext('Connection'), mode: ['create'],
deps: ['connect_now'], visible: function(model) {
return model.get('connect_now') && model.isNew();
},
disabled: function() {
if (!current_user.allow_save_password)
return true;
return false;
},
},{
id: 'role', label: gettext('Role'), type: 'text', group: gettext('Connection'),
mode: ['properties', 'edit', 'create'], disabled: 'isConnected',
},{
id: 'sslmode', label: gettext('SSL mode'), type: 'options', group: gettext('SSL'),
mode: ['properties', 'edit', 'create'], disabled: 'isConnected',
'options': [
{label: gettext('Allow'), value: 'allow'},
{label: gettext('Prefer'), value: 'prefer'},
{label: gettext('Require'), value: 'require'},
{label: gettext('Disable'), value: 'disable'},
{label: gettext('Verify-CA'), value: 'verify-ca'},
{label: gettext('Verify-Full'), value: 'verify-full'},
],
},{
id: 'sslcert', label: gettext('Client certificate'), type: 'text',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: 'isSSL', control: Backform.FileControl,
dialog_type: 'select_file', supp_types: ['*'],
deps: ['sslmode'],
},{
id: 'sslkey', label: gettext('Client certificate key'), type: 'text',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: 'isSSL', control: Backform.FileControl,
dialog_type: 'select_file', supp_types: ['*'],
deps: ['sslmode'],
},{
id: 'sslrootcert', label: gettext('Root certificate'), type: 'text',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: 'isSSL', control: Backform.FileControl,
dialog_type: 'select_file', supp_types: ['*'],
deps: ['sslmode'],
},{
id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'text',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: 'isSSL', control: Backform.FileControl,
dialog_type: 'select_file', supp_types: ['*'],
deps: ['sslmode'],
},{
id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
mode: ['edit', 'create'], group: gettext('SSL'),
'options': {'size': 'mini'},
deps: ['sslmode'], disabled: 'isSSL',
},{
id: 'sslcert', label: gettext('Client certificate'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(model) {
var sslcert = model.get('sslcert');
return !_.isUndefined(sslcert) && !_.isNull(sslcert);
},
},{
id: 'sslkey', label: gettext('Client certificate key'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(model) {
var sslkey = model.get('sslkey');
return !_.isUndefined(sslkey) && !_.isNull(sslkey);
},
},{
id: 'sslrootcert', label: gettext('Root certificate'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(model) {
var sslrootcert = model.get('sslrootcert');
return !_.isUndefined(sslrootcert) && !_.isNull(sslrootcert);
},
},{
id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(model) {
var sslcrl = model.get('sslcrl');
return !_.isUndefined(sslcrl) && !_.isNull(sslcrl);
},
},{
id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
mode: ['properties'], group: gettext('SSL'),
'options': {'size': 'mini'},
deps: ['sslmode'], visible: function(model) {
var sslmode = model.get('sslmode');
return _.indexOf(SSL_MODES, sslmode) != -1;
},
},{
id: 'use_ssh_tunnel', label: gettext('Use SSH tunneling'), type: 'switch',
mode: ['properties', 'edit', 'create'], group: gettext('SSH Tunnel'),
'options': {'size': 'mini'},
disabled: function(model) {
if (!pgAdmin.Browser.utils.support_ssh_tunnel) {
setTimeout(function() {
model.set('use_ssh_tunnel', 0);
}, 10);
return true;
}
return model.get('connected');
},
},{
id: 'tunnel_host', label: gettext('Tunnel host'), type: 'text', group: gettext('SSH Tunnel'),
mode: ['properties', 'edit', 'create'], deps: ['use_ssh_tunnel'],
disabled: function(model) {
return !model.get('use_ssh_tunnel') || model.get('connected');
},
},{
id: 'tunnel_port', label: gettext('Tunnel port'), type: 'int', group: gettext('SSH Tunnel'),
mode: ['properties', 'edit', 'create'], deps: ['use_ssh_tunnel'], max: 65535,
disabled: function(model) {
return !model.get('use_ssh_tunnel') || model.get('connected');
},
},{
id: 'tunnel_username', label: gettext('Username'), type: 'text', group: gettext('SSH Tunnel'),
mode: ['properties', 'edit', 'create'], deps: ['use_ssh_tunnel'],
disabled: function(model) {
return !model.get('use_ssh_tunnel') || model.get('connected');
},
},{
id: 'tunnel_authentication', label: gettext('Authentication'), type: 'switch',
mode: ['properties', 'edit', 'create'], group: gettext('SSH Tunnel'),
'options': {'onText': gettext('Identity file'),
'offText': gettext('Password'), 'size': 'mini', width: '90'},
deps: ['use_ssh_tunnel'],
disabled: function(model) {
return !model.get('use_ssh_tunnel') || model.get('connected');
},
}, {
id: 'tunnel_identity_file', label: gettext('Identity file'), type: 'text',
group: gettext('SSH Tunnel'), mode: ['edit', 'create'],
control: Backform.FileControl, dialog_type: 'select_file', supp_types: ['*'],
deps: ['tunnel_authentication', 'use_ssh_tunnel'],
disabled: function(model) {
let file = model.get('tunnel_identity_file');
if (!model.get('tunnel_authentication') && file) {
setTimeout(function() {
model.set('tunnel_identity_file', null);
}, 10);
}
return !model.get('tunnel_authentication') || !model.get('use_ssh_tunnel');
},
},{
id: 'tunnel_identity_file', label: gettext('Identity file'), type: 'text',
group: gettext('SSH Tunnel'), mode: ['properties'],
},{
id: 'tunnel_password', label: gettext('Password'), type: 'password',
group: gettext('SSH Tunnel'), control: 'input', mode: ['create'],
deps: ['use_ssh_tunnel'],
disabled: function(model) {
return !model.get('use_ssh_tunnel') || model.get('connected');
},
}, {
id: 'save_tunnel_password', controlLabel: gettext('Save password?'),
type: 'checkbox', group: gettext('SSH Tunnel'), mode: ['create'],
deps: ['connect_now', 'use_ssh_tunnel'], visible: function(model) {
return model.get('connect_now') && model.isNew();
},
disabled: function(model) {
if (!current_user.allow_save_tunnel_password ||
!model.get('use_ssh_tunnel'))
return true;
return false;
},
}, {
id: 'hostaddr', label: gettext('Host address'), type: 'text', group: gettext('Advanced'),
mode: ['properties', 'edit', 'create'], disabled: 'isConnected',
},{
id: 'db_res', label: gettext('DB restriction'), type: 'select2', group: gettext('Advanced'),
mode: ['properties', 'edit', 'create'], disabled: 'isConnected', select2: {multiple: true, allowClear: false,
tags: true, tokenSeparators: [','], first_empty: false, selectOnClose: true, emptyOptions: true},
},{
id: 'passfile', label: gettext('Password file'), type: 'text',
group: gettext('Advanced'), mode: ['edit', 'create'],
disabled: 'isConnectedWithValidLib', control: Backform.FileControl,
dialog_type: 'select_file', supp_types: ['*'],
},{
id: 'passfile', label: gettext('Password file'), type: 'text',
group: gettext('Advanced'), mode: ['properties'],
visible: function(model) {
var passfile = model.get('passfile');
return !_.isUndefined(passfile) && !_.isNull(passfile);
},
},{
id: 'service', label: gettext('Service'), type: 'text',
mode: ['properties', 'edit', 'create'], disabled: 'isConnected',
group: gettext('Connection'),
},{
id: 'connect_timeout', label: gettext('Connection timeout (seconds)'),
type: 'int', group: gettext('Advanced'),
mode: ['properties', 'edit', 'create'], disabled: 'isConnected',
min: 0,
}],
validate: function() {
const validateModel = new modelValidation.ModelValidation(this);
return validateModel.validate();
},
isConnected: function(model) {
return model.get('connected');
},
isfgColorSet: function(model) {
var bgcolor = model.get('bgcolor'),
fgcolor = model.get('fgcolor');
if(model.get('connected')) {
return true;
}
// If fgcolor is set and bgcolor is not set then force bgcolor
// to set as white
if(_.isUndefined(bgcolor) || _.isNull(bgcolor) || !bgcolor) {
if(fgcolor) {
model.set('bgcolor', '#ffffff');
}
}
return false;
},
isSSL: function(model) {
var ssl_mode = model.get('sslmode');
// If server is not connected and have required SSL option
if(model.get('connected')) {
return true;
}
return _.indexOf(SSL_MODES, ssl_mode) == -1;
},
isConnectedWithValidLib: function(model) {
if(model.get('connected')) {
return true;
}
// older version of libpq do not support 'passfile' parameter in
// connect method, valid libpq must have version >= 100000
return pgBrowser.utils.pg_libpq_version < 100000;
},
}),
connection_lost: function(i, resp) {
if (pgBrowser.tree) {
var t = pgBrowser.tree,
d = i && t.itemData(i),
self = this;
while (d && d._type != 'server') {
i = t.parent(i);
d = i && t.itemData(i);
}
if (i && d && d._type == 'server') {
if (_.isUndefined(d.is_connecting) || !d.is_connecting) {
d.is_connecting = true;
var disconnect = function(_sid) {
if (d._id == _sid) {
d.is_connecting = false;
// Stop listening to the connection cancellation event
pgBrowser.Events.off(
'pgadmin:server:connect:cancelled', disconnect
);
// Connection to the database will also be cancelled
pgBrowser.Events.trigger(
'pgadmin:database:connect:cancelled',_sid,
resp.data.database || d.db
);
// Make sure - the server is disconnected properly
pgBrowser.Events.trigger(
'pgadmin:server:disconnect',
{item: i, data: d}, false
);
}
};
// Listen for the server connection cancellation event
pgBrowser.Events.on(
'pgadmin:server:connect:cancelled', disconnect
);
Alertify.confirm(
gettext('Connection lost'),
gettext('Would you like to reconnect to the database?'),
function() {
connect_to_server(self, d, t, i, true);
},
function() {
d.is_connecting = false;
t.unload(i);
t.setInode(i);
t.addIcon(i, {icon: 'icon-database-not-connected'});
pgBrowser.Events.trigger(
'pgadmin:server:connect:cancelled', i, d, self
);
t.select(i);
});
}
}
}
},
});
var connect_to_server = function(obj, data, tree, item, reconnect) {
var wasConnected = reconnect || data.connected,
onFailure = function(
xhr, status, error, _node, _data, _tree, _item, _wasConnected
) {
data.connected = false;
// It should be attempt to reconnect.
// Let's not change the status of the tree node now.
if (!_wasConnected) {
tree.setInode(_item);
tree.addIcon(_item, {icon: 'icon-server-not-connected'});
}
Alertify.pgNotifier('error', xhr, error, function(msg) {
setTimeout(function() {
if (msg == 'CRYPTKEY_SET') {
connect_to_server(_node, _data, _tree, _item, _wasConnected);
} else {
Alertify.dlgServerPass(
gettext('Connect to Server'),
msg, _node, _data, _tree, _item, _wasConnected
).resizeTo();
}
}, 100);
});
},
onSuccess = function(res, node, data, tree, item, _wasConnected) {
if (res && res.data) {
if (typeof res.data.icon == 'string') {
tree.removeIcon(item);
data.icon = res.data.icon;
tree.addIcon(item, {icon: data.icon});
}
_.extend(data, res.data);
data.is_connecting = false;
var serverInfo = pgBrowser.serverInfo =
pgBrowser.serverInfo || {};
serverInfo[data._id] = _.extend({}, data);
Alertify.success(res.info);
obj.trigger('connected', obj, item, data);
// Generate the event that server is connected
pgBrowser.Events.trigger(
'pgadmin:server:connected', data._id, item, data
);
// Generate the event that database is connected
pgBrowser.Events.trigger(
'pgadmin:database:connected', data._id, data.db, item, data
);
// We're not reconnecting
if (!_wasConnected) {
tree.setInode(item);
tree.deselect(item);
setTimeout(function() {
tree.select(item);
tree.open(item);
}, 10);
} else {
// We just need to refresh the tree now.
setTimeout(function() {
node.callbacks.refresh.apply(node, [true]);
}, 10);
}
}
};
// Ask Password and send it back to the connect server
if (!Alertify.dlgServerPass) {
Alertify.dialog('dlgServerPass', function factory() {
return {
main: function(
title, message, node, data, tree, item,
_status, _onSuccess, _onFailure, _onCancel
) {
this.set('title', title);
this.message = message;
this.tree = tree;
this.nodeData = data;
this.nodeItem = item;
this.node= node;
this.connected = _status;
this.onSuccess = _onSuccess || onSuccess;
this.onFailure = _onFailure || onFailure;
this.onCancel = _onCancel || onCancel;
},
setup:function() {
return {
buttons:[{
text: gettext('Cancel'), className: 'btn btn-secondary fa fa-times pg-alertify-button',
key: 27,
},{
text: gettext('OK'), key: 13, className: 'btn btn-primary fa fa-check pg-alertify-button',
}],
focus: {element: '#password', select: true},
options: {
modal: 0, resizable: false, maximizable: false, pinnable: false,
},
};
},
build:function() {},
prepare:function() {
this.setContent(this.message);
},
callback: function(closeEvent) {
var _tree = this.tree,
_item = this.nodeItem,
_node = this.node,
_data = this.nodeData,
_status = this.connected,
_onSuccess = this.onSuccess,
_onFailure = this.onFailure,
_onCancel = this.onCancel;
if (closeEvent.button.text == gettext('OK')) {
var _url = _node.generate_url(_item, 'connect', _data, true);
if (!_status) {
_tree.setLeaf(_item);
_tree.removeIcon(_item);
_tree.addIcon(_item, {icon: 'icon-server-connecting'});
}
$.ajax({
type: 'POST',
timeout: 30000,
url: _url,
data: $('#frmPassword').serialize(),
})
.done(function(res) {
return _onSuccess(
res, _node, _data, _tree, _item, _status
);
})
.fail(function(xhr, status, error) {
return _onFailure(
xhr, status, error, _node, _data, _tree, _item, _status
);
});
} else {
_onCancel && typeof(_onCancel) == 'function' &&
_onCancel(_tree, _item, _data, _status);
}
},
};
});
}
var onCancel = function(_tree, _item, _data, _status) {
_data.is_connecting = false;
_tree.unload(_item);
_tree.setInode(_item);
_tree.removeIcon(_item);
_tree.addIcon(_item, {icon: 'icon-server-not-connected'});
obj.trigger('connect:cancelled', data._id, data.db, obj, _item, _data);
pgBrowser.Events.trigger(
'pgadmin:server:connect:cancelled', data._id, _item, _data, obj
);
pgBrowser.Events.trigger(
'pgadmin:database:connect:cancelled', data._id, data.db, _item, _data, obj
);
if (_status) {
_tree.select(_item);
}
};
data.is_connecting = true;
var url = obj.generate_url(item, 'connect', data, true);
$.post(url)
.done(function(res) {
if (res.success == 1) {
return onSuccess(
res, obj, data, tree, item, wasConnected
);
}
})
.fail(function(xhr, status, error) {
return onFailure(
xhr, status, error, obj, data, tree, item, wasConnected
);
});
};
}
return pgBrowser.Nodes['server'];
});