2021-05-25 20:12:57 +05:30
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
2024-01-01 14:13:48 +05:30
// Copyright (C) 2013 - 2024, 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' ;
2023-10-23 17:43:17 +05:30
import { getRandomInt , hasBinariesConfiguration } 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' ;
2022-09-27 14:28:31 +05:30
import { copyToClipboard } from '../../../../static/js/clipboard' ;
2022-04-07 17:36:56 +05:30
import { generateTitle , refresh _db _node } from 'tools/sqleditor/static/js/sqleditor_title' ;
2023-10-23 17:43:17 +05:30
import { BROWSER _PANELS } from '../../../../browser/static/js/constants' ;
2024-02-09 16:33:30 +05:30
import usePreferences , { listenPreferenceBroadcast } from '../../../../preferences/static/js/store' ;
import 'pgadmin.browser.keyboard' ;
2021-05-25 20:12:57 +05:30
export function setPanelTitle ( psqlToolPanel , panelTitle ) {
2023-07-13 17:02:51 +05:30
psqlToolPanel . title ( '<span title="' + _ . escape ( panelTitle ) + '">' + _ . escape ( panelTitle ) + '</span>' ) ;
2021-05-25 20:12:57 +05:30
}
2023-02-10 10:28:39 +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 ) ;
}
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 ;
}
} ) ( ) ;
return isEnabled ;
} ,
2023-07-13 17:02:51 +05:30
psql _tool : function ( data , treeIdentifier ) {
2022-04-13 17:35:01 +05:30
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 ( ) ) {
2023-10-23 17:43:17 +05:30
pgAdmin . Browser . notifier . 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 ) ) {
2023-10-23 17:43:17 +05:30
pgAdmin . Browser . notifier . 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 = {
2023-11-02 11:00:27 +05:30
'database' : parentData . database ? _ . unescape ( 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' ,
} ;
2023-10-23 17:43:17 +05:30
let tab _title _placeholder = usePreferences . getState ( ) . getPreferencesForModule ( 'browser' ) . psql _tab _title _placeholder ;
2021-05-25 20:12:57 +05:30
panelTitle = generateTitle ( tab _title _placeholder , title _data ) ;
2023-10-23 17:43:17 +05:30
const [ panelUrl , db _label ] = this . getPanelUrls ( transId , parentData ) ;
2021-05-25 20:12:57 +05:30
2023-10-23 17:43:17 +05:30
const escapedTitle = _ . escape ( panelTitle ) ;
const open _new _tab = usePreferences . getState ( ) . getPreferencesForModule ( 'browser' ) . new _browser _tab _open ;
pgAdmin . Browser . Events . trigger (
'pgadmin:tool:show' ,
` ${ BROWSER _PANELS . PSQL _TOOL } _ ${ transId } ` ,
panelUrl ,
{ title : escapedTitle , db : db _label } ,
{ title : panelTitle , icon : 'fas fa-terminal psql-tab-style' , manualClose : false , renamable : true } ,
Boolean ( open _new _tab ? . includes ( 'psql_tool' ) )
) ;
return true ;
2021-05-25 20:12:57 +05:30
} ,
2023-07-13 17:02:51 +05:30
getPanelUrls : function ( transId , pData ) {
2021-05-25 20:12:57 +05:30
let openUrl = url _for ( 'psql.panel' , {
trans _id : transId ,
} ) ;
2023-10-23 17:43:17 +05:30
const misc _preferences = usePreferences . getState ( ) . getPreferencesForModule ( '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 } ` ;
2023-07-13 17:02:51 +05:30
2024-01-25 16:51:40 +05:30
if ( pData . database ? . _id ) {
2023-07-13 17:02:51 +05:30
openUrl += ` &db= ${ encodeURIComponent ( pData . database . _label ) } ` ;
2021-05-25 20:12:57 +05:30
} else {
openUrl += ` &db= ${ '' } ` ;
}
2023-10-23 17:43:17 +05:30
return [ openUrl , pData . database . _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 ,
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 ) ;
} ,
2023-11-02 11:00:27 +05:30
psql _socket _io : function ( socket , is _enable , sid , db , server _type , fitAddon , term , role ) {
2021-05-25 20:12:57 +05:30
// Listen all the socket events emit from server.
2023-11-02 11:00:27 +05:30
let init _psql = true ;
2021-05-25 20:12:57 +05:30
socket . on ( 'pty-output' , function ( data ) {
if ( data . error ) {
term . write ( '\r\n' ) ;
}
term . write ( data . result ) ;
if ( data . error ) {
term . write ( '\r\n' ) ;
}
2023-11-02 11:00:27 +05:30
if ( init _psql && data && role !== 'None' ) {
// setting role if available
socket . emit ( 'socket_set_role' , { 'role' : _ . unescape ( role ) } ) ;
init _psql = false ;
}
2021-05-25 20:12:57 +05:30
} ) ;
// 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 ) ;
2024-02-09 16:33:30 +05:30
} else {
2023-10-23 17:43:17 +05:30
pgAdmin . Browser . notifier . alert ( gettext ( 'Clipboard write permission required' ) , gettext ( 'To copy data from PSQL terminal, Clipboard write permission required.' ) ) ;
2022-09-27 14:28:31 +05:30
}
} ) ;
2024-02-09 16:33:30 +05:30
} else {
self . pgAdmin . Browser . keyboardNavigation . triggerIframeEventsBroadcast ( e , true ) ;
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 {
2023-10-23 17:43:17 +05:30
pgAdmin . Browser . notifier . 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 ) {
2023-12-11 11:28:33 +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 ) ;
}
} ,
2024-02-09 16:33:30 +05:30
psql _mount : async function ( params ) {
self . pgAdmin . Browser . keyboardNavigation . init ( ) ;
await listenPreferenceBroadcast ( ) ;
const term = self . pgAdmin . Browser . psql . psql _terminal ( ) ;
/* Addon for fitAddon, webLinkAddon, SearchAddon */
const fitAddon = self . pgAdmin . Browser . psql . psql _Addon ( term ) ;
/* Update the theme for terminal as per pgAdmin 4 theme. */
self . pgAdmin . Browser . psql . set _theme ( term ) ;
/* Open the terminal */
term . open ( document . getElementById ( 'psql-terminal' ) ) ;
/* Socket */
const socket = self . pgAdmin . Browser . psql . psql _socket ( ) ;
self . pgAdmin . Browser . psql . psql _socket _io ( socket , params . is _enable , params . sid , params . db , params . server _type , fitAddon , term , params . role ) ;
self . pgAdmin . Browser . psql . psql _terminal _io ( term , socket , params . platform ) ;
self . pgAdmin . Browser . psql . check _db _name _change ( params . db , params . o _db _name ) ;
/* Set terminal size */
setTimeout ( function ( ) {
socket . emit ( 'resize' , { 'cols' : term . cols , 'rows' : term . rows } ) ;
} , 1000 ) ;
/* Resize the terminal */
function fitToscreen ( ) {
fitAddon . fit ( ) ;
socket . emit ( 'resize' , { 'cols' : term . cols , 'rows' : term . rows } ) ;
}
function debounce ( func , wait _ms ) {
let timeout ;
return function ( ... args ) {
const context = this ;
clearTimeout ( timeout ) ;
timeout = setTimeout ( ( ) => func . apply ( context , args ) , wait _ms ) ;
} ;
}
window . onresize = debounce ( fitToscreen , 25 ) ;
}
2021-05-25 20:12:57 +05:30
} ;
return pgBrowser . psql ;
}