2021-05-25 09:42:57 -05:00
/////////////////////////////////////////////////////////////
//
// 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' ;
2021-06-04 07:25:35 -05:00
import { getRandomInt , hasBinariesConfiguration } from 'sources/utils' ;
import { retrieveAncestorOfTypeServer } from 'sources/tree/tree_utils' ;
2021-05-31 02:11:09 -05:00
import pgWindow from 'sources/window' ;
2021-05-25 09:42:57 -05:00
import { getTreeNodeHierarchyFromIdentifier } from 'sources/tree/pgadmin_tree_node' ;
2021-05-31 02:11:09 -05:00
import { generateTitle , refresh _db _node } from 'tools/datagrid/static/js/datagrid_panel_title' ;
2021-05-25 09:42:57 -05:00
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' ] ;
2021-06-01 09:34:43 -05:00
if ( pgAdmin [ 'enable_psql' ] && pgAdmin [ 'platform' ] != 'win32' ) {
2021-05-25 09:42:57 -05:00
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 ;
/ * C a c h e m a y t a k e t i m e t o l o a d f o r t h e f i r s t t i m e
* 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 ;
} ,
/ * E n a b l e / d i s a b l e P S Q L t o o l m e n u i n t o o l s b a s e d
* 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 ) {
2021-06-04 07:25:35 -05:00
const serverInformation = retrieveAncestorOfTypeServer ( pgBrowser , aciTreeIdentifier , gettext ( 'PSQL Error' ) , Alertify ) ;
if ( ! hasBinariesConfiguration ( pgBrowser , serverInformation , Alertify ) ) {
return ;
2021-05-25 09:42:57 -05:00
}
2021-06-04 07:25:35 -05:00
2021-05-25 09:42:57 -05:00
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 ] = this . getPanelUrls ( transId , panelTitle , parentData , gen ) ;
let psqlToolForm = `
< form id = "psqlToolForm" action = "${panelUrl}" method = "post" >
< input id = "title" name = "title" hidden / >
< input name = "close_url" value = "${panelCloseUrl}" hidden / >
< / f o r m >
< script >
document . getElementById ( "title" ) . value = "${_.escape(panelTitle)}" ;
document . getElementById ( "psqlToolForm" ) . submit ( ) ;
< / s c r i p t >
` ;
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 {
/ * O n s u c c e s s f u l l y i n i t i a l i z a t i o n f i n d t h e p r o p e r t i e s p a n e l ,
* 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" > < / d i v >
< / d i v >
< / d i v >
< / d i v > ` ) . a p p e n d T o ( $ ( j ) . d a t a ( ' e m b e d d e d F r a m e ' ) . $ c o n t a i n e r ) ;
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 } `
2021-05-31 02:11:09 -05:00
+ ` &did= ${ parentData . database . _id } `
2021-05-25 09:42:57 -05:00
+ ` &server_type= ${ parentData . server . server _type } `
+ ` &theme= ${ theme } ` ;
if ( parentData . database && parentData . database . _id ) {
let db _label = parentData . database . _label . replace ( '\\' , '\\\\' ) ;
openUrl += ` &db= ${ db _label } ` ;
} else {
openUrl += ` &db= ${ '' } ` ;
}
let closeUrl = url _for ( 'psql.close' , {
trans _id : transId ,
} ) ;
return [ openUrl , closeUrl ] ;
} ,
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 ) {
2021-05-29 02:25:59 -05:00
socket . emit ( 'socket_input' , { 'input' : ev . key , 'key_name' : ev . domEvent . code } ) ;
2021-05-25 09:42:57 -05:00
} ) ;
2021-05-31 02:11:09 -05:00
} ,
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 ) ;
}
} ,
2021-05-25 09:42:57 -05:00
} ;
return pgBrowser . psql ;
}