Added support to preserve the workspace, query windows, and pgAdmin state during an abrupt shutdown or restart. #3319
parent
c2ef9d06ca
commit
68e559c613
Binary file not shown.
|
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 294 KiB |
|
|
@ -309,12 +309,21 @@ Use the fields on the *User Interface* panel to set the user interface related p
|
|||
this setting is False, meaning that Query Tool/PSQL tabs will open in the currently
|
||||
active workspace (either the default or the workspace selected at the time of opening).
|
||||
|
||||
* When the *Save the application state?* option is enabled the current state of various
|
||||
tools—such as Query Tool, ERD, Schema Diff, and PSQL—will be saved in the encrypted
|
||||
format.If the application is closed unexpectedly, the tab is accidentally closed,
|
||||
or the page is refreshed, the saved state will be automatically restored for
|
||||
each respective tool.**Note:**
|
||||
|
||||
* Use the *Themes* drop-down listbox to select the theme for pgAdmin. You'll also get a preview just below the
|
||||
drop down. You can also submit your own themes,
|
||||
check `here <https://github.com/pgadmin-org/pgadmin4/blob/master/README.md>`_ how.
|
||||
Currently we support Light, Dark, High Contrast and System theme. Selecting System option will follow
|
||||
your computer's settings.
|
||||
|
||||
**Note:** Saving the application state will not preserve data for tool tabs opened in
|
||||
separate browser tabs when running in server mode..
|
||||
|
||||
The Paths Node
|
||||
**************
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,16 @@ def upgrade():
|
|||
))
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'application_state',
|
||||
sa.Column('uid', sa.Integer(), nullable=False),
|
||||
sa.Column('id', sa.Integer()),
|
||||
sa.Column('connection_info', sa.JSON()),
|
||||
sa.Column('tool_name', sa.String(length=64)),
|
||||
sa.Column('tool_data', sa.String()),
|
||||
sa.ForeignKeyConstraint(['uid'], ['user.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id', 'uid'))
|
||||
|
||||
|
||||
def downgrade():
|
||||
# pgAdmin only upgrades, downgrade not implemented.
|
||||
|
|
|
|||
|
|
@ -292,7 +292,6 @@ class AuthSourceManager:
|
|||
current_app.logger.debug(
|
||||
"Authentication initiated via source: %s is failed." %
|
||||
source.get_source_name())
|
||||
|
||||
return status, msg
|
||||
|
||||
def login(self):
|
||||
|
|
|
|||
|
|
@ -12,9 +12,11 @@ import _ from 'lodash';
|
|||
import { checkMasterPassword, showQuickSearch } from '../../../static/js/Dialogs/index';
|
||||
import { pgHandleItemError } from '../../../static/js/utils';
|
||||
import { send_heartbeat, stop_heartbeat } from './heartbeat';
|
||||
import getApiInstance from '../../../static/js/api_instance';
|
||||
import getApiInstance, {parseApiError} from '../../../static/js/api_instance';
|
||||
import usePreferences, { setupPreferenceBroadcast } from '../../../preferences/static/js/store';
|
||||
import checkNodeVisibility from '../../../static/js/check_node_visibility';
|
||||
import * as showQueryTool from '../../../tools/sqleditor/static/js/show_query_tool';
|
||||
import {getRandomInt} from 'sources/utils';
|
||||
|
||||
define('pgadmin.browser', [
|
||||
'sources/gettext', 'sources/url_for', 'sources/pgadmin',
|
||||
|
|
@ -206,6 +208,12 @@ define('pgadmin.browser', [
|
|||
uiloaded: function() {
|
||||
this.set_master_password('');
|
||||
this.check_version_update();
|
||||
const prefStore = usePreferences.getState();
|
||||
let save_the_workspace = prefStore.getPreferencesForModule('misc').save_app_state;
|
||||
if(save_the_workspace){
|
||||
this.restore_pgadmin_state();
|
||||
pgBrowser.docker.default_workspace.focus();
|
||||
}
|
||||
},
|
||||
check_corrupted_db_file: function() {
|
||||
getApiInstance().get(
|
||||
|
|
@ -291,6 +299,42 @@ define('pgadmin.browser', [
|
|||
});
|
||||
},
|
||||
|
||||
restore_pgadmin_state: function () {
|
||||
getApiInstance({'Content-Encoding': 'gzip'}).get(
|
||||
url_for('settings.get_application_state')
|
||||
).then((res)=> {
|
||||
if(res.data.success && res.data.data.result.length > 0){
|
||||
_.each(res.data.data.result, function(toolState){
|
||||
let toolNme = toolState.tool_name;
|
||||
let toolDataId = `${toolNme}-${getRandomInt(1, 9999999)}`;
|
||||
let connectionInfo = toolState.connection_info;
|
||||
localStorage.setItem(toolDataId, toolState.tool_data);
|
||||
|
||||
if (toolNme == 'sqleditor'){
|
||||
showQueryTool.relaunchSqlTool(connectionInfo, toolDataId);
|
||||
}else if(toolNme == 'psql'){
|
||||
pgAdmin.Tools.Psql.openPsqlTool(null, null, connectionInfo);
|
||||
}else if(toolNme == 'ERD'){
|
||||
pgAdmin.Tools.ERD.showErdTool(null, null, false, connectionInfo, toolDataId);
|
||||
}else if(toolNme == 'schema_diff'){
|
||||
pgAdmin.Tools.SchemaDiff.launchSchemaDiff(toolDataId);
|
||||
}
|
||||
});
|
||||
|
||||
// call clear application state data.
|
||||
try {
|
||||
getApiInstance().delete(url_for('settings.delete_application_state'), {});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
pgAdmin.Browser.notifier.error(gettext('Failed to remove query data.') + parseApiError(error));
|
||||
}
|
||||
}
|
||||
}).catch(function(error) {
|
||||
pgAdmin.Browser.notifier.pgRespErrorNotify(error);
|
||||
getApiInstance().delete(url_for('settings.delete_application_state'), {});
|
||||
});
|
||||
},
|
||||
|
||||
bind_beforeunload: function() {
|
||||
window.addEventListener('beforeunload', function(e) {
|
||||
/* Can open you in new tab */
|
||||
|
|
|
|||
|
|
@ -124,6 +124,19 @@ class MiscModule(PgAdminModule):
|
|||
)
|
||||
)
|
||||
|
||||
self.preference.register(
|
||||
'user_interface', 'save_app_state',
|
||||
gettext("Save the application state?"),
|
||||
'boolean', True,
|
||||
category_label=PREF_LABEL_USER_INTERFACE,
|
||||
help_str=gettext(
|
||||
'If set to True, pgAdmin will save the state of opened tools'
|
||||
' (such as Query Tool, PSQL, Schema Diff, and ERD), including'
|
||||
' any unsaved data. This data will be automatically restored'
|
||||
' in the event of an unexpected shutdown or browser refresh.'
|
||||
)
|
||||
)
|
||||
|
||||
if not config.SERVER_MODE:
|
||||
self.preference.register(
|
||||
'file_downloads', 'automatically_open_downloaded_file',
|
||||
|
|
|
|||
|
|
@ -455,7 +455,7 @@ export default function AdHocConnection({mode}) {
|
|||
'pgadmin:tool:show',
|
||||
`${BROWSER_PANELS.PSQL_TOOL}_${transId}`,
|
||||
openUrl,
|
||||
{title: escapedTitle, db: db_name},
|
||||
{title: escapedTitle, db: db_name, server_name: formData.server_name, 'user': user_name},
|
||||
{title: panelTitle, icon: 'pg-font-icon icon-terminal', manualClose: false, renamable: true},
|
||||
Boolean(open_new_tab?.includes('psql_tool'))
|
||||
);
|
||||
|
|
|
|||
|
|
@ -75,16 +75,16 @@ export function WorkspaceProvider({children}) {
|
|||
pgAdmin.Browser.docker.currentWorkspace = newVal;
|
||||
if (newVal == WORKSPACES.DEFAULT) {
|
||||
setTimeout(() => {
|
||||
pgAdmin.Browser.tree.selectNode(lastSelectedTreeItem.current, true, 'center');
|
||||
pgAdmin.Browser.tree?.selectNode(lastSelectedTreeItem.current, true, 'center');
|
||||
lastSelectedTreeItem.current = null;
|
||||
}, 250);
|
||||
} else {
|
||||
// Get the selected tree node and save it into the state variable.
|
||||
let selItem = pgAdmin.Browser.tree.selected();
|
||||
let selItem = pgAdmin.Browser.tree?.selected();
|
||||
if (selItem)
|
||||
lastSelectedTreeItem.current = selItem;
|
||||
// Deselect the node to disable the menu options.
|
||||
pgAdmin.Browser.tree.deselect(selItem);
|
||||
pgAdmin.Browser.tree?.deselect(selItem);
|
||||
}
|
||||
setCurrentWorkspace(newVal);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -392,6 +392,17 @@ class QueryHistoryModel(db.Model):
|
|||
last_updated_flag = db.Column(db.String(), nullable=False)
|
||||
|
||||
|
||||
class ApplicationState(db.Model):
|
||||
"""Define the application state SQL table."""
|
||||
__tablename__ = 'application_state'
|
||||
uid = db.Column(db.Integer(), db.ForeignKey(USER_ID), nullable=False,
|
||||
primary_key=True)
|
||||
id = db.Column(db.Integer(),nullable=False,primary_key=True)
|
||||
connection_info = db.Column(MutableDict.as_mutable(types.JSON))
|
||||
tool_name = db.Column(db.String(64), nullable=False)
|
||||
tool_data = db.Column(PgAdminDbBinaryString())
|
||||
|
||||
|
||||
class Database(db.Model):
|
||||
"""
|
||||
Define a Database.
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import config
|
|||
import json
|
||||
from flask import render_template, Response, request, session, current_app
|
||||
from flask_babel import gettext
|
||||
|
||||
from pgadmin.settings import delete_tool_data
|
||||
from pgadmin.user_login_check import pga_login_required
|
||||
from pgadmin.utils import PgAdminModule
|
||||
from pgadmin.utils.ajax import success_return, \
|
||||
|
|
@ -238,6 +240,9 @@ def save():
|
|||
data['mid'], data['category_id'], data['id'], data['value'])
|
||||
sgm.get_nodes(sgm)
|
||||
|
||||
if data['name'] == 'save_app_state' and not data['value']:
|
||||
delete_tool_data()
|
||||
|
||||
if not res:
|
||||
return internal_server_error(errormsg=msg)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,22 +8,24 @@
|
|||
##########################################################################
|
||||
|
||||
"""Utility functions for storing and retrieving user configuration settings."""
|
||||
|
||||
import traceback
|
||||
import os
|
||||
import json
|
||||
|
||||
from flask import Response, request, render_template, url_for, current_app
|
||||
from flask import Response, request, render_template, current_app
|
||||
from flask_babel import gettext
|
||||
from flask_login import current_user
|
||||
|
||||
from pgadmin.user_login_check import pga_login_required
|
||||
from pgadmin.utils import PgAdminModule
|
||||
from pgadmin.utils import PgAdminModule, get_complete_file_path
|
||||
from pgadmin.utils.ajax import make_json_response, bad_request,\
|
||||
success_return, internal_server_error
|
||||
from pgadmin.utils.menu import MenuItem
|
||||
|
||||
from pgadmin.model import db, Setting
|
||||
from pgadmin.model import db, Setting, ApplicationState
|
||||
from pgadmin.utils.constants import MIMETYPE_APP_JS
|
||||
from .utils import get_dialog_type, get_file_type_setting
|
||||
from cryptography.fernet import Fernet
|
||||
import hashlib
|
||||
|
||||
MODULE_NAME = 'settings'
|
||||
|
||||
|
|
@ -52,7 +54,10 @@ class SettingsModule(PgAdminModule):
|
|||
'settings.save_tree_state', 'settings.get_tree_state',
|
||||
'settings.reset_tree_state',
|
||||
'settings.save_file_format_setting',
|
||||
'settings.get_file_format_setting'
|
||||
'settings.get_file_format_setting',
|
||||
'settings.save_application_state',
|
||||
'settings.get_application_state',
|
||||
'settings.delete_application_state'
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -256,3 +261,170 @@ def get_file_format_setting():
|
|||
|
||||
return make_json_response(success=True,
|
||||
info=get_file_type_setting(list(data.values())))
|
||||
|
||||
|
||||
@blueprint.route(
|
||||
'/save_application_state',
|
||||
methods=["POST"], endpoint='save_application_state'
|
||||
)
|
||||
@pga_login_required
|
||||
def save_application_state():
|
||||
"""
|
||||
Expose an api to save the application state which stores the data from
|
||||
query tool, ERD, schema-diff, psql
|
||||
"""
|
||||
data = json.loads(request.data)
|
||||
trans_id = data['trans_id']
|
||||
fernet = Fernet(current_app.config['SECRET_KEY'].encode())
|
||||
tool_data = fernet.encrypt(json.dumps(data['tool_data']).encode())
|
||||
connection_info = data['connection_info'] \
|
||||
if 'connection_info' in data else None
|
||||
if ('open_file_name' in connection_info and
|
||||
connection_info['open_file_name']):
|
||||
file_path = get_complete_file_path(connection_info['open_file_name'])
|
||||
connection_info['last_saved_file_hash'] = (
|
||||
get_last_saved_file_hash(file_path, trans_id))
|
||||
|
||||
try:
|
||||
data_entry = ApplicationState(
|
||||
uid=current_user.id, id=trans_id,connection_info=connection_info,
|
||||
tool_name=data['tool_name'], tool_data=tool_data)
|
||||
|
||||
db.session.merge(data_entry)
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
db.session.rollback()
|
||||
|
||||
return make_json_response(
|
||||
data={
|
||||
'status': True,
|
||||
'msg': 'Success',
|
||||
})
|
||||
|
||||
|
||||
def get_last_saved_file_hash(file_path, trans_id):
|
||||
result = db.session \
|
||||
.query(ApplicationState) \
|
||||
.filter(ApplicationState.uid == current_user.id,
|
||||
ApplicationState.id == trans_id).all()
|
||||
file_hash_update_require = True
|
||||
last_saved_file_hash = None
|
||||
|
||||
for row in result:
|
||||
connection_info = row.connection_info
|
||||
if ('open_file_name' in connection_info and
|
||||
connection_info['open_file_name']):
|
||||
file_hash_update_require = not connection_info['is_editor_dirty']
|
||||
last_saved_file_hash = connection_info['last_saved_file_hash']
|
||||
|
||||
if file_hash_update_require:
|
||||
last_saved_file_hash = compute_md5_hash_file(file_path)
|
||||
|
||||
return last_saved_file_hash
|
||||
|
||||
|
||||
@blueprint.route(
|
||||
'/get_application_state',
|
||||
methods=["GET"], endpoint='get_application_state'
|
||||
)
|
||||
@pga_login_required
|
||||
def get_application_state():
|
||||
"""
|
||||
Returns application state if any stored.
|
||||
"""
|
||||
fernet = Fernet(current_app.config['SECRET_KEY'].encode())
|
||||
result = db.session \
|
||||
.query(ApplicationState) \
|
||||
.filter(ApplicationState.uid == current_user.id) \
|
||||
.all()
|
||||
|
||||
res = []
|
||||
for row in result:
|
||||
connection_info = row.connection_info
|
||||
if ('open_file_name' in connection_info and
|
||||
connection_info['open_file_name']):
|
||||
file_path = get_complete_file_path(
|
||||
connection_info['open_file_name'])
|
||||
file_deleted = False if os.path.exists(file_path) else True
|
||||
connection_info['file_deleted'] = file_deleted
|
||||
|
||||
if (not file_deleted and connection_info['is_editor_dirty'] and
|
||||
'last_saved_file_hash' in connection_info and
|
||||
connection_info['last_saved_file_hash']):
|
||||
connection_info['external_file_changes'] = \
|
||||
check_external_file_changes(
|
||||
file_path, connection_info['last_saved_file_hash'])
|
||||
|
||||
res.append({'tool_name': row.tool_name,
|
||||
'connection_info': connection_info,
|
||||
'tool_data': fernet.decrypt(row.tool_data).decode(),
|
||||
'id': row.id
|
||||
})
|
||||
return make_json_response(
|
||||
data={
|
||||
'status': True,
|
||||
'msg': '',
|
||||
'result': res
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route(
|
||||
'/delete_application_state/',
|
||||
methods=["DELETE"], endpoint='delete_application_state')
|
||||
@pga_login_required
|
||||
def delete_application_state():
|
||||
trans_id = None
|
||||
if request.data:
|
||||
data = json.loads(request.data)
|
||||
trans_id = int(data['panelId'].split('_')[-1])
|
||||
status, msg = delete_tool_data(trans_id)
|
||||
return make_json_response(
|
||||
data={
|
||||
'status': status,
|
||||
'msg': msg,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def delete_tool_data(trans_id=None):
|
||||
try:
|
||||
if trans_id:
|
||||
results = db.session \
|
||||
.query(ApplicationState) \
|
||||
.filter(ApplicationState.uid == current_user.id,
|
||||
ApplicationState.id == trans_id) \
|
||||
.all()
|
||||
else:
|
||||
results = db.session \
|
||||
.query(ApplicationState) \
|
||||
.filter(ApplicationState.uid == current_user.id) \
|
||||
.all()
|
||||
for result in results:
|
||||
db.session.delete(result)
|
||||
db.session.commit()
|
||||
return True, 'Success'
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return False, str(e)
|
||||
|
||||
|
||||
def compute_md5_hash_file(file_path, chunk_size=8192):
|
||||
"""Compute md5 hash for large files by reading in chunks."""
|
||||
md5_hash = hashlib.md5()
|
||||
|
||||
# Open the file in binary mode
|
||||
with open(file_path, "rb") as file:
|
||||
# Read and hash in 8 KB chunks (can adjust the chunk size if needed)
|
||||
for chunk in iter(lambda: file.read(chunk_size), b""):
|
||||
md5_hash.update(chunk)
|
||||
|
||||
return md5_hash.hexdigest()
|
||||
|
||||
|
||||
def check_external_file_changes(file_path, last_saved_file_hash):
|
||||
current_file_hash = compute_md5_hash_file(file_path)
|
||||
if current_file_hash != last_saved_file_hash:
|
||||
return True
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2025, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import React, { useContext, useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import getApiInstance from '../../static/js/api_instance';
|
||||
import url_for from 'sources/url_for';
|
||||
import { getBrowser } from '../../static/js/utils';
|
||||
import usePreferences from '../../preferences/static/js/store';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
|
||||
const ApplicationStateContext = React.createContext();
|
||||
export const useApplicationState = ()=>useContext(ApplicationStateContext);
|
||||
|
||||
export function getToolData(localStorageId){
|
||||
let toolDataJson = JSON.parse(localStorage.getItem(localStorageId));
|
||||
localStorage.removeItem(localStorageId);
|
||||
return toolDataJson;
|
||||
}
|
||||
|
||||
export function deleteToolData(panelId, closePanelId){
|
||||
const saveAppState = usePreferences.getState().getPreferencesForModule('misc')?.save_app_state;
|
||||
if(saveAppState){
|
||||
if(panelId == closePanelId){
|
||||
let api = getApiInstance();
|
||||
api.delete(
|
||||
url_for('settings.delete_application_state'), {data:{'panelId': panelId}}
|
||||
).then(()=> { /* Sonar Qube */}).catch(function(error) {
|
||||
pgAdmin.Browser.notifier.pgRespErrorNotify(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function ApplicationStateProvider({children}){
|
||||
const preferencesStore = usePreferences();
|
||||
const saveAppState = preferencesStore?.getPreferencesForModule('misc')?.save_app_state;
|
||||
const openNewTab = preferencesStore?.getPreferencesForModule('browser')?.new_browser_tab_open;
|
||||
|
||||
const saveToolData = (toolName, connectionInfo, transId, toolData) =>{
|
||||
let data = {
|
||||
'tool_name': toolName,
|
||||
'connection_info': connectionInfo,
|
||||
'trans_id': transId,
|
||||
'tool_data': toolData
|
||||
};
|
||||
getApiInstance({'Content-Encoding': 'gzip'}).post(
|
||||
url_for('settings.save_application_state'),
|
||||
JSON.stringify(data),
|
||||
).catch((error)=>{console.error(error);});
|
||||
};
|
||||
|
||||
const isSaveToolDataEnabled = (toolName)=>{
|
||||
let toolMapping = {'sqleditor': 'qt', 'schema_diff': 'schema_diff', 'psql': 'psql_tool', 'ERD': 'erd_tool'};
|
||||
if(openNewTab?.includes(toolMapping[toolName])){
|
||||
return saveAppState && getBrowser().name == 'Electron';
|
||||
}
|
||||
return saveAppState;
|
||||
};
|
||||
|
||||
const value = useMemo(()=>({
|
||||
saveToolData,
|
||||
isSaveToolDataEnabled,
|
||||
}), []);
|
||||
|
||||
return <ApplicationStateContext.Provider value={value}>
|
||||
{children}
|
||||
</ApplicationStateContext.Provider>;
|
||||
|
||||
}
|
||||
|
||||
ApplicationStateProvider.propTypes = {
|
||||
children: PropTypes.object
|
||||
};
|
||||
|
|
@ -18,7 +18,6 @@ export function usePgAdmin() {
|
|||
}
|
||||
|
||||
export function PgAdminProvider({children, value}) {
|
||||
|
||||
return <PgAdminContext.Provider value={value}>
|
||||
{children}
|
||||
</PgAdminContext.Provider>;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import gettext from 'sources/gettext';
|
||||
import { LAYOUT_EVENTS } from './helpers/Layout';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { Box } from '@mui/material';
|
||||
import { FormHelperText } from '@mui/material';
|
||||
import HTMLReactParse from 'html-react-parser';
|
||||
|
||||
const StyledBox = styled(Box)(({theme}) => ({
|
||||
color: theme.palette.text.primary,
|
||||
margin: '24px auto 12px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100%',
|
||||
}));
|
||||
|
||||
export default function ToolErrorView({error, panelId, panelDocker}){
|
||||
|
||||
panelDocker.eventBus.registerListener(LAYOUT_EVENTS.CLOSING, (id)=>{
|
||||
if(panelId == id) {
|
||||
panelDocker.close(panelId, true);
|
||||
}
|
||||
});
|
||||
|
||||
let err_msg = gettext(`There was some error while opening: ${error}`);
|
||||
return (<StyledBox>
|
||||
<FormHelperText variant="outlined" error= {true} style={{marginLeft: '4px'}} >{HTMLReactParse(err_msg)}</FormHelperText>
|
||||
</StyledBox>);
|
||||
}
|
||||
|
||||
ToolErrorView.propTypes = {
|
||||
error: PropTypes.string,
|
||||
panelId: PropTypes.string,
|
||||
panelDocker: PropTypes.object,
|
||||
pgAdmin: PropTypes.object,
|
||||
toolName: PropTypes.string,
|
||||
};
|
||||
|
|
@ -13,6 +13,9 @@ import { usePgAdmin } from './PgAdminProvider';
|
|||
import { BROWSER_PANELS } from '../../browser/static/js/constants';
|
||||
import PropTypes from 'prop-types';
|
||||
import LayoutIframeTab from './helpers/Layout/LayoutIframeTab';
|
||||
import { LAYOUT_EVENTS } from './helpers/Layout';
|
||||
import { deleteToolData } from '../../settings/static/ApplicationStateProvider';
|
||||
|
||||
|
||||
function ToolForm({actionUrl, params}) {
|
||||
const formRef = useRef(null);
|
||||
|
|
@ -56,6 +59,11 @@ export default function ToolView({dockerObj}) {
|
|||
// Handler here will return which layout instance the tool should go in
|
||||
// case of workspace layout.
|
||||
let handler = pgAdmin.Browser.getDockerHandler?.(panelId, dockerObj);
|
||||
const deregisterRemove = handler.docker.eventBus.registerListener(LAYOUT_EVENTS.REMOVE, (closePanelId)=>{
|
||||
deleteToolData(panelId, closePanelId);
|
||||
deregisterRemove();
|
||||
});
|
||||
|
||||
handler.focus();
|
||||
handler.docker.openTab({
|
||||
id: panelId,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import usePreferences from '../../../../preferences/static/js/store';
|
|||
import _ from 'lodash';
|
||||
import UtilityView from '../../UtilityView';
|
||||
import ToolView from '../../ToolView';
|
||||
import { ApplicationStateProvider } from '../../../../settings/static/ApplicationStateProvider';
|
||||
|
||||
function TabTitle({id, closable, defaultInternal}) {
|
||||
const layoutDocker = React.useContext(LayoutDockerContext);
|
||||
|
|
@ -497,7 +498,9 @@ export default function Layout({groups, noContextGroups, getLayoutInstance, layo
|
|||
label="Layout Context Menu" />
|
||||
{enableToolEvents && <>
|
||||
<UtilityView dockerObj={layoutDockerObj} />
|
||||
<ToolView dockerObj={layoutDockerObj} />
|
||||
<ApplicationStateProvider>
|
||||
<ToolView dockerObj={layoutDockerObj} />
|
||||
</ApplicationStateProvider>
|
||||
</>}
|
||||
</LayoutDockerContext.Provider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"""A blueprint module implementing the erd tool."""
|
||||
import json
|
||||
|
||||
from flask import url_for, request, Response
|
||||
from flask import request, Response
|
||||
from flask import render_template, current_app as app
|
||||
from flask_security import permissions_required
|
||||
from pgadmin.user_login_check import pga_login_required
|
||||
|
|
@ -18,8 +18,7 @@ from flask_babel import gettext
|
|||
from werkzeug.user_agent import UserAgent
|
||||
from pgadmin.utils import PgAdminModule, \
|
||||
SHORTCUT_FIELDS as shortcut_fields
|
||||
from pgadmin.utils.ajax import make_json_response, bad_request, \
|
||||
internal_server_error
|
||||
from pgadmin.utils.ajax import make_json_response, internal_server_error
|
||||
from pgadmin.model import Server
|
||||
from config import PG_DEFAULT_DRIVER
|
||||
from pgadmin.utils.driver import get_driver
|
||||
|
|
@ -29,13 +28,14 @@ from pgadmin.browser.server_groups.servers.databases.schemas.utils \
|
|||
from pgadmin.browser.server_groups.servers.databases.schemas.tables. \
|
||||
constraints.foreign_key import utils as fkey_utils
|
||||
from pgadmin.utils.constants import PREF_LABEL_KEYBOARD_SHORTCUTS, \
|
||||
PREF_LABEL_DISPLAY, PREF_LABEL_OPTIONS
|
||||
PREF_LABEL_OPTIONS
|
||||
from .utils import ERDHelper
|
||||
from pgadmin.utils.exception import ConnectionLost
|
||||
from pgadmin.authenticate import socket_login_required
|
||||
from pgadmin.tools.user_management.PgAdminPermissions import AllPermissionTypes
|
||||
from ... import socketio
|
||||
|
||||
|
||||
MODULE_NAME = 'erd'
|
||||
SOCKETIO_NAMESPACE = '/{0}'.format(MODULE_NAME)
|
||||
|
||||
|
|
@ -462,11 +462,11 @@ def panel(trans_id):
|
|||
Args:
|
||||
panel_title: Title of the panel
|
||||
"""
|
||||
params = {'trans_id': trans_id, }
|
||||
if request.form:
|
||||
for key, val in request.form.items():
|
||||
params[key] = val
|
||||
|
||||
params = {
|
||||
'trans_id': trans_id,
|
||||
'title': request.form['title']
|
||||
}
|
||||
if request.args:
|
||||
params.update({k: v for k, v in request.args.items()})
|
||||
|
||||
|
|
@ -500,19 +500,26 @@ def panel(trans_id):
|
|||
|
||||
s = Server.query.filter_by(id=int(params['sid'])).first()
|
||||
|
||||
params.update({
|
||||
'bgcolor': s.bgcolor,
|
||||
'fgcolor': s.fgcolor,
|
||||
'client_platform': user_agent.platform,
|
||||
'is_desktop_mode': app.PGADMIN_RUNTIME,
|
||||
'is_linux': is_linux_platform
|
||||
})
|
||||
if s:
|
||||
params.update({
|
||||
'bgcolor': s.bgcolor,
|
||||
'fgcolor': s.fgcolor,
|
||||
'client_platform': user_agent.platform,
|
||||
'is_desktop_mode': app.PGADMIN_RUNTIME,
|
||||
'is_linux': is_linux_platform
|
||||
})
|
||||
|
||||
return render_template(
|
||||
"erd/index.html",
|
||||
title=underscore_unescape(params['title']),
|
||||
params=json.dumps(params),
|
||||
)
|
||||
return render_template(
|
||||
"erd/index.html",
|
||||
title=underscore_unescape(params['title']),
|
||||
params=json.dumps(params),
|
||||
)
|
||||
else:
|
||||
params['error'] = 'Server did not find.'
|
||||
return render_template(
|
||||
"erd/index.html",
|
||||
title=None,
|
||||
params=json.dumps(params))
|
||||
|
||||
|
||||
@blueprint.route(
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import { NotifierProvider } from '../../../../static/js/helpers/Notifier';
|
|||
import usePreferences, { listenPreferenceBroadcast } from '../../../../preferences/static/js/store';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import { PgAdminProvider } from '../../../../static/js/PgAdminProvider';
|
||||
import { ApplicationStateProvider } from '../../../../settings/static/ApplicationStateProvider';
|
||||
import ToolErrorView from '../../../../static/js/ToolErrorView';
|
||||
|
||||
export function setPanelTitle(docker, panelId, panelTitle) {
|
||||
docker.setTitle(panelId, panelTitle);
|
||||
|
|
@ -86,27 +88,53 @@ export default class ERDModule {
|
|||
}
|
||||
|
||||
// Callback to draw ERD Tool for objects
|
||||
showErdTool(_data, treeIdentifier, gen=false) {
|
||||
if (treeIdentifier === undefined) {
|
||||
pgAdmin.Browser.notifier.alert(
|
||||
gettext('ERD Error'),
|
||||
gettext('No object selected.')
|
||||
);
|
||||
return;
|
||||
}
|
||||
showErdTool(_data, treeIdentifier, gen=false, connectionInfo=null, toolDataId=null) {
|
||||
let parentData = null;
|
||||
let panelTitle = null;
|
||||
if(connectionInfo){
|
||||
panelTitle = connectionInfo.title;
|
||||
|
||||
parentData = {
|
||||
server_group: {
|
||||
_id: connectionInfo.sgid || 0,
|
||||
server_type: connectionInfo.server_type
|
||||
},
|
||||
server: {
|
||||
_id: connectionInfo.sid,
|
||||
},
|
||||
database: {
|
||||
_id: connectionInfo.did,
|
||||
},
|
||||
schema: {
|
||||
_id: connectionInfo.scid || null,
|
||||
|
||||
},
|
||||
table: {
|
||||
_id: connectionInfo.tid || null,
|
||||
}
|
||||
};
|
||||
|
||||
const parentData = this.pgBrowser.tree.getTreeNodeHierarchy(treeIdentifier);
|
||||
}else{
|
||||
if (treeIdentifier === undefined) {
|
||||
pgAdmin.Browser.notifier.alert(
|
||||
gettext('ERD Error'),
|
||||
gettext('No object selected.')
|
||||
);
|
||||
return;
|
||||
}
|
||||
parentData = this.pgBrowser.tree.getTreeNodeHierarchy(treeIdentifier);
|
||||
|
||||
if(_.isUndefined(parentData.database)) {
|
||||
pgAdmin.Browser.notifier.alert(
|
||||
gettext('ERD Error'),
|
||||
gettext('Please select a database/database object.')
|
||||
);
|
||||
return;
|
||||
if(_.isUndefined(parentData.database)) {
|
||||
pgAdmin.Browser.notifier.alert(
|
||||
gettext('ERD Error'),
|
||||
gettext('Please select a database/database object.')
|
||||
);
|
||||
return;
|
||||
}
|
||||
panelTitle = getPanelTitle(this.pgBrowser, treeIdentifier);
|
||||
}
|
||||
|
||||
const transId = getRandomInt(1, 9999999);
|
||||
const panelTitle = getPanelTitle(this.pgBrowser, treeIdentifier);
|
||||
const panelUrl = this.getPanelUrl(transId, parentData, gen);
|
||||
const open_new_tab = usePreferences.getState().getPreferencesForModule('browser').new_browser_tab_open;
|
||||
|
||||
|
|
@ -114,7 +142,7 @@ export default class ERDModule {
|
|||
'pgadmin:tool:show',
|
||||
`${BROWSER_PANELS.ERD_TOOL}_${transId}`,
|
||||
panelUrl,
|
||||
{title: _.escape(panelTitle)},
|
||||
{sql_id: toolDataId, title: _.escape(panelTitle)},
|
||||
{title: 'Untitled', icon: 'fa fa-sitemap'},
|
||||
Boolean(open_new_tab?.includes('erd_tool'))
|
||||
);
|
||||
|
|
@ -149,18 +177,26 @@ export default class ERDModule {
|
|||
root.render(
|
||||
<Theme>
|
||||
<PgAdminProvider value={pgAdmin}>
|
||||
<ModalProvider>
|
||||
<NotifierProvider pgAdmin={this.pgAdmin} pgWindow={pgWindow} />
|
||||
<ERDTool
|
||||
params={params}
|
||||
pgWindow={pgWindow}
|
||||
pgAdmin={this.pgAdmin}
|
||||
panelId={`${BROWSER_PANELS.ERD_TOOL}_${params.trans_id}`}
|
||||
panelDocker={pgWindow.pgAdmin.Browser.docker.default_workspace}
|
||||
/>
|
||||
</ModalProvider>
|
||||
<ApplicationStateProvider>
|
||||
<ModalProvider>
|
||||
<NotifierProvider pgAdmin={this.pgAdmin} pgWindow={pgWindow} />
|
||||
{ params.error ?
|
||||
<ToolErrorView
|
||||
error={params.error}
|
||||
panelId={`${BROWSER_PANELS.ERD_TOOL}_${params.trans_id}`}
|
||||
panelDocker={pgWindow.pgAdmin.Browser.docker.default_workspace}
|
||||
/> :
|
||||
<ERDTool
|
||||
params={params}
|
||||
pgWindow={pgWindow}
|
||||
pgAdmin={this.pgAdmin}
|
||||
panelId={`${BROWSER_PANELS.ERD_TOOL}_${params.trans_id}`}
|
||||
panelDocker={pgWindow.pgAdmin.Browser.docker.default_workspace}
|
||||
/> }
|
||||
</ModalProvider>
|
||||
</ApplicationStateProvider>
|
||||
</PgAdminProvider>
|
||||
</Theme>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,5 +20,5 @@ export const ERD_EVENTS = {
|
|||
ZOOM_OUT: 'ZOOM_OUT',
|
||||
SINGLE_NODE_SELECTED: 'SINGLE_NODE_SELECTED',
|
||||
ANY_ITEM_SELECTED: 'ANY_ITEM_SELECTED',
|
||||
DIRTY: 'DIRTY',
|
||||
DIRTY: 'DIRTY'
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import { styled } from '@mui/material/styles';
|
|||
import BeforeUnload from './BeforeUnload';
|
||||
import { isMac } from '../../../../../../static/js/keyboard_shortcuts';
|
||||
import DownloadUtils from '../../../../../../static/js/DownloadUtils';
|
||||
import { getToolData } from '../../../../../../settings/static/ApplicationStateProvider';
|
||||
|
||||
/* Custom react-diagram action for keyboard events */
|
||||
export class KeyboardShortcutAction extends Action {
|
||||
|
|
@ -193,11 +194,11 @@ export default class ERDTool extends React.Component {
|
|||
},
|
||||
'linksUpdated': () => {
|
||||
this.setState({dirty: true});
|
||||
this.eventBus.fireEvent(ERD_EVENTS.DIRTY, true);
|
||||
this.eventBus.fireEvent(ERD_EVENTS.DIRTY, true, this.serializeFile());
|
||||
},
|
||||
'nodesUpdated': ()=>{
|
||||
this.setState({dirty: true});
|
||||
this.eventBus.fireEvent(ERD_EVENTS.DIRTY, true);
|
||||
this.eventBus.fireEvent(ERD_EVENTS.DIRTY, true, this.serializeFile());
|
||||
},
|
||||
'showNote': (event)=>{
|
||||
this.showNote(event.node);
|
||||
|
|
@ -352,7 +353,16 @@ export default class ERDTool extends React.Component {
|
|||
done = await this.loadPrequisiteData();
|
||||
if(!done) return;
|
||||
|
||||
if(this.props.params.gen) {
|
||||
|
||||
if(this.props.params.sql_id){
|
||||
let sqlValue = getToolData(this.props.params.sql_id);
|
||||
if (sqlValue) {
|
||||
this.diagram.deserialize(sqlValue);
|
||||
this.diagram.clearSelection();
|
||||
this.registerModelEvents();
|
||||
}
|
||||
}
|
||||
else if(this.props.params.gen) {
|
||||
await this.loadTablesData();
|
||||
}
|
||||
}
|
||||
|
|
@ -829,6 +839,10 @@ export default class ERDTool extends React.Component {
|
|||
updated && this.diagram.fireEvent({}, 'nodesUpdated', true);
|
||||
}
|
||||
|
||||
serializeFile(){
|
||||
return this.diagram.serialize(this.props.pgAdmin.Browser.utils.app_version_int);
|
||||
}
|
||||
|
||||
async initConnection() {
|
||||
this.setLoading(gettext('Initializing connection...'));
|
||||
this.setState({conn_status: CONNECT_STATUS.CONNECTING});
|
||||
|
|
@ -928,7 +942,7 @@ export default class ERDTool extends React.Component {
|
|||
fgcolor={this.props.params.fgcolor} title={_.unescape(this.props.params.title)}/>
|
||||
<MainToolBar preferences={this.state.preferences} eventBus={this.eventBus}
|
||||
fillColor={this.state.fill_color} textColor={this.state.text_color}
|
||||
notation={this.state.cardinality_notation} onNotationChange={this.onNotationChange}
|
||||
notation={this.state.cardinality_notation} onNotationChange={this.onNotationChange} connectionInfo={this.props.params}
|
||||
/>
|
||||
<FloatingNote open={this.state.note_open} onClose={this.onNoteClose}
|
||||
anchorEl={this.noteRefEle} noteNode={this.state.note_node} appendTo={this.diagramContainerRef.current} rows={8}/>
|
||||
|
|
@ -958,6 +972,7 @@ ERDTool.propTypes = {
|
|||
bgcolor: PropTypes.string,
|
||||
fgcolor: PropTypes.string,
|
||||
gen: PropTypes.bool.isRequired,
|
||||
sql_id: PropTypes.string,
|
||||
}),
|
||||
pgWindow: PropTypes.object.isRequired,
|
||||
pgAdmin: PropTypes.object.isRequired,
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ import { ERD_EVENTS } from '../ERDConstants';
|
|||
import { MagicIcon, SQLFileIcon } from '../../../../../../static/js/components/ExternalIcon';
|
||||
import { useModal } from '../../../../../../static/js/helpers/ModalProvider';
|
||||
import { withColorPicker } from '../../../../../../static/js/helpers/withColorPicker';
|
||||
import { useApplicationState } from '../../../../../../settings/static/ApplicationStateProvider';
|
||||
import { useDelayDebounce } from '../../../../../../static/js/custom_hooks';
|
||||
|
||||
const StyledBox = styled(Box)(({theme}) => ({
|
||||
padding: '2px 4px',
|
||||
|
|
@ -48,7 +50,7 @@ const StyledBox = styled(Box)(({theme}) => ({
|
|||
...theme.mixins.panelBorder.bottom,
|
||||
}));
|
||||
|
||||
export function MainToolBar({preferences, eventBus, fillColor, textColor, notation, onNotationChange}) {
|
||||
export function MainToolBar({preferences, eventBus, fillColor, textColor, notation, onNotationChange, connectionInfo}) {
|
||||
const theme = useTheme();
|
||||
const [buttonsDisabled, setButtonsDisabled] = useState({
|
||||
'save': true,
|
||||
|
|
@ -62,6 +64,7 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor, notati
|
|||
});
|
||||
const [showDetails, setShowDetails] = useState(true);
|
||||
|
||||
const {saveToolData, isSaveToolDataEnabled} = useApplicationState();
|
||||
const {openMenuName, toggleMenu, onMenuClose} = usePgMenuGroup();
|
||||
const saveAsMenuRef = React.useRef(null);
|
||||
const sqlMenuRef = React.useRef(null);
|
||||
|
|
@ -130,9 +133,12 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor, notati
|
|||
[ERD_EVENTS.ANY_ITEM_SELECTED, (selected)=>{
|
||||
setDisableButton('drop-table', !selected);
|
||||
}],
|
||||
[ERD_EVENTS.DIRTY, (isDirty)=>{
|
||||
[ERD_EVENTS.DIRTY, (isDirty, data)=>{
|
||||
isDirtyRef.current = isDirty;
|
||||
setDisableButton('save', !isDirty);
|
||||
if(isDirty && isSaveToolDataEnabled('ERD')){
|
||||
setSaveERDData(data);
|
||||
}
|
||||
}],
|
||||
];
|
||||
events.forEach((e)=>{
|
||||
|
|
@ -145,6 +151,11 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor, notati
|
|||
};
|
||||
}, []);
|
||||
|
||||
const [saveERDData, setSaveERDData] = useState(null);
|
||||
useDelayDebounce((erdData)=>{
|
||||
saveToolData('ERD', connectionInfo, connectionInfo.trans_id, erdData);
|
||||
}, saveERDData, 500);
|
||||
|
||||
useEffect(()=>{
|
||||
const showSql = ()=>{
|
||||
eventBus.fireEvent(ERD_EVENTS.SHOW_SQL, checkedMenuItems['sql_with_drop']);
|
||||
|
|
@ -333,6 +344,7 @@ MainToolBar.propTypes = {
|
|||
textColor: PropTypes.string,
|
||||
notation: PropTypes.string,
|
||||
onNotationChange: PropTypes.func,
|
||||
connectionInfo: PropTypes.object,
|
||||
};
|
||||
|
||||
const ColorButton = withColorPicker(PgIconButton);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import json
|
|||
import os
|
||||
import select
|
||||
import struct
|
||||
|
||||
import config
|
||||
import re
|
||||
import subprocess
|
||||
|
|
@ -23,12 +24,11 @@ from flask_security import current_user
|
|||
from pgadmin.user_login_check import pga_login_required
|
||||
from pgadmin.browser.utils import underscore_unescape, underscore_escape
|
||||
from pgadmin.utils import PgAdminModule
|
||||
from pgadmin.utils.constants import MIMETYPE_APP_JS
|
||||
from pgadmin.utils.driver import get_driver
|
||||
from ... import socketio as sio
|
||||
from pgadmin.utils import get_complete_file_path
|
||||
from pgadmin.authenticate import socket_login_required
|
||||
|
||||
from pgadmin.model import Server
|
||||
|
||||
if _platform == 'win32':
|
||||
# Check Windows platform support for WinPty api, Disable psql
|
||||
|
|
@ -81,32 +81,37 @@ def panel(trans_id):
|
|||
Return panel template for PSQL tools.
|
||||
:param trans_id:
|
||||
"""
|
||||
params = {
|
||||
'trans_id': trans_id,
|
||||
'title': request.form['title']
|
||||
}
|
||||
if 'sid_soid_mapping' not in app.config:
|
||||
app.config['sid_soid_mapping'] = dict()
|
||||
params = {'trans_id': trans_id,
|
||||
'is_enable':config.ENABLE_PSQL,
|
||||
'platform': _platform
|
||||
}
|
||||
if request.args:
|
||||
params.update({k: v for k, v in request.args.items()})
|
||||
if request.form:
|
||||
for key, val in request.form.items():
|
||||
params[key] = val
|
||||
|
||||
data = _get_database_role(params['sid'], params['did'])
|
||||
params['title'] = underscore_escape(params['title'])
|
||||
if 'user' in params:
|
||||
params['user'] = underscore_escape(params['user'])
|
||||
|
||||
params = {
|
||||
'sid': params['sid'],
|
||||
'db': underscore_escape(data['db_name']),
|
||||
'server_type': params['server_type'],
|
||||
'is_enable': config.ENABLE_PSQL,
|
||||
'title': underscore_escape(params['title']),
|
||||
'theme': params['theme'],
|
||||
'o_db_name': underscore_escape(data['db_name']),
|
||||
'role': underscore_escape(data['role']),
|
||||
'platform': _platform
|
||||
}
|
||||
if 'sid_soid_mapping' not in app.config:
|
||||
app.config['sid_soid_mapping'] = dict()
|
||||
|
||||
set_env_variables(is_win=_platform == 'win32')
|
||||
return render_template("psql/index.html",
|
||||
params=json.dumps(params))
|
||||
s = Server.query.filter_by(id=int(params['sid'])).first()
|
||||
if s:
|
||||
data = _get_database_role(params['sid'], params['did'])
|
||||
params['db'] = underscore_escape(data['db_name']) \
|
||||
if 'db_name' in data else 'postgres'
|
||||
params['role'] = underscore_escape(data['role'])
|
||||
set_env_variables(is_win=_platform == 'win32')
|
||||
return render_template("psql/index.html",
|
||||
params=json.dumps(params))
|
||||
else:
|
||||
params['error'] = 'Server did not find.'
|
||||
return render_template(
|
||||
"psql/index.html",
|
||||
params=json.dumps(params))
|
||||
|
||||
|
||||
def set_env_variables(is_win=False):
|
||||
|
|
@ -618,7 +623,8 @@ def _get_database_role(sid, did):
|
|||
db_name = conn.db
|
||||
role = manager.role if manager.role else None
|
||||
return {'db_name': db_name, 'role': role}
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
return None
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
import { getRandomInt, hasBinariesConfiguration } from 'sources/utils';
|
||||
import { retrieveAncestorOfTypeServer } from 'sources/tree/tree_utils';
|
||||
import { generateTitle } from 'tools/sqleditor/static/js/sqleditor_title';
|
||||
import { AllPermissionTypes, BROWSER_PANELS } from '../../../../browser/static/js/constants';
|
||||
import { AllPermissionTypes, BROWSER_PANELS, WORKSPACES } from '../../../../browser/static/js/constants';
|
||||
import usePreferences,{ listenPreferenceBroadcast } from '../../../../preferences/static/js/store';
|
||||
import 'pgadmin.browser.keyboard';
|
||||
import pgWindow from 'sources/window';
|
||||
|
|
@ -25,7 +25,8 @@ import Theme from '../../../../static/js/Theme';
|
|||
import { NotifierProvider } from '../../../../static/js/helpers/Notifier';
|
||||
import ModalProvider from '../../../../static/js/helpers/ModalProvider';
|
||||
import * as csrfToken from 'sources/csrf';
|
||||
|
||||
import { ApplicationStateProvider } from '../../../../settings/static/ApplicationStateProvider';
|
||||
import ToolErrorView from '../../../../static/js/ToolErrorView';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
|
||||
|
|
@ -103,35 +104,61 @@ export default class Psql {
|
|||
}
|
||||
}
|
||||
|
||||
openPsqlTool(data, treeIdentifier) {
|
||||
openPsqlTool(_data, treeIdentifier, connectionInfo=null) {
|
||||
let parentData = null;
|
||||
let panelTitle = '';
|
||||
if (connectionInfo){
|
||||
parentData = {
|
||||
server_group: {
|
||||
_id: connectionInfo.sgid || 0
|
||||
},
|
||||
server: {
|
||||
_id: connectionInfo.sid,
|
||||
server_type: connectionInfo.server_type,
|
||||
label: connectionInfo.server_name,
|
||||
user: {
|
||||
name: connectionInfo.user
|
||||
}
|
||||
},
|
||||
database: {
|
||||
_id: connectionInfo.did,
|
||||
label: connectionInfo.db
|
||||
},
|
||||
schema: {
|
||||
_id: connectionInfo.scid || null,
|
||||
},
|
||||
table: {
|
||||
_id: connectionInfo.tid || null,
|
||||
}
|
||||
};
|
||||
|
||||
const serverInformation = retrieveAncestorOfTypeServer(pgBrowser, treeIdentifier, gettext('PSQL Error'));
|
||||
if (!hasBinariesConfiguration(pgBrowser, serverInformation)) {
|
||||
return;
|
||||
}
|
||||
}else{
|
||||
const serverInformation = retrieveAncestorOfTypeServer(pgBrowser, treeIdentifier, gettext('PSQL Error'));
|
||||
if (!hasBinariesConfiguration(pgBrowser, serverInformation)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = pgBrowser.tree.findNodeByDomElement(treeIdentifier);
|
||||
if (node === undefined || !node.getData()) {
|
||||
pgAdmin.Browser.notifier.alert(
|
||||
gettext('PSQL Error'),
|
||||
gettext('No object selected.')
|
||||
);
|
||||
return;
|
||||
}
|
||||
const node = pgBrowser.tree.findNodeByDomElement(treeIdentifier);
|
||||
if (node === undefined || !node.getData()) {
|
||||
pgAdmin.Browser.notifier.alert(
|
||||
gettext('PSQL Error'),
|
||||
gettext('No object selected.')
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const parentData = pgBrowser.tree.getTreeNodeHierarchy(treeIdentifier);
|
||||
parentData = pgBrowser.tree.getTreeNodeHierarchy(treeIdentifier);
|
||||
if(_.isUndefined(parentData.server)) {
|
||||
pgAdmin.Browser.notifier.alert(
|
||||
gettext('PSQL Error'),
|
||||
gettext('Please select a server/database object.')
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if(_.isUndefined(parentData.server)) {
|
||||
pgAdmin.Browser.notifier.alert(
|
||||
gettext('PSQL Error'),
|
||||
gettext('Please select a server/database object.')
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const transId = getRandomInt(1, 9999999);
|
||||
|
||||
let panelTitle = '';
|
||||
// Set psql tab title as per prefrences setting.
|
||||
let title_data = {
|
||||
'database': parentData.database ? _.unescape(parentData.database.label) : 'postgres' ,
|
||||
|
|
@ -139,6 +166,7 @@ export default class Psql {
|
|||
'server': parentData.server.label,
|
||||
'type': 'psql_tool',
|
||||
};
|
||||
|
||||
let tab_title_placeholder = usePreferences.getState().getPreferencesForModule('browser').psql_tab_title_placeholder;
|
||||
panelTitle = generateTitle(tab_title_placeholder, title_data);
|
||||
|
||||
|
|
@ -150,7 +178,7 @@ export default class Psql {
|
|||
'pgadmin:tool:show',
|
||||
`${BROWSER_PANELS.PSQL_TOOL}_${transId}`,
|
||||
panelUrl,
|
||||
{title: panelTitle, db: db_label},
|
||||
{title: panelTitle, db: db_label, server_name: parentData.server.label, 'user': parentData.server.user.name },
|
||||
{title: panelTitle, icon: 'pg-font-icon icon-terminal', manualClose: false, renamable: true},
|
||||
Boolean(open_new_tab?.includes('psql_tool'))
|
||||
);
|
||||
|
|
@ -180,23 +208,35 @@ export default class Psql {
|
|||
return [openUrl, pData.database._label];
|
||||
}
|
||||
|
||||
|
||||
async loadComponent(container, params) {
|
||||
let panelDocker = pgWindow.pgAdmin.Browser.docker.psql_workspace;
|
||||
if (pgWindow.pgAdmin.Browser.docker.currentWorkspace == WORKSPACES.DEFAULT) {
|
||||
panelDocker = pgWindow.pgAdmin.Browser.docker.default_workspace;
|
||||
}
|
||||
|
||||
pgAdmin.Browser.keyboardNavigation.init();
|
||||
await listenPreferenceBroadcast();
|
||||
const root = ReactDOM.createRoot(container);
|
||||
root.render(
|
||||
<Theme>
|
||||
<PgAdminProvider value={pgAdmin}>
|
||||
<ModalProvider>
|
||||
<NotifierProvider pgAdmin={pgAdmin} pgWindow={pgWindow} />
|
||||
<PsqlComponent params={params} pgAdmin={pgAdmin} />
|
||||
</ModalProvider>
|
||||
<ApplicationStateProvider>
|
||||
<ModalProvider>
|
||||
<NotifierProvider pgAdmin={pgAdmin} pgWindow={pgWindow} />
|
||||
{ params.error ?
|
||||
<ToolErrorView
|
||||
error={params.error}
|
||||
panelId={`${BROWSER_PANELS.PSQL_TOOL}_${params.trans_id}`}
|
||||
panelDocker={panelDocker}
|
||||
/> :
|
||||
<PsqlComponent
|
||||
params={params}
|
||||
pgAdmin={pgAdmin} />
|
||||
}
|
||||
</ModalProvider>
|
||||
</ApplicationStateProvider>
|
||||
</PgAdminProvider>
|
||||
</Theme>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useCallback, useRef } from 'react';
|
||||
import { Box, styled, useTheme } from '@mui/material';
|
||||
import url_for from 'sources/url_for';
|
||||
import PropTypes from 'prop-types';
|
||||
|
|
@ -20,7 +20,7 @@ import { io } from 'socketio';
|
|||
import { copyToClipboard } from '../../../../../static/js/clipboard';
|
||||
import 'pgadmin.browser.keyboard';
|
||||
import gettext from 'sources/gettext';
|
||||
|
||||
import { useApplicationState } from '../../../../../settings/static/ApplicationStateProvider';
|
||||
|
||||
const Root = styled(Box)(()=>({
|
||||
width: '100%',
|
||||
|
|
@ -129,14 +129,8 @@ function psql_terminal_io(term, socket, platform, pgAdmin) {
|
|||
function psql_Addon(term) {
|
||||
const fitAddon = new FitAddon();
|
||||
term.loadAddon(fitAddon);
|
||||
|
||||
term.loadAddon(new WebLinksAddon());
|
||||
|
||||
term.loadAddon(new SearchAddon());
|
||||
|
||||
fitAddon.fit();
|
||||
term.resize(15, 50);
|
||||
fitAddon.fit();
|
||||
return fitAddon;
|
||||
}
|
||||
|
||||
|
|
@ -152,32 +146,29 @@ export default function PsqlComponent({ params, pgAdmin }) {
|
|||
const theme = useTheme();
|
||||
const termRef = React.useRef(null);
|
||||
const containerRef = React.useRef(null);
|
||||
const fitAddonRef = useRef(null);
|
||||
const {saveToolData, isSaveToolDataEnabled} = useApplicationState();
|
||||
|
||||
const initializePsqlTool = (params)=>{
|
||||
const initializePsqlTool = useCallback((params)=>{
|
||||
const term = new Terminal({
|
||||
cursorBlink: true,
|
||||
scrollback: 5000,
|
||||
});
|
||||
/* Addon for fitAddon, webLinkAddon, SearchAddon */
|
||||
const fitAddon = psql_Addon(term);
|
||||
|
||||
fitAddonRef.current = psql_Addon(term);
|
||||
/* Open terminal */
|
||||
term.open(containerRef.current);
|
||||
|
||||
/* Socket */
|
||||
const socket = psql_socket();
|
||||
|
||||
psql_socket_io(socket, params.is_enable, params.sid, params.db, params.server_type, fitAddon, term, params.role);
|
||||
psql_socket_io(socket, params.is_enable, params.sid, params.db, params.server_type, fitAddonRef.current, term, params.role);
|
||||
|
||||
psql_terminal_io(term, socket, params.platform, pgAdmin);
|
||||
|
||||
/* Set terminal size */
|
||||
setTimeout(function(){
|
||||
socket.emit('resize', {'cols': term.cols, 'rows': term.rows});
|
||||
}, 1000);
|
||||
return [term, socket];
|
||||
};
|
||||
}, [params, pgAdmin]);;
|
||||
|
||||
const setTheme = ()=>{
|
||||
const setTheme = useCallback(()=>{
|
||||
if(termRef.current) {
|
||||
termRef.current.options.theme = {
|
||||
background: theme.palette.background.default,
|
||||
|
|
@ -187,30 +178,43 @@ export default function PsqlComponent({ params, pgAdmin }) {
|
|||
selectionBackground: `${theme.otherVars.editor.selectionBg}`,
|
||||
};
|
||||
}
|
||||
};
|
||||
}, [theme]);
|
||||
|
||||
useEffect(()=>{
|
||||
const [term, socket] = initializePsqlTool(params);
|
||||
termRef.current = term;
|
||||
|
||||
setTheme();
|
||||
|
||||
termRef.current.focus();
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
for (let entry of entries) {
|
||||
if (entry.contentRect.width > 0 && entry.contentRect.height > 0) {
|
||||
fitAddonRef.current?.fit();
|
||||
socket.emit('resize', { cols: term.cols, rows: term.rows});
|
||||
term.focus();
|
||||
observer.disconnect(); // Only do this once
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
termRef.current.focus();
|
||||
if (containerRef.current) {
|
||||
observer.observe(containerRef.current);
|
||||
}
|
||||
|
||||
if(isSaveToolDataEnabled('psql')){
|
||||
saveToolData('psql', params, params.trans_id, null);
|
||||
}
|
||||
|
||||
return () => {
|
||||
term.dispose();
|
||||
socket.disconnect();
|
||||
observer.disconnect();
|
||||
};
|
||||
|
||||
}, []);
|
||||
|
||||
useEffect(()=>{
|
||||
setTheme();
|
||||
},[theme]);
|
||||
|
||||
|
||||
return (
|
||||
<Root ref={containerRef}>
|
||||
</Root>
|
||||
|
|
@ -224,7 +228,8 @@ PsqlComponent.propTypes = {
|
|||
db: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
|
||||
server_type: PropTypes.string,
|
||||
role: PropTypes.string,
|
||||
platform: PropTypes.string
|
||||
platform: PropTypes.string,
|
||||
trans_id: PropTypes.number
|
||||
}),
|
||||
pgAdmin: PropTypes.object.isRequired,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,10 +10,9 @@
|
|||
"""A blueprint module implementing the schema_diff frame."""
|
||||
import json
|
||||
import pickle
|
||||
import secrets
|
||||
import copy
|
||||
|
||||
from flask import Response, session, url_for, request
|
||||
from flask import Response, session, request
|
||||
from flask import render_template, current_app as app
|
||||
from flask_security import current_user, permissions_required
|
||||
from pgadmin.user_login_check import pga_login_required
|
||||
|
|
@ -26,7 +25,7 @@ from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
|
|||
from pgadmin.tools.schema_diff.model import SchemaDiffModel
|
||||
from config import PG_DEFAULT_DRIVER
|
||||
from pgadmin.utils.driver import get_driver
|
||||
from pgadmin.utils.constants import PREF_LABEL_DISPLAY, MIMETYPE_APP_JS,\
|
||||
from pgadmin.utils.constants import PREF_LABEL_DISPLAY, \
|
||||
ERROR_MSG_TRANS_ID_NOT_FOUND
|
||||
from sqlalchemy import or_
|
||||
from pgadmin.authenticate import socket_login_required
|
||||
|
|
@ -121,7 +120,7 @@ def index():
|
|||
|
||||
@blueprint.route(
|
||||
'/panel/<int:trans_id>/<path:editor_title>',
|
||||
methods=["GET"],
|
||||
methods=["POST"],
|
||||
endpoint='panel'
|
||||
)
|
||||
@permissions_required(AllPermissionTypes.tools_schema_diff)
|
||||
|
|
@ -133,6 +132,7 @@ def panel(trans_id, editor_title):
|
|||
Args:
|
||||
editor_title: Title of the editor
|
||||
"""
|
||||
params = {}
|
||||
# If title has slash(es) in it then replace it
|
||||
if request.args and request.args['fslashes'] != '':
|
||||
try:
|
||||
|
|
@ -142,12 +142,18 @@ def panel(trans_id, editor_title):
|
|||
editor_title = editor_title[:idx] + '/' + editor_title[idx:]
|
||||
except IndexError as e:
|
||||
app.logger.exception(e)
|
||||
if request.args:
|
||||
params = {k: v for k, v in request.args.items()}
|
||||
if request.form:
|
||||
for key, val in request.form.items():
|
||||
params[key] = val
|
||||
|
||||
return render_template(
|
||||
"schema_diff/index.html",
|
||||
_=gettext,
|
||||
trans_id=trans_id,
|
||||
editor_title=editor_title,
|
||||
params=json.dumps(params)
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { NotifierProvider } from '../../../../static/js/helpers/Notifier';
|
|||
import usePreferences, { listenPreferenceBroadcast } from '../../../../preferences/static/js/store';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import { PgAdminProvider } from '../../../../static/js/PgAdminProvider';
|
||||
import { ApplicationStateProvider } from '../../../../settings/static/ApplicationStateProvider';
|
||||
|
||||
export default class SchemaDiff {
|
||||
static instance;
|
||||
|
|
@ -61,7 +62,7 @@ export default class SchemaDiff {
|
|||
}]);
|
||||
}
|
||||
|
||||
launchSchemaDiff() {
|
||||
launchSchemaDiff(toolDataId=null) {
|
||||
let panelTitle = SchemaDiff.panelTitleCount > 1 ? gettext('Schema Diff - %s', SchemaDiff.panelTitleCount) : gettext('Schema Diff');
|
||||
SchemaDiff.panelTitleCount++;
|
||||
const trans_id = commonUtils.getRandomInt(1, 9999999);
|
||||
|
|
@ -79,24 +80,26 @@ export default class SchemaDiff {
|
|||
'pgadmin:tool:show',
|
||||
`${BROWSER_PANELS.SCHEMA_DIFF_TOOL}_${trans_id}`,
|
||||
baseUrl,
|
||||
null,
|
||||
{toolDataId: toolDataId},
|
||||
{title: panelTitle, icon: 'pg-font-icon icon-compare', manualClose: false, renamable: true},
|
||||
Boolean(openInNewTab?.includes('schema_diff'))
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
async load(container, trans_id) {
|
||||
async load(container, trans_id, params) {
|
||||
pgAdmin.Browser.keyboardNavigation.init();
|
||||
await listenPreferenceBroadcast();
|
||||
const root = ReactDOM.createRoot(container);
|
||||
root.render(
|
||||
<Theme>
|
||||
<PgAdminProvider value={pgAdmin}>
|
||||
<ModalProvider>
|
||||
<NotifierProvider pgAdmin={pgAdmin} pgWindow={pgWindow} />
|
||||
<SchemaDiffComponent params={{ transId: trans_id, pgAdmin: pgWindow.pgAdmin }}></SchemaDiffComponent>
|
||||
</ModalProvider>
|
||||
<ApplicationStateProvider>
|
||||
<ModalProvider>
|
||||
<NotifierProvider pgAdmin={pgAdmin} pgWindow={pgWindow} />
|
||||
<SchemaDiffComponent params={{ transId: trans_id, pgAdmin: pgWindow.pgAdmin, params:params }}></SchemaDiffComponent>
|
||||
</ModalProvider>
|
||||
</ApplicationStateProvider>
|
||||
</PgAdminProvider>
|
||||
</Theme>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,16 +7,12 @@
|
|||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import React, { useContext, useState, useEffect } from 'react';
|
||||
|
||||
import { Box, Grid, Typography } from '@mui/material';
|
||||
|
||||
import { InputSelect } from '../../../../../static/js/components/FormComponents';
|
||||
import { SchemaDiffEventsContext } from './SchemaDiffComponent';
|
||||
import { SCHEMA_DIFF_EVENT } from '../SchemaDiffConstants';
|
||||
|
||||
|
||||
export function InputComponent({ label, serverList, databaseList, schemaList, diff_type, selectedSid = null, selectedDid=null, selectedScid=null, onServerSchemaChange }) {
|
||||
const [selectedServer, setSelectedServer] = useState(selectedSid);
|
||||
const [selectedDatabase, setSelectedDatabase] = useState(selectedDid);
|
||||
|
|
@ -25,10 +21,18 @@ export function InputComponent({ label, serverList, databaseList, schemaList, di
|
|||
const [disableDBSelection, setDisableDBSelection] = useState(selectedSid == null);
|
||||
const [disableSchemaSelection, setDisableSchemaSelection] = useState(selectedDid == null);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedDatabase(selectedDid);
|
||||
if (selectedDid) setDisableSchemaSelection(false);
|
||||
}, [selectedSid, selectedDid, selectedScid]);
|
||||
|
||||
useEffect(()=>{
|
||||
changeServer(selectedSid);
|
||||
},[selectedSid]);
|
||||
|
||||
useEffect(()=>{
|
||||
changeDatabase(selectedDid);
|
||||
},[selectedDid]);
|
||||
|
||||
useEffect(()=>{
|
||||
changeSchema(selectedScid);
|
||||
},[selectedScid]);
|
||||
|
||||
const changeServer = (selectedOption) => {
|
||||
setDisableDBSelection(false);
|
||||
|
|
@ -43,6 +47,7 @@ export function InputComponent({ label, serverList, databaseList, schemaList, di
|
|||
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_SELECT_SERVER, { selectedOption, diff_type, serverList });
|
||||
};
|
||||
|
||||
|
||||
const changeDatabase = (selectedDB) => {
|
||||
setSelectedDatabase(selectedDB);
|
||||
setDisableSchemaSelection(false);
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ import { ResultGridComponent } from './ResultGridComponent';
|
|||
import { openSocket, socketApiGet } from '../../../../../static/js/socket_instance';
|
||||
import { parseApiError } from '../../../../../static/js/api_instance';
|
||||
import { usePgAdmin } from '../../../../../static/js/PgAdminProvider';
|
||||
|
||||
import { useApplicationState } from '../../../../../settings/static/ApplicationStateProvider';
|
||||
import { getToolData } from '../../../../../settings/static/ApplicationStateProvider';
|
||||
|
||||
function generateFinalScript(script_array, scriptHeader, script_body) {
|
||||
_.each(Object.keys(script_array).reverse(), function (s) {
|
||||
|
|
@ -117,6 +118,8 @@ export function SchemaDiffCompare({ params }) {
|
|||
const [isInit, setIsInit] = useState(true);
|
||||
|
||||
const pgAdmin = usePgAdmin();
|
||||
const {saveToolData, isSaveToolDataEnabled} = useApplicationState();
|
||||
const [oldSchemaDiffData, setOldSchemaDiffData] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
schemaDiffToolContext.api.get(url_for('schema_diff.servers')).then((res) => {
|
||||
|
|
@ -137,6 +140,23 @@ export function SchemaDiffCompare({ params }) {
|
|||
});
|
||||
}, []);
|
||||
|
||||
useEffect(()=>{
|
||||
let oldSchemaDiffData1 = getToolData(params.params?.toolDataId);
|
||||
setOldSchemaDiffData(oldSchemaDiffData1);
|
||||
},[]);
|
||||
|
||||
useEffect(()=>{
|
||||
if(oldSchemaDiffData){
|
||||
_.each(oldSchemaDiffData,(d)=>{
|
||||
if(d.diff_type == TYPE.SOURCE){
|
||||
setSelectedSourceSid(d.selectedSourceSid);
|
||||
}else{
|
||||
setSelectedTargetSid(d.selectedTargetSid);
|
||||
}
|
||||
});
|
||||
}
|
||||
},[sourceGroupServerList]);
|
||||
|
||||
useEffect(() => {
|
||||
// Register all eventes for debugger.
|
||||
eventBus.registerListener(
|
||||
|
|
@ -145,7 +165,6 @@ export function SchemaDiffCompare({ params }) {
|
|||
eventBus.registerListener(
|
||||
SCHEMA_DIFF_EVENT.TRIGGER_SELECT_DATABASE, triggerSelectDatabase);
|
||||
|
||||
|
||||
eventBus.registerListener(
|
||||
SCHEMA_DIFF_EVENT.TRIGGER_SELECT_SCHEMA, triggerSelectSchema);
|
||||
|
||||
|
|
@ -159,7 +178,7 @@ export function SchemaDiffCompare({ params }) {
|
|||
SCHEMA_DIFF_EVENT.TRIGGER_GENERATE_SCRIPT, triggerGenerateScript);
|
||||
|
||||
}, []);
|
||||
|
||||
|
||||
function checkAndSetSourceData(diff_type, selectedOption) {
|
||||
if(selectedOption == null) {
|
||||
setSelectedRowIds([]);
|
||||
|
|
@ -264,6 +283,15 @@ export function SchemaDiffCompare({ params }) {
|
|||
pgAdmin.Browser.notifier.alert(gettext('Selection Error'),
|
||||
gettext('Please select the different source and target.'));
|
||||
} else {
|
||||
|
||||
if(isSaveToolDataEnabled('schema_diff')){
|
||||
let toolData = [
|
||||
{ diff_type: TYPE.SOURCE, selectedSourceSid: sourceData.sid, selectedSourceDid:sourceData.did, selectedSourceScid: sourceData.scid},
|
||||
{ diff_type: TYPE.TARGET, selectedTargetSid:targetData.sid, selectedTargetDid:targetData.did, selectedTargetScid:targetData.scid },
|
||||
];
|
||||
saveToolData('schema_diff', null, params.transId, toolData);
|
||||
}
|
||||
|
||||
setLoaderText('Comparing objects... (this may take a few minutes)...');
|
||||
let url_params = {
|
||||
'trans_id': params.transId,
|
||||
|
|
@ -626,7 +654,6 @@ export function SchemaDiffCompare({ params }) {
|
|||
url_for('schema_diff.databases', { 'sid': sid })
|
||||
).then((res) => {
|
||||
res.data.data.map((opt) => {
|
||||
|
||||
if (opt.is_maintenance_db) {
|
||||
if (diff_type == TYPE.SOURCE) {
|
||||
setSelectedSourceDid(opt.value);
|
||||
|
|
@ -641,10 +668,21 @@ export function SchemaDiffCompare({ params }) {
|
|||
} else {
|
||||
setTargetDatabaseList(res.data.data);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
if(oldSchemaDiffData){
|
||||
_.each(oldSchemaDiffData,(d)=>{
|
||||
if(d.diff_type == TYPE.SOURCE){
|
||||
setSelectedSourceDid(d.selectedSourceDid);
|
||||
}else{
|
||||
setSelectedTargetDid(d.selectedTargetDid);
|
||||
}
|
||||
});
|
||||
}
|
||||
},[targetDatabaseList, sourceDatabaseList]);
|
||||
|
||||
function getSchemaList(sid, did, diff_type) {
|
||||
schemaDiffToolContext.api.get(
|
||||
url_for('schema_diff.schemas', { 'sid': sid, 'did': did })
|
||||
|
|
@ -654,10 +692,21 @@ export function SchemaDiffCompare({ params }) {
|
|||
} else {
|
||||
setTargetSchemaList(res.data.data);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
if(oldSchemaDiffData){
|
||||
_.each(oldSchemaDiffData,(d)=>{
|
||||
if(d.diff_type == TYPE.SOURCE){
|
||||
setSelectedSourceScid(d.selectedSourceScid);
|
||||
}else{
|
||||
setSelectedTargetScid(d.selectedTargetScid);
|
||||
}
|
||||
});
|
||||
}
|
||||
},[targetSchemaList, sourceSchemaList]);
|
||||
|
||||
function showConnectServer(result, sid, diff_type, serverList) {
|
||||
schemaDiffToolContext.modal.showModal(gettext('Connect to server'), (closeModal) => {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import getApiInstance, { callFetch } from '../../../../../static/js/api_instance
|
|||
import { useModal } from '../../../../../static/js/helpers/ModalProvider';
|
||||
import usePreferences from '../../../../../preferences/static/js/store';
|
||||
|
||||
|
||||
export const SchemaDiffEventsContext = createContext();
|
||||
export const SchemaDiffContext = createContext();
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ try {
|
|||
['sources/generated/browser_nodes', 'sources/generated/schema_diff'],
|
||||
function() {
|
||||
var pgSchemaDiff = window.pgAdmin.Tools.SchemaDiff;
|
||||
pgSchemaDiff.load(document.getElementById('schema-diff-main-container'),{{trans_id}});
|
||||
pgSchemaDiff.load(document.getElementById('schema-diff-main-container'),{{trans_id}},{{ params|safe }});
|
||||
},
|
||||
function() {
|
||||
console.log(arguments);
|
||||
|
|
|
|||
|
|
@ -42,11 +42,11 @@ from pgadmin.tools.sqleditor.utils.update_session_grid_transaction import \
|
|||
from pgadmin.utils import PgAdminModule
|
||||
from pgadmin.utils import get_storage_directory
|
||||
from pgadmin.utils.ajax import make_json_response, bad_request, \
|
||||
success_return, internal_server_error, service_unavailable
|
||||
success_return, internal_server_error, service_unavailable, gone
|
||||
from pgadmin.utils.driver import get_driver
|
||||
from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost, \
|
||||
CryptKeyMissing, ObjectGone
|
||||
from pgadmin.browser.utils import underscore_unescape, underscore_escape
|
||||
from pgadmin.browser.utils import underscore_escape
|
||||
from pgadmin.utils.menu import MenuItem
|
||||
from pgadmin.utils.sqlautocomplete.autocomplete import SQLAutoComplete
|
||||
from pgadmin.tools.sqleditor.utils.query_tool_preferences import \
|
||||
|
|
@ -146,7 +146,7 @@ class SqlEditorModule(PgAdminModule):
|
|||
'sqleditor.get_new_connection_user',
|
||||
'sqleditor._check_server_connection_status',
|
||||
'sqleditor.get_new_connection_role',
|
||||
'sqleditor.connect_server',
|
||||
'sqleditor.connect_server'
|
||||
]
|
||||
|
||||
def on_logout(self):
|
||||
|
|
@ -325,38 +325,47 @@ def panel(trans_id):
|
|||
params['fgcolor'] = None
|
||||
|
||||
s = Server.query.filter_by(id=int(params['sid'])).first()
|
||||
if s.shared and s.user_id != current_user.id:
|
||||
# Import here to avoid circular dependency
|
||||
from pgadmin.browser.server_groups.servers import ServerModule
|
||||
shared_server = ServerModule.get_shared_server(s, params['sgid'])
|
||||
s = ServerModule.get_shared_server_properties(s, shared_server)
|
||||
if s:
|
||||
if s.shared and s.user_id != current_user.id:
|
||||
# Import here to avoid circular dependency
|
||||
from pgadmin.browser.server_groups.servers import ServerModule
|
||||
shared_server = ServerModule.get_shared_server(s, params['sgid'])
|
||||
s = ServerModule.get_shared_server_properties(s, shared_server)
|
||||
|
||||
if s and s.bgcolor:
|
||||
# If background is set to white means we do not have to change
|
||||
# the title background else change it as per user specified
|
||||
# background
|
||||
if s.bgcolor != '#ffffff':
|
||||
params['bgcolor'] = s.bgcolor
|
||||
params['fgcolor'] = s.fgcolor or 'black'
|
||||
if s and s.bgcolor:
|
||||
# If background is set to white means we do not have to change
|
||||
# the title background else change it as per user specified
|
||||
# background
|
||||
if s.bgcolor != '#ffffff':
|
||||
params['bgcolor'] = s.bgcolor
|
||||
params['fgcolor'] = s.fgcolor or 'black'
|
||||
|
||||
params['server_name'] = underscore_escape(s.name)
|
||||
if 'user' not in params:
|
||||
params['user'] = underscore_escape(s.username)
|
||||
if 'role' not in params and s.role:
|
||||
params['role'] = underscore_escape(s.role)
|
||||
params['layout'] = get_setting('SQLEditor/Layout')
|
||||
params['macros'] = get_user_macros()
|
||||
params['is_desktop_mode'] = current_app.PGADMIN_RUNTIME
|
||||
params['title'] = underscore_escape(params['title'])
|
||||
params['selectedNodeInfo'] = underscore_escape(params['selectedNodeInfo'])
|
||||
if 'database_name' in params:
|
||||
params['database_name'] = underscore_escape(params['database_name'])
|
||||
params['server_name'] = underscore_escape(s.name)
|
||||
if 'user' not in params:
|
||||
params['user'] = underscore_escape(s.username)
|
||||
if 'role' not in params and s.role:
|
||||
params['role'] = underscore_escape(s.role)
|
||||
params['layout'] = get_setting('SQLEditor/Layout')
|
||||
params['macros'] = get_user_macros()
|
||||
params['is_desktop_mode'] = current_app.PGADMIN_RUNTIME
|
||||
params['title'] = underscore_escape(params['title'])
|
||||
params['selectedNodeInfo'] = (
|
||||
underscore_escape(params['selectedNodeInfo']))
|
||||
if 'database_name' in params:
|
||||
params['database_name'] = (
|
||||
underscore_escape(params['database_name']))
|
||||
|
||||
return render_template(
|
||||
"sqleditor/index.html",
|
||||
title=underscore_escape(params['title']),
|
||||
params=json.dumps(params),
|
||||
)
|
||||
return render_template(
|
||||
"sqleditor/index.html",
|
||||
title=underscore_escape(params['title']),
|
||||
params=json.dumps(params),
|
||||
)
|
||||
else:
|
||||
params['error'] = 'Server did not find.'
|
||||
return render_template(
|
||||
"sqleditor/index.html",
|
||||
title=None,
|
||||
params=json.dumps(params))
|
||||
|
||||
|
||||
@blueprint.route(
|
||||
|
|
@ -655,6 +664,7 @@ def close(trans_id):
|
|||
# session variable.
|
||||
grid_data.pop(str(trans_id), None)
|
||||
session['gridData'] = grid_data
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(e)
|
||||
return internal_server_error(errormsg=str(e))
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ import { AllPermissionTypes, BROWSER_PANELS, WORKSPACES } from '../../../../brow
|
|||
import { NotifierProvider } from '../../../../static/js/helpers/Notifier';
|
||||
import usePreferences, { listenPreferenceBroadcast } from '../../../../preferences/static/js/store';
|
||||
import { PgAdminProvider } from '../../../../static/js/PgAdminProvider';
|
||||
import { ApplicationStateProvider } from '../../../../settings/static/ApplicationStateProvider';
|
||||
import ToolErrorView from '../../../../static/js/ToolErrorView';
|
||||
|
||||
export default class SQLEditor {
|
||||
static instance;
|
||||
|
|
@ -218,7 +220,7 @@ export default class SQLEditor {
|
|||
let browser_preferences = usePreferences.getState().getPreferencesForModule('browser');
|
||||
let open_new_tab = browser_preferences.new_browser_tab_open;
|
||||
const [icon, tooltip] = panelTitleFunc.getQueryToolIcon(panel_title, is_query_tool);
|
||||
let selectedNodeInfo = pgAdmin.Browser.tree.getTreeNodeHierarchy(
|
||||
let selectedNodeInfo = pgAdmin.Browser.tree?.getTreeNodeHierarchy(
|
||||
pgAdmin.Browser.tree.selected()
|
||||
);
|
||||
|
||||
|
|
@ -246,11 +248,24 @@ export default class SQLEditor {
|
|||
root.render(
|
||||
<Theme>
|
||||
<PgAdminProvider value={pgAdmin}>
|
||||
<ModalProvider>
|
||||
<NotifierProvider pgAdmin={pgAdmin} pgWindow={pgWindow} />
|
||||
<QueryToolComponent params={params} pgWindow={pgWindow} pgAdmin={pgAdmin} qtPanelDocker={panelDocker}
|
||||
qtPanelId={`${BROWSER_PANELS.QUERY_TOOL}_${params.trans_id}`} selectedNodeInfo={selectedNodeInfo}/>
|
||||
</ModalProvider>
|
||||
<ApplicationStateProvider>
|
||||
<ModalProvider>
|
||||
<NotifierProvider pgAdmin={pgAdmin} pgWindow={pgWindow} />
|
||||
{ params.error ?
|
||||
<ToolErrorView
|
||||
error={params.error}
|
||||
panelId={`${BROWSER_PANELS.QUERY_TOOL}_${params.trans_id}`}
|
||||
panelDocker={panelDocker}
|
||||
/> :
|
||||
<QueryToolComponent params={params}
|
||||
pgWindow={pgWindow}
|
||||
pgAdmin={pgAdmin}
|
||||
qtPanelDocker={panelDocker}
|
||||
qtPanelId={`${BROWSER_PANELS.QUERY_TOOL}_${params.trans_id}`}
|
||||
selectedNodeInfo={selectedNodeInfo}
|
||||
/>}
|
||||
</ModalProvider>
|
||||
</ApplicationStateProvider>
|
||||
</PgAdminProvider>
|
||||
</Theme>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import { retrieveNodeName } from '../show_view_data';
|
|||
import { useModal } from '../../../../../static/js/helpers/ModalProvider';
|
||||
import ConnectServerContent from '../../../../../static/js/Dialogs/ConnectServerContent';
|
||||
import usePreferences from '../../../../../preferences/static/js/store';
|
||||
import { useApplicationState } from '../../../../../settings/static/ApplicationStateProvider';
|
||||
|
||||
export const QueryToolContext = React.createContext();
|
||||
export const QueryToolConnectionContext = React.createContext();
|
||||
|
|
@ -215,6 +216,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
|
|||
const docker = useRef(null);
|
||||
const api = useMemo(()=>getApiInstance(), []);
|
||||
const modal = useModal();
|
||||
const {isSaveToolDataEnabled} = useApplicationState();
|
||||
|
||||
/* Connection status poller */
|
||||
let pollTime = qtState.preferences.sqleditor.connection_status_fetch_time > 0
|
||||
|
|
@ -332,17 +334,35 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
|
|||
setQtStatePartial({ editor_disabled: true });
|
||||
});
|
||||
} else if (qtState.params.sql_id) {
|
||||
let sqlValue = localStorage.getItem(qtState.params.sql_id);
|
||||
localStorage.removeItem(qtState.params.sql_id);
|
||||
if (sqlValue) {
|
||||
eventBus.current.fireEvent(QUERY_TOOL_EVENTS.EDITOR_SET_SQL, sqlValue);
|
||||
}
|
||||
setQtStatePartial({ editor_disabled: false });
|
||||
populateEditorData();
|
||||
} else {
|
||||
setQtStatePartial({ editor_disabled: false });
|
||||
}
|
||||
};
|
||||
|
||||
const populateEditorData = () =>{
|
||||
let sqlId = qtState.params.sql_id,
|
||||
loadSqlFromLocalStorage = true;
|
||||
|
||||
if(qtState.params.open_file_name){
|
||||
if(qtState.params.file_deleted == 'false' && qtState.params.is_editor_dirty == 'false'){
|
||||
// call load file from disk as no fil changes
|
||||
eventBus.current.fireEvent(QUERY_TOOL_EVENTS.LOAD_FILE, qtState.params.open_file_name, qtState.params?.storage);
|
||||
}else{
|
||||
if(qtState.params.file_deleted != 'true'){
|
||||
if(qtState.params.external_file_changes == 'true'){
|
||||
loadSqlFromLocalStorage = false;
|
||||
eventBus.current.fireEvent(QUERY_TOOL_EVENTS.WARN_RELOAD_FILE, qtState.params.open_file_name, sqlId);
|
||||
}else{
|
||||
eventBus.current.fireEvent(QUERY_TOOL_EVENTS.LOAD_FILE_DONE, qtState.params.open_file_name, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(loadSqlFromLocalStorage) eventBus.current.fireEvent(QUERY_TOOL_EVENTS.LOAD_SQL_FROM_LOCAL_STORAGE, sqlId);
|
||||
setQtStatePartial({ editor_disabled: false });
|
||||
};
|
||||
|
||||
const initializeQueryTool = (password, explainObject=null, macroSQL='', executeCursor=false, reexecute=false)=>{
|
||||
let selectedConn = _.find(qtState.connection_list, (c)=>c.is_selected);
|
||||
let baseUrl = '';
|
||||
|
|
@ -499,7 +519,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
|
|||
'trans_id': qtState.params.trans_id,
|
||||
}), {
|
||||
keepalive: true,
|
||||
method: 'DELETE',
|
||||
method: 'DELETE'
|
||||
}
|
||||
)
|
||||
.then(()=>{/* Success */})
|
||||
|
|
@ -573,10 +593,12 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
|
|||
const fileDone = (fileName, success=true)=>{
|
||||
if(success) {
|
||||
setQtStatePartial({
|
||||
current_file: fileName,
|
||||
current_file: fileName
|
||||
});
|
||||
isDirtyRef.current = false;
|
||||
setPanelTitle(qtPanelDocker, qtPanelId, fileName, {...qtState, current_file: fileName}, isDirtyRef.current);
|
||||
|
||||
if(isSaveToolDataEnabled('sqleditor'))eventBus.current.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_SAVE_QUERY_TOOL_DATA);
|
||||
}
|
||||
eventBus.current.fireEvent(QUERY_TOOL_EVENTS.EDITOR_LAST_FOCUS);
|
||||
};
|
||||
|
|
@ -898,6 +920,8 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
|
|||
mainContainerRef: containerRef,
|
||||
editor_disabled: qtState.editor_disabled,
|
||||
eol: qtState.eol,
|
||||
connection_list: qtState.connection_list,
|
||||
current_file: qtState.current_file,
|
||||
toggleQueryTool: () => setQtStatePartial((prev)=>{
|
||||
return {
|
||||
...prev,
|
||||
|
|
@ -928,7 +952,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
|
|||
};
|
||||
});
|
||||
},
|
||||
}), [qtState.params, qtState.preferences, containerRef.current, qtState.editor_disabled, qtState.eol]);
|
||||
}), [qtState.params, qtState.preferences, containerRef.current, qtState.editor_disabled, qtState.eol, qtState.current_file]);
|
||||
|
||||
const queryToolConnContextValue = React.useMemo(()=>({
|
||||
connected: qtState.connected,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export const QUERY_TOOL_EVENTS = {
|
|||
TRIGGER_FORMAT_SQL: 'TRIGGER_FORMAT_SQL',
|
||||
TRIGGER_GRAPH_VISUALISER: 'TRIGGER_GRAPH_VISUALISER',
|
||||
TRIGGER_SELECT_ALL: 'TRIGGER_SELECT_ALL',
|
||||
TRIGGER_SAVE_QUERY_TOOL_DATA: 'TRIGGER_SAVE_QUERY_TOOL_DATA',
|
||||
|
||||
COPY_DATA: 'COPY_DATA',
|
||||
SET_LIMIT_VALUE: 'SET_LIMIT_VALUE',
|
||||
|
|
@ -48,6 +49,8 @@ export const QUERY_TOOL_EVENTS = {
|
|||
LOAD_FILE_DONE: 'LOAD_FILE_DONE',
|
||||
SAVE_FILE: 'SAVE_FILE',
|
||||
SAVE_FILE_DONE: 'SAVE_FILE_DONE',
|
||||
SAVE_QUERY_TOOL_DATA: 'SAVE_QUERY_TOOL_DATA',
|
||||
LOAD_SQL_FROM_LOCAL_STORAGE: 'LOAD_SQL_FROM_LOCAL_STORAGE',
|
||||
QUERY_CHANGED: 'QUERY_CHANGED',
|
||||
API_ERROR: 'API_ERROR',
|
||||
TASK_START: 'TASK_START',
|
||||
|
|
@ -73,6 +76,7 @@ export const QUERY_TOOL_EVENTS = {
|
|||
|
||||
WARN_SAVE_DATA_CLOSE: 'WARN_SAVE_DATA_CLOSE',
|
||||
WARN_SAVE_TEXT_CLOSE: 'WARN_SAVE_TEXT_CLOSE',
|
||||
WARN_RELOAD_FILE: 'WARN_RELOAD_FILE',
|
||||
WARN_TXN_CLOSE: 'WARN_TXN_CLOSE',
|
||||
EXECUTE_CURSOR_WARNING: 'EXECUTE_CURSOR_WARNING',
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import React, {useContext, useCallback, useEffect, useMemo } from 'react';
|
||||
import React, {useContext, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { format } from 'sql-formatter';
|
||||
import { QueryToolContext, QueryToolEventsContext } from '../QueryToolComponent';
|
||||
import CodeMirror from '../../../../../../static/js/components/ReactCodeMirror';
|
||||
|
|
@ -25,6 +25,9 @@ import usePreferences from '../../../../../../preferences/static/js/store';
|
|||
import { getTitle } from '../../sqleditor_title';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MODAL_DIALOGS } from '../QueryToolConstants';
|
||||
import { useApplicationState } from '../../../../../../settings/static/ApplicationStateProvider';
|
||||
import { useDelayDebounce } from '../../../../../../static/js/custom_hooks';
|
||||
import { getToolData } from '../../../../../../settings/static/ApplicationStateProvider';
|
||||
|
||||
|
||||
async function registerAutocomplete(editor, api, transId) {
|
||||
|
|
@ -64,9 +67,11 @@ export default function Query({onTextSelect, setQtStatePartial}) {
|
|||
const layoutDocker = useContext(LayoutDockerContext);
|
||||
const lastCursorPos = React.useRef();
|
||||
const pgAdmin = usePgAdmin();
|
||||
const {saveToolData, isSaveToolDataEnabled} = useApplicationState();
|
||||
const preferencesStore = usePreferences();
|
||||
const queryToolPref = queryToolCtx.preferences.sqleditor;
|
||||
const modalId = MODAL_DIALOGS.QT_CONFIRMATIONS;
|
||||
|
||||
const highlightError = (cmObj, {errormsg: result, data}, executeCursor)=>{
|
||||
let errorLineNo = 0,
|
||||
startMarker = 0,
|
||||
|
|
@ -136,7 +141,6 @@ export default function Query({onTextSelect, setQtStatePartial}) {
|
|||
cmObj.setCursor(errorLineNo, endMarker);
|
||||
}
|
||||
};
|
||||
|
||||
const triggerExecution = (explainObject, macroSQL, executeCursor=false)=>{
|
||||
if(queryToolCtx.params.is_query_tool) {
|
||||
let external = null;
|
||||
|
|
@ -160,12 +164,27 @@ export default function Query({onTextSelect, setQtStatePartial}) {
|
|||
}
|
||||
};
|
||||
|
||||
const warnReloadFile = (fileName, sqlId, storage=null)=>{
|
||||
queryToolCtx.modal.confirm(
|
||||
gettext('Reload file?'),
|
||||
gettext('The file has been modified by another program. Do you want to reload it and loose changes made in pgadmin?'),
|
||||
function() {
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.LOAD_FILE, fileName);
|
||||
},
|
||||
function() {
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.LOAD_SQL_FROM_LOCAL_STORAGE, sqlId);
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.LOAD_FILE_DONE, fileName, true, storage);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
layoutDocker.eventBus.registerListener(LAYOUT_EVENTS.ACTIVE, (currentTabId)=>{
|
||||
currentTabId == PANELS.QUERY && editor.current.focus();
|
||||
});
|
||||
|
||||
eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_EXECUTION, triggerExecution);
|
||||
|
||||
eventBus.registerListener(QUERY_TOOL_EVENTS.EXECUTE_CURSOR_WARNING, checkUnderlineQueryCursorWarning);
|
||||
|
||||
eventBus.registerListener(QUERY_TOOL_EVENTS.HIGHLIGHT_ERROR, (result, executeCursor)=>{
|
||||
|
|
@ -189,7 +208,7 @@ export default function Query({onTextSelect, setQtStatePartial}) {
|
|||
editor.current.setValue(res.data);
|
||||
//Check the file content for Trojan Source
|
||||
checkTrojanSource(res.data);
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.LOAD_FILE_DONE, fileName, true);
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.LOAD_FILE_DONE, fileName, true, storage);
|
||||
// Detect line separator from content and editor's EOL.
|
||||
const lineSep = editor.current?.detectEOL(res.data);
|
||||
// Update the EOL if it differs from the current editor EOL
|
||||
|
|
@ -197,7 +216,7 @@ export default function Query({onTextSelect, setQtStatePartial}) {
|
|||
// Mark the editor content as clean
|
||||
editor.current?.markClean();
|
||||
}).catch((err)=>{
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.LOAD_FILE_DONE, null, false);
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.LOAD_FILE_DONE, null, false, storage);
|
||||
pgAdmin.Browser.notifier.error(parseApiError(err));
|
||||
});
|
||||
});
|
||||
|
|
@ -233,6 +252,7 @@ export default function Query({onTextSelect, setQtStatePartial}) {
|
|||
editor.current?.execCommand(cmd);
|
||||
}
|
||||
});
|
||||
|
||||
eventBus.registerListener(QUERY_TOOL_EVENTS.COPY_TO_EDITOR, (text)=>{
|
||||
editor.current?.setValue(text);
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.FOCUS_PANEL, PANELS.QUERY);
|
||||
|
|
@ -241,6 +261,7 @@ export default function Query({onTextSelect, setQtStatePartial}) {
|
|||
editor.current?.setCursor(editor.current.lineCount(), 0);
|
||||
}, 250);
|
||||
});
|
||||
|
||||
eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_FIND_REPLACE, (replace=false)=>{
|
||||
editor.current?.focus();
|
||||
let key = {
|
||||
|
|
@ -254,6 +275,7 @@ export default function Query({onTextSelect, setQtStatePartial}) {
|
|||
}
|
||||
editor.current?.fireDOMEvent(new KeyboardEvent('keydown', key));
|
||||
});
|
||||
|
||||
eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_SET_SQL, (value, focus=true)=>{
|
||||
focus && editor.current?.focus();
|
||||
editor.current?.setValue(value, !queryToolCtx.params.is_query_tool);
|
||||
|
|
@ -261,6 +283,7 @@ export default function Query({onTextSelect, setQtStatePartial}) {
|
|||
eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_QUERY_CHANGE, ()=>{
|
||||
change();
|
||||
});
|
||||
|
||||
eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_FORMAT_SQL, ()=>{
|
||||
let selection = true, sql = editor.current?.getSelection();
|
||||
let sqlEditorPref = preferencesStore.getPreferencesForModule('sqleditor');
|
||||
|
|
@ -297,7 +320,7 @@ export default function Query({onTextSelect, setQtStatePartial}) {
|
|||
eventBus.registerListener(QUERY_TOOL_EVENTS.CHANGE_EOL, (lineSep)=>{
|
||||
// Set the new EOL character in the editor.
|
||||
editor.current?.setEOL(lineSep);
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.QUERY_CHANGED, editor.current?.isDirty());
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.QUERY_CHANGED, editor.current?.isDirty());
|
||||
});
|
||||
|
||||
eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_TOGGLE_CASE, ()=>{
|
||||
|
|
@ -317,10 +340,24 @@ export default function Query({onTextSelect, setQtStatePartial}) {
|
|||
editor.current.setCursor(lastCursorPos.current.line, lastCursorPos.current.ch);
|
||||
}
|
||||
};
|
||||
|
||||
eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_LAST_FOCUS, lastFocus);
|
||||
setTimeout(()=>{
|
||||
(queryToolCtx.params.is_query_tool|| queryToolCtx.preferences.view_edit_promotion_warning) && editor.current.focus();
|
||||
}, 250);
|
||||
|
||||
eventBus.registerListener(QUERY_TOOL_EVENTS.WARN_RELOAD_FILE, warnReloadFile);
|
||||
|
||||
eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_SAVE_QUERY_TOOL_DATA, ()=>{
|
||||
setSaveQtData(true);
|
||||
});
|
||||
|
||||
eventBus.registerListener(QUERY_TOOL_EVENTS.LOAD_SQL_FROM_LOCAL_STORAGE, (sqlId)=>{
|
||||
let sqlValue = getToolData(sqlId);
|
||||
if (sqlValue) {
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.EDITOR_SET_SQL, sqlValue);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(()=>{
|
||||
|
|
@ -405,6 +442,11 @@ export default function Query({onTextSelect, setQtStatePartial}) {
|
|||
|
||||
const change = useCallback(()=>{
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.QUERY_CHANGED, editor.current.isDirty());
|
||||
|
||||
if(isSaveToolDataEnabled('sqleditor') && editor.current.isDirty()){
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_SAVE_QUERY_TOOL_DATA);
|
||||
}
|
||||
|
||||
if(!queryToolCtx.params.is_query_tool && editor.current.isDirty()){
|
||||
if(queryToolCtx.preferences.sqleditor.view_edit_promotion_warning){
|
||||
checkViewEditDataPromotion();
|
||||
|
|
@ -414,6 +456,15 @@ export default function Query({onTextSelect, setQtStatePartial}) {
|
|||
}
|
||||
}, []);
|
||||
|
||||
|
||||
const [saveQtData, setSaveQtData] = useState(false);
|
||||
useDelayDebounce(()=>{
|
||||
let connectionInfo = { ..._.find(queryToolCtx.connection_list, c => c.is_selected),
|
||||
'open_file_name':queryToolCtx.current_file, 'is_editor_dirty': editor.current.isDirty() };
|
||||
saveToolData('sqleditor', connectionInfo, queryToolCtx.params.trans_id, editor.current.getValue());
|
||||
setSaveQtData(false);
|
||||
}, saveQtData, 500);
|
||||
|
||||
const closePromotionWarning = (closeModal)=>{
|
||||
if(editor.current.isDirty()) {
|
||||
editor.current.execCommand('undo');
|
||||
|
|
|
|||
|
|
@ -9,9 +9,11 @@
|
|||
|
||||
import gettext from 'sources/gettext';
|
||||
import url_for from 'sources/url_for';
|
||||
import {getPanelTitle} from './sqleditor_title';
|
||||
import {getPanelTitle, getTitle} from './sqleditor_title';
|
||||
import {getRandomInt} from 'sources/utils';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import usePreferences from '../../../../preferences/static/js/store';
|
||||
import pgWindow from 'sources/window';
|
||||
|
||||
function hasDatabaseInformation(parentData) {
|
||||
return parentData.database;
|
||||
|
|
@ -112,6 +114,33 @@ export function showERDSqlTool(parentData, erdSqlId, queryToolTitle, queryToolMo
|
|||
launchQueryTool(queryToolMod, transId, gridUrl, queryToolTitle, {});
|
||||
}
|
||||
|
||||
export function relaunchSqlTool(connectionInfo, sqlId){
|
||||
let browserPref = usePreferences.getState().getPreferencesForModule('browser');
|
||||
let parentData = {
|
||||
server_group: {
|
||||
_id: connectionInfo.sgid || 0,
|
||||
},
|
||||
server: {
|
||||
_id: connectionInfo.sid,
|
||||
label: connectionInfo.server,
|
||||
},
|
||||
database: {
|
||||
_id: connectionInfo.did,
|
||||
label: connectionInfo.database_name,
|
||||
_label: connectionInfo.database_name,
|
||||
},
|
||||
};
|
||||
|
||||
const transId = getRandomInt(1, 9999999);
|
||||
const qtUrl = generateUrl(transId, parentData, null);
|
||||
const title = getTitle(pgAdmin, browserPref, parentData, false, connectionInfo.server_name, connectionInfo.database_name, connectionInfo.role || connectionInfo.user);
|
||||
launchQueryTool(pgWindow.pgAdmin.Tools.SQLEditor, transId, qtUrl, title, {
|
||||
sql_id: sqlId,
|
||||
...connectionInfo,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export function launchQueryTool(queryToolMod, transId, gridUrl, queryToolTitle, params) {
|
||||
let retVal = queryToolMod.launch(transId, gridUrl, true, queryToolTitle, params);
|
||||
|
||||
|
|
|
|||
|
|
@ -90,9 +90,10 @@ class PGUtilitiesMaintenanceFeatureTest(BaseFeatureTest):
|
|||
|
||||
def _open_maintenance_dialogue(self):
|
||||
if self.test_level == 'table':
|
||||
self.page.expand_tables_node("Server", self.server['name'],
|
||||
self.server['db_password'],
|
||||
self.database_name, 'public')
|
||||
self.assertTrue(self.page.expand_tables_node(
|
||||
"Server", self.server['name'], self.server['db_password'],
|
||||
self.database_name, 'public'),
|
||||
'Tree not expanded to the table node')
|
||||
|
||||
table_node = self.page.check_if_element_exists_with_scroll(
|
||||
TreeAreaLocators.table_node(self.table_name))
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@ class TableDdlFeatureTest(BaseFeatureTest):
|
|||
secrets.choice(range(1000, 3000)))
|
||||
test_utils.create_table(self.server, self.test_db,
|
||||
self.test_table_name)
|
||||
self.page.expand_tables_node("Server", self.server['name'],
|
||||
self.server['db_password'], self.test_db,
|
||||
'public')
|
||||
self.assertTrue(self.page.expand_tables_node(
|
||||
"Server", self.server['name'], self.server['db_password'],
|
||||
self.test_db,'public'), 'Tree not expanded to the table node.')
|
||||
table_node = self.page.check_if_element_exists_with_scroll(
|
||||
TreeAreaLocators.table_node(self.test_table_name))
|
||||
|
||||
|
|
|
|||
|
|
@ -83,9 +83,11 @@ class CopySQLFeatureTest(BaseFeatureTest):
|
|||
secrets.choice(range(1000, 3000)))
|
||||
test_utils.create_table(self.server, self.test_db,
|
||||
self.test_table_name)
|
||||
self.page.expand_tables_node("Server", self.server['name'],
|
||||
self.server['db_password'], self.test_db,
|
||||
'public')
|
||||
self.assertTrue(self.page.expand_tables_node(
|
||||
"Server", self.server['name'], self.server['db_password'],
|
||||
self.test_db, 'public'),
|
||||
'Tree not expanded to the table node.')
|
||||
|
||||
table_node = self.page.check_if_element_exists_with_scroll(
|
||||
TreeAreaLocators.table_node(self.test_table_name))
|
||||
table_node.click()
|
||||
|
|
|
|||
|
|
@ -115,10 +115,10 @@ CREATE TABLE public.nonintpkey
|
|||
try:
|
||||
self.page.wait_for_spinner_to_disappear()
|
||||
self.page.add_server(self.server)
|
||||
self.page.expand_tables_node("Server", self.server['name'],
|
||||
self.server['db_password'],
|
||||
self.test_db,
|
||||
'public')
|
||||
self.assertTrue(self.page.expand_tables_node(
|
||||
"Server", self.server['name'], self.server['db_password'],
|
||||
self.test_db, 'public'),
|
||||
'Tree not expanded to the table node.')
|
||||
|
||||
self._load_config_data('table_insert_update_cases')
|
||||
data_local = config_data
|
||||
|
|
|
|||
|
|
@ -91,9 +91,10 @@ class CheckForXssFeatureTest(BaseFeatureTest):
|
|||
self.server, self.test_db, self.test_table_name)
|
||||
|
||||
def _tables_node_expandable(self):
|
||||
self.page.expand_tables_node("Server", self.server['name'],
|
||||
self.server['db_password'], self.test_db,
|
||||
'public')
|
||||
self.assertTrue(self.page.expand_tables_node(
|
||||
"Server", self.server['name'],self.server['db_password'],
|
||||
self.test_db, 'public'),
|
||||
'Tree not expanded to the table node.')
|
||||
|
||||
table_node = self.page.check_if_element_exists_with_scroll(
|
||||
TreeAreaLocators.table_node(self.test_table_name))
|
||||
|
|
|
|||
|
|
@ -152,6 +152,12 @@ class PgadminPage:
|
|||
def open_query_tool(self):
|
||||
self.click_element(self.find_by_css_selector(
|
||||
"button[data-label='Tools']"))
|
||||
WebDriverWait(self.driver, 3).until(
|
||||
EC.visibility_of_element_located(
|
||||
(By.CSS_SELECTOR, "li[data-label='Query Tool']")))
|
||||
ActionChains(self.driver).move_to_element(
|
||||
self.driver.find_element(
|
||||
By.CSS_SELECTOR, "li[data-label='Query Tool']")).perform()
|
||||
self.click_element(self.find_by_css_selector(
|
||||
"li[data-label='Query Tool']"))
|
||||
|
||||
|
|
@ -502,7 +508,10 @@ class PgadminPage:
|
|||
TreeAreaLocators.server_connection_status_element(server_name))
|
||||
server_class = server_connection_status_element.get_attribute(
|
||||
'class')
|
||||
if server_class == 'icon-pg' or server_class == 'icon-ppas':
|
||||
print("(click_expand_server_node)"
|
||||
"server_class = " + str(server_class), file=sys.stderr)
|
||||
if (server_class.find('icon-pg') != -1 or
|
||||
server_class.find('icon-ppas') != -1):
|
||||
server_connected = True
|
||||
except Exception as e:
|
||||
print("There is some exception thrown in the function "
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import url_for from 'sources/url_for';
|
|||
import Theme from '../../../pgadmin/static/js/Theme';
|
||||
import SchemaDiffComponent from '../../../pgadmin/tools/schema_diff/static/js/components/SchemaDiffComponent';
|
||||
import SchemaDiff from '../../../pgadmin/tools/schema_diff/static/js/SchemaDiffModule';
|
||||
import { ApplicationStateProvider } from '../../../pgadmin/settings/static/ApplicationStateProvider';
|
||||
|
||||
|
||||
describe('Schema Diff Component', () => {
|
||||
|
|
@ -63,10 +64,12 @@ describe('Schema Diff Component', () => {
|
|||
await act(async ()=>{
|
||||
render(
|
||||
<Theme>
|
||||
<SchemaDiffComponent
|
||||
params={{ transId: params.transId, pgAdmin: pgWindow.pgAdmin }}
|
||||
>
|
||||
</SchemaDiffComponent>
|
||||
<ApplicationStateProvider>
|
||||
<SchemaDiffComponent
|
||||
params={{ transId: params.transId, pgAdmin: pgWindow.pgAdmin }}
|
||||
>
|
||||
</SchemaDiffComponent>
|
||||
</ApplicationStateProvider>
|
||||
</Theme>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -955,6 +955,25 @@ def configure_preferences(default_binary_path=None):
|
|||
('False', pref_breadcrumbs_enable.pid)
|
||||
)
|
||||
|
||||
# Disable workspace save feature
|
||||
misc_pref = Preferences.module('misc')
|
||||
save_app_state = misc_pref.preference('save_app_state')
|
||||
|
||||
user_pref = cur.execute(
|
||||
select_preference_query, (save_app_state.pid,)
|
||||
)
|
||||
|
||||
if len(user_pref.fetchall()) == 0:
|
||||
cur.execute(
|
||||
insert_preferences_query,
|
||||
(save_app_state.pid, 1, 'False')
|
||||
)
|
||||
else:
|
||||
cur.execute(
|
||||
update_preference_query,
|
||||
('False', save_app_state.pid)
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue