Compare commits

...
36 Commits
Author SHA1 Message Date
Akshay Joshi a144815201 Fixed an issue where migration on external database is not working. #5941 2023-03-20 16:18:21 +05:30
Akshay Joshi c6220bd8e2 Fixed an issue where the file name in the query tool tab was not changing if another file is opened. #5909 2023-03-08 16:59:36 +05:30
Akshay Joshi bff425f49e Hardcoded NWjs version to 0.72.0 as the latest version throws error on CentOS 7 2023-03-06 15:39:18 +05:30
Akshay Joshi 843925e5bc Ensure that the database migration does not fail with a NoSuchTableError exception. #5870 2023-03-06 15:36:09 +05:30
Akshay Joshi 18ee965da6 Update version for release v6.21 2023-03-01 16:56:06 +05:30
Akshay Joshi ca534aaabc Update Dependencies. 2023-03-01 16:55:17 +05:30
Akshay Joshi f7acbad684 Update release note. 2023-03-01 14:13:03 +05:30
Dave Page d0c53e1621 Add missing import. 2023-03-01 13:44:07 +05:30
Akshay Joshi c75f72e6e2 Fixed the RESQL test cases for Sequences. #5810 2023-03-01 13:43:46 +05:30
Akshay Joshi 6fa5d1f061 Fixed an issue where the database server is not connected using a service file. #5889 2023-03-01 13:41:16 +05:30
Nikhil Mohite 12c44cd0fa Fixed schema diff UI alignment issues. 2023-03-01 13:39:31 +05:30
Akshay Joshi 623204d062 Update release note 2023-03-01 13:39:31 +05:30
Aditya Toshniwal 004e777032 Fix an issue where sequence owner is remove on sequence edit. #5810 2023-03-01 13:39:27 +05:30
Aditya Toshniwal 0e9d59fe51 Fix an issue where query tool is stripping spaces in grid cell. #5803 2023-03-01 13:33:22 +05:30
Akshay Joshi 919d973ec0 Fixed an issue where the user can't create trigger AFTER UPDATE OF. #5790 2023-03-01 13:33:22 +05:30
Aditya Toshniwal e86363bbc0 Do not allow to save invalid JSON in query tool JSON editor. #5822 2023-03-01 13:33:19 +05:30
Aditya Toshniwal feaa47497c Handle MERGE operation in query tool explain introduced in PostgreSQL 15. #5872 2023-03-01 13:27:22 +05:30
Akshay Joshi a4451fead8 Update release note 2023-03-01 13:27:22 +05:30
Pravesh Sharma 5e8d99fbbd Fixed an issue where query was not pasted to editor after trojan source warning. #5760 2023-03-01 13:27:22 +05:30
Aditya Toshniwal 45b7abd267 Few dashboard graph improvements. 2023-03-01 13:27:18 +05:30
Akshay Joshi 724fa361ea Ensure that content on the DDL comparison panel should get refreshed on selecting the object using the up and down arrow keys. #5747 2023-03-01 12:39:32 +05:30
Anil Sahoo eb68176ee1 Fixed an issue where Clear Saved Password should be disabled if the password is already cleared. #5773 2023-03-01 12:39:26 +05:30
Akshay Joshi 65ede27a38 Update release note 2023-03-01 12:34:47 +05:30
Pravesh Sharma 92c9d757b2 Fix for query tool prompting for unsaved changes even if no changes have been made. #5756 2023-03-01 12:34:47 +05:30
Akshay Joshi 465106206b Ensure that the schema diff tool should pick up the change in the column grants. #5269 2023-03-01 12:34:47 +05:30
Akshay Joshi 81dc2161cb Fixed an issue where pgAdmin failed to connect when the Postgres password included special characters. #5847 2023-03-01 12:34:42 +05:30
Yogesh Mahajan ae1eca5b3f Removed logging added for feature test failure. Added more assets to avoid false failures. 2023-03-01 12:28:57 +05:30
Paul Milbank c599edb342 Add additional logging for successful logins and user creation. #5842 2023-03-01 12:28:57 +05:30
Anil Sahoo 4d03fbd34f Fix an issue where the maintenance dialog for Materialized View gives an error. #5764 2023-03-01 12:28:57 +05:30
Yogesh Mahajan b2b52b2f66 Added logging for feature test failure on jenkins job. 2023-03-01 12:28:57 +05:30
Akshay Joshi 23c3a27201 Correct the OR operator. #5758 2023-03-01 12:28:57 +05:30
Akshay Joshi c8356f2bfd Update release note 2023-03-01 12:28:57 +05:30
Pravesh Sharma b5e7e7848c Fixed an issue where lock layout menu was not in sync with preferences. #5758 2023-03-01 12:28:57 +05:30
Aditya Toshniwal f58dbf36bb Allow changing cardinality notation in ERD to use Chen notation. #5832 2023-03-01 12:28:56 +05:30
Akshay Joshi 57431ca754 Ensure that Grant column permission to a view is visible in the SQL tab. #5685 2023-03-01 12:28:56 +05:30
Dave Page ab62af0d55 Update copyright notices in Dockerfile and Makefile and ensure they don't get missed again. 2023-03-01 12:28:56 +05:30
60 changed files with 466 additions and 205 deletions

No files matched your search

+3 -4
View File
@@ -61,8 +61,8 @@ requests 2.25.1
pyotp 2.8.0 MIT License https://github.com/pyotp/pyotp
qrcode 7.4.2 BSD https://github.com/lincolnloop/python-qrcode
Pillow 9.4.0 HPND https://python-pillow.org
boto3 1.26.64 Apache License 2.0 https://github.com/boto/boto3
botocore 1.29.64 Apache License 2.0 https://github.com/boto/botocore
boto3 1.26.71 Apache License 2.0 https://github.com/boto/boto3
botocore 1.29.71 Apache License 2.0 https://github.com/boto/botocore
urllib3 1.26.14 MIT https://urllib3.readthedocs.io/
Werkzeug 2.1.2 BSD-3-Clause https://palletsprojects.com/p/werkzeug/
azure-mgmt-rdbms 10.1.0 MIT License https://github.com/Azure/azure-sdk-for-python
@@ -315,7 +315,6 @@ json-schema-traverse 1.0.0
json-schema-traverse 0.4.1 MIT git+https://github.com/epoberezkin/json-schema-traverse.git
json-source-map 0.6.1 MIT git+https://github.com/epoberezkin/json-source-map.git
json5 2.2.3 MIT git+https://github.com/json5/json5.git
jsoneditor-react 3.1.2 MIT git+https://github.com/vankop/jsoneditor-react.git
jss 10.9.2 MIT https://github.com/cssinjs/jss.git
jss-plugin-camel-case 10.9.2 MIT https://github.com/cssinjs/jss.git
jss-plugin-default-unit 10.9.2 MIT https://github.com/cssinjs/jss.git
@@ -573,5 +572,5 @@ tslib 2.3.1
type-fest 1.4.0 (MIT OR CC0-1.0) https://github.com/sindresorhus/type-fest.git
webcabin-docker 2.2.5 MIT* Unknown
492 dependencies listed.
491 dependencies listed.
+1 -1
View File
@@ -2,7 +2,7 @@
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# Copyright (C) 2013 - 2023, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
#########################################################################
+10 -4
View File
@@ -274,10 +274,16 @@ REM Main build sequence Ends
REM YARN END
REM WGET
FOR /f "tokens=2 delims='" %%i IN ('yarn info nw ^| findstr "latest: "') DO SET "NW_VERSION=%%i"
:GET_NW
wget https://dl.nwjs.io/v%NW_VERSION%/nwjs-v%NW_VERSION%-win-x64.zip -O "%TMPDIR%\nwjs-v%NW_VERSION%-win-x64.zip"
IF %ERRORLEVEL% NEQ 0 GOTO GET_NW
REM Comment out the below for loop as the latest version having
REM some problem to launch on CentOS 7, so for
REM the time being hardcoded the version to 0.72.0
REM FOR /f "tokens=2 delims='" %%i IN ('yarn info nw ^| findstr "latest: "') DO SET "NW_VERSION=%%i"
REM :GET_NW
REM wget https://dl.nwjs.io/v%NW_VERSION%/nwjs-v%NW_VERSION%-win-x64.zip -O "%TMPDIR%\nwjs-v%NW_VERSION%-win-x64.zip"
REM IF %ERRORLEVEL% NEQ 0 GOTO GET_NW
SET "NW_VERSION=0.72.0"
wget https://dl.nwjs.io/v%NW_VERSION%/nwjs-v%NW_VERSION%-win-x64.zip -O "%TMPDIR%\nwjs-v%NW_VERSION%-win-x64.zip"
tar -C "%TMPDIR%" -xvf "%TMPDIR%\nwjs-v%NW_VERSION%-win-x64.zip" || EXIT /B 1
REM WGET END
+1 -1
View File
@@ -2,7 +2,7 @@
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# Copyright (C) 2013 - 2023, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
#########################################################################
+15 -12
View File
@@ -135,18 +135,21 @@ Utility Options
:class: longtable
:widths: 1 4 1
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| Icon | Behavior | Shortcut |
+======================+===================================================================================================+================+
| *Add/Edit note* | Click this button to make notes on tables nodes while designing the database. | Option/Alt + |
| | | Ctrl + N |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| *Auto align* | Click this button to auto align all tables and links to make it look more cleaner. | Option/Alt + |
| | | Ctrl + L |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| *Show details* | Click this button to toggle the column details visibility. It allows you to show few or more | Option/Alt + |
| | column details. | Shift + D |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
+-------------------------+------------------------------------------------------------------------------------------------+----------------+
| Icon | Behavior | Shortcut |
+=========================+================================================================================================+================+
| *Add/Edit note* | Click this button to make notes on tables nodes while designing the database. | Option/Alt + |
| | | Ctrl + N |
+-------------------------+------------------------------------------------------------------------------------------------+----------------+
| *Auto align* | Click this button to auto align all tables and links to make it look more cleaner. | Option/Alt + |
| | | Ctrl + L |
+-------------------------+------------------------------------------------------------------------------------------------+----------------+
| *Show details* | Click this button to toggle the column details visibility. It allows you to show few or more | Option/Alt + |
| | column details. | Shift + D |
+-------------------------+------------------------------------------------------------------------------------------------+----------------+
| *Cardinality Notation* | Change the cardinality notation format used to present relationship links. Options available | |
| | are - Crow's Foot Notation and Chen Notation. | |
+-------------------------+------------------------------------------------------------------------------------------------+----------------+
Zoom Options
************
Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 22 KiB

+1
View File
@@ -11,6 +11,7 @@ notes for it.
.. toctree::
:maxdepth: 1
release_notes_6_21
release_notes_6_20
release_notes_6_19
release_notes_6_18
+49
View File
@@ -0,0 +1,49 @@
************
Version 6.21
************
Release date: 2023-03-09
This release contains a number of bug fixes and new features since the release of pgAdmin 4 v6.20.
Supported Database Servers
**************************
**PostgreSQL**: 10, 11, 12, 13, 14 and 15
**EDB Advanced Server**: 10, 11, 12, 13, 14 and 15
Bundled PostgreSQL Utilities
****************************
**psql**, **pg_dump**, **pg_dumpall**, **pg_restore**: 15.1
New features
************
| `Issue #5832 <https://github.com/pgadmin-org/pgadmin4/issues/5832>`_ - Allow changing cardinality notation in ERD to use Chen notation.
| `Issue #5842 <https://github.com/pgadmin-org/pgadmin4/issues/5842>`_ - Add additional logging for successful logins and user creation.
Housekeeping
************
Bug fixes
*********
| `Issue #5269 <https://github.com/pgadmin-org/pgadmin4/issues/5269>`_ - Ensure that the schema diff tool should pick up the change in the column grants.
| `Issue #5685 <https://github.com/pgadmin-org/pgadmin4/issues/5685>`_ - Ensure that Grant column permission to a view is visible in the SQL tab.
| `Issue #5747 <https://github.com/pgadmin-org/pgadmin4/issues/5747>`_ - Ensure that content on the DDL comparison panel should get refreshed on selecting the object using the up and down arrow keys.
| `Issue #5756 <https://github.com/pgadmin-org/pgadmin4/issues/5756>`_ - Fix for query tool prompting for unsaved changes even if no changes have been made.
| `Issue #5758 <https://github.com/pgadmin-org/pgadmin4/issues/5758>`_ - Fixed an issue where lock layout menu was not in sync with preferences.
| `Issue #5760 <https://github.com/pgadmin-org/pgadmin4/issues/5760>`_ - Fixed an issue where query was not pasted to editor after trojan source warning.
| `Issue #5764 <https://github.com/pgadmin-org/pgadmin4/issues/5764>`_ - Fix an issue where the maintenance dialog for Materialized View gives an error.
| `Issue #5773 <https://github.com/pgadmin-org/pgadmin4/issues/5773>`_ - Fixed an issue where Clear Saved Password should be disabled if the password is already cleared.
| `Issue #5790 <https://github.com/pgadmin-org/pgadmin4/issues/5790>`_ - Fixed an issue where the user can't create trigger AFTER UPDATE OF.
| `Issue #5803 <https://github.com/pgadmin-org/pgadmin4/issues/5803>`_ - Fix an issue where query tool is stripping spaces in grid cell.
| `Issue #5810 <https://github.com/pgadmin-org/pgadmin4/issues/5810>`_ - Fix an issue where sequence owner is remove on sequence edit.
| `Issue #5822 <https://github.com/pgadmin-org/pgadmin4/issues/5822>`_ - Do not allow to save invalid JSON in query tool JSON editor.
| `Issue #5847 <https://github.com/pgadmin-org/pgadmin4/issues/5847>`_ - Fixed an issue where pgAdmin failed to connect when the Postgres password included special characters.
| `Issue #5870 <https://github.com/pgadmin-org/pgadmin4/issues/5870>`_ - Ensure that the database migration does not fail with a NoSuchTableError exception.
| `Issue #5872 <https://github.com/pgadmin-org/pgadmin4/issues/5872>`_ - Handle MERGE operation in query tool explain introduced in PostgreSQL 15.
| `Issue #5889 <https://github.com/pgadmin-org/pgadmin4/issues/5889>`_ - Fixed an issue where the database server is not connected using a service file.
| `Issue #5909 <https://github.com/pgadmin-org/pgadmin4/issues/5909>`_ - Fixed an issue where the file name in the query tool tab was not changing if another file is opened.
+5 -1
View File
@@ -130,7 +130,11 @@ _build_runtime() {
# YARN END
# WGET:
NW_VERSION=$(yarn info nw | grep latest | awk -F "'" '{ print $2}')
# Comment out the below line as the latest version (0.73.0) having some
# problem in launching the pgAdmin 4 in CentOS 7
# hardcoded the version to 0.72.0
# NW_VERSION=$(yarn info nw | grep latest | awk -F "'" '{ print $2}')
NW_VERSION="0.72.0"
pushd "${BUILDROOT}" > /dev/null || exit
while true;do
+5 -1
View File
@@ -34,7 +34,11 @@ _build_runtime() {
# YARN END
# WGET:
NW_VERSION=$(yarn info nw | grep latest | awk -F "'" '{ print $2}')
# Comment out the below line as the latest version (0.73.0) having some
# problem in launching the pgAdmin 4 in CentOS 7
# hardcoded the version to 0.72.0
# NW_VERSION=$(yarn info nw | grep latest | awk -F "'" '{ print $2}')
NW_VERSION="0.72.0"
pushd "${BUILD_ROOT}" > /dev/null || exit
while true;do
+1 -1
View File
@@ -25,7 +25,7 @@
},
"devDependencies": {
"eslint": "^6.3.0",
"nw": "^0.70.0"
"nw": "0.72.0"
},
"scripts": {
"linter": "yarn eslint --no-eslintrc -c .eslintrc.js --ext .js ."
+4 -4
View File
@@ -1008,10 +1008,10 @@ number-is-nan@^1.0.0:
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==
nw@^0.70.0:
version "0.70.1"
resolved "https://registry.yarnpkg.com/nw/-/nw-0.70.1.tgz#592bd715097970090440183974d79ae5005ad377"
integrity sha512-q0zyS04L+d8vzxC5jCnfouu1G0NR3PTAuTT9x4GY25i14TFk+7H8wQmrZCV9MUvEWK7m/NkjcfxRmHkFqog7zw==
nw@0.72.0:
version "0.72.0"
resolved "https://registry.yarnpkg.com/nw/-/nw-0.72.0.tgz#b8d340c165756b2f07320e999f5aaa486131e38f"
integrity sha512-uMLsvXe3cDJ+YY8Jcd+YoLg7ggqJAbpTskNx0X21Z6gn9oWWnte9eJsWNC/9J5itg3TzzqyvIyUfXsMGekqPmw==
dependencies:
chalk "~1.1.3"
decompress "^4.2.0"
+1 -1
View File
@@ -20,7 +20,7 @@ import codecs
ALLOWED_FILE_EXTENSIONS = (
".py", ".js", ".sql", ".cpp", ".h", ".rc", ".am", ".wsgi", ".pro",
".plist", ".rst", ".sh", ".in", ".mako", ".ini", ".jsx", ".rtf", ".rst",
"LICENSE", ".ts", ".tsx"
"LICENSE", "Dockerfile", "Makefile", ".ts", ".tsx"
)
EXCLUDE_DIR = ("node_modules")
+2 -2
View File
@@ -43,7 +43,7 @@ APP_ICON = 'pg-icon'
# Application version number components
APP_RELEASE = 6
APP_REVISION = 20
APP_REVISION = 21
# Application version suffix, e.g. 'beta1', 'dev'. Usually an empty string
# for GA releases.
@@ -52,7 +52,7 @@ APP_SUFFIX = ''
# Numeric application version for upgrade checks. Should be in the format:
# [X]XYYZZ, where X is the release version, Y is the revision, with a leading
# zero if needed, and Z represents the suffix, with a leading zero if needed
APP_VERSION_INT = 62000
APP_VERSION_INT = 62100
# DO NOT CHANGE!
# The application version string, constructed from the components
+20 -1
View File
@@ -14,7 +14,7 @@ Create Date: 2023-01-02 14:52:48.109290
"""
import sqlalchemy as sa
from alembic import op
from alembic import op, context
# revision identifiers, used by Alembic.
@@ -32,6 +32,25 @@ def migrate_connection_params(table_name):
op.add_column(table_name,
sa.Column('connection_params', sa.JSON()))
# Fixed the NoSuchTableError exception when migrating from v6.2 and below
# to directly v6.16 and above. Below statements has been removed from
# migration file 3ce25f562f3b_.py as a part of using the alembic code
# instead of SQL queries directly.
if context.get_impl().bind.dialect.name == "sqlite":
try:
# Rename user table to user_old and again user_old to user to
# change the foreign key reference of user_old table which is not
# exists
op.execute("ALTER TABLE \"user\" RENAME TO user_old")
op.execute("ALTER TABLE user_old RENAME TO \"user\"")
# Rename user table to server_old and again server_old to server to
# change the foreign key reference of user_old table which is not
# exists.
op.execute("ALTER TABLE server RENAME TO server_old")
op.execute("ALTER TABLE server_old RENAME TO server")
except Exception as _:
pass
# define table representation
meta = sa.MetaData(bind=op.get_bind())
meta.reflect(only=(table_name,))
-1
View File
@@ -124,7 +124,6 @@
"jquery": "^3.6.0",
"json-bignumber": "^1.0.1",
"jsoneditor": "^9.5.4",
"jsoneditor-react": "^3.1.1",
"karma-coverage": "^2.0.3",
"leaflet": "^1.5.1",
"lodash": "4.*",
+2
View File
@@ -78,6 +78,8 @@ class BaseAuthentication(metaclass=AuthSourceRegistry):
if not status:
current_app.logger.exception(self.messages('LOGIN_FAILED'))
return False, self.messages('LOGIN_FAILED')
current_app.logger.info(
"Internal user {0} logged in.".format(username))
return True, None
def messages(self, msg_key):
+4
View File
@@ -273,6 +273,10 @@ class KerberosAuthentication(BaseAuthentication):
user = User.query.filter_by(
username=username, auth_source=KERBEROS).first()
if user is None:
create_msg = ("Creating user {0} with email {1} "
"from auth source KERBEROS.")
current_app.logger.info(create_msg.format(username,
username))
return create_user({
'username': username,
'email': username,
+6
View File
@@ -175,6 +175,8 @@ class LDAPAuthentication(BaseAuthentication):
if not status:
current_app.logger.exception(self.messages('LOGIN_FAILED'))
return False, self.messages('LOGIN_FAILED')
current_app.logger.info(
"LDAP user {0} logged in.".format(user))
return True, None
def __auto_create_user(self, user_email):
@@ -188,6 +190,10 @@ class LDAPAuthentication(BaseAuthentication):
self.username)).first()
if user is None:
create_msg = ("Creating user {0} with email {1} "
"from auth source LDAP.")
current_app.logger.info(create_msg.format(self.username,
user_email))
return create_user({
'username': self.username,
'email': user_email,
+6
View File
@@ -157,6 +157,8 @@ class OAuth2Authentication(BaseAuthentication):
username=username, auth_source=OAUTH2).first()
current_app.login_manager.logout_view = \
OAuth2Authentication.LOGOUT_VIEW
current_app.logger.info(
"OAUTH2 user {0} logged in.".format(username))
return login_user(user), None
return False, msg
@@ -189,6 +191,10 @@ class OAuth2Authentication(BaseAuthentication):
user = User.query.filter_by(username=username,
auth_source=OAUTH2).first()
if not user:
create_msg = ("Creating user {0} with email {1} "
"from auth source OAUTH2.")
current_app.logger.info(create_msg.format(username,
email))
return create_user({
'username': username,
'email': email,
+6
View File
@@ -104,6 +104,8 @@ class WebserverAuthentication(BaseAuthentication):
if not status:
current_app.logger.exception(self.messages('LOGIN_FAILED'))
return False, self.messages('LOGIN_FAILED')
current_app.logger.info(
"Webserver user {0} logged in.".format(username))
return True, None
return False, self.messages('LOGIN_FAILED')
@@ -112,6 +114,10 @@ class WebserverAuthentication(BaseAuthentication):
if config.WEBSERVER_AUTO_CREATE_USER:
user = User.query.filter_by(username=username).first()
if not user:
create_msg = ("Creating user {0} with email {1} "
"from auth source Webserver.")
current_app.logger.info(create_msg.format(username,
useremail))
return create_user({
'username': username,
'email': useremail,
+4 -1
View File
@@ -566,6 +566,8 @@ def utils():
# This will be opposite of use_space option
editor_indent_with_tabs = False if editor_use_spaces else True
prefs = Preferences.module('browser')
current_ui_lock = prefs.preference('lock_layout').get()
# Try to fetch current libpq version from the driver
try:
from config import PG_DEFAULT_DRIVER
@@ -628,7 +630,8 @@ def utils():
username=current_user.username,
auth_source=auth_source,
heartbeat_timeout=config.SERVER_HEARTBEAT_TIMEOUT,
password_length_min=config.PASSWORD_LENGTH_MIN
password_length_min=config.PASSWORD_LENGTH_MIN,
current_ui_lock=current_ui_lock
),
200, {'Content-Type': MIMETYPE_APP_JS})
@@ -51,6 +51,13 @@ export class OwnedBySchema extends BaseUISchema {
optionsReloadBasis: state.owned_table,
};
},
depChange: (state)=>{
if(!state.owned_table) {
return {
owned_column: null,
};
}
}
}
];
}
@@ -11,7 +11,7 @@ ALTER SEQUENCE IF EXISTS {{ conn|qtIdent(o_data.schema, data.name) }}
OWNER TO {{ conn|qtIdent(data.seqowner) }};
{% endif %}
{% if (data.owned_table == None or data.owned_table is not defined) and (o_data.owned_table is defined and o_data.owned_table != None) and (data.owned_column == None or data.owned_column is not defined) %}
{% if (data.owned_table == None) and (data.owned_column == None) %}
ALTER SEQUENCE IF EXISTS {{ conn|qtIdent(o_data.schema, data.name) }}
OWNED BY NONE;
{% elif (data.owned_table is defined or data.owned_column is defined) and (data.owned_table != o_data.owned_table or data.owned_column != o_data.owned_column) %}
@@ -7,7 +7,8 @@ CREATE SEQUENCE IF NOT EXISTS public."Seq1_$%{}[]()&*^!@""'`\/#"
START 5
MINVALUE 5
MAXVALUE 900
CACHE 1;
CACHE 1
OWNED BY tableforownedby.col2;
ALTER SEQUENCE public."Seq1_$%{}[]()&*^!@""'`\/#"
OWNER TO postgres;
@@ -1,4 +1,2 @@
ALTER SEQUENCE IF EXISTS public."Seq1_$%{}[]()&*^!@""'`\/#"
OWNED BY NONE;
ALTER SEQUENCE IF EXISTS public."Seq1_$%{}[]()&*^!@""'`\/#"
MAXVALUE 900;
@@ -7,7 +7,8 @@ CREATE SEQUENCE IF NOT EXISTS public."Seq1_$%{}[]()&*^!@""'`\/#"
START 5
MINVALUE 5
MAXVALUE 900
CACHE 1;
CACHE 1
OWNED BY tableforownedby.col2;
ALTER SEQUENCE public."Seq1_$%{}[]()&*^!@""'`\/#"
OWNER TO enterprisedb;
@@ -1,4 +1,2 @@
ALTER SEQUENCE IF EXISTS public."Seq1_$%{}[]()&*^!@""'`\/#"
OWNED BY NONE;
ALTER SEQUENCE IF EXISTS public."Seq1_$%{}[]()&*^!@""'`\/#"
MAXVALUE 900;
@@ -176,6 +176,20 @@ class SchemaDiffTableCompare(SchemaDiffObjectCompare):
source['attnum'] = tmp['attnum']
if tmp and source != tmp:
# check column level grants
acl_dict = dict()
if 'attacl' in source and 'attacl' in tmp and \
source['attacl'] != tmp['attacl']:
if len(source['attacl']) > 0 and len(tmp['attacl']) < 1:
acl_dict['added'] = source['attacl'].copy()
source['attacl'] = acl_dict
elif len(source['attacl']) < 1 and len(tmp['attacl']) > 0:
acl_dict['deleted'] = tmp['attacl'].copy()
source['attacl'] = acl_dict
else:
acl_dict['changed'] = source['attacl'].copy()
source['attacl'] = acl_dict
updated.append(source)
target_cols.remove(tmp)
elif tmp and source == tmp:
@@ -181,10 +181,6 @@ export default class TriggerSchema extends BaseUISchema {
flag = (state.evnt_update && (_.size(state.columns) >= 1 && state.columns[0] != ''));
}
if(flag && state.name) {
state.name = null;
}
return flag;
}
@@ -252,6 +252,14 @@ def check_precondition(f):
self.column_template_path = 'columns/sql/#{0}#'.format(
self.manager.version)
try:
self.allowed_acls = render_template(
"/".join([self.template_path, self._ALLOWED_PRIVS_JSON])
)
self.allowed_acls = json.loads(self.allowed_acls, encoding='utf-8')
except Exception as e:
current_app.logger.exception(e)
return f(*args, **kwargs)
return wrap
@@ -370,6 +378,7 @@ class ViewNode(PGChildNodeView, VacuumSettings, SchemaDiffObjectCompare):
self.conn = None
self.template_path = None
self.template_initial = 'views'
self.allowed_acls = []
@staticmethod
def ppas_template_path(ver):
@@ -793,22 +802,15 @@ class ViewNode(PGChildNodeView, VacuumSettings, SchemaDiffObjectCompare):
ViewNode._get_info_from_data(data, res)
try:
acls = render_template(
"/".join([self.template_path, self._ALLOWED_PRIVS_JSON])
)
acls = json.loads(acls, encoding='utf-8')
except Exception as e:
current_app.logger.exception(e)
# Privileges
ViewNode._parse_privilege_data(acls, data)
ViewNode._parse_privilege_data(self.allowed_acls, data)
data['del_sql'] = False
old_data['acl_sql'] = ''
is_error, errmsg = self._get_definition_data(vid, data, old_data,
res, acls)
res,
self.allowed_acls)
if is_error:
return None, errmsg
@@ -866,17 +868,8 @@ class ViewNode(PGChildNodeView, VacuumSettings, SchemaDiffObjectCompare):
if 'schema' in data and isinstance(data['schema'], int):
data['schema'] = self._get_schema(data['schema'])
acls = []
try:
acls = render_template(
"/".join([self.template_path, self._ALLOWED_PRIVS_JSON])
)
acls = json.loads(acls, encoding='utf-8')
except Exception as e:
current_app.logger.exception(e)
# Privileges
ViewNode._parse_priv_data(acls, data)
ViewNode._parse_priv_data(self.allowed_acls, data)
sql = render_template("/".join(
[self.template_path, self._SQL_PREFIX + self._CREATE_SQL]),
@@ -1361,6 +1354,43 @@ class ViewNode(PGChildNodeView, VacuumSettings, SchemaDiffObjectCompare):
self._UPDATE_SQL.format(self.manager.version)]),
o_data=o_data, data=res, is_view_only=True)
sql_data += SQL
# Get Column Grant SQL
for rows in data['rows']:
res = {
'name': rows['name'],
'table': rows['relname'],
'schema': self.view_schema
}
if 'attacl' in rows and rows['attacl']:
# We need to parse & convert ACL coming from database to json
# format
SQL = render_template(
"/".join([self.column_template_path, 'acl.sql']),
tid=vid, clid=rows['attnum'])
status, acl = self.conn.execute_dict(SQL)
if not status:
return internal_server_error(errormsg=acl)
allowed_acls = []
if self.allowed_acls and 'datacl' in self.allowed_acls and \
'acl' in self.allowed_acls['datacl']:
allowed_acls = self.allowed_acls['datacl']['acl']
for row in acl['rows']:
priv = parse_priv_from_db(row)
res.setdefault(row['deftype'], []).append(priv)
res[row['deftype']] = \
parse_priv_to_db(res[row['deftype']], allowed_acls)
grant_sql = render_template("/".join(
[self.template_path, self._SQL_PREFIX + self._GRANT_SQL]),
data=res)
sql_data += grant_sql
return sql_data
@check_precondition
@@ -1414,19 +1444,10 @@ class ViewNode(PGChildNodeView, VacuumSettings, SchemaDiffObjectCompare):
result.update(res['rows'][0])
acls = []
try:
acls = render_template(
"/".join([self.template_path, self._ALLOWED_PRIVS_JSON])
)
acls = json.loads(acls, encoding='utf-8')
except Exception as e:
current_app.logger.exception(e)
# Privileges
for aclcol in acls:
for aclcol in self.allowed_acls:
if aclcol in result:
allowedacl = acls[aclcol]
allowedacl = self.allowed_acls[aclcol]
result[aclcol] = parse_priv_to_db(
result[aclcol], allowedacl['acl']
)
@@ -1766,6 +1787,7 @@ class MViewNode(ViewNode, VacuumSettings):
super().__init__(*args, **kwargs)
self.template_initial = 'mviews'
self.allowed_acls = []
@staticmethod
def ppas_template_path(ver):
@@ -1834,19 +1856,10 @@ class MViewNode(ViewNode, VacuumSettings):
# table vacuum toast: separate list of changed and reset data for
self.merge_to_vacuum_data(old_data, data, 'vacuum_toast')
acls = []
try:
acls = render_template(
"/".join([self.template_path, self._ALLOWED_PRIVS_JSON])
)
acls = json.loads(acls, encoding='utf-8')
except Exception as e:
current_app.logger.exception(e)
# Privileges
for aclcol in acls:
for aclcol in self.allowed_acls:
if aclcol in data:
allowedacl = acls[aclcol]
allowedacl = self.allowed_acls[aclcol]
for key in ['added', 'changed', 'deleted']:
if key in data[aclcol]:
@@ -1902,19 +1915,10 @@ class MViewNode(ViewNode, VacuumSettings):
if data.get('toast_autovacuum', False):
data['vacuum_data'] += vacuum_toast
acls = []
try:
acls = render_template(
"/".join([self.template_path, self._ALLOWED_PRIVS_JSON])
)
acls = json.loads(acls, encoding='utf-8')
except Exception as e:
current_app.logger.exception(e)
# Privileges
for aclcol in acls:
for aclcol in self.allowed_acls:
if aclcol in data:
allowedacl = acls[aclcol]
allowedacl = self.allowed_acls[aclcol]
data[aclcol] = parse_priv_to_db(
data[aclcol], allowedacl['acl']
)
@@ -1970,19 +1974,10 @@ class MViewNode(ViewNode, VacuumSettings):
result['vacuum_data'] = vacuum_table + vacuum_toast
acls = []
try:
acls = render_template(
"/".join([self.template_path, self._ALLOWED_PRIVS_JSON])
)
acls = json.loads(acls, encoding='utf-8')
except Exception as e:
current_app.logger.exception(e)
# Privileges
for aclcol in acls:
for aclcol in self.allowed_acls:
if aclcol in result:
allowedacl = acls[aclcol]
allowedacl = self.allowed_acls[aclcol]
result[aclcol] = parse_priv_to_db(
result[aclcol], allowedacl['acl']
)
@@ -1,6 +1,8 @@
{# ===== Grant Permissions to User Role on Views/Tables ===== #}
{% import 'macros/schemas/security.macros' as SECLABEL %}
{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
{% import 'columns/macros/privilege.macros' as COLUMN_PRIVILEGE %}
{# ===== We will generate Security Label SQL using macro ===== #}
{% if data.seclabels %}{% for r in data.seclabels %}{{ SECLABEL.SET(conn, 'VIEW', data.name, r.provider, r.label, data.schema) }}{{'\r'}}{% endfor %}{{'\r'}}{% endif %}{% if data.datacl %}
{% for priv in data.datacl %}{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }}{% endfor %}{% endif %}
{% for priv in data.datacl %}{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }}{% endfor %}{% endif %}{% if data.attacl %}
{% for priv in data.attacl %}{{ COLUMN_PRIVILEGE.APPLY(conn, data.schema, data.table, data.name, priv.grantee, priv.without_grant, priv.with_grant) }}{% endfor %}{% endif %}
@@ -447,6 +447,10 @@ define('pgadmin.node.server', [
if (res.success == 1) {
Notify.success(res.info);
t.itemData(i).is_password_saved=res.data.is_password_saved;
t.deselect(i);
setTimeout(function() {
t.select(i);
});
}
else {
Notify.error(res.info);
@@ -20,7 +20,7 @@
{% endif %}label: "{{ item.label }}", applies: ["{{ key.lower() }}"],
priority: {{ item.priority }},
enable: "{{ item.enable }}",
{% if item.checked is defined %}checked: {% if item.checked %}true{% else %}false{% endif %},
{% if item.checked is defined %}checked: {% if (item.checked or item.name == 'mnu_lock_'+current_ui_lock) %}true{% else %}false{% endif %},
{% endif %}
{% if item.below is defined %}below: {% if item.below %}true{% else %}false{% endif %},
{% endif %}
@@ -574,6 +574,19 @@ export default function PreferencesComponent({ ...props }) {
gettext('Later')
);
}
// Sync the lock layout menu with preferences
if (pref.name == 'lock_layout') {
let fileMenu = pgAdmin.Browser.MainMenus.find((menu) => menu.name == 'file');
let layoutSubMenu = fileMenu['menuItems'].find(menu => menu.name == 'mnu_locklayout');
layoutSubMenu['menu_items'].forEach(item => {
if (item.name === 'mnu_lock_'+save_data[0]['value']) {
item.checked = true;
} else {
item.checked = false;
}
});
pgAdmin.Browser.Events.trigger('pgadmin:nw-refresh-menu-item', 'lock_layout');
}
}
if (requires_refresh) {
+1
View File
@@ -8,6 +8,7 @@
##########################################################################
import os
import sys
import getpass
from pgadmin.utils.constants import KERBEROS
+1 -1
View File
@@ -122,7 +122,7 @@ ExplainRow.propTypes = {
Plans: PropTypes.array,
level: PropTypes.array,
_serial: PropTypes.number,
parent_node: PropTypes.number,
parent_node: PropTypes.string,
exclusive: PropTypes.number,
exclusive_flag: PropTypes.string,
inclusive: PropTypes.number,
+1 -1
View File
@@ -92,7 +92,7 @@ function Multitext({currentXpos, currentYpos, label, maxWidth}) {
wordBreakIndex = Math.round((maxWidth / letterWidth)) - 1;
svgElem.remove();
let words = label.split(' '),
let words = label?.split(' ') ?? '',
widthSoFar = 0,
lines = [],
currLine = '',
@@ -168,6 +168,11 @@ const ImageMapper = {
'image': 'ex_delete.svg',
'image_text': 'Delete',
};
case 'Merge':
return {
'image': 'ex_merge.svg',
'image_text': 'Merge',
};
}
},
'Named Tuplestore Scan': {
+1 -1
View File
@@ -73,7 +73,7 @@ function getForQueryParams(data) {
let retData = {...data};
Object.keys(retData).forEach((key)=>{
let value = retData[key];
if(_.isObject(value)) {
if(_.isObject(value) || _.isNull(value)) {
retData[key] = JSON.stringify(value);
}
});
@@ -21,6 +21,7 @@ export default function uplotOverride(theme) {
borderRadius: theme.shape.borderRadius,
color: theme.palette.background.default,
backgroundColor: theme.palette.text.primary,
zIndex: 9999,
'& .uplot-tooltip-label': {
display: 'flex',
@@ -368,8 +368,8 @@ function calcFontSize(fontSize) {
return '1em';
}
function handlePaste(_editor, e) {
let copiedText = e.clipboardData.getData('text');
async function handlePaste(_editor, e) {
let copiedText = await e.clipboardData.getData('text');
checkTrojanSource(copiedText, true);
}
@@ -69,6 +69,7 @@ export default function StreamingChart({xRange=75, data, options}) {
alpha: 0.3,
},
cursor: {
y: false,
drag: {
setScale: false,
}
@@ -28,6 +28,7 @@ const useStyles = makeStyles((theme)=>({
...theme.mixins.panelBorder.right,
...theme.mixins.panelBorder.bottom,
fontWeight: 'abc',
whiteSpace: 'pre',
'&[aria-colindex="1"]': {
padding: 0,
},
+25
View File
@@ -400,6 +400,31 @@ class ERDModule(PgAdminModule):
)
)
self.preference.register(
'options', 'cardinality_notation',
gettext('Cardinality Notation'), 'radioModern', 'crows',
category_label=PREF_LABEL_OPTIONS, options=[
{'label': gettext('Crow\'s foot'), 'value': 'crows'},
{'label': gettext('Chen'), 'value': 'chen'},
],
help_str=gettext(
'Notation to be used to present cardinality.'
)
)
self.preference.register(
'options',
'sql_with_drop',
gettext('SQL With DROP Table'),
'boolean',
False,
category_label=PREF_LABEL_OPTIONS,
help_str=gettext(
'If enabled, the SQL generated by the ERD Tool will add '
'DROP table DDL before each CREATE table DDL.'
)
)
blueprint = ERDModule(MODULE_NAME, __name__, static_url_path='/static')
@@ -141,7 +141,7 @@ class ERDTool extends React.Component {
_.bindAll(this, ['onLoadDiagram', 'onSaveDiagram', 'onSaveAsDiagram', 'onSQLClick',
'onImageClick', 'onAddNewNode', 'onEditTable', 'onCloneNode', 'onDeleteNode', 'onNoteClick',
'onNoteClose', 'onOneToManyClick', 'onManyToManyClick', 'onAutoDistribute', 'onDetailsToggle',
'onDetailsToggle', 'onChangeColors', 'onHelpClick', 'onDropNode', 'onBeforeUnload',
'onChangeColors', 'onHelpClick', 'onDropNode', 'onBeforeUnload', 'onNotationChange',
]);
this.diagram.zoomToFit = this.diagram.zoomToFit.bind(this.diagram);
@@ -293,11 +293,13 @@ class ERDTool extends React.Component {
this.setLoading(gettext('Preparing...'));
this.registerEvents();
const erdPref = this.props.pgWindow.pgAdmin.Browser.get_preferences_for_module('erd');
this.setState({
preferences: this.props.pgWindow.pgAdmin.Browser.get_preferences_for_module('erd'),
preferences: erdPref,
is_new_tab: (this.props.pgWindow.pgAdmin.Browser.get_preferences_for_module('browser').new_browser_tab_open || '')
.includes('erd_tool'),
is_close_tab_warning: this.props.pgWindow.pgAdmin.Browser.get_preferences_for_module('browser').confirm_on_refresh_close,
cardinality_notation: erdPref.cardinality_notation,
}, ()=>{
this.registerKeyboardShortcuts();
this.setTitle(this.state.current_file);
@@ -559,6 +561,10 @@ class ERDTool extends React.Component {
});
}
onNotationChange(e) {
this.setState({cardinality_notation: e.value});
}
onHelpClick() {
let url = url_for('help.static', {'filename': 'erd_tool.html'});
if (this.props.pgWindow) {
@@ -948,12 +954,17 @@ class ERDTool extends React.Component {
fgcolor={this.props.params.fgcolor} title={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}
/>
<FloatingNote open={this.state.note_open} onClose={this.onNoteClose}
anchorEl={this.noteRefEle} noteNode={this.state.note_node} appendTo={this.diagramContainerRef.current} rows={8}/>
<div className={this.props.classes.diagramContainer} data-test="diagram-container" ref={this.diagramContainerRef} onDrop={this.onDropNode} onDragOver={e => {e.preventDefault();}}>
<Loader message={this.state.loading_msg} autoEllipsis={true}/>
<CanvasWidget className={this.props.classes.diagramCanvas} ref={(ele)=>{this.canvasEle = ele?.ref?.current;}} engine={this.diagram.getEngine()} />
<ERDCanvasSettings.Provider value={{
cardinality_notation: this.state.cardinality_notation
}}>
<CanvasWidget className={this.props.classes.diagramCanvas} ref={(ele)=>{this.canvasEle = ele?.ref?.current;}} engine={this.diagram.getEngine()} />
</ERDCanvasSettings.Provider>
</div>
</Box>
);
@@ -981,3 +992,5 @@ ERDTool.propTypes = {
panel: PropTypes.object,
classes: PropTypes.object,
};
export const ERDCanvasSettings = React.createContext({});
@@ -27,6 +27,7 @@ import VisibilityOffRoundedIcon from '@material-ui/icons/VisibilityOffRounded';
import ImageRoundedIcon from '@material-ui/icons/ImageRounded';
import FormatColorFillRoundedIcon from '@material-ui/icons/FormatColorFillRounded';
import FormatColorTextRoundedIcon from '@material-ui/icons/FormatColorTextRounded';
import AccountTreeOutlinedIcon from '@material-ui/icons/AccountTreeOutlined';
import { PgMenu, PgMenuItem, usePgMenuGroup } from '../../../../../../static/js/components/Menu';
import gettext from 'sources/gettext';
@@ -69,7 +70,7 @@ const useStyles = makeStyles((theme)=>({
}),
}));
export function MainToolBar({preferences, eventBus, fillColor, textColor}) {
export function MainToolBar({preferences, eventBus, fillColor, textColor, notation, onNotationChange}) {
const classes = useStyles({fillColor,textColor});
const theme = useTheme();
const [buttonsDisabled, setButtonsDisabled] = useState({
@@ -86,6 +87,7 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor}) {
const {openMenuName, toggleMenu, onMenuClose} = usePgMenuGroup();
const saveAsMenuRef = React.useRef(null);
const sqlMenuRef = React.useRef(null);
const notationMenuRef = React.useRef(null);
const isDirtyRef = React.useRef(null);
const [checkedMenuItems, setCheckedMenuItems] = React.useState({});
const modal = useModal();
@@ -283,6 +285,9 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor}) {
eventBus.fireEvent(ERD_EVENTS.TOGGLE_DETAILS);
setShowDetails((prev)=>!prev);
}} />
<PgIconButton title={gettext('Cardinality Notation')} icon={
<><AccountTreeOutlinedIcon /><KeyboardArrowDownIcon style={{marginLeft: '-10px'}} /></>}
name="menu-notation" ref={notationMenuRef} onClick={toggleMenu} />
</PgButtonGroup>
<PgButtonGroup size="small">
<PgIconButton title={gettext('Zoom In')} icon={<ZoomInIcon />}
@@ -323,6 +328,16 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor}) {
>
<PgMenuItem hasCheck value="sql_with_drop" checked={checkedMenuItems['sql_with_drop']} onClick={checkMenuClick}>{gettext('With DROP Table')}</PgMenuItem>
</PgMenu>
<PgMenu
anchorRef={notationMenuRef}
open={openMenuName=='menu-notation'}
onClose={onMenuClose}
label={gettext('Cardinality Notation')}
>
<PgMenuItem hasCheck closeOnCheck value="crows" checked={notation == 'crows'} onClick={onNotationChange}>{gettext('Crow\'s Foot Notation')}</PgMenuItem>
<PgMenuItem hasCheck closeOnCheck value="chen" checked={notation == 'chen'} onClick={onNotationChange}>{gettext('Chen Notation')}</PgMenuItem>
</PgMenu>
</>
);
}
@@ -332,6 +347,8 @@ MainToolBar.propTypes = {
eventBus: PropTypes.object,
fillColor: PropTypes.string,
textColor: PropTypes.string,
notation: PropTypes.string,
onNotationChange: PropTypes.func,
};
const ColorButton = withColorPicker(PgIconButton);
@@ -7,7 +7,7 @@
//
//////////////////////////////////////////////////////////////
import React, { forwardRef } from 'react';
import React, { forwardRef, useContext } from 'react';
import {
RightAngleLinkModel,
RightAngleLinkWidget,
@@ -21,6 +21,7 @@ import _ from 'lodash';
import PropTypes from 'prop-types';
import { makeStyles } from '@material-ui/core';
import clsx from 'clsx';
import { ERDCanvasSettings } from '../components/ERDTool';
export const POINTER_SIZE = 30;
@@ -81,6 +82,7 @@ export class OneToManyLinkModel extends RightAngleLinkModel {
const useStyles = makeStyles((theme)=>({
svgLink: {
stroke: theme.palette.text.primary,
fontSize: '0.8em',
},
'@keyframes svgLinkSelected': {
'from': { strokeDashoffset: 24},
@@ -99,15 +101,37 @@ const useStyles = makeStyles((theme)=>({
}
}));
const CustomLinkEndWidget = props => {
function ChenNotation({rotation, type}) {
const classes = useStyles();
const textX = Math.sign(rotation) > 0 ? -14 : 8;
const textY = -5;
return (
<>
<text className={classes.svgLink} x={textX} y={textY} transform={'rotate(' + -rotation + ')' }>
{type == 'one' ? '1' : 'N'}
</text>
<line className={classes.svgLink} x1="0" y1="0" x2="0" y2="30"></line>
</>
);
}
ChenNotation.propTypes = {
rotation: PropTypes.number,
type: PropTypes.string,
};
function CustomLinkEndWidget(props) {
const { point, rotation, tx, ty, type } = props;
const classes = useStyles();
const settings = useContext(ERDCanvasSettings);
const svgForType = (itype) => {
if(settings.cardinality_notation == 'chen') {
return <ChenNotation rotation={rotation} type={itype} />;
}
if(itype == 'many') {
return (
<>
<circle className={clsx(classes.svgLink, classes.svgLinkCircle)} cx="0" cy="16" r={props.width*1.75} strokeWidth={props.width} />
<circle className={clsx(classes.svgLink, classes.svgLinkCircle)} cx="0" cy="16" r={props.width*2.5} strokeWidth={props.width} />
<polyline className={classes.svgLink} points="-8,0 0,15 0,0 0,30 0,15 8,0" fill="none" strokeWidth={props.width} />
</>
);
@@ -127,7 +151,7 @@ const CustomLinkEndWidget = props => {
</g>
</g>
);
};
}
CustomLinkEndWidget.propTypes = {
point: PropTypes.instanceOf(PointModel).isRequired,
@@ -202,6 +202,17 @@ const styles = (theme)=>({
padding: '0.125rem 0.25rem',
display: 'flex',
},
columnSection: {
display:'flex',
width: '100%' ,
...theme.mixins.panelBorder.bottom,
},
columnName: {
display:'flex',
width: '100%' ,
padding: '0.125rem 0.25rem',
wordBreak: 'break-all',
},
tableToolbar: {
background: theme.otherVars.editorToolbarBg,
borderTopLeftRadius: 'inherit',
@@ -269,11 +280,11 @@ class TableNodeWidgetRaw extends React.Component {
const {classes} = this.props;
return (
<div className={classes.tableSection} key={col.attnum} data-test="column-row">
<Box className={classes.columnSection} key={col.attnum} data-test="column-row">
<Box marginRight="auto" padding="0" minHeight="0" display="flex" alignItems="center">
{this.generatePort(leftPort)}
</Box>
<Box display="flex" width="100%" style={{wordBreak: 'break-all'}}>
<Box className={classes.columnName}>
<RowIcon icon={icon} />
<Box margin="auto 0">
<span data-test="column-name">{col.name}</span>&nbsp;
@@ -284,7 +295,7 @@ class TableNodeWidgetRaw extends React.Component {
<Box marginLeft="auto" padding="0" minHeight="0" display="flex" alignItems="center">
{this.generatePort(rightPort)}
</Box>
</div>
</Box>
);
}
@@ -99,8 +99,13 @@ define([
extraData['database'] = treeInfo.database._label;
if(treeInfo?.schema) {
extraData['schema'] = treeInfo?.schema._label;
}
if(treeInfo?.table) {
extraData['table'] = treeInfo?.table._label;
}
if(treeInfo?.mview) {
extraData['table'] = treeInfo?.mview._label;
}
extraData['save_btn_icon'] = 'done';
return extraData;
},
@@ -115,10 +115,12 @@ export default class MaintenanceSchema extends BaseUISchema {
{
'label': gettext('REINDEX'),
value: 'REINDEX',
disabled: obj.nodeInfo?.mview?true:false
},
{
'label': gettext('CLUSTER'),
value: 'CLUSTER',
disabled: obj.nodeInfo?.mview?true:false
},
],
},
@@ -111,7 +111,6 @@ const useStyles = makeStyles((theme) => ({
blockSize: '100%',
'& span': {
display: 'table-cell',
verticalAlign: 'middle',
cursor: 'pointer',
}
@@ -661,38 +660,49 @@ export function ResultGridComponent({ gridData, allRowIds, filterParams, selecte
}, [filterParams]);
const eventBus = useContext(SchemaDiffEventsContext);
const rowSelectionTimeoutRef = useRef();
const rowSelection = (row) => {
if (row.ddlData != undefined && row.status != FILTER_NAME.IDENTICAL) {
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_CHANGE_RESULT_SQL, row.ddlData);
} else if (row.status == FILTER_NAME.IDENTICAL) {
let url_params = {
'trans_id': transId,
'source_sid': sourceData.sid,
'source_did': sourceData.did,
'source_scid': row.source_scid,
'target_sid': targetData.sid,
'target_did': targetData.did,
'target_scid': row.target_scid,
'comp_status': row.status,
'source_oid': row.source_oid,
'target_oid': row.target_oid,
'node_type': row.itemType,
};
let baseUrl = url_for('schema_diff.ddl_compare', url_params);
schemaDiffToolContext.api.get(baseUrl).then((res) => {
row.ddlData = {
'SQLdiff': res.data.diff_ddl,
'sourceSQL': res.data.source_ddl,
'targetSQL': res.data.target_ddl
};
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_CHANGE_RESULT_SQL, row.ddlData);
}).catch((err) => {
Notifier.alert(err.message);
});
const rowSelection = (rowIdx) => {
if (rowSelectionTimeoutRef.current) {
clearTimeout(rowSelectionTimeoutRef.current);
rowSelectionTimeoutRef.current = null;
}
rowSelectionTimeoutRef.current = setTimeout(()=> {
rowSelectionTimeoutRef.current = null;
const row = rows[rowIdx];
if (row.ddlData != undefined && row.status != FILTER_NAME.IDENTICAL) {
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_CHANGE_RESULT_SQL, row.ddlData);
} else if (row.status == FILTER_NAME.IDENTICAL) {
let url_params = {
'trans_id': transId,
'source_sid': sourceData.sid,
'source_did': sourceData.did,
'source_scid': row.source_scid,
'target_sid': targetData.sid,
'target_did': targetData.did,
'target_scid': row.target_scid,
'comp_status': row.status,
'source_oid': row.source_oid,
'target_oid': row.target_oid,
'node_type': row.itemType,
};
let baseUrl = url_for('schema_diff.ddl_compare', url_params);
schemaDiffToolContext.api.get(baseUrl).then((res) => {
row.ddlData = {
'SQLdiff': res.data.diff_ddl,
'sourceSQL': res.data.source_ddl,
'targetSQL': res.data.target_ddl
};
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_CHANGE_RESULT_SQL, row.ddlData);
}).catch((err) => {
Notifier.alert(err.message);
});
} else {
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_CHANGE_RESULT_SQL, {});
}
}, 250);
};
function rowKeyGetter(row) {
@@ -714,7 +724,7 @@ export function ResultGridComponent({ gridData, allRowIds, filterParams, selecte
}}
headerRowHeight={28}
rowHeight={28}
onRowClick={rowSelection}
onItemSelect={rowSelection}
enableCellSelect={false}
rowKeyGetter={rowKeyGetter}
direction={'vertical-lr'}
@@ -349,6 +349,7 @@ export function JsonTextEditor({row, column, onRowChange, onClose}) {
return newVal;
});
const [localVal, setLocalVal] = React.useState(value);
const [hasError, setHasError] = React.useState(false);
const onChange = React.useCallback((newVal)=>{
setLocalVal(newVal);
@@ -358,6 +359,10 @@ export function JsonTextEditor({row, column, onRowChange, onClose}) {
onClose(false);
return;
}
if(hasError) {
Notifier.error(gettext('Invalid JSON input'));
return;
}
onRowChange({ ...row, [column.key]: localVal}, true);
onClose();
};
@@ -370,7 +375,7 @@ export function JsonTextEditor({row, column, onRowChange, onClose}) {
value={localVal}
options={{
onChange: onChange,
onError: (error)=>Notifier.error('Invalid Json: ' + error.message.split(':')[0]),
onValidationError: (errors)=>{setHasError(Boolean(errors.length));}
}}
className={'jsoneditor-div'}
/>
@@ -251,6 +251,9 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
eventBus.registerListener(QUERY_TOOL_EVENTS.QUERY_CHANGED, (isDirty)=>{
setDisableButton('save', !isDirty);
});
eventBus.registerListener(QUERY_TOOL_EVENTS.LOAD_FILE_DONE, ()=>{
setDisableButton('save', true);
});
eventBus.registerListener(QUERY_TOOL_EVENTS.DATAGRID_CHANGED, (isDirty)=>{
setDisableButton('save-data', !isDirty);
});
@@ -29,6 +29,7 @@ from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost,\
from pgadmin.utils.master_password import get_crypt_key
from pgadmin.utils.exception import ObjectGone
from pgadmin.utils.passexec import PasswordExec
from psycopg2.extensions import make_dsn
if config.SUPPORT_SSH_TUNNEL:
from sshtunnel import SSHTunnelForwarder, BaseSSHTunnelForwarderError
@@ -633,22 +634,23 @@ WHERE db.oid = {0}""".format(did))
This function is used to create connection string based on the
parameters.
"""
full_connection_string = \
'host=\'{0}\' port=\'{1}\' dbname=\'{2}\' user=\'{3}\''.format(
self.local_bind_host if self.use_ssh_tunnel else self.host,
self.local_bind_port if self.use_ssh_tunnel else self.port,
database, user)
self.display_connection_string = full_connection_string
dsn_args = dict()
dsn_args['host'] = \
self.local_bind_host if self.use_ssh_tunnel else self.host
dsn_args['port'] = \
self.local_bind_port if self.use_ssh_tunnel else self.port
dsn_args['dbname'] = database
dsn_args['user'] = user
if self.service is not None:
dsn_args['service'] = self.service
# Make a copy to display the connection string on GUI.
display_dsn_args = dsn_args.copy()
# Password should not be visible into the connection string, so
# setting the value with password to 'xxxxxxx'.
if password:
self.display_connection_string = "{0} password='xxxxxxx'".format(
self.display_connection_string)
full_connection_string = "{0} password='{1}'".format(
full_connection_string, password)
display_dsn_args['password'] = 'xxxxxxx'
dsn_args['password'] = password
# Loop through all the connection parameters set in the server dialog.
if self.connection_params and isinstance(self.connection_params, dict):
@@ -657,7 +659,7 @@ WHERE db.oid = {0}""".format(did))
orig_value = value
# Getting complete file path if the key is one of the below.
if key in ['passfile', 'sslcert', 'sslkey', 'sslrootcert',
'sslcrl', 'service', 'sslcrldir']:
'sslcrl', 'sslcrldir']:
with_complete_path = True
value = get_complete_file_path(value)
@@ -666,11 +668,10 @@ WHERE db.oid = {0}""".format(did))
value = self.local_bind_host if self.use_ssh_tunnel else \
value
self.display_connection_string = "{0} {1}='{2}'".format(
self.display_connection_string, key,
orig_value if with_complete_path else value)
dsn_args[key] = value
display_dsn_args[key] = orig_value if with_complete_path else \
value
full_connection_string = \
"{0} {1}='{2}'".format(full_connection_string, key, value)
self.display_connection_string = make_dsn(**display_dsn_args)
return full_connection_string
return make_dsn(**dsn_args)
@@ -61,9 +61,12 @@ class BrowserToolBarFeatureTest(BaseFeatureTest):
self.test_table_name)
def test_query_tool_button(self):
self.page.expand_database_node("Servers", self.server['name'],
self.server['db_password'],
self.test_db)
self.assertTrue(self.page.expand_database_node("Servers",
self.server['name'],
self.server[
'db_password'],
self.test_db),
'Tree is not expanded to database node')
self.assertTrue(self.page.retry_click(
(By.CSS_SELECTOR,
BrowserToolBarLocators.open_query_tool_button_css),
@@ -72,9 +75,12 @@ class BrowserToolBarFeatureTest(BaseFeatureTest):
self.page.close_query_tool(prompt=False)
def test_view_data_tool_button(self):
self.page.expand_tables_node("Servers", self.server['name'],
self.server['db_password'], self.test_db,
"public")
self.assertTrue(
self.page.expand_tables_node("Servers", self.server['name'],
self.server['db_password'],
self.test_db,
"public"),
'Tree is not expanded to table node')
table_node = self.page.check_if_element_exists_with_scroll(
TreeAreaLocators.table_node(self.test_table_name))
@@ -40,6 +40,7 @@ class QueryToolJourneyTest(BaseFeatureTest):
query_history_tab_id = "id-history"
query_editor_tab_name = "Query Editor"
query_editor_tab_id = "id-query"
query_tool_opened = False
def before(self):
self.test_table_name = "test_table" + str(
@@ -421,10 +422,12 @@ class QueryToolJourneyTest(BaseFeatureTest):
self.page.click_modal('Yes')
def _navigate_to_query_tool(self):
self.page.expand_database_node("Server", self.server['name'],
self.server['db_password'],
self.test_db)
self.page.open_query_tool()
self.assertTrue(
self.page.expand_database_node("Server", self.server['name'],
self.server['db_password'],
self.test_db),
'Tree is not expanded to database node')
self.query_tool_opened = self.page.open_query_tool()
self.page.wait_for_spinner_to_disappear()
def _explain_query(self, query):
@@ -498,7 +501,8 @@ class QueryToolJourneyTest(BaseFeatureTest):
QueryToolLocators.new_row_xpath)
def after(self):
self.page.close_query_tool()
if self.query_tool_opened:
self.page.close_query_tool()
test_utils.delete_table(
self.server, self.test_db, self.test_table_name)
test_utils.delete_table(
@@ -291,13 +291,6 @@ describe('ERDCore', ()=>{
]));
});
it('dagreDistributeNodes', ()=>{
spyOn(erdCoreObj.dagre_engine, 'redistribute');
erdCoreObj.dagreDistributeNodes();
expect(erdEngine.getLinkFactories().getFactory().calculateRoutingMatrix).toHaveBeenCalled();
expect(erdCoreObj.dagre_engine.redistribute).toHaveBeenCalledWith(erdEngine.getModel());
});
it('zoomIn', ()=>{
spyOn(erdEngine.getModel(), 'getZoomLevel').and.returnValue(100);
spyOn(erdCoreObj, 'repaint');
-7
View File
@@ -6235,13 +6235,6 @@ json5@^2.1.2, json5@^2.2.1:
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity "sha1-eM1vGhm9wStz21rQxh79ZsHikoM= sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
jsoneditor-react@^3.1.1:
version "3.1.2"
resolved "https://registry.yarnpkg.com/jsoneditor-react/-/jsoneditor-react-3.1.2.tgz#bc36356ac5ecb0d8f88d49a5ccbeecdd7f996164"
integrity sha512-XqU8BMdIhrlS5HUnn7rGhgZw315bdJGQrf6NG5UH40FSw2xNirQrxnM05aeAplHkp8FNkzN2WX0tfvEWdl2UUA==
dependencies:
prop-types "^15.7.2"
jsoneditor@^9.5.4:
version "9.9.2"
resolved "https://registry.yarnpkg.com/jsoneditor/-/jsoneditor-9.9.2.tgz#176112f71acbf37e977aae8656a17bc33a7a4269"