diff --git a/docs/en_US/release_notes_9_0.rst b/docs/en_US/release_notes_9_0.rst index 7580a8108..78cff77f3 100644 --- a/docs/en_US/release_notes_9_0.rst +++ b/docs/en_US/release_notes_9_0.rst @@ -20,6 +20,7 @@ Bundled PostgreSQL Utilities New features ************ + | `Issue #6513 `_ - Change button labels and color in delete confirmation dialog for all objects to improve UX. | `Issue #7708 `_ - Enhanced pgAdmin 4 with support for Workspace layouts. Housekeeping diff --git a/web/pgadmin/misc/workspaces/__init__.py b/web/pgadmin/misc/workspaces/__init__.py index 546757f71..f8a2e3f52 100644 --- a/web/pgadmin/misc/workspaces/__init__.py +++ b/web/pgadmin/misc/workspaces/__init__.py @@ -61,12 +61,27 @@ def index(): ) @pga_login_required def adhoc_connect_server(): - required_args = ['host', 'port', 'user'] + required_args = ['server_name', 'did'] data = request.form if request.form else json.loads( request.data ) + # Loop through data and if found any value is blank string then + # convert it to None as after porting into React, from frontend + # '' blank string is coming as a value instead of null. + for item in data: + if data[item] == '': + data[item] = None + + # Some fields can be provided with service file so they are optional + if 'service' in data and not data['service']: + required_args.extend([ + 'host', + 'port', + 'user' + ]) + for arg in required_args: if arg not in data: return make_json_response( @@ -95,24 +110,28 @@ def adhoc_connect_server(): data['connection_params'] = connection_params # Fetch all the new data in case of non-existing servers + new_host = data.get('host', None) + new_port = data.get('port', None) new_db = data.get('database_name', None) if new_db is None: new_db = data.get('did') new_username = data.get('user') new_role = data.get('role', None) new_server_name = data.get('server_name', None) + new_service = data.get('service', None) try: server = None if config.CONFIG_DATABASE_URI is not None and \ len(config.CONFIG_DATABASE_URI) > 0: # Filter out all the servers with the below combination. - servers = Server.query.filter_by(host=data['host'], - port=data['port'], + servers = Server.query.filter_by(host=new_host, + port=new_port, maintenance_db=new_db, username=new_username, name=new_server_name, - role=new_role + role=new_role, + service=new_service ).all() # If found matching servers then compare the connection_params as @@ -123,12 +142,13 @@ def adhoc_connect_server(): server = existing_server break else: - server = Server.query.filter_by(host=data['host'], - port=data['port'], + server = Server.query.filter_by(host=new_host, + port=new_port, maintenance_db=new_db, username=new_username, name=new_server_name, role=new_role, + service=new_service, connection_params=connection_params ).first() @@ -156,12 +176,12 @@ def adhoc_connect_server(): user_id=current_user.id, servergroup_id=data.get('gid', 1), name=new_server_name, - host=data.get('host', None), - port=data.get('port'), + host=new_host, + port=new_port, maintenance_db=new_db, username=new_username, role=new_role, - service=data.get('service', None), + service=new_service, connection_params=connection_params, is_adhoc=1 ) diff --git a/web/pgadmin/misc/workspaces/static/js/AdHocConnection.jsx b/web/pgadmin/misc/workspaces/static/js/AdHocConnection.jsx index 9cf9111f7..c5dae8318 100644 --- a/web/pgadmin/misc/workspaces/static/js/AdHocConnection.jsx +++ b/web/pgadmin/misc/workspaces/static/js/AdHocConnection.jsx @@ -28,6 +28,7 @@ import * as showQueryTool from '../../../../tools/sqleditor/static/js/show_query import { getTitle, generateTitle } from '../../../../tools/sqleditor/static/js/sqleditor_title'; import usePreferences from '../../../../preferences/static/js/store'; import { BROWSER_PANELS } from '../../../../browser/static/js/constants'; +import { isEmptyString } from '../../../../static/js/validators'; class AdHocConnectionSchema extends BaseUISchema { constructor(connectExistingServer, initValues={}) { @@ -183,11 +184,11 @@ class AdHocConnectionSchema extends BaseUISchema { deps: ['sid', 'connected'], disabled: (state) => state.sid, }, { - id: 'host', label: gettext('Host name/address'), type: 'text', noEmpty: true, + id: 'host', label: gettext('Host name/address'), type: 'text', deps: ['sid', 'connected'], disabled: (state) => state.sid, }, { - id: 'port', label: gettext('Port'), type: 'int', min: 1, max: 65535, noEmpty: true, + id: 'port', label: gettext('Port'), type: 'int', min: 1, max: 65535, deps: ['sid', 'connected'], disabled: (state) => state.sid, },{ @@ -215,7 +216,7 @@ class AdHocConnectionSchema extends BaseUISchema { } }, { id: 'user', label: gettext('User'), deps: ['sid', 'connected'], - noEmpty: true, controlProps: {creatable: true}, + controlProps: {creatable: true}, type: (state) => { if (state?.sid) { return { @@ -261,8 +262,54 @@ class AdHocConnectionSchema extends BaseUISchema { } ]; } -} + validate(state, setError) { + let errmsg = null; + + if (isEmptyString(state.service)) { + errmsg = gettext('Either Host name or Service must be specified.'); + if(isEmptyString(state.host)) { + setError('host', errmsg); + return true; + } else { + setError('host', null); + } + + /* Hostname, IP address validate */ + if (state.host) { + // Check for leading and trailing spaces. + if (/(^\s)|(\s$)/.test(state.host)){ + errmsg = gettext('Host name must be valid hostname or IPv4 or IPv6 address.'); + setError('host', errmsg); + return true; + } else { + setError('host', null); + } + } + + if(isEmptyString(state.username)) { + errmsg = gettext('Username must be specified.'); + setError('username', errmsg); + return true; + } else { + setError('username', null); + } + + if(isEmptyString(state.port)) { + errmsg = gettext('Port must be specified.'); + setError('port', errmsg); + return true; + } else { + setError('port', null); + } + } else { + _.each(['host', 'db', 'username', 'port'], (item) => { + setError(item, null); + }); + } + return false; + } +} export default function AdHocConnection({mode}) { const [connecting, setConnecting] = useState(false); @@ -313,6 +360,7 @@ export default function AdHocConnection({mode}) { const openQueryTool = (respData, formData)=>{ const transId = commonUtils.getRandomInt(1, 9999999); let db_name = _.isNil(formData.database_name) ? formData.did : formData.database_name; + let user_name = formData.role || formData.user || respData.data.user.name; let parentData = { server_group: {_id: 1}, @@ -328,7 +376,7 @@ export default function AdHocConnection({mode}) { }; const gridUrl = showQueryTool.generateUrl(transId, parentData, null); - const title = getTitle(pgAdmin, preferencesStore.getPreferencesForModule('browser'), null, false, formData.server_name, db_name, formData.role || formData.user); + const title = getTitle(pgAdmin, preferencesStore.getPreferencesForModule('browser'), null, false, formData.server_name, db_name, user_name); showQueryTool.launchQueryTool(pgWindow.pgAdmin.Tools.SQLEditor, transId, gridUrl, title, { user: formData.user, role: formData.role, @@ -338,12 +386,13 @@ export default function AdHocConnection({mode}) { const openPSQLTool = (respData, formData)=> { const transId = commonUtils.getRandomInt(1, 9999999); let db_name = _.isNil(formData.database_name) ? formData.did : formData.database_name; + let user_name = formData.role || formData.user || respData.data.user.name; let panelTitle = ''; // Set psql tab title as per prefrences setting. let title_data = { 'database': db_name ? _.unescape(db_name) : 'postgres' , - 'username': formData.user, + 'username': user_name, 'server': formData.server_name, 'type': 'psql_tool', }; diff --git a/web/pgadmin/misc/workspaces/static/js/WorkspaceProvider.jsx b/web/pgadmin/misc/workspaces/static/js/WorkspaceProvider.jsx index 4f39ca7b4..319f666e7 100644 --- a/web/pgadmin/misc/workspaces/static/js/WorkspaceProvider.jsx +++ b/web/pgadmin/misc/workspaces/static/js/WorkspaceProvider.jsx @@ -76,7 +76,8 @@ export function WorkspaceProvider({children}) { const hasOpenTabs = (forWs)=>{ const wsConfig = config.find((i)=>i.workspace == forWs); if(wsConfig) { - return Boolean(pgAdmin.Browser.docker[wsConfig.docker]?.layoutObj?.getRootElement().querySelector('.dock-tab')); + //return Boolean(pgAdmin.Browser.docker[wsConfig.docker]?.layoutObj?.getRootElement().querySelector('.dock-tab')); + return Boolean(pgAdmin.Browser.docker[wsConfig.docker]?.layoutObj?.getLayout()?.dockbox?.children?.[0]?.tabs?.length); } return true; }; diff --git a/web/pgadmin/misc/workspaces/static/js/WorkspaceToolbar.jsx b/web/pgadmin/misc/workspaces/static/js/WorkspaceToolbar.jsx index ac63c0783..4c99c835d 100644 --- a/web/pgadmin/misc/workspaces/static/js/WorkspaceToolbar.jsx +++ b/web/pgadmin/misc/workspaces/static/js/WorkspaceToolbar.jsx @@ -55,9 +55,11 @@ function WorkspaceButton({menuItem, value, ...props}) { setDisabled(!hasOpenTabs(value)); }); const deregChange = layout.eventBus.registerListener(LAYOUT_EVENTS.CHANGE, ()=>{ + console.log(layout); setDisabled(!hasOpenTabs(value)); }); const deregRemove = layout.eventBus.registerListener(LAYOUT_EVENTS.REMOVE, ()=>{ + console.log(layout); setDisabled(!hasOpenTabs(value)); });