pgadmin4/web/pgadmin/tools/psql/static/js/psql_module.js
Nikhil Mohite 9f12747d9b Added PSQL tool support for the Windows platform.
Fixed following issues:
1. If the database name contains escape characters then PSQL unable to connect.
2. If the user terminates the connection by typing the 'exit' command, PSQL
   will show the connection termination msg.

Fixes #2341
2021-06-08 14:58:43 +05:30

381 lines
13 KiB
JavaScript

/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import { WebLinksAddon } from 'xterm-addon-web-links';
import { SearchAddon } from 'xterm-addon-search';
import { io } from 'socketio';
import Alertify from 'pgadmin.alertifyjs';
import {enable} from 'pgadmin.browser.toolbar';
import clipboard from 'sources/selection/clipboard';
import 'wcdocker';
import {getRandomInt, hasBinariesConfiguration} from 'sources/utils';
import {retrieveAncestorOfTypeServer} from 'sources/tree/tree_utils';
import pgWindow from 'sources/window';
import {getTreeNodeHierarchyFromIdentifier} from 'sources/tree/pgadmin_tree_node';
import {generateTitle, refresh_db_node} from 'tools/datagrid/static/js/datagrid_panel_title';
export function setPanelTitle(psqlToolPanel, panelTitle) {
psqlToolPanel.title('<span title="'+panelTitle+'">'+panelTitle+'</span>');
}
var wcDocker = window.wcDocker;
export function initialize(gettext, url_for, $, _, pgAdmin, csrfToken, Browser) {
var pgBrowser = Browser;
var terminal = Terminal;
var parentData = null;
/* Return back, this has been called more than once */
if (pgBrowser.psql)
return pgBrowser.psql;
// Create an Object Restore of pgBrowser class
pgBrowser.psql = {
init: function() {
this.initialized = true;
csrfToken.setPGCSRFToken(pgAdmin.csrf_token_header, pgAdmin.csrf_token);
// Define the nodes on which the menus to be appear
var menus = [{
name: 'psql',
module: this,
applies: ['tools'],
callback: 'psql_tool',
priority: 1,
label: gettext('PSQL Tool (Beta)'),
enable: this.psqlToolEnabled,
}];
this.enable_psql_tool = pgAdmin['enable_psql'];
if(pgAdmin['enable_psql']) {
pgBrowser.add_menus(menus);
}
// Creating a new pgBrowser frame to show the data.
var psqlFrameType = new pgBrowser.Frame({
name: 'frm_psqltool',
showTitle: true,
isCloseable: true,
isPrivate: true,
url: 'about:blank',
});
var self = this;
/* Cache may take time to load for the first time
* Keep trying till available
*/
let cacheIntervalId = setInterval(function() {
if(pgBrowser.preference_version() > 0) {
self.preferences = pgBrowser.get_preferences_for_module('psql');
clearInterval(cacheIntervalId);
}
},0);
pgBrowser.onPreferencesChange('psql', function() {
self.preferences = pgBrowser.get_preferences_for_module('psql');
});
// Load the newly created frame
psqlFrameType.load(pgBrowser.docker);
return this;
},
/* Enable/disable PSQL tool menu in tools based
* on node selected. if selected node is present
* in unsupported_nodes, menu will be disabled
* otherwise enabled.
*/
psqlToolEnabled: function(obj) {
var isEnabled = (() => {
if (!_.isUndefined(obj) && !_.isNull(obj) && pgAdmin['enable_psql']) {
if (_.indexOf(pgAdmin.unsupported_nodes, obj._type) == -1) {
if (obj._type == 'database' && obj.allowConn) {
return true;
} else if (obj._type != 'database') {
return true;
} else {
return false;
}
} else {
return false;
}
} else {
return false;
}
})();
enable(gettext('PSQL Tool'), isEnabled);
return isEnabled;
},
psql_tool: function(data, aciTreeIdentifier, gen=false) {
const serverInformation = retrieveAncestorOfTypeServer(pgBrowser, aciTreeIdentifier, gettext('PSQL Error'), Alertify);
if (!hasBinariesConfiguration(pgBrowser, serverInformation, Alertify)) {
return;
}
const node = pgBrowser.treeMenu.findNodeByDomElement(aciTreeIdentifier);
if (node === undefined || !node.getData()) {
Alertify.alert(
gettext('PSQL Error'),
gettext('No object selected.')
);
return;
}
parentData = getTreeNodeHierarchyFromIdentifier.call(
pgBrowser,
aciTreeIdentifier
);
if(_.isUndefined(parentData.server)) {
Alertify.alert(
gettext('PSQL Error'),
gettext('Please select a server/database object.')
);
return;
}
const transId = getRandomInt(1, 9999999);
var panelTitle = '';
// Set psql tab title as per prefrences setting.
var title_data = {
'database': parentData.database ? parentData.database.label : 'postgres' ,
'username': parentData.server.user_name,
'server': parentData.server.label,
'type': 'psql_tool',
};
var tab_title_placeholder = pgBrowser.get_preferences_for_module('browser').psql_tab_title_placeholder;
panelTitle = generateTitle(tab_title_placeholder, title_data);
const [panelUrl, panelCloseUrl, db_label] = this.getPanelUrls(transId, panelTitle, parentData, gen);
let psqlToolForm = `
<form id="psqlToolForm" action="${panelUrl}" method="post">
<input id="title" name="title" hidden />
<input id='db' value='${db_label}' hidden />
<input name="close_url" value="${panelCloseUrl}" hidden />
</form>
<script>
document.getElementById("title").value = "${_.escape(panelTitle)}";
document.getElementById("psqlToolForm").submit();
</script>
`;
var open_new_tab = pgBrowser.get_preferences_for_module('browser').new_browser_tab_open;
if (open_new_tab && open_new_tab.includes('psql_tool')) {
var newWin = window.open('', '_blank');
newWin.document.write(psqlToolForm);
newWin.document.title = panelTitle;
} else {
/* On successfully initialization find the properties panel,
* create new panel and add it to the dashboard panel.
*/
var propertiesPanel = pgBrowser.docker.findPanels('properties');
var psqlToolPanel = pgBrowser.docker.addPanel('frm_psqltool', wcDocker.DOCK.STACKED, propertiesPanel[0]);
// Set panel title and icon
setPanelTitle(psqlToolPanel, panelTitle);
psqlToolPanel.icon('fas fa-terminal psql-tab-style');
psqlToolPanel.focus();
var openPSQLToolURL = function(j) {
// add spinner element
let $spinner_el =
$(`<div class="pg-sp-container">
<div class="pg-sp-content">
<div class="row">
<div class="col-12 pg-sp-icon"></div>
</div>
</div>
</div>`).appendTo($(j).data('embeddedFrame').$container);
let init_poller_id = setInterval(function() {
var frameInitialized = $(j).data('frameInitialized');
if (frameInitialized) {
clearInterval(init_poller_id);
var frame = $(j).data('embeddedFrame');
if (frame) {
frame.onLoaded(()=>{
$spinner_el.remove();
});
frame.openHTML(psqlToolForm);
}
}
}, 100);
};
openPSQLToolURL(psqlToolPanel);
}
},
getPanelUrls: function(transId, panelTitle, parentData) {
let openUrl = url_for('psql.panel', {
trans_id: transId,
});
const misc_preferences = pgBrowser.get_preferences_for_module('misc');
var theme = misc_preferences.theme;
openUrl += `?sgid=${parentData.server_group._id}`
+`&sid=${parentData.server._id}`
+`&did=${parentData.database._id}`
+`&server_type=${parentData.server.server_type}`
+ `&theme=${theme}`;
let db_label = '';
if(parentData.database && parentData.database._id) {
db_label = _.escape(parentData.database._label.replace('\\', '\\\\'));
db_label = db_label.replace('\'', '\'');
db_label = db_label.replace('"', '\"');
openUrl += `&db=${db_label}`;
} else {
openUrl += `&db=${''}`;
}
let closeUrl = url_for('psql.close', {
trans_id: transId,
});
return [openUrl, closeUrl, db_label];
},
psql_terminal: function() {
// theme colors
var term = new terminal({
cursorBlink: true,
macOptionIsMeta: true,
scrollback: 5000,
});
return term;
},
psql_Addon: function(term) {
const fitAddon = this.psql_fit_screen();
term.loadAddon(fitAddon);
const webLinksAddon = this.psql_web_link();
term.loadAddon(webLinksAddon);
const searchAddon = this.psql_search();
term.loadAddon(searchAddon);
fitAddon.fit();
term.resize(15, 50);
fitAddon.fit();
return fitAddon;
},
psql_fit_screen: function() {
return new FitAddon();
},
psql_web_link: function() {
return new WebLinksAddon();
},
psql_search: function() {
return new SearchAddon();
},
psql_socket: function() {
return io('/pty', {pingTimeout: 120000, pingInterval: 25000});
},
set_theme: function(term) {
var theme = {
background: getComputedStyle(document.documentElement).getPropertyValue('--psql-background'),
foreground: getComputedStyle(document.documentElement).getPropertyValue('--psql-foreground'),
cursor: getComputedStyle(document.documentElement).getPropertyValue('--psql-cursor'),
cursorAccent: getComputedStyle(document.documentElement).getPropertyValue('--psql-cursorAccent'),
selection: getComputedStyle(document.documentElement).getPropertyValue('--psql-selection'),
};
term.setOption('theme', theme);
},
psql_socket_io: function(socket, is_enable, sid, db, server_type, fitAddon, term) {
// Listen all the socket events emit from server.
socket.on('pty-output', function(data){
if(data.error) {
term.write('\r\n');
}
term.write(data.result);
if(data.error) {
term.write('\r\n');
}
});
// Connect socket
socket.on('connect', () => {
if(is_enable == 'True'){
socket.emit('start_process', {'sid': sid, 'db': db, 'stype': server_type });
}
fitAddon.fit();
socket.emit('resize', {'cols': term.cols, 'rows': term.rows});
});
socket.on('conn_error', (response) => {
term.write(response.error);
fitAddon.fit();
socket.emit('resize', {'cols': term.cols, 'rows': term.rows});
});
socket.on('conn_not_allow', () => {
term.write('PSQL connection not allowed');
fitAddon.fit();
socket.emit('resize', {'cols': term.cols, 'rows': term.rows});
});
socket.on('disconnect-psql', () => {
socket.emit('server-disconnect', {'sid': sid});
term.write('\r\nServer disconnected, Connection terminated, To create new connection please open another psql tool.');
});
},
psql_terminal_io: function(term, socket) {
// Listen key press event from terminal and emit socket event.
let selected_text = '';
term.attachCustomKeyEventHandler(e => {
e.stopPropagation();
if(e.type=='keydown' && e.metaKey &&(e.key == 'v' || e.key == 'V')) {
if(selected_text != '') {
if (selected_text.length > 0) {
socket.emit('socket_input', {'input': selected_text, 'key_name': e.code});
selected_text = '';
}
} else {
navigator.clipboard.readText().then( clipText => {
selected_text = clipText;
if (selected_text.length > 0) {
socket.emit('socket_input', {'input': selected_text, 'key_name': e.code});
selected_text = '';
}
});
}
}else if(e.type=='keydown' && e.metaKey && (e.key == 'c' || e.key == 'C')) {
if (term.hasSelection()) {
selected_text = term.getSelection();
} else {
selected_text = clipboard.readText();
}
}
return true;
});
term.onKey(function (ev) {
socket.emit('socket_input', {'input': ev.key, 'key_name': ev.domEvent.code});
});
},
check_db_name_change: function(db_name, o_db_name) {
if (db_name != o_db_name) {
var selected_item = pgWindow.pgAdmin.Browser.treeMenu.selected(),
tree_data = pgWindow.pgAdmin.Browser.treeMenu.translateTreeNodeIdFromACITree(selected_item),
database_data = pgWindow.pgAdmin.Browser.treeMenu.findNode(tree_data.slice(0,4)),
dbNode = database_data.domNode;
var message = `Current database has been moved or renamed to ${o_db_name}. Click on the OK button to refresh the database name, and reopen the psql again.`;
refresh_db_node(message, dbNode);
}
},
};
return pgBrowser.psql;
}