2021-05-25 20:12:57 +05:30
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
2023-01-02 11:53:55 +05:30
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
2021-05-25 20:12:57 +05:30
// 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 { enable } from 'pgadmin.browser.toolbar' ;
import 'wcdocker' ;
2021-06-25 11:38:02 +05:30
import { getRandomInt , hasBinariesConfiguration , registerDetachEvent } from 'sources/utils' ;
2021-06-04 17:55:35 +05:30
import { retrieveAncestorOfTypeServer } from 'sources/tree/tree_utils' ;
2021-05-31 12:41:09 +05:30
import pgWindow from 'sources/window' ;
2021-12-07 18:52:40 +05:30
import Notify from '../../../../static/js/helpers/Notifier' ;
2022-09-27 14:28:31 +05:30
import { copyToClipboard } from '../../../../static/js/clipboard' ;
2022-12-08 19:17:12 +05:30
import { openNewWindow } from '../../../../static/js/utils' ;
2022-04-07 17:36:56 +05:30
import { generateTitle , refresh _db _node } from 'tools/sqleditor/static/js/sqleditor_title' ;
2021-05-25 20:12:57 +05:30
export function setPanelTitle ( psqlToolPanel , panelTitle ) {
psqlToolPanel . title ( '<span title="' + panelTitle + '">' + panelTitle + '</span>' ) ;
}
2022-09-08 18:08:58 +05:30
let wcDocker = window . wcDocker ;
2021-05-25 20:12:57 +05:30
export function initialize ( gettext , url _for , $ , _ , pgAdmin , csrfToken , Browser ) {
2022-09-08 18:08:58 +05:30
let pgBrowser = Browser ;
let terminal = Terminal ;
let parentData = null ;
2021-05-25 20:12:57 +05:30
/* 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
2022-09-08 18:08:58 +05:30
let menus = [ {
2021-05-25 20:12:57 +05:30
name : 'psql' ,
module : this ,
applies : [ 'tools' ] ,
callback : 'psql_tool' ,
priority : 1 ,
2021-09-23 16:17:39 +05:30
label : gettext ( 'PSQL Tool' ) ,
2021-05-25 20:12:57 +05:30
enable : this . psqlToolEnabled ,
} ] ;
this . enable _psql _tool = pgAdmin [ 'enable_psql' ] ;
2021-06-08 14:58:43 +05:30
if ( pgAdmin [ 'enable_psql' ] ) {
2021-05-25 20:12:57 +05:30
pgBrowser . add _menus ( menus ) ;
}
// Creating a new pgBrowser frame to show the data.
2022-09-08 18:08:58 +05:30
let psqlFrameType = new pgBrowser . Frame ( {
2021-05-25 20:12:57 +05:30
name : 'frm_psqltool' ,
showTitle : true ,
isCloseable : true ,
isPrivate : true ,
url : 'about:blank' ,
} ) ;
2022-09-08 18:08:58 +05:30
let self = this ;
2021-05-25 20:12:57 +05:30
/ * 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 ) {
2022-09-08 18:08:58 +05:30
let isEnabled = ( ( ) => {
2021-05-25 20:12:57 +05:30
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 ;
} ,
2022-04-13 17:35:01 +05:30
psql _tool : function ( data , treeIdentifier , gen = false ) {
const serverInformation = retrieveAncestorOfTypeServer ( pgBrowser , treeIdentifier , gettext ( 'PSQL Error' ) ) ;
2021-12-07 18:52:40 +05:30
if ( ! hasBinariesConfiguration ( pgBrowser , serverInformation ) ) {
2021-06-04 17:55:35 +05:30
return ;
2021-05-25 20:12:57 +05:30
}
2021-06-04 17:55:35 +05:30
2022-04-13 17:35:01 +05:30
const node = pgBrowser . tree . findNodeByDomElement ( treeIdentifier ) ;
2021-05-25 20:12:57 +05:30
if ( node === undefined || ! node . getData ( ) ) {
2021-12-07 18:52:40 +05:30
Notify . alert (
2021-05-25 20:12:57 +05:30
gettext ( 'PSQL Error' ) ,
gettext ( 'No object selected.' )
) ;
return ;
}
2022-04-13 17:35:01 +05:30
parentData = pgBrowser . tree . getTreeNodeHierarchy ( treeIdentifier ) ;
2021-05-25 20:12:57 +05:30
if ( _ . isUndefined ( parentData . server ) ) {
2021-12-07 18:52:40 +05:30
Notify . alert (
2021-05-25 20:12:57 +05:30
gettext ( 'PSQL Error' ) ,
gettext ( 'Please select a server/database object.' )
) ;
return ;
}
const transId = getRandomInt ( 1 , 9999999 ) ;
2022-09-08 18:08:58 +05:30
let panelTitle = '' ;
2021-05-25 20:12:57 +05:30
// Set psql tab title as per prefrences setting.
2022-09-08 18:08:58 +05:30
let title _data = {
2021-05-25 20:12:57 +05:30
'database' : parentData . database ? parentData . database . label : 'postgres' ,
2022-10-13 10:59:05 +05:30
'username' : parentData . server . user . name ,
2021-05-25 20:12:57 +05:30
'server' : parentData . server . label ,
'type' : 'psql_tool' ,
} ;
2022-09-08 18:08:58 +05:30
let tab _title _placeholder = pgBrowser . get _preferences _for _module ( 'browser' ) . psql _tab _title _placeholder ;
2021-05-25 20:12:57 +05:30
panelTitle = generateTitle ( tab _title _placeholder , title _data ) ;
2021-06-08 14:58:43 +05:30
const [ panelUrl , panelCloseUrl , db _label ] = this . getPanelUrls ( transId , panelTitle , parentData , gen ) ;
2021-05-25 20:12:57 +05:30
let psqlToolForm = `
< form id = "psqlToolForm" action = "${panelUrl}" method = "post" >
< input id = "title" name = "title" hidden / >
2021-06-08 14:58:43 +05:30
< input id = 'db' value = '${db_label}' hidden / >
2021-05-25 20:12:57 +05:30
< 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 >
` ;
2022-09-08 18:08:58 +05:30
let open _new _tab = pgBrowser . get _preferences _for _module ( 'browser' ) . new _browser _tab _open ;
2021-05-25 20:12:57 +05:30
if ( open _new _tab && open _new _tab . includes ( 'psql_tool' ) ) {
2022-12-08 19:17:12 +05:30
openNewWindow ( psqlToolForm , panelTitle ) ;
2021-05-25 20:12:57 +05:30
} 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 .
* /
2022-09-08 18:08:58 +05:30
let propertiesPanel = pgBrowser . docker . findPanels ( 'properties' ) ;
let psqlToolPanel = pgBrowser . docker . addPanel ( 'frm_psqltool' , wcDocker . DOCK . STACKED , propertiesPanel [ 0 ] ) ;
2021-05-25 20:12:57 +05:30
2021-06-25 11:38:02 +05:30
registerDetachEvent ( psqlToolPanel ) ;
2021-06-23 11:49:28 +05:30
2021-05-25 20:12:57 +05:30
// Set panel title and icon
setPanelTitle ( psqlToolPanel , panelTitle ) ;
psqlToolPanel . icon ( 'fas fa-terminal psql-tab-style' ) ;
psqlToolPanel . focus ( ) ;
2022-09-08 18:08:58 +05:30
let openPSQLToolURL = function ( j ) {
2021-05-25 20:12:57 +05:30
// 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 ( ) {
2022-09-08 18:08:58 +05:30
let frameInitialized = $ ( j ) . data ( 'frameInitialized' ) ;
2021-05-25 20:12:57 +05:30
if ( frameInitialized ) {
clearInterval ( init _poller _id ) ;
2022-09-08 18:08:58 +05:30
let frame = $ ( j ) . data ( 'embeddedFrame' ) ;
2021-05-25 20:12:57 +05:30
if ( frame ) {
frame . onLoaded ( ( ) => {
$spinner _el . remove ( ) ;
} ) ;
frame . openHTML ( psqlToolForm ) ;
}
}
} , 100 ) ;
} ;
openPSQLToolURL ( psqlToolPanel ) ;
}
} ,
2022-01-21 18:53:48 +05:30
getPanelUrls : function ( transId , panelTitle , pData ) {
2021-05-25 20:12:57 +05:30
let openUrl = url _for ( 'psql.panel' , {
trans _id : transId ,
} ) ;
const misc _preferences = pgBrowser . get _preferences _for _module ( 'misc' ) ;
2022-09-08 18:08:58 +05:30
let theme = misc _preferences . theme ;
2021-05-25 20:12:57 +05:30
2022-01-21 18:53:48 +05:30
openUrl += ` ?sgid= ${ pData . server _group . _id } `
+ ` &sid= ${ pData . server . _id } `
+ ` &did= ${ pData . database . _id } `
+ ` &server_type= ${ pData . server . server _type } `
2021-05-25 20:12:57 +05:30
+ ` &theme= ${ theme } ` ;
2021-06-08 14:58:43 +05:30
let db _label = '' ;
2022-01-21 18:53:48 +05:30
if ( pData . database && pData . database . _id ) {
db _label = _ . escape ( pData . database . _label . replace ( '\\' , '\\\\' ) ) ;
2021-06-08 14:58:43 +05:30
db _label = db _label . replace ( '\'' , '\'' ) ;
db _label = db _label . replace ( '"' , '\"' ) ;
2021-05-25 20:12:57 +05:30
openUrl += ` &db= ${ db _label } ` ;
} else {
openUrl += ` &db= ${ '' } ` ;
}
let closeUrl = url _for ( 'psql.close' , {
trans _id : transId ,
} ) ;
2021-06-08 14:58:43 +05:30
return [ openUrl , closeUrl , db _label ] ;
2021-05-25 20:12:57 +05:30
} ,
psql _terminal : function ( ) {
// theme colors
2022-01-18 14:49:54 +05:30
return new terminal ( {
2021-05-25 20:12:57 +05:30
cursorBlink : true ,
macOptionIsMeta : true ,
scrollback : 5000 ,
} ) ;
} ,
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 ( ) {
2022-11-15 13:51:12 +05:30
return io ( '/pty' , {
path : ` ${ url _for ( 'pgadmin.root' ) } /socket.io ` ,
pingTimeout : 120000 ,
pingInterval : 25000
} ) ;
2021-05-25 20:12:57 +05:30
} ,
set _theme : function ( term ) {
2022-09-08 18:08:58 +05:30
let theme = {
2021-05-25 20:12:57 +05:30
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.' ) ;
} ) ;
} ,
2021-06-24 20:00:52 +05:30
psql _terminal _io : function ( term , socket , platform ) {
2021-05-25 20:12:57 +05:30
// Listen key press event from terminal and emit socket event.
term . attachCustomKeyEventHandler ( e => {
e . stopPropagation ( ) ;
2021-07-23 12:41:48 +05:30
if ( e . type == 'keydown' && ( e . metaKey || e . ctrlKey ) && ( e . key == 'c' || e . key == 'C' ) ) {
2022-09-27 14:28:31 +05:30
let selected _text = term . getSelection ( ) ;
navigator . permissions . query ( { name : 'clipboard-write' } ) . then ( function ( result ) {
if ( result . state === 'granted' || result . state === 'prompt' ) {
copyToClipboard ( selected _text ) ;
} else {
Notify . alert ( gettext ( 'Clipboard write permission required' ) , gettext ( 'To copy data from PSQL terminal, Clipboard write permission required.' ) ) ;
}
} ) ;
2021-07-23 12:41:48 +05:30
}
2022-09-08 19:56:02 +05:30
return ! ( e . ctrlKey && platform == 'win32' ) ;
2021-07-23 12:41:48 +05:30
} ) ;
2021-07-23 12:57:40 +05:30
term . textarea . addEventListener ( 'paste' , function ( ) {
2021-07-23 12:41:48 +05:30
navigator . permissions . query ( { name : 'clipboard-read' } ) . then ( function ( result ) {
2021-07-23 12:57:40 +05:30
if ( result . state === 'granted' || result . state === 'prompt' ) {
navigator . clipboard . readText ( ) . then ( clipText => {
2022-09-08 18:08:58 +05:30
let selected _text = clipText ;
2021-07-23 12:57:40 +05:30
if ( selected _text . length > 0 ) {
socket . emit ( 'socket_input' , { 'input' : selected _text , 'key_name' : 'paste' } ) ;
}
} ) ;
} else {
2021-12-07 18:52:40 +05:30
Notify . alert ( gettext ( 'Clipboard read permission required' ) , gettext ( 'To paste data on the PSQL terminal, Clipboard read permission required.' ) ) ;
2021-07-23 12:57:40 +05:30
}
} ) ;
2021-05-25 20:12:57 +05:30
} ) ;
term . onKey ( function ( ev ) {
2021-05-29 12:55:59 +05:30
socket . emit ( 'socket_input' , { 'input' : ev . key , 'key_name' : ev . domEvent . code } ) ;
2021-05-25 20:12:57 +05:30
} ) ;
2021-05-31 12:41:09 +05:30
} ,
check _db _name _change : function ( db _name , o _db _name ) {
if ( db _name != o _db _name ) {
2022-09-08 18:08:58 +05:30
let selected _item = pgWindow . pgAdmin . Browser . tree . selected ( ) ,
2021-09-27 16:44:26 +05:30
tree _data = pgWindow . pgAdmin . Browser . tree . translateTreeNodeIdFromReactTree ( selected _item ) ,
database _data = pgWindow . pgAdmin . Browser . tree . findNode ( tree _data [ 3 ] ) ,
2021-05-31 12:41:09 +05:30
dbNode = database _data . domNode ;
2022-09-08 18:08:58 +05:30
let 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. ` ;
2021-05-31 12:41:09 +05:30
refresh _db _node ( message , dbNode ) ;
}
} ,
2021-05-25 20:12:57 +05:30
} ;
return pgBrowser . psql ;
}