Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cad6025ce9 | ||
|
|
f8b894d52b | ||
|
|
9d0ec04667 | ||
|
|
24b4d35565 | ||
|
|
0c8571de9e | ||
|
|
2075dd5270 | ||
|
|
7d0241008b | ||
|
|
ecb652c7c5 | ||
|
|
9b5f0f0f63 | ||
|
|
1eb7c9d3e2 | ||
|
|
a4c5e5683c | ||
|
|
d53a34b8df | ||
|
|
8a4a3814ab | ||
|
|
6aaf47ddb4 | ||
|
|
4d078ab747 | ||
|
|
96de08212a | ||
|
|
2dd27d20c1 | ||
|
|
86c2769221 | ||
|
|
1316a3d7d8 | ||
|
|
9f866b0b27 | ||
|
|
829d6323b8 | ||
|
|
559c6cfa65 | ||
|
|
1f7fbb91f7 | ||
|
|
3b9359945e | ||
|
|
99b822e472 | ||
|
|
b2ec3a5acc | ||
|
|
9eec4f5b8c | ||
|
|
6db0cc5c5d | ||
|
|
a197c85f3a | ||
|
|
aa0a1f676d | ||
|
|
5963593fed | ||
|
|
e0e99ddbb5 | ||
|
|
85510b961f | ||
|
|
bf229c0d79 | ||
|
|
33dcb36db5 | ||
|
|
8a4eb4f8ad | ||
|
|
62eb330423 | ||
|
|
e73d8f4532 | ||
|
|
79aeb5204c | ||
|
|
de48d4ecda | ||
|
|
0d204d0554 | ||
|
|
37bdd22e24 | ||
|
|
2a43d1c939 | ||
|
|
c4f7a18ec5 | ||
|
|
fdc38f75f8 | ||
|
|
56d5cab13e | ||
|
|
bec47845be | ||
|
|
6dc5807192 | ||
|
|
ea085cd009 | ||
|
|
6fdb82dd76 | ||
|
|
a3e1a3672c | ||
|
|
8046c238d8 | ||
|
|
82730da392 | ||
|
|
fe2332f606 | ||
|
|
d26b63fdf5 | ||
|
|
770a0f27af | ||
|
|
ced002a6ad | ||
|
|
3aad58751a | ||
|
|
61a420c9fc | ||
|
|
8c662424b2 | ||
|
|
13ade4c0b2 | ||
|
|
1195f14327 | ||
|
|
65a35ab23d | ||
|
|
b31c8f78c5 | ||
|
|
82e6b57d04 | ||
|
|
1e4e00886a | ||
|
|
0fd7d0dedb | ||
|
|
0ebf78ca61 | ||
|
|
60c8e5fe05 | ||
|
|
1d68074a59 | ||
|
|
8723896da0 | ||
|
|
e4f17e5a6c | ||
|
|
454ba52c88 | ||
|
|
d5c0fac885 | ||
|
|
3f7683854b | ||
|
|
385148cf6e | ||
|
|
f207818afa | ||
|
|
1280cf5108 |
No files matched your search
@@ -34,7 +34,7 @@ If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. Windows 10, OSX monterey]
|
||||
- Version: [e.g. 6.15]
|
||||
- pgAdmin version: [e.g. 6.15]
|
||||
- Mode: [e.g Desktop or Server]
|
||||
- Browser (if running in server mode): [e.g. chrome, safari]
|
||||
- Package type: [e.g. RPM, DEB, Python, Container, etc.]
|
||||
|
||||
@@ -177,7 +177,7 @@ COPY --from=pg17-builder /usr/lib/liblz4.so.1.10.0 /usr/lib/
|
||||
|
||||
RUN ln -s libpq.so.5.17 /usr/lib/libpq.so.5 && \
|
||||
ln -s libpq.so.5.17 /usr/lib/libpq.so && \
|
||||
ln -s libzstd.so.1.5.6 /usr/lib/libzstd.so.1 && \
|
||||
ln -s libzstd.so.1.5.7 /usr/lib/libzstd.so.1 && \
|
||||
ln -s liblz4.so.1.10.0 /usr/lib/liblz4.so.1
|
||||
|
||||
WORKDIR /pgadmin4
|
||||
|
||||
@@ -28,7 +28,7 @@ CALL :CREATE_PYTHON_ENV || EXIT /B 1
|
||||
CALL :CREATE_RUNTIME_ENV || EXIT /B 1
|
||||
CALL :GENERATE_SBOM || EXIT /B 1
|
||||
CALL :CREATE_INSTALLER || EXIT /B 1
|
||||
CALL :SIGN_INSTALLER || EXIT /B 1
|
||||
CALL :VERIFY_SIGNATURE || EXIT /B 1
|
||||
|
||||
EXIT /B %ERRORLEVEL%
|
||||
REM Main build sequence Ends
|
||||
@@ -356,7 +356,7 @@ REM Main build sequence Ends
|
||||
DEL /s "%WD%\pkg\win32\installer.iss.in_stage*" > nul
|
||||
|
||||
ECHO Creating windows installer using INNO tool...
|
||||
CALL "%PGADMIN_INNOTOOL_DIR%\ISCC.exe" "%WD%\pkg\win32\installer.iss" || EXIT /B 1
|
||||
CALL "%PGADMIN_INNOTOOL_DIR%\ISCC.exe" "%WD%\pkg\win32\installer.iss" "/SpgAdminSigntool=%PGADMIN_SIGNTOOL_DIR%\signtool.exe sign /fd certHash /tr http://timestamp.digicert.com /td SHA256 $f" || EXIT /B 1
|
||||
|
||||
ECHO Renaming installer...
|
||||
MOVE "%WD%\pkg\win32\Output\pgadmin4-setup.exe" "%DISTROOT%\%INSTALLERNAME%" > nul || EXIT /B 1
|
||||
@@ -373,13 +373,14 @@ REM Main build sequence Ends
|
||||
|
||||
EXIT /B 0
|
||||
|
||||
:SIGN_INSTALLER
|
||||
ECHO Attempting to sign the installer...
|
||||
CALL "%PGADMIN_SIGNTOOL_DIR%\signtool.exe" sign /fd certHash /tr http://timestamp.digicert.com /td SHA256 "%DISTROOT%\%INSTALLERNAME%"
|
||||
:VERIFY_SIGNATURE
|
||||
ECHO Verifying the installer signature...
|
||||
|
||||
CALL "%PGADMIN_SIGNTOOL_DIR%\signtool.exe" verify /pa /v "%DISTROOT%\%INSTALLERNAME%"
|
||||
IF %ERRORLEVEL% NEQ 0 (
|
||||
ECHO.
|
||||
ECHO ************************************************************
|
||||
ECHO * Failed to sign the installer
|
||||
ECHO * Failed to verify signature of the installer
|
||||
ECHO ************************************************************
|
||||
PAUSE
|
||||
)
|
||||
|
||||
@@ -20,8 +20,9 @@ APP_REVISION := $(shell grep ^APP_REVISION web/version.py | awk -F"=" '{print $$
|
||||
# Include only platform-independent builds in all
|
||||
all: docs pip src
|
||||
|
||||
# Add BUILD_OPTS variable to pass arguments
|
||||
appbundle:
|
||||
./pkg/mac/build.sh
|
||||
./pkg/mac/build.sh $(BUILD_OPTS)
|
||||
|
||||
install-node:
|
||||
cd web && yarn install
|
||||
|
||||
@@ -20,7 +20,7 @@ which will fork a Python server process and display the UI.
|
||||
## Prerequisites
|
||||
1. Install Node.js 20 and above (https://nodejs.org/en/download)
|
||||
2. yarn (https://yarnpkg.com/getting-started/install)
|
||||
3. Python 3.8 and above (https://www.python.org/downloads/)
|
||||
3. Python 3.9 and above (https://www.python.org/downloads/)
|
||||
4. PostgreSQL server (https://www.postgresql.org/download)
|
||||
|
||||
Start by enabling Corepack, if it isn't already;
|
||||
@@ -59,7 +59,7 @@ C:\$PGADMIN4_SRC\web> yarn run bundle
|
||||
# Configuring the Python Environment
|
||||
|
||||
In order to run the Python code, a suitable runtime environment is required.
|
||||
Python version 3.8 and later are currently supported. It is recommended that a
|
||||
Python version 3.9 and later are currently supported. It is recommended that a
|
||||
Python Virtual Environment is setup for this purpose, rather than using the
|
||||
system Python environment. On Linux and Mac systems, the process is fairly
|
||||
simple - adapt as required for your distribution:
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
Auto-Update of pgAdmin 4 Desktop Application
|
||||
********************************************
|
||||
|
||||
pgAdmin 4's desktop application includes an automated update system built using
|
||||
Electron's ``autoUpdater`` module. This feature enables users to receive and install
|
||||
updates seamlessly, ensuring they always have access to the latest features and security fixes.
|
||||
|
||||
Supported Platforms
|
||||
===================
|
||||
|
||||
- **macOS:** Fully supported with automatic updates enabled by default
|
||||
- **Windows:** Not supported
|
||||
- **Linux:** Not supported
|
||||
|
||||
Update Process Overview
|
||||
=======================
|
||||
|
||||
1. **Check for Updates:**
|
||||
|
||||
- Automatic check on application startup
|
||||
- Manual check available via pgAdmin 4 menu > Check for Updates
|
||||
- Uses Electron's ``autoUpdater`` API to query update server
|
||||
|
||||
2. **Download Process:**
|
||||
|
||||
- Updates download automatically when detected
|
||||
- Progress shown via notifications
|
||||
- Background download prevents interruption of work
|
||||
|
||||
3. **Installation Flow:**
|
||||
|
||||
- User prompted to Install & Restart or Restart Later when update ready
|
||||
- Update applied during application restart
|
||||
|
||||
The flow chart for the update process is as follows:
|
||||
|
||||
.. image:: images/auto_update_desktop_app.png
|
||||
:alt: Auto-update Desktop App
|
||||
:align: center
|
||||
|
||||
User Interface Components
|
||||
=========================
|
||||
|
||||
1. **Notification Types:**
|
||||
|
||||
- Update available
|
||||
- Download progress
|
||||
- Update ready to install
|
||||
- Error notifications
|
||||
|
||||
2. **Menu Integration:**
|
||||
|
||||
- Check for Updates option in pgAdmin 4 menu
|
||||
- Restart to Update option when update available
|
||||
|
||||
Error Handling
|
||||
==============
|
||||
|
||||
The system includes comprehensive error handling:
|
||||
|
||||
1. **Network Errors:**
|
||||
|
||||
- Connection timeouts
|
||||
- Download failures
|
||||
- Server unavailability
|
||||
|
||||
2. **Installation Errors:**
|
||||
|
||||
- Corrupted downloads
|
||||
|
||||
3. **Recovery Mechanisms:**
|
||||
|
||||
- Fallback to manual update
|
||||
- Error reporting to logs
|
||||
|
||||
Security Considerations
|
||||
=======================
|
||||
|
||||
The update system implements below security measures:
|
||||
|
||||
1. **Secure Communication:**
|
||||
|
||||
- Protected update metadata
|
||||
|
||||
Platform-Specific Notes
|
||||
=======================
|
||||
|
||||
1. **macOS:**
|
||||
|
||||
- Uses native update mechanisms
|
||||
- Requires signed packages
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
- `Electron autoUpdater API Documentation <https://www.electronjs.org/docs/latest/api/auto-updater>`_
|
||||
@@ -40,6 +40,9 @@ settings:
|
||||
as the existing one, but will be an independent object. If you choose to copy
|
||||
an existing collation, you cannot modify the collation properties displayed on
|
||||
this tab.
|
||||
* Use the drop-down listbox next to *Provider* to select a locale services associated
|
||||
with the collation. Possible values are: icu, libc and builtin.
|
||||
**Note:** The builtin option is available from v17 onwards.
|
||||
* Use the *Locale* field to specify a locale; a locale specifies language and
|
||||
language formatting characteristics. If you specify this, you cannot specify
|
||||
either of the following parameters. To view a list of locales supported by
|
||||
@@ -50,8 +53,6 @@ settings:
|
||||
* Use the *LC_CTYPE* field to specify a locale with specified character
|
||||
classification. The locale must be applicable to the current database encoding.
|
||||
(See CREATE DATABASE for details.)
|
||||
* Use the drop-down listbox next to *Locale Provider* to select a locale services associated
|
||||
with the collation. Possible values are: icu, libc. libc is the default.
|
||||
* Move the switch next to *Deterministic* to *YES* to specify whether the collation should use
|
||||
deterministic comparisons. By default, this option is set to true. In a
|
||||
deterministic comparison, strings that are not byte-wise equal are considered
|
||||
|
||||
@@ -128,3 +128,10 @@ The configuration settings are stored in *runtime_config.json* file, which
|
||||
will be available on Unix systems (~/.local/share/pgadmin/),
|
||||
on Mac OS X (~/Library/Preferences/pgadmin),
|
||||
and on Windows (%APPDATA%/pgadmin).
|
||||
|
||||
For details on the auto-update system for the desktop application, see
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
auto_update_desktop_app
|
||||
|
After Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 149 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 215 KiB |
|
After Width: | Height: | Size: 212 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 267 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 247 KiB |
|
Before Width: | Height: | Size: 236 KiB After Width: | Height: | Size: 222 KiB |
|
Before Width: | Height: | Size: 261 KiB After Width: | Height: | Size: 252 KiB |
|
Before Width: | Height: | Size: 206 KiB After Width: | Height: | Size: 179 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 34 KiB |
@@ -194,4 +194,6 @@ The following example shows both a minimally defined and a fully defined server:
|
||||
"PostConnectionSQL": "set timezone='America/New_York'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.. note:: If you're running pgAdmin in Server Mode, the values for parameters that require file paths should be specified relative to the user's home directory which is /<DATA_DIR>/storage/<user_id>/. If DATA_DIR is not specified, then it will /var/libe/pgadmin/storage/<user_id>/
|
||||
@@ -63,11 +63,11 @@ following options (in alphabetical order):
|
||||
| *Create* | Click *Create* to access a context menu that provides context-sensitive selections. |
|
||||
| | Your selection opens a *Create* dialog for creating a new object. |
|
||||
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
|
||||
| *Delete* | Click to delete the currently selected object from the server. |
|
||||
| *Drop* | Click to drop the currently selected object from the server. |
|
||||
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
|
||||
| *Delete (Cascade)* | Click to delete the currently selected object and all dependent objects from the server. |
|
||||
| *Drop (Cascade)* | Click to drop the currently selected object and all dependent objects from the server. |
|
||||
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
|
||||
| *Delete (Force)* | Click to delete the currently selected database with force option. |
|
||||
| *Drop (Force)* | Click to drop the currently selected database with force option. |
|
||||
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
|
||||
| *Disconnect from server* | Click to disconnect from the currently selected server. |
|
||||
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
@@ -48,6 +48,8 @@ and modify the values for the following parameters:
|
||||
Useful for checking AzureAD_ *wids* or *groups*, GitLab_ *owner*, *maintainer* and *reporter* claims."
|
||||
"OAUTH2_SSL_CERT_VERIFICATION", "Set this variable to False to disable SSL certificate verification for OAuth2 provider.
|
||||
This may need to set False, in case of self-signed certificates."
|
||||
"OAUTH2_CHALLENGE_METHOD", "Enable PKCE workflow. PKCE method name, only *S256* is supported"
|
||||
"OAUTH2_RESPONSE_TYPE", "Enable PKCE workflow. Mandatory with OAUTH2_CHALLENGE_METHOD, must be set to *code*"
|
||||
|
||||
Redirect URL
|
||||
============
|
||||
@@ -65,12 +67,19 @@ the PostgreSQL server password.
|
||||
To accomplish this, set the configuration parameter MASTER_PASSWORD to *True*, so upon setting the master password,
|
||||
it will be used as an encryption key while storing the password. If it is False, the server password can not be stored.
|
||||
|
||||
|
||||
Login Page
|
||||
============
|
||||
==========
|
||||
|
||||
After configuration, on restart, you can see the login page with the Oauth2 login button(s).
|
||||
|
||||
.. image:: images/oauth2_login.png
|
||||
:alt: Oauth2 login
|
||||
:align: center
|
||||
|
||||
PKCE Workflow
|
||||
=============
|
||||
|
||||
Ref: https://oauth.net/2/pkce
|
||||
|
||||
To enable PKCE workflow, set the configuration parameters OAUTH2_CHALLENGE_METHOD to *S256* and OAUTH2_RESPONSE_TYPE to *code*.
|
||||
Both parameters are mandatory to enable PKCE workflow.
|
||||
@@ -262,6 +262,93 @@ Use the fields on the *Options* panel to manage ERD preferences.
|
||||
It allows to set the limit on the depth level pgAdmin should traverse
|
||||
to find the relations. Use -1 to set no limit.
|
||||
|
||||
|
||||
The Editor Node
|
||||
*****************
|
||||
|
||||
Expand the *Editor* node to access panels that allow you to specify your preferences
|
||||
for the editor across pgAdmin 4
|
||||
|
||||
.. image:: images/preferences_editor_keyboard_shortcuts.png
|
||||
:alt: Preferences editor keyboard shortcuts section
|
||||
:align: center
|
||||
|
||||
Use the fields on the *Keyboard shortcuts* panel to configure shortcuts for the
|
||||
editor.
|
||||
|
||||
.. image:: images/preferences_editor_options.png
|
||||
:alt: Preferences editor options section
|
||||
:align: center
|
||||
|
||||
Use the fields on the *Options* panel to manage editor preferences.
|
||||
|
||||
* Use the *Auto-indent new line?* switch to specify whether the editor will
|
||||
automatically indent new lines. When set to *True*, the editor will indent
|
||||
new lines according to the indentation of the previous line.
|
||||
|
||||
* When the *Brace matching?* switch is set to *True*, the editor will highlight
|
||||
pairs of matched braces.
|
||||
|
||||
* When the *Code folding?* switch is set to *False*, the editor will disable
|
||||
code folding. Disabling will improve editor performance with large files.
|
||||
|
||||
* Use the *Cursor blink rate* field to adjust the speed at which the text cursor
|
||||
blinks within the editors.
|
||||
|
||||
* Use the *Font family* field to be used for all SQL editors. The specified
|
||||
font should already be installed on your system. If the font is not found,
|
||||
the editor will fall back to the default font, Source Code Pro.
|
||||
|
||||
* When the *Font ligatures?* switch is set to *True*, ligatures will be
|
||||
enabled in SQL text boxes and editors provided the configured font family
|
||||
supports them.
|
||||
|
||||
* Use the *Font size* field to specify the font size that will be used in text
|
||||
boxes and editors.
|
||||
|
||||
* When the *Highlight selection matches?* switch is set to *True*, the editor will
|
||||
highlight matched selected text.
|
||||
|
||||
* When the *Insert bracket pairs?* switch is set to *True*, the editor will
|
||||
automatically insert paired brackets.
|
||||
|
||||
* When the *Line wrapping* switch is set to *True*, the editor will implement
|
||||
line-wrapping behavior.
|
||||
|
||||
* When the *Plain text mode?* switch is set to *True*, the editor mode will be
|
||||
changed to text/plain. Keyword highlighting and code folding will be disabled.
|
||||
This will improve editor performance with large files.
|
||||
|
||||
.. image:: images/preferences_editor_sql_formatting.png
|
||||
:alt: Preferences SQL Formatting section
|
||||
:align: center
|
||||
|
||||
Use the fields on the *SQL formatting* panel to specify your preferences for
|
||||
reformatting of SQL.
|
||||
|
||||
* Use the *Data type case* option to specify whether to change data types
|
||||
into upper, lower, or preserve case.
|
||||
* Use the *Expression width* option to specify maximum number of characters
|
||||
in parenthesized expressions to be kept on single line.
|
||||
* Use the *Function case* option to specify whether to change function
|
||||
names into upper, lower, or preserve case.
|
||||
* Use the *Identifier case* option to specify whether to change identifiers
|
||||
(object names) into upper, lower, or capitalized case.
|
||||
* Use the *Keyword case* option to specify whether to change keywords into
|
||||
upper, lower, or preserve case.
|
||||
* Use *Lines between queries* to specify how many empty lines to leave
|
||||
between SQL statements. If set to zero it puts no new line.
|
||||
* Use *Logical operator new line* to specify newline placement before or
|
||||
after logical operators (AND, OR, XOR).
|
||||
* Use *New line before semicolon?* to specify whether to place query
|
||||
separator (;) on a separate line.
|
||||
* Use the *Spaces around operators?* option to specify whether or not to include
|
||||
spaces on either side of operators.
|
||||
* Use the *Tab size* option to specify the number of spaces per tab or indent.
|
||||
* Use the *Use spaces?* option to select whether to use spaces or tabs when
|
||||
indenting.
|
||||
|
||||
|
||||
The Graphs Node
|
||||
***************
|
||||
|
||||
@@ -427,46 +514,6 @@ Tool display.
|
||||
* When the *Show query success notification?* switch is set to *True*, the Query Tool
|
||||
will show notifications on successful query execution.
|
||||
|
||||
.. image:: images/preferences_sql_editor.png
|
||||
:alt: Preferences sqleditor editor settings
|
||||
:align: center
|
||||
|
||||
Use the fields on the *Editor* panel to change settings of the query editor.
|
||||
|
||||
* Use the *Auto-indent new line?* switch to specify whether the editor will
|
||||
automatically indent new lines. When set to *True*, the editor will indent
|
||||
new lines according to the indentation of the previous line.
|
||||
|
||||
* When the *Brace matching?* switch is set to *True*, the editor will highlight
|
||||
pairs of matched braces.
|
||||
|
||||
* When the *Code folding?* switch is set to *False*, the editor will disable
|
||||
code folding. Disabling will improve editor performance with large files.
|
||||
|
||||
* Use the *Font family* field to be used for all SQL editors. The specified
|
||||
font should already be installed on your system. If the font is not found,
|
||||
the editor will fall back to the default font, Source Code Pro.
|
||||
|
||||
* When the *Font ligatures?* switch is set to *True*, ligatures will be
|
||||
enabled in SQL text boxes and editors provided the configured font family
|
||||
supports them.
|
||||
|
||||
* Use the *Font size* field to specify the font size that will be used in text
|
||||
boxes and editors.
|
||||
|
||||
* When the *Highlight selection matches?* switch is set to *True*, the editor will
|
||||
highlight matched selected text.
|
||||
|
||||
* When the *Insert bracket pairs?* switch is set to *True*, the editor will
|
||||
automatically insert paired brackets.
|
||||
|
||||
* When the *Line wrapping* switch is set to *True*, the editor will implement
|
||||
line-wrapping behavior.
|
||||
|
||||
* When the *Plain text mode?* switch is set to *True*, the editor mode will be
|
||||
changed to text/plain. Keyword highlighting and code folding will be disabled.
|
||||
This will improve editor performance with large files.
|
||||
|
||||
.. image:: images/preferences_sql_explain.png
|
||||
:alt: Preferences sqleditor explain options
|
||||
:align: center
|
||||
@@ -552,6 +599,10 @@ Use the fields on the *Options* panel to manage editor preferences.
|
||||
will warn upon clicking the *Execute Query* button in the query tool. The warning
|
||||
will appear only if *Underline query at cursor?* is set to *False*.
|
||||
|
||||
* When the *Use server cursor?* switch is set to *True*, the dataset will be fetched
|
||||
using a server-side cursor after the query is executed.
|
||||
|
||||
|
||||
.. image:: images/preferences_sql_results_grid.png
|
||||
:alt: Preferences sql results grid section
|
||||
:align: center
|
||||
@@ -588,34 +639,6 @@ preferences for copied data.
|
||||
Use the fields on the *Keyboard shortcuts* panel to configure shortcuts for the
|
||||
Query Tool window navigation.
|
||||
|
||||
.. image:: images/preferences_sql_formatting.png
|
||||
:alt: Preferences SQL Formatting section
|
||||
:align: center
|
||||
|
||||
Use the fields on the *SQL formatting* panel to specify your preferences for
|
||||
reformatting of SQL.
|
||||
|
||||
* Use the *Data type case* option to specify whether to change data types
|
||||
into upper, lower, or preserve case.
|
||||
* Use the *Expression width* option to specify maximum number of characters
|
||||
in parenthesized expressions to be kept on single line.
|
||||
* Use the *Function case* option to specify whether to change function
|
||||
names into upper, lower, or preserve case.
|
||||
* Use the *Identifier case* option to specify whether to change identifiers
|
||||
(object names) into upper, lower, or capitalized case.
|
||||
* Use the *Keyword case* option to specify whether to change keywords into
|
||||
upper, lower, or preserve case.
|
||||
* Use *Lines between queries* to specify how many empty lines to leave
|
||||
between SQL statements. If set to zero it puts no new line.
|
||||
* Use *Logical operator new line* to specify newline placement before or
|
||||
after logical operators (AND, OR, XOR).
|
||||
* Use *New line before semicolon?* to specify whether to place query
|
||||
separator (;) on a separate line.
|
||||
* Use the *Spaces around operators?* option to specify whether or not to include
|
||||
spaces on either side of operators.
|
||||
* Use the *Tab size* option to specify the number of spaces per tab or indent.
|
||||
* Use the *Use spaces?* option to select whether to use spaces or tabs when
|
||||
indenting.
|
||||
|
||||
The Schema Diff Node
|
||||
********************
|
||||
|
||||
@@ -558,3 +558,31 @@ To execute a macro, simply select the appropriate shortcut keys, or select it fr
|
||||
.. image:: images/query_output_data.png
|
||||
:alt: Query Tool Macros Execution
|
||||
:align: center
|
||||
|
||||
|
||||
Server Side Cursor
|
||||
******************
|
||||
|
||||
Server-side cursors allow partial retrieval of large datasets, making them particularly useful when working with
|
||||
very large result sets. However, they may offer lower performance in typical, everyday usage scenarios.
|
||||
|
||||
To enable server-side cursors:
|
||||
|
||||
* Go to Preferences > Query Tool > Options and set "Use server cursor?" to True.
|
||||
* Alternatively, you can enable it on a per-session basis via the Query Tool’s Execute menu.
|
||||
|
||||
.. image:: images/query_tool_server_cursor_execute_menu.png
|
||||
:alt: Query Tool Server Cursor
|
||||
:align: center
|
||||
|
||||
|
||||
Limitations:
|
||||
|
||||
1. Transaction Requirement: Server-side cursors work only in transaction mode.
|
||||
If enabled pgAdmin will automatically ensure queries run within a transaction.
|
||||
|
||||
2. Limited Use Case: Use server-side cursors only when fetching large datasets.
|
||||
|
||||
3. Pagination Limitation: In the Result Grid, the First and Last page buttons will be disabled,
|
||||
as server-side cursors do not return a total row count. Consequently, the total number of rows
|
||||
will not be displayed after execution.
|
||||
@@ -140,20 +140,27 @@ Query Execution
|
||||
| | | |
|
||||
| | Navigate through the *Explain Options* menu to select options for the EXPLAIN command: | |
|
||||
| | | |
|
||||
| | * Select *Verbose* to display additional information regarding the query plan. | |
|
||||
| | * Select *Buffers* to include information on buffer usage. | |
|
||||
| | | |
|
||||
| | * Select *Costs* to include information on the estimated startup and total cost of each | |
|
||||
| | plan node, as well as the estimated number of rows and the estimated width of each | |
|
||||
| | row. | |
|
||||
| | | |
|
||||
| | * Select *Buffers* to include information on buffer usage. | |
|
||||
| | * Select *Generic Plan* to include the information on the Generic Plan. | |
|
||||
| | | |
|
||||
| | * Select *Memory* to include the information on memory consumption by the query planning phase. | |
|
||||
| | | |
|
||||
| | * Select *Serialize* to include information on the cost of serializing the query's output data, | |
|
||||
| | that is converting it to text or binary format to send to the client. | |
|
||||
| | | |
|
||||
| | * Select *Settings* to include the information on the configuration parameters. | |
|
||||
| | | |
|
||||
| | * Select *Summary* to include the summary information about the query plan. | |
|
||||
| | | |
|
||||
| | * Select *Timing* to include information about the startup time and the amount of time | |
|
||||
| | spent in each node of the query. | |
|
||||
| | | |
|
||||
| | * Select *Summary* to include the summary information about the query plan. | |
|
||||
| | | |
|
||||
| | * Select *Settings* to include the information on the configuration parameters. | |
|
||||
| | * Select *Verbose* to display additional information regarding the query plan. | |
|
||||
| | | |
|
||||
| | * Select *Wal* to include the information on WAL record generation. | |
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
|
||||
@@ -12,6 +12,8 @@ notes for it.
|
||||
:maxdepth: 1
|
||||
|
||||
|
||||
release_notes_9_7
|
||||
release_notes_9_6
|
||||
release_notes_9_5
|
||||
release_notes_9_4
|
||||
release_notes_9_3
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
***********
|
||||
Version 9.6
|
||||
***********
|
||||
|
||||
Release date: 2025-07-25
|
||||
|
||||
This release contains a number of bug fixes and new features since the release of pgAdmin 4 v9.5.
|
||||
|
||||
Supported Database Servers
|
||||
**************************
|
||||
**PostgreSQL**: 13, 14, 15, 16 and 17
|
||||
|
||||
**EDB Advanced Server**: 13, 14, 15, 16 and 17
|
||||
|
||||
Bundled PostgreSQL Utilities
|
||||
****************************
|
||||
**psql**, **pg_dump**, **pg_dumpall**, **pg_restore**: 17.2
|
||||
|
||||
|
||||
New features
|
||||
************
|
||||
|
||||
| `Issue #1923 <https://github.com/pgadmin-org/pgadmin4/issues/1923>`_ - Ensure that keyboard shortcuts are displayed in tooltips, context menus, and the main menu.
|
||||
| `Issue #5797 <https://github.com/pgadmin-org/pgadmin4/issues/5797>`_ - Implemented a server-side cursor to enhance performance when retrieving large datasets.
|
||||
| `Issue #7979 <https://github.com/pgadmin-org/pgadmin4/issues/7979>`_ - Added support for setting a custom SESSION_DIGEST_METHOD in pgAdmin 4 to facilitate FIPS compliance.
|
||||
| `Issue #8941 <https://github.com/pgadmin-org/pgadmin4/issues/8941>`_ - Enable the PKCE workflow for OAuth 2 authentication.
|
||||
|
||||
Housekeeping
|
||||
************
|
||||
|
||||
| `Issue #8828 <https://github.com/pgadmin-org/pgadmin4/issues/8828>`_ - Ensure that pgAdmin 4 is compatible with PG/EPAS v18.
|
||||
| `Issue #8958 <https://github.com/pgadmin-org/pgadmin4/issues/8958>`_ - Use 'Drop' instead of 'Delete' for object commands to make destructive actions more deliberate.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Issue #8420 <https://github.com/pgadmin-org/pgadmin4/issues/8420>`_ - Fixed an issue where windows installer is not signing tmp files generated by InnoSetup.
|
||||
| `Issue #8675 <https://github.com/pgadmin-org/pgadmin4/issues/8675>`_ - Fixed an issue in the Search Objects tool where selecting a node occasionally selected an incorrect node.
|
||||
| `Issue #8901 <https://github.com/pgadmin-org/pgadmin4/issues/8901>`_ - Improved the application state restoration by leveraging rc-dock layout and ensure the order and layout of tabs is maintained on restore.
|
||||
| `Issue #8907 <https://github.com/pgadmin-org/pgadmin4/issues/8907>`_ - Fixed an issue where json editor in result grid was crashing if null value is set in the json editor.
|
||||
| `Issue #8914 <https://github.com/pgadmin-org/pgadmin4/issues/8914>`_ - Update zstd library link to 1.5.7 in Dockerfile.
|
||||
@@ -0,0 +1,51 @@
|
||||
***********
|
||||
Version 9.7
|
||||
***********
|
||||
|
||||
Release date: 2025-08-21
|
||||
|
||||
This release contains a number of bug fixes and new features since the release of pgAdmin 4 v9.6.
|
||||
|
||||
Supported Database Servers
|
||||
**************************
|
||||
**PostgreSQL**: 13, 14, 15, 16 and 17
|
||||
|
||||
**EDB Advanced Server**: 13, 14, 15, 16 and 17
|
||||
|
||||
Bundled PostgreSQL Utilities
|
||||
****************************
|
||||
**psql**, **pg_dump**, **pg_dumpall**, **pg_restore**: 17.2
|
||||
|
||||
|
||||
New features
|
||||
************
|
||||
|
||||
| `Issue #5766 <https://github.com/pgadmin-org/pgadmin4/issues/5766>`_ - Add support for automatic updates in the pgAdmin 4 Desktop application on macOS.
|
||||
| `Issue #6456 <https://github.com/pgadmin-org/pgadmin4/issues/6456>`_ - Added GENERIC_PLAN, MEMORY, SERIALIZE option to EXPLAIN/EXPLAIN ANALYZE command.
|
||||
| `Issue #8712 <https://github.com/pgadmin-org/pgadmin4/issues/8712>`_ - Add support for changing cursor blink rate within the editors.
|
||||
| `Issue #8917 <https://github.com/pgadmin-org/pgadmin4/issues/8917>`_ - Add support for server tag-based filtering in the Object Explorer.
|
||||
| `Issue #8931 <https://github.com/pgadmin-org/pgadmin4/issues/8931>`_ - Added support for builtin locale provider while creating Collation.
|
||||
|
||||
Housekeeping
|
||||
************
|
||||
|
||||
| `Issue #6384 <https://github.com/pgadmin-org/pgadmin4/issues/6384>`_ - Replace keyword PROCEDURE with FUNCTION while creating trigger and event trigger.
|
||||
| `Issue #8861 <https://github.com/pgadmin-org/pgadmin4/issues/8861>`_ - Introduced an ‘Editor’ preferences category and migrated all editor related settings into it.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Issue #7057 <https://github.com/pgadmin-org/pgadmin4/issues/7057>`_ - Fixed an issue where custom column widths in the result grid of Query Tool or View/Edit Data were reset after re-executing a query.
|
||||
| `Issue #7617 <https://github.com/pgadmin-org/pgadmin4/issues/7617>`_ - Fixed the issue where updating the name of a table column does not reflect in the corresponding primary key constraint.
|
||||
| `Issue #8149 <https://github.com/pgadmin-org/pgadmin4/issues/8149>`_ - Fixed an issue where pgAdmin failed to update the server connection status when the server was disconnected in the background and a refresh was performed on that server.
|
||||
| `Issue #8650 <https://github.com/pgadmin-org/pgadmin4/issues/8650>`_ - Make Dashboard tables to be vertically resizable.
|
||||
| `Issue #8756 <https://github.com/pgadmin-org/pgadmin4/issues/8756>`_ - Fixed an issue in Firefox where the query window would shift to the left after opening the history tab or selecting a column header in the results grid.
|
||||
| `Issue #8864 <https://github.com/pgadmin-org/pgadmin4/issues/8864>`_ - Fixed an issue where CPU usage was very high on Windows when opening the psql tool.
|
||||
| `Issue #8867 <https://github.com/pgadmin-org/pgadmin4/issues/8867>`_ - Ensure DB restriction type is preserved while import and export server.
|
||||
| `Issue #8969 <https://github.com/pgadmin-org/pgadmin4/issues/8969>`_ - Fixed incorrect behaviour of the option deduplicate items after creating the index.
|
||||
| `Issue #8971 <https://github.com/pgadmin-org/pgadmin4/issues/8971>`_ - Added PKEY index in the index statistics summary.
|
||||
| `Issue #9073 <https://github.com/pgadmin-org/pgadmin4/issues/9073>`_ - Fixed an issue where adding breakpoints caused errors, and stepping out of a nested function removed breakpoints from the parent function.
|
||||
| `Issue #9007 <https://github.com/pgadmin-org/pgadmin4/issues/9007>`_ - Ensure the scratch pad in the Query Tool is not restored after it is closed.
|
||||
| `Issue #9008 <https://github.com/pgadmin-org/pgadmin4/issues/9008>`_ - Update the documentation for parameters that require file paths.
|
||||
| `Issue #9047 <https://github.com/pgadmin-org/pgadmin4/issues/9047>`_ - Fixed an issue where downloading images on the ERD tool was not working in desktop mode.
|
||||
| `Issue #9067 <https://github.com/pgadmin-org/pgadmin4/issues/9067>`_ - Ensure that disabling "Save Application State" in Preferences prevents tool data from being saved and stops it from being restored on application restart.
|
||||
@@ -13,6 +13,8 @@ the selected object node.
|
||||
:alt: pgAdmin Toolbar
|
||||
:align: center
|
||||
|
||||
* Use the :ref:`Object filter <object-explorer-filter>` button to access
|
||||
the Object Filter popup. It helps you filter objects in the Object Explorer tree.
|
||||
* Use the :ref:`Query Tool <query_tool>` button to open the Query Tool in the
|
||||
current database context.
|
||||
* Use the :ref:`View Data <editgrid>` button to view/edit the data stored in a
|
||||
@@ -23,3 +25,24 @@ the selected object node.
|
||||
dialog. It helps you search any database object.
|
||||
* Use the :ref:`PSQL Tool <psql_tool>` button to open the PSQL in the current
|
||||
database context.
|
||||
|
||||
|
||||
|
||||
.. _object-explorer-filter:
|
||||
|
||||
*******************************
|
||||
`Object Explorer Filter`:index:
|
||||
*******************************
|
||||
.. image:: /images/object_explorer_filter.png
|
||||
:alt: Object Explorer Filter Dialog
|
||||
:align: center
|
||||
|
||||
Use this tool to filter objects in the Object Explorer by
|
||||
following fields:
|
||||
|
||||
* Use the *Tags* field to filter the servers with one or more server tags. The
|
||||
servers with any of the selected tags will be displayed in the Object Explorer.
|
||||
You can also create a new tag by typing in the field and pressing Enter.
|
||||
|
||||
Click the **Apply** button to apply the filter. Please note the object explorer will
|
||||
refresh after applying the filter.
|
||||
@@ -49,11 +49,11 @@ following selections (options appear in alphabetical order):
|
||||
+---------------------------+---------------------------------------------------------------------------------------------------------------------------+
|
||||
| *Debugging* | Click through to open the :ref:`Debug <debugger>` tool or to select *Set breakpoint* to stop or pause a script execution. |
|
||||
+---------------------------+---------------------------------------------------------------------------------------------------------------------------+
|
||||
| *Delete* | Click to delete the currently selected object from the server. |
|
||||
| *Drop* | Click to drop the currently selected object from the server. |
|
||||
+---------------------------+---------------------------------------------------------------------------------------------------------------------------+
|
||||
| *Delete (Cascade)* | Click to delete the currently selected object and all dependent objects from the server. |
|
||||
| *Drop (Cascade)* | Click to drop the currently selected object and all dependent objects from the server. |
|
||||
+---------------------------+---------------------------------------------------------------------------------------------------------------------------+
|
||||
| *Delete (Force)* | Click to delete the currently selected database with force option. |
|
||||
| *Drop (Force)* | Click to drop the currently selected database with force option. |
|
||||
+---------------------------+---------------------------------------------------------------------------------------------------------------------------+
|
||||
| *Disconnect Database...* | Click to terminate a database connection. |
|
||||
+---------------------------+---------------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
@@ -31,11 +31,15 @@ Either build the sources or get them from macports or similar:
|
||||
*notarization.conf* and set the values accordingly. Note that notarization
|
||||
will fail if the code isn't signed.
|
||||
|
||||
4. To build, go to pgAdmin4 source root directory and execute:
|
||||
4. To build only DMG file, go to pgAdmin4 source root directory and execute:
|
||||
|
||||
make appbundle
|
||||
|
||||
To build both DMG and ZIP files, go to pgAdmin4 source root directory and execute:
|
||||
|
||||
make appbundle BUILD_OPTS="--zip"
|
||||
|
||||
This will create the python virtual environment and install all the required
|
||||
python modules mentioned in the requirements file using pip, build the
|
||||
runtime code and finally create the app bundle and the DMG in *./dist*
|
||||
runtime code and finally create the app bundle and the DMG and/or ZIP in *./dist*
|
||||
directory.
|
||||
@@ -385,6 +385,24 @@ _codesign_bundle() {
|
||||
-i org.pgadmin.pgadmin4 \
|
||||
--sign "${DEVELOPER_ID}" \
|
||||
"${BUNDLE_DIR}"
|
||||
|
||||
echo "Verifying the signature from bundle dir..."
|
||||
codesign --verify --deep --verbose=4 "${BUNDLE_DIR}"
|
||||
}
|
||||
|
||||
_create_zip() {
|
||||
ZIP_NAME="${DMG_NAME%.dmg}.zip"
|
||||
echo "ZIP_NAME: ${ZIP_NAME}"
|
||||
|
||||
echo "Compressing pgAdmin 4.app in bundle dir into ${ZIP_NAME}..."
|
||||
ditto -c -k --sequesterRsrc --keepParent "${BUNDLE_DIR}" "${ZIP_NAME}"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to create the ZIP file. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Successfully created ZIP file: ${ZIP_NAME}"
|
||||
}
|
||||
|
||||
_create_dmg() {
|
||||
@@ -426,17 +444,22 @@ _codesign_dmg() {
|
||||
"${DMG_NAME}"
|
||||
}
|
||||
|
||||
|
||||
_notarize_pkg() {
|
||||
local FILE_NAME="$1"
|
||||
local STAPLE_TARGET="$2"
|
||||
local FILE_LABEL="$3"
|
||||
|
||||
if [ "${CODESIGN}" -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo "Uploading DMG for Notarization ..."
|
||||
STATUS=$(xcrun notarytool submit "${DMG_NAME}" \
|
||||
--team-id "${DEVELOPER_TEAM_ID}" \
|
||||
--apple-id "${DEVELOPER_USER}" \
|
||||
--password "${DEVELOPER_ASP}" 2>&1)
|
||||
echo "Uploading ${FILE_LABEL} for Notarization ..."
|
||||
STATUS=$(xcrun notarytool submit "${FILE_NAME}" \
|
||||
--team-id "${DEVELOPER_TEAM_ID}" \
|
||||
--apple-id "${DEVELOPER_USER}" \
|
||||
--password "${DEVELOPER_ASP}" 2>&1)
|
||||
|
||||
echo "${STATUS}"
|
||||
|
||||
# Get the submission ID
|
||||
SUBMISSION_ID=$(echo "${STATUS}" | awk -F ': ' '/id:/ { print $2; exit; }')
|
||||
@@ -444,16 +467,16 @@ _notarize_pkg() {
|
||||
|
||||
echo "Waiting for Notarization to be completed ..."
|
||||
xcrun notarytool wait "${SUBMISSION_ID}" \
|
||||
--team-id "${DEVELOPER_TEAM_ID}" \
|
||||
--apple-id "${DEVELOPER_USER}" \
|
||||
--password "${DEVELOPER_ASP}"
|
||||
--team-id "${DEVELOPER_TEAM_ID}" \
|
||||
--apple-id "${DEVELOPER_USER}" \
|
||||
--password "${DEVELOPER_ASP}"
|
||||
|
||||
# Print status information
|
||||
REQUEST_STATUS=$(xcrun notarytool info "${SUBMISSION_ID}" \
|
||||
--team-id "${DEVELOPER_TEAM_ID}" \
|
||||
--apple-id "${DEVELOPER_USER}" \
|
||||
--password "${DEVELOPER_ASP}" 2>&1 | \
|
||||
awk -F ': ' '/status:/ { print $2; }')
|
||||
--team-id "${DEVELOPER_TEAM_ID}" \
|
||||
--apple-id "${DEVELOPER_USER}" \
|
||||
--password "${DEVELOPER_ASP}" 2>&1 | \
|
||||
awk -F ': ' '/status:/ { print $2; }')
|
||||
|
||||
if [[ "${REQUEST_STATUS}" != "Accepted" ]]; then
|
||||
echo "Notarization failed."
|
||||
@@ -461,11 +484,28 @@ _notarize_pkg() {
|
||||
fi
|
||||
|
||||
# Staple the notarization
|
||||
echo "Stapling the notarization to the pgAdmin DMG..."
|
||||
if ! xcrun stapler staple "${DMG_NAME}"; then
|
||||
echo "Stapling the notarization to the ${FILE_LABEL}..."
|
||||
if ! xcrun stapler staple "${STAPLE_TARGET}"; then
|
||||
echo "Stapling failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# For ZIP, recreate the zip after stapling
|
||||
if [[ "${FILE_LABEL}" == "ZIP" ]]; then
|
||||
ditto -c -k --keepParent "${BUNDLE_DIR}" "${ZIP_NAME}"
|
||||
if [ $? != 0 ]; then
|
||||
echo "ERROR: could not staple ${ZIP_NAME}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Notarization completed successfully."
|
||||
}
|
||||
|
||||
_notarize_zip() {
|
||||
_notarize_pkg "${ZIP_NAME}" "${BUNDLE_DIR}" "ZIP"
|
||||
}
|
||||
|
||||
_notarize_dmg() {
|
||||
_notarize_pkg "${DMG_NAME}" "${DMG_NAME}" "DMG"
|
||||
}
|
||||
@@ -57,6 +57,32 @@ if [ "${PGADMIN_PYTHON_VERSION}" == "" ]; then
|
||||
export PGADMIN_PYTHON_VERSION=3.13.1
|
||||
fi
|
||||
|
||||
# Initialize variables
|
||||
CREATE_ZIP=0
|
||||
CREATE_DMG=1
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--zip)
|
||||
CREATE_ZIP=1
|
||||
shift
|
||||
;;
|
||||
--help)
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo "Options:"
|
||||
echo " --zip Create both ZIP and DMG files"
|
||||
echo " --help Display this help message"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
echo "Use --help for usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source "${SCRIPT_DIR}/build-functions.sh"
|
||||
|
||||
@@ -69,6 +95,16 @@ _complete_bundle
|
||||
_generate_sbom
|
||||
_codesign_binaries
|
||||
_codesign_bundle
|
||||
_create_dmg
|
||||
_codesign_dmg
|
||||
_notarize_pkg
|
||||
|
||||
# Handle ZIP creation if requested
|
||||
if [ "${CREATE_ZIP}" -eq 1 ]; then
|
||||
_create_zip
|
||||
_notarize_zip
|
||||
fi
|
||||
|
||||
# Handle DMG creation if not disabled
|
||||
if [ "${CREATE_DMG}" -eq 1 ]; then
|
||||
_create_dmg
|
||||
_codesign_dmg
|
||||
_notarize_dmg
|
||||
fi
|
||||
@@ -80,7 +80,6 @@ setup(
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
|
||||
# Supported programming languages
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
|
||||
@@ -163,7 +163,7 @@ BuildArch: noarch
|
||||
Summary: The web interface for pgAdmin, hosted under Apache HTTPD.
|
||||
License: PostgreSQL
|
||||
URL: https://www.pgadmin.org/
|
||||
Requires: ${APP_NAME}-server = ${RPM_VERSION}, httpd, ${PYTHON_BINARY_WITHOUT_DOTS}-mod_wsgi
|
||||
Requires: ${APP_NAME}-server = ${RPM_VERSION}, httpd, ${PYTHON_BINARY_WITHOUT_DOTS}-mod_wsgi, policycoreutils-python-utils
|
||||
|
||||
%description
|
||||
The web interface for pgAdmin, hosted under Apache HTTPD. pgAdmin is the most popular and feature rich Open Source administration and development platform for PostgreSQL, the most advanced Open Source database in the world.
|
||||
|
||||
@@ -35,6 +35,12 @@ DOC_TARBALL_NAME=$(echo "${APP_NAME}-${APP_LONG_VERSION}-docs" | sed 's/ //g' |
|
||||
# Get the github timestamp
|
||||
git log -1 --format='%H %as' > web/commit_hash
|
||||
|
||||
pushd web > /dev/null || exit
|
||||
# Set the git:hash to empty as we already set the commit_hash
|
||||
# this helps in building pgAdmin directly from tar ball without needing git.
|
||||
npm pkg set "scripts.git:hash=exit 0"
|
||||
popd > /dev/null || exit
|
||||
|
||||
# Output basic details to show we're working
|
||||
echo "Building tarballs for ${APP_NAME} version ${APP_LONG_VERSION}..."
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ ArchitecturesInstallIn64BitMode={#MyAppArchitecturesMode}
|
||||
AllowNoIcons=yes
|
||||
WizardImageFile=sidebar.bmp
|
||||
MinVersion={#MinimumWindowsVer}
|
||||
SignTool=pgAdminSigntool
|
||||
SignedUninstaller=yes
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
###############################################################################
|
||||
|
||||
Authlib==1.6.*
|
||||
azure-identity==1.23.0
|
||||
azure-identity==1.24.0
|
||||
azure-mgmt-rdbms==10.1.0
|
||||
azure-mgmt-resource==23.4.0
|
||||
azure-mgmt-resource==24.0.0
|
||||
azure-mgmt-subscription==3.1.1
|
||||
bcrypt==4.3.*
|
||||
boto3==1.38.*
|
||||
boto3==1.40.*
|
||||
cryptography==45.0.*
|
||||
Flask-Babel==4.0.*
|
||||
Flask-Compress==1.*
|
||||
@@ -47,7 +47,7 @@ python-dateutil==2.*
|
||||
pytz==2025.*
|
||||
pywinpty==2.0.*; sys_platform=="win32"
|
||||
qrcode[pil]==8.*; python_version >= '3.9'
|
||||
setuptools==80.*; python_version >= '3.12'
|
||||
setuptools==80.*
|
||||
SQLAlchemy==2.*
|
||||
sqlparse==0.*
|
||||
sshtunnel==0.*
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
"start": "electron .",
|
||||
"linter": "yarn run eslint -c .eslintrc.js ."
|
||||
},
|
||||
"packageManager": "yarn@3.8.7",
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"devDependencies": {
|
||||
"electron": "36.4.0",
|
||||
"eslint": "^9.29.0",
|
||||
"electron": "37.2.6",
|
||||
"eslint": "^9.33.0",
|
||||
"eslint-plugin-unused-imports": "^4.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.10.0",
|
||||
"axios": "^1.11.0",
|
||||
"electron-context-menu": "^4.1.0",
|
||||
"electron-store": "^10.1.0"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
import { autoUpdater, ipcMain } from 'electron';
|
||||
import { refreshMenus } from './menu.js';
|
||||
import * as misc from './misc.js';
|
||||
|
||||
// This function stores the flags in configStore that are needed
|
||||
// for auto-update and refreshes menus
|
||||
export function updateConfigAndMenus(event, configStore, pgAdminMainScreen, menuCallbacks) {
|
||||
const flags = {
|
||||
'update-available': { update_downloading: true },
|
||||
'update-not-available': { update_downloading: false },
|
||||
'update-downloaded': { update_downloading: false, update_downloaded: true },
|
||||
'error-close': { update_downloading: false, update_downloaded: false },
|
||||
};
|
||||
const flag = flags[event];
|
||||
if (flag) {
|
||||
Object.entries(flag).forEach(([k, v]) => configStore.set(k, v));
|
||||
refreshMenus(pgAdminMainScreen, configStore, menuCallbacks);
|
||||
}
|
||||
}
|
||||
|
||||
// This function registers autoUpdater event listeners ONCE
|
||||
function registerAutoUpdaterEvents({ pgAdminMainScreen, configStore, menuCallbacks }) {
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
misc.writeServerLog('[Auto-Updater]: Checking for update...');
|
||||
});
|
||||
|
||||
autoUpdater.on('update-available', () => {
|
||||
updateConfigAndMenus('update-available', configStore, pgAdminMainScreen, menuCallbacks);
|
||||
misc.writeServerLog('[Auto-Updater]: Update downloading...');
|
||||
pgAdminMainScreen.webContents.send('notifyAppAutoUpdate', { update_downloading: true });
|
||||
});
|
||||
|
||||
autoUpdater.on('update-not-available', () => {
|
||||
updateConfigAndMenus('update-not-available', configStore, pgAdminMainScreen, menuCallbacks);
|
||||
misc.writeServerLog('[Auto-Updater]: No update available...');
|
||||
pgAdminMainScreen.webContents.send('notifyAppAutoUpdate', { no_update_available: true });
|
||||
});
|
||||
|
||||
autoUpdater.on('update-downloaded', () => {
|
||||
updateConfigAndMenus('update-downloaded', configStore, pgAdminMainScreen, menuCallbacks);
|
||||
misc.writeServerLog('[Auto-Updater]: Update downloaded...');
|
||||
pgAdminMainScreen.webContents.send('notifyAppAutoUpdate', { update_downloaded: true });
|
||||
});
|
||||
|
||||
autoUpdater.on('error', (message) => {
|
||||
updateConfigAndMenus('error-close', configStore, pgAdminMainScreen, menuCallbacks);
|
||||
misc.writeServerLog(`[Auto-Updater]: ${message}`);
|
||||
pgAdminMainScreen.webContents.send('notifyAppAutoUpdate', { error: true, errMsg: message });
|
||||
});
|
||||
}
|
||||
|
||||
// Handles 'sendDataForAppUpdate' IPC event: updates config, refreshes menus, triggers update check, or installs update if requested.
|
||||
function handleSendDataForAppUpdate({
|
||||
pgAdminMainScreen,
|
||||
configStore,
|
||||
menuCallbacks,
|
||||
baseUrl,
|
||||
UUID,
|
||||
forceQuitAndInstallUpdate,
|
||||
}) {
|
||||
return (_, data) => {
|
||||
// Only update the auto-update enabled flag and refresh menus if the value has changed or is not set
|
||||
if (typeof data.check_for_updates !== 'undefined') {
|
||||
const currentFlag = configStore.get('auto_update_enabled');
|
||||
if (typeof currentFlag === 'undefined' || currentFlag !== data.check_for_updates) {
|
||||
configStore.set('auto_update_enabled', data.check_for_updates);
|
||||
refreshMenus(pgAdminMainScreen, configStore, menuCallbacks);
|
||||
}
|
||||
}
|
||||
// If auto-update is enabled, proceed with the update check
|
||||
if (
|
||||
data.auto_update_url &&
|
||||
data.upgrade_version &&
|
||||
data.upgrade_version_int &&
|
||||
data.current_version_int &&
|
||||
data.product_name
|
||||
) {
|
||||
const ftpUrl = encodeURIComponent(
|
||||
`${data.auto_update_url}/pgadmin4-${data.upgrade_version}-${process.arch}.zip`
|
||||
);
|
||||
let serverUrl = `${baseUrl}/misc/auto_update/${data.current_version_int}/${data.upgrade_version}/${data.upgrade_version_int}/${data.product_name}/${ftpUrl}/?key=${UUID}`;
|
||||
|
||||
try {
|
||||
autoUpdater.setFeedURL({ url: serverUrl });
|
||||
misc.writeServerLog('[Auto-Updater]: Initiating update check...');
|
||||
autoUpdater.checkForUpdates();
|
||||
} catch (err) {
|
||||
misc.writeServerLog('[Auto-Updater]: Error setting autoUpdater feed URL: ' + err.message);
|
||||
if (pgAdminMainScreen) {
|
||||
pgAdminMainScreen.webContents.send('notifyAppAutoUpdate', {
|
||||
error: true,
|
||||
errMsg: 'Failed to check for updates. Please try again later.',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If the user has requested to install the update immediately
|
||||
if (data.install_update_now) {
|
||||
forceQuitAndInstallUpdate();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function setupAutoUpdater({
|
||||
pgAdminMainScreen,
|
||||
configStore,
|
||||
menuCallbacks,
|
||||
baseUrl,
|
||||
UUID,
|
||||
forceQuitAndInstallUpdate,
|
||||
}) {
|
||||
// For now only macOS is supported for electron auto-update
|
||||
if (process.platform === 'darwin') {
|
||||
registerAutoUpdaterEvents({ pgAdminMainScreen, configStore, menuCallbacks });
|
||||
ipcMain.on(
|
||||
'sendDataForAppUpdate',
|
||||
handleSendDataForAppUpdate({
|
||||
pgAdminMainScreen,
|
||||
configStore,
|
||||
menuCallbacks,
|
||||
baseUrl,
|
||||
UUID,
|
||||
forceQuitAndInstallUpdate,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
import { app, ipcMain, dialog, BrowserWindow, shell } from 'electron';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { Buffer } from 'buffer';
|
||||
import { setBadge, clearBadge, clearProgress, setProgress } from './progress.js';
|
||||
import { writeServerLog } from './misc.js';
|
||||
|
||||
|
||||
@@ -7,77 +7,145 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import { app, Menu, ipcMain, BrowserWindow } from 'electron';
|
||||
import { app, Menu, ipcMain, BrowserWindow, globalShortcut } from 'electron';
|
||||
|
||||
const isMac = process.platform == 'darwin';
|
||||
const isLinux = process.platform == 'linux';
|
||||
let mainMenu;
|
||||
let cachedMenus;
|
||||
|
||||
function buildMenu(pgadminMenus, pgAdminMainScreen, callbacks) {
|
||||
// Use to convert shortcut to accelerator for electron.
|
||||
function convertShortcutToAccelerator({ control, meta, shift, alt, key } = {}) {
|
||||
// Store active modifier keys into an array.
|
||||
const mods = [
|
||||
control && 'Ctrl',
|
||||
meta && 'Cmd',
|
||||
shift && 'Shift',
|
||||
alt && 'Alt',
|
||||
].filter(Boolean); // Remove any falsy values
|
||||
// Get the actual key character and convert to uppercase.
|
||||
const k = key?.char?.toUpperCase();
|
||||
if (!k) return;
|
||||
// Combine modifiers and key into a single string.
|
||||
return [...mods, k].join('+');
|
||||
}
|
||||
|
||||
// Binds click events to all menu and submenu items recursively.
|
||||
function bindMenuClicks(pgadminMenus, pgAdminMainScreen) {
|
||||
return pgadminMenus.map((menuItem) => ({
|
||||
...menuItem,
|
||||
submenu: menuItem.submenu?.map((subMenuItem) => {
|
||||
const smName = `${menuItem.name}_${subMenuItem.name}`;
|
||||
return {
|
||||
...subMenuItem,
|
||||
accelerator: convertShortcutToAccelerator(subMenuItem.shortcut),
|
||||
click: (_menuItem, _browserWindow, event)=>{
|
||||
if(event?.triggeredByAccelerator) {
|
||||
// We will ignore the click event if it is triggered by an accelerator.
|
||||
// We use accelerator to only show the shortcut title in the menu.
|
||||
// The actual shortcut is already handled by pgAdmin.
|
||||
return;
|
||||
}
|
||||
pgAdminMainScreen.webContents.send('menu-click', smName);
|
||||
},
|
||||
submenu: subMenuItem.submenu?.map((deeperSubMenuItem) => ({
|
||||
...deeperSubMenuItem,
|
||||
accelerator: convertShortcutToAccelerator(deeperSubMenuItem.shortcut),
|
||||
click: (_menuItem, _browserWindow, event)=>{
|
||||
if(event?.triggeredByAccelerator) {
|
||||
// We will ignore the click event if it is triggered by an accelerator.
|
||||
// We use accelerator to only show the shortcut title in the menu.
|
||||
// The actual shortcut is already handled by pgAdmin.
|
||||
return;
|
||||
}
|
||||
pgAdminMainScreen.webContents.send('menu-click', `${smName}_${deeperSubMenuItem.name}`);
|
||||
},
|
||||
})),
|
||||
};
|
||||
}),
|
||||
}));
|
||||
}
|
||||
|
||||
// Handles auto-update related menu items for macOS.
|
||||
// Adds or disables update menu items based on config state.
|
||||
function handleAutoUpdateMenu(menuFile, configStore, callbacks) {
|
||||
if (!configStore.get('auto_update_enabled')) return;
|
||||
if (configStore.get('update_downloaded')) {
|
||||
// Add "Restart to Update" if update is downloaded
|
||||
menuFile.submenu.unshift({
|
||||
name: 'mnu_restart_to_update',
|
||||
id: 'mnu_restart_to_update',
|
||||
label: 'Restart to Update...',
|
||||
enabled: true,
|
||||
priority: 998,
|
||||
click: callbacks['restart_to_update'],
|
||||
});
|
||||
} else {
|
||||
// Add "Check for Updates" if update is not downloaded
|
||||
menuFile.submenu.unshift({
|
||||
name: 'mnu_check_updates',
|
||||
id: 'mnu_check_updates',
|
||||
label: 'Check for Updates...',
|
||||
enabled: true,
|
||||
priority: 998,
|
||||
click: callbacks['check_for_updates'],
|
||||
});
|
||||
}
|
||||
// Disable "Check for Updates" if update is downloading
|
||||
if (configStore.get('update_downloading')) {
|
||||
menuFile.submenu.forEach((item) => {
|
||||
if (item.id == 'mnu_check_updates') item.enabled = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Remove About pgAdmin 4 from help menu and add it to the top of menuFile submenu.
|
||||
function moveAboutMenuToTop(pgadminMenus, menuFile) {
|
||||
const helpMenu = pgadminMenus.find((menu) => menu.name == 'help');
|
||||
if (!helpMenu) return;
|
||||
const aboutItem = helpMenu.submenu.find((item) => item.name === 'mnu_about');
|
||||
if (!aboutItem) return;
|
||||
helpMenu.submenu = helpMenu.submenu.filter((item) => item.name !== 'mnu_about');
|
||||
menuFile.submenu.unshift(aboutItem);
|
||||
menuFile.submenu.splice(2, 0, { type: 'separator' });
|
||||
}
|
||||
|
||||
// Builds the application menu template and binds menu click events.
|
||||
// Handles platform-specific menu structure and dynamic menu items.
|
||||
function buildMenu(pgadminMenus, pgAdminMainScreen, configStore, callbacks) {
|
||||
const template = [];
|
||||
|
||||
// bind all menus click event.
|
||||
pgadminMenus = pgadminMenus.map((menuItem)=>{
|
||||
return {
|
||||
...menuItem,
|
||||
submenu: menuItem.submenu?.map((subMenuItem)=>{
|
||||
const smName = `${menuItem.name}_${subMenuItem.name}`;
|
||||
return {
|
||||
...subMenuItem,
|
||||
click: ()=>{
|
||||
pgAdminMainScreen.webContents.send('menu-click', smName);
|
||||
},
|
||||
submenu: subMenuItem.submenu?.map((deeperSubMenuItem)=>{
|
||||
return {
|
||||
...deeperSubMenuItem,
|
||||
click: ()=>{
|
||||
pgAdminMainScreen.webContents.send('menu-click', `${smName}_${deeperSubMenuItem.name}`);
|
||||
},
|
||||
};
|
||||
}),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
pgadminMenus = bindMenuClicks(pgadminMenus, pgAdminMainScreen);
|
||||
|
||||
let menuFile = pgadminMenus.shift();
|
||||
|
||||
// macOS-specific menu modifications
|
||||
if (isMac) {
|
||||
// Remove About pgAdmin 4 from help menu and add it to the top of menuFile submenu.
|
||||
const helpMenu = pgadminMenus.find((menu) => menu.name == 'help');
|
||||
if (helpMenu) {
|
||||
const aboutItem = helpMenu.submenu.find((item) => item.name === 'mnu_about');
|
||||
if (aboutItem) {
|
||||
helpMenu.submenu = helpMenu.submenu.filter((item) => item.name !== 'mnu_about');
|
||||
menuFile.submenu.unshift(aboutItem);
|
||||
menuFile.submenu.splice(1, 0, { type: 'separator' });
|
||||
}
|
||||
}
|
||||
handleAutoUpdateMenu(menuFile, configStore, callbacks);
|
||||
moveAboutMenuToTop(pgadminMenus, menuFile);
|
||||
}
|
||||
|
||||
|
||||
template.push({
|
||||
...menuFile,
|
||||
submenu: [
|
||||
...menuFile.submenu,
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'View Logs...', click: callbacks['view_logs'],
|
||||
},
|
||||
{
|
||||
label: 'Configure runtime...', click: callbacks['configure'],
|
||||
},
|
||||
{ label: 'View Logs...', click: callbacks['view_logs'] },
|
||||
{ label: 'Configure runtime...', click: callbacks['configure'] },
|
||||
{ type: 'separator' },
|
||||
...(isMac ? [
|
||||
{ role: 'hide' },
|
||||
{ role: 'hideOthers' },
|
||||
{ role: 'unhide' },
|
||||
{ type: 'separator' },
|
||||
] : []),
|
||||
...(isMac
|
||||
? [
|
||||
{ role: 'hide' },
|
||||
{ role: 'hideOthers' },
|
||||
{ role: 'unhide' },
|
||||
{ type: 'separator' },
|
||||
]
|
||||
: []),
|
||||
{ role: 'quit' },
|
||||
],
|
||||
});
|
||||
|
||||
if(isMac) {
|
||||
if (isMac) {
|
||||
template[0].label = app.name;
|
||||
}
|
||||
|
||||
@@ -89,8 +157,14 @@ function buildMenu(pgadminMenus, pgAdminMainScreen, callbacks) {
|
||||
{
|
||||
label: 'View',
|
||||
submenu: [
|
||||
{ label: 'Reload', click: callbacks['reloadApp']},
|
||||
{ label: 'Toggle Developer Tools', click: ()=>BrowserWindow.getFocusedWindow().webContents.openDevTools({ mode: 'bottom' })},
|
||||
{ label: 'Reload', click: callbacks['reloadApp'] },
|
||||
{
|
||||
label: 'Toggle Developer Tools',
|
||||
click: () =>
|
||||
BrowserWindow.getFocusedWindow().webContents.openDevTools({
|
||||
mode: 'bottom',
|
||||
}),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ role: 'resetZoom' },
|
||||
{ role: 'zoomIn' },
|
||||
@@ -98,22 +172,34 @@ function buildMenu(pgadminMenus, pgAdminMainScreen, callbacks) {
|
||||
{ type: 'separator' },
|
||||
].concat(isLinux ? [] : [{ role: 'togglefullscreen' }]),
|
||||
},
|
||||
{ role: 'windowMenu' },
|
||||
{ role: 'windowMenu' }
|
||||
);
|
||||
|
||||
template.push(pgadminMenus[pgadminMenus.length-1]);
|
||||
template.push(pgadminMenus[pgadminMenus.length - 1]);
|
||||
|
||||
return Menu.buildFromTemplate(template);
|
||||
}
|
||||
|
||||
export function setupMenu(pgAdminMainScreen, callbacks={}) {
|
||||
function buildAndSetMenus(menus, pgAdminMainScreen, configStore, callbacks={}) {
|
||||
mainMenu = buildMenu(menus, pgAdminMainScreen, configStore, callbacks);
|
||||
if(isMac) {
|
||||
Menu.setApplicationMenu(mainMenu);
|
||||
} else {
|
||||
pgAdminMainScreen.setMenu(mainMenu);
|
||||
}
|
||||
}
|
||||
|
||||
export function refreshMenus(pgAdminMainScreen, configStore, callbacks={}) {
|
||||
buildAndSetMenus(cachedMenus, pgAdminMainScreen, configStore, callbacks);
|
||||
}
|
||||
|
||||
export function setupMenu(pgAdminMainScreen, configStore, callbacks={}) {
|
||||
ipcMain.on('setMenus', (event, menus)=>{
|
||||
mainMenu = buildMenu(menus, pgAdminMainScreen, callbacks);
|
||||
if(isMac) {
|
||||
Menu.setApplicationMenu(mainMenu);
|
||||
} else {
|
||||
pgAdminMainScreen.setMenu(mainMenu);
|
||||
}
|
||||
// this is important because the shortcuts are registered multiple times
|
||||
// when the menu is set multiple times using accelerators.
|
||||
globalShortcut.unregisterAll();
|
||||
cachedMenus = menus; //It will be used later for refreshing the menus
|
||||
buildAndSetMenus(menus, pgAdminMainScreen, configStore, callbacks);
|
||||
|
||||
ipcMain.on('enable-disable-menu-items', (event, menu, menuItem)=>{
|
||||
const menuItemObj = mainMenu.getMenuItemById(menuItem?.id);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import { app, BrowserWindow, dialog, ipcMain, Menu, shell, screen } from 'electron';
|
||||
import { app, BrowserWindow, dialog, ipcMain, Menu, shell, screen, autoUpdater } from 'electron';
|
||||
import axios from 'axios';
|
||||
import Store from 'electron-store';
|
||||
import fs from 'fs';
|
||||
@@ -17,6 +17,7 @@ import { fileURLToPath } from 'url';
|
||||
import { setupMenu } from './menu.js';
|
||||
import contextMenu from 'electron-context-menu';
|
||||
import { setupDownloader } from './downloader.js';
|
||||
import { setupAutoUpdater, updateConfigAndMenus } from './autoUpdaterHandler.js';
|
||||
|
||||
const configStore = new Store({
|
||||
defaults: {
|
||||
@@ -35,9 +36,13 @@ let configureWindow = null,
|
||||
viewLogWindow = null;
|
||||
|
||||
let serverPort = 5050;
|
||||
let UUID = crypto.randomUUID();
|
||||
|
||||
let appStartTime = (new Date()).getTime();
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
let baseUrl = `http://127.0.0.1:${serverPort}`;
|
||||
|
||||
let docsURLSubStrings = ['www.enterprisedb.com', 'www.postgresql.org', 'www.pgadmin.org', 'help/help'];
|
||||
|
||||
process.env['ELECTRON_ENABLE_SECURITY_WARNINGS'] = false;
|
||||
@@ -45,6 +50,40 @@ process.env['ELECTRON_ENABLE_SECURITY_WARNINGS'] = false;
|
||||
// Paths to the rest of the app
|
||||
let [pythonPath, pgadminFile] = misc.getAppPaths(__dirname);
|
||||
|
||||
const menuCallbacks = {
|
||||
'check_for_updates': ()=>{
|
||||
pgAdminMainScreen.webContents.send('notifyAppAutoUpdate', {check_version_update: true});
|
||||
},
|
||||
'restart_to_update': ()=>{
|
||||
forceQuitAndInstallUpdate();
|
||||
},
|
||||
'view_logs': ()=>{
|
||||
if(viewLogWindow === null || viewLogWindow?.isDestroyed()) {
|
||||
viewLogWindow = new BrowserWindow({
|
||||
show: false,
|
||||
width: 800,
|
||||
height: 460,
|
||||
position: 'center',
|
||||
resizable: false,
|
||||
parent: pgAdminMainScreen,
|
||||
icon: '../../assets/pgAdmin4.png',
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'other_preload.js'),
|
||||
},
|
||||
});
|
||||
viewLogWindow.loadFile('./src/html/view_log.html');
|
||||
viewLogWindow.once('ready-to-show', ()=>{
|
||||
viewLogWindow.show();
|
||||
});
|
||||
} else {
|
||||
viewLogWindow.hide();
|
||||
viewLogWindow.show();
|
||||
}
|
||||
},
|
||||
'configure': openConfigure,
|
||||
'reloadApp': reloadApp,
|
||||
};
|
||||
|
||||
// Do not allow a second instance of pgAdmin to run.
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
if (!gotTheLock) {
|
||||
@@ -153,6 +192,28 @@ function reloadApp() {
|
||||
currWin.webContents.reload();
|
||||
}
|
||||
|
||||
|
||||
// Remove auto_update_enabled from configStore on app close or quit
|
||||
function cleanupAutoUpdateFlag() {
|
||||
if (configStore.has('auto_update_enabled')) {
|
||||
configStore.delete('auto_update_enabled');
|
||||
}
|
||||
}
|
||||
|
||||
// This function will force quit and install update and restart the app
|
||||
function forceQuitAndInstallUpdate() {
|
||||
// Disable beforeunload handlers
|
||||
const preventUnload = (event) => {
|
||||
event.preventDefault();
|
||||
pgAdminMainScreen.webContents.off('will-prevent-unload', preventUnload);
|
||||
};
|
||||
pgAdminMainScreen.webContents.on('will-prevent-unload', preventUnload);
|
||||
// Set flag to show notification after restart
|
||||
configStore.set('update_installed', true);
|
||||
cleanupAutoUpdateFlag();
|
||||
autoUpdater.quitAndInstall();
|
||||
}
|
||||
|
||||
// This functions is used to start the pgAdmin4 server by spawning a
|
||||
// separate process.
|
||||
function startDesktopMode() {
|
||||
@@ -162,7 +223,6 @@ function startDesktopMode() {
|
||||
return;
|
||||
|
||||
let pingIntervalID;
|
||||
let UUID = crypto.randomUUID();
|
||||
// Set the environment variables so that pgAdmin 4 server
|
||||
// starts listening on the appropriate port.
|
||||
process.env.PGADMIN_INT_PORT = serverPort;
|
||||
@@ -170,7 +230,7 @@ function startDesktopMode() {
|
||||
process.env.PGADMIN_SERVER_MODE = 'OFF';
|
||||
|
||||
// Start Page URL
|
||||
const baseUrl = `http://127.0.0.1:${serverPort}`;
|
||||
baseUrl = `http://127.0.0.1:${serverPort}`;
|
||||
startPageUrl = `${baseUrl}/?key=${UUID}`;
|
||||
serverCheckUrl = `${baseUrl}/misc/ping?key=${UUID}`;
|
||||
|
||||
@@ -307,36 +367,10 @@ function launchPgAdminWindow() {
|
||||
splashWindow.close();
|
||||
pgAdminMainScreen.webContents.session.clearCache();
|
||||
|
||||
setupMenu(pgAdminMainScreen, {
|
||||
'view_logs': ()=>{
|
||||
if(viewLogWindow === null || viewLogWindow?.isDestroyed()) {
|
||||
viewLogWindow = new BrowserWindow({
|
||||
show: false,
|
||||
width: 800,
|
||||
height: 460,
|
||||
position: 'center',
|
||||
resizable: false,
|
||||
parent: pgAdminMainScreen,
|
||||
icon: '../../assets/pgAdmin4.png',
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'other_preload.js'),
|
||||
},
|
||||
});
|
||||
viewLogWindow.loadFile('./src/html/view_log.html');
|
||||
viewLogWindow.once('ready-to-show', ()=>{
|
||||
viewLogWindow.show();
|
||||
});
|
||||
} else {
|
||||
viewLogWindow.hide();
|
||||
viewLogWindow.show();
|
||||
}
|
||||
},
|
||||
'configure': openConfigure,
|
||||
'reloadApp': reloadApp,
|
||||
});
|
||||
|
||||
setupDownloader();
|
||||
|
||||
setupMenu(pgAdminMainScreen, configStore, menuCallbacks);
|
||||
|
||||
setupDownloader()
|
||||
|
||||
pgAdminMainScreen.loadURL(startPageUrl);
|
||||
|
||||
const bounds = configStore.get('bounds');
|
||||
@@ -346,6 +380,15 @@ function launchPgAdminWindow() {
|
||||
|
||||
pgAdminMainScreen.show();
|
||||
|
||||
setupAutoUpdater({
|
||||
pgAdminMainScreen,
|
||||
configStore,
|
||||
menuCallbacks,
|
||||
baseUrl,
|
||||
UUID,
|
||||
forceQuitAndInstallUpdate,
|
||||
});
|
||||
|
||||
pgAdminMainScreen.webContents.setWindowOpenHandler(({url})=>{
|
||||
let openDocsInBrowser = configStore.get('openDocsInBrowser', true);
|
||||
let isDocURL = false;
|
||||
@@ -377,18 +420,50 @@ function launchPgAdminWindow() {
|
||||
});
|
||||
|
||||
pgAdminMainScreen.on('closed', ()=>{
|
||||
cleanupAutoUpdateFlag();
|
||||
misc.cleanupAndQuitApp();
|
||||
});
|
||||
|
||||
pgAdminMainScreen.on('close', () => {
|
||||
configStore.set('bounds', pgAdminMainScreen.getBounds());
|
||||
updateConfigAndMenus('error-close', configStore, pgAdminMainScreen, menuCallbacks);
|
||||
pgAdminMainScreen.removeAllListeners('close');
|
||||
pgAdminMainScreen.close();
|
||||
});
|
||||
|
||||
// Notify if update was installed (fix: always check after main window is ready)
|
||||
notifyUpdateInstalled();
|
||||
}
|
||||
|
||||
let splashWindow;
|
||||
|
||||
// Helper to notify update installed after restart
|
||||
function notifyUpdateInstalled() {
|
||||
if (configStore.get('update_installed')) {
|
||||
try {
|
||||
// Notify renderer
|
||||
if (pgAdminMainScreen) {
|
||||
misc.writeServerLog('[Auto-Updater]: Update installed successfully...');
|
||||
setTimeout(() => {
|
||||
pgAdminMainScreen.webContents.send('notifyAppAutoUpdate', {update_installed: true});
|
||||
}, 10000);
|
||||
} else {
|
||||
// If main screen not ready, wait and send after it's created
|
||||
app.once('browser-window-created', (event, window) => {
|
||||
misc.writeServerLog('[Auto-Updater]: Update installed successfully...');
|
||||
setTimeout(() => {
|
||||
pgAdminMainScreen.webContents.send('notifyAppAutoUpdate', {update_installed: true});
|
||||
}, 10000);
|
||||
});
|
||||
}
|
||||
// Reset the flag
|
||||
configStore.set('update_installed', false);
|
||||
} catch (err) {
|
||||
misc.writeServerLog(`[Auto-Updater]: ${err}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setup preload events.
|
||||
ipcMain.handle('showOpenDialog', (e, options) => dialog.showOpenDialog(BrowserWindow.fromWebContents(e.sender), options));
|
||||
ipcMain.handle('showSaveDialog', (e, options) => dialog.showSaveDialog(BrowserWindow.fromWebContents(e.sender), options));
|
||||
@@ -406,12 +481,12 @@ ipcMain.on('restartApp', ()=>{
|
||||
app.relaunch();
|
||||
app.exit(0);
|
||||
});
|
||||
ipcMain.on('log', (_e, text) => ()=>{
|
||||
ipcMain.on('log', (_e, text) => {
|
||||
misc.writeServerLog(text);
|
||||
});
|
||||
ipcMain.on('focus', (e) => {
|
||||
app.focus({steal: true});
|
||||
const callerWindow = BrowserWindow.fromWebContents(e.sender)
|
||||
const callerWindow = BrowserWindow.fromWebContents(e.sender);
|
||||
if (callerWindow) {
|
||||
if (callerWindow.isMinimized()) callerWindow.restore();
|
||||
callerWindow.focus();
|
||||
@@ -428,6 +503,7 @@ ipcMain.handle('checkPortAvailable', async (_e, fixedPort)=>{
|
||||
});
|
||||
ipcMain.handle('openConfigure', openConfigure);
|
||||
|
||||
|
||||
app.whenReady().then(() => {
|
||||
splashWindow = new BrowserWindow({
|
||||
transparent: true,
|
||||
|
||||
@@ -31,5 +31,11 @@ contextBridge.exposeInMainWorld('electronUI', {
|
||||
downloadStreamSaveTotal: (...args) => ipcRenderer.send('download-stream-save-total', ...args),
|
||||
downloadStreamSaveEnd: (...args) => ipcRenderer.send('download-stream-save-end', ...args),
|
||||
downloadBase64UrlData: (...args) => ipcRenderer.invoke('download-base64-url-data', ...args),
|
||||
downloadTextData: (...args) => ipcRenderer.invoke('download-text-data', ...args)
|
||||
downloadTextData: (...args) => ipcRenderer.invoke('download-text-data', ...args),
|
||||
//Auto-updater related functions
|
||||
sendDataForAppUpdate: (data) => ipcRenderer.send('sendDataForAppUpdate', data),
|
||||
notifyAppAutoUpdate: (callback) => {
|
||||
ipcRenderer.removeAllListeners('notifyAppAutoUpdate'); // Clean up previous listeners
|
||||
ipcRenderer.on('notifyAppAutoUpdate', (_, data) => callback(data));
|
||||
},
|
||||
});
|
||||
@@ -173,7 +173,15 @@ def format_email_html(data, skip_issues_set=None):
|
||||
num_bugs_housekeeping = len(filtered_bugs)
|
||||
|
||||
# Build HTML output
|
||||
output = f"<h2><strong>pgAdmin 4 v{version} Released</strong></h2>\n"
|
||||
output = """<style>
|
||||
* {
|
||||
font-size: 14px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
li {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
</style>"""
|
||||
output += (
|
||||
f"<p>The pgAdmin Development Team is pleased to announce "
|
||||
f"pgAdmin 4 version {version}.</p>\n"
|
||||
@@ -186,8 +194,9 @@ def format_email_html(data, skip_issues_set=None):
|
||||
f"{'s' if num_bugs_housekeeping != 1 else ''}. "
|
||||
)
|
||||
output += (
|
||||
f'For more details please see the '
|
||||
f'<a href="{release_url}">Release Notes</a>.</p>\n'
|
||||
f'For more details please see the release notes at:'
|
||||
f'<p> '
|
||||
f'<a href="{release_url}">{release_url}</a></p>\n'
|
||||
)
|
||||
output += (
|
||||
'<p>pgAdmin is the leading Open Source graphical management '
|
||||
@@ -199,7 +208,7 @@ def format_email_html(data, skip_issues_set=None):
|
||||
|
||||
# Add features section only if items remain after filtering
|
||||
if filtered_features:
|
||||
output += "<p><strong>Features</strong></p>\n<ul>\n"
|
||||
output += "<p><strong>Features:</strong></p>\n<ul>\n"
|
||||
for item in filtered_features:
|
||||
desc = html.escape(item.get('description', 'N/A').strip())
|
||||
output += f" <li>{desc}.</li>\n"
|
||||
@@ -209,7 +218,7 @@ def format_email_html(data, skip_issues_set=None):
|
||||
if filtered_bugs:
|
||||
# Note: Heading was "Bugs/Housekeeping" in pasted code,
|
||||
# changed to "Bug fixes" to match GDoc style mentioned earlier
|
||||
output += "<p><strong>Bug fixes</strong></p>\n<ul>\n"
|
||||
output += "<p><strong>Bug fixes:</strong></p>\n<ul>\n"
|
||||
for item in filtered_bugs:
|
||||
desc = html.escape(item.get('description', 'N/A').strip())
|
||||
output += f" <li>{desc}.</li>\n"
|
||||
@@ -222,9 +231,8 @@ def format_email_html(data, skip_issues_set=None):
|
||||
output += (
|
||||
"Docker Container, RPM, DEB Package, and source code tarball from:<br>"
|
||||
)
|
||||
output += f'<a href="{download_url}">{download_url}</a></p>\n'
|
||||
# Note: Adjusted sign-off slightly from pasted code to match GDoc examples
|
||||
output += "<p>--<br>The pgAdmin Team</p>\n"
|
||||
output += (f' '
|
||||
f'<a href="{download_url}">{download_url}</a></p>\n')
|
||||
|
||||
return output
|
||||
|
||||
|
||||
@@ -365,6 +365,9 @@ SESSION_DB_PATH = os.path.join(DATA_DIR, 'sessions')
|
||||
|
||||
SESSION_COOKIE_NAME = 'pga4_session'
|
||||
|
||||
# Session digest method
|
||||
SESSION_DIGEST_METHOD = 'hashlib.sha1'
|
||||
|
||||
##########################################################################
|
||||
# Mail server settings
|
||||
##########################################################################
|
||||
@@ -465,12 +468,14 @@ DEFAULT_BINARY_PATHS = {
|
||||
"pg-15": "",
|
||||
"pg-16": "",
|
||||
"pg-17": "",
|
||||
"pg-18": "",
|
||||
"ppas": "",
|
||||
"ppas-13": "",
|
||||
"ppas-14": "",
|
||||
"ppas-15": "",
|
||||
"ppas-16": "",
|
||||
"ppas-17": ""
|
||||
"ppas-17": "",
|
||||
"ppas-18": ""
|
||||
}
|
||||
|
||||
##########################################################################
|
||||
@@ -485,12 +490,14 @@ FIXED_BINARY_PATHS = {
|
||||
"pg-15": "",
|
||||
"pg-16": "",
|
||||
"pg-17": "",
|
||||
"pg-18": "",
|
||||
"ppas": "",
|
||||
"ppas-13": "",
|
||||
"ppas-14": "",
|
||||
"ppas-15": "",
|
||||
"ppas-16": "",
|
||||
"ppas-17": ""
|
||||
"ppas-17": "",
|
||||
"ppas-18": ""
|
||||
}
|
||||
|
||||
##########################################################################
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2025, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
"""
|
||||
|
||||
Revision ID: e24b1c4def17
|
||||
Revises: c62bcc14c3d6
|
||||
Create Date: 2025-07-14 10:49:07.292086
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'e24b1c4def17'
|
||||
down_revision = 'c62bcc14c3d6'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# delete all data from the table
|
||||
op.execute("DELETE FROM application_state")
|
||||
|
||||
# Drop unused columns
|
||||
with op.batch_alter_table('application_state') as batch_op:
|
||||
batch_op.drop_column('tool_name')
|
||||
|
||||
|
||||
def downgrade():
|
||||
# pgAdmin only upgrades, downgrade not implemented.
|
||||
pass
|
||||
@@ -0,0 +1,107 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2025, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
"""Migrate the user saved preferences from the 'sqleditor' module to
|
||||
the 'editor' module.
|
||||
|
||||
Revision ID: e6ed5dac37c2
|
||||
Revises: e24b1c4def17
|
||||
Create Date: 2025-07-15 12:27:48.780562
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
from sqlalchemy.orm.session import Session
|
||||
from pgadmin.model import Preferences, ModulePreference, PreferenceCategory,\
|
||||
UserPreference
|
||||
from pgadmin.browser import register_editor_preferences
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'e6ed5dac37c2'
|
||||
down_revision = 'e24b1c4def17'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
class Migration:
|
||||
"""
|
||||
This is a placeholder class for registering editor preferences
|
||||
"""
|
||||
def __init__(self, value=None):
|
||||
self.editor_preference = value
|
||||
|
||||
|
||||
def upgrade():
|
||||
migration_obj = Migration()
|
||||
register_editor_preferences(migration_obj, migration_gettext=lambda x: x)
|
||||
|
||||
session = Session(bind=op.get_bind())
|
||||
pref_categories = ['keyboard_shortcuts', 'editor', 'Editor']
|
||||
new_categories = ['keyboard_shortcuts', 'sql_formatting', 'options']
|
||||
prefs = ['find', 'replace', 'goto_line_col',
|
||||
'comment', 'format_sql', 'plain_editor_mode',
|
||||
'code_folding', 'wrap_code', 'insert_pair_brackets',
|
||||
'highlight_selection_matches', 'brace_matching', 'sql_font_size',
|
||||
'sql_font_ligatures', 'sql_font_family', 'indent_new_line',
|
||||
'keyword_case', 'identifier_case', 'function_case',
|
||||
'data_type_case', 'spaces_around_operators', 'tab_size',
|
||||
'use_spaces', 'expression_width', 'logical_operator_new_line',
|
||||
'lines_between_queries', 'new_line_before_semicolon']
|
||||
category_ids = []
|
||||
new_ids = []
|
||||
pref_map = {}
|
||||
|
||||
category_data = session.query(ModulePreference, PreferenceCategory,).join(
|
||||
PreferenceCategory).filter(
|
||||
ModulePreference.name == 'sqleditor',
|
||||
PreferenceCategory.name.in_(pref_categories)).all()
|
||||
|
||||
for module_data, pref_cat in category_data:
|
||||
category_ids.append(pref_cat.id)
|
||||
|
||||
new_data = session.query(ModulePreference, PreferenceCategory).join(
|
||||
PreferenceCategory).filter(
|
||||
ModulePreference.name == 'editor', PreferenceCategory.name.in_(
|
||||
new_categories)).all()
|
||||
|
||||
for module_data, pref_cat in new_data:
|
||||
new_ids.append(pref_cat.id)
|
||||
|
||||
prefs_data = session.query(Preferences).filter(Preferences.cid.in_(
|
||||
category_ids), Preferences.name.in_(prefs)).all()
|
||||
|
||||
new_prefs_data = session.query(Preferences).filter(Preferences.cid.in_(
|
||||
new_ids), Preferences.name.in_(prefs)).all()
|
||||
|
||||
for pref in prefs_data:
|
||||
for new_pref in new_prefs_data:
|
||||
if pref.name == new_pref.name:
|
||||
pref_map[pref.id] = new_pref.id
|
||||
|
||||
for key, val in pref_map.items():
|
||||
record_to_update = session.query(UserPreference).filter_by(
|
||||
pid=key).first()
|
||||
if record_to_update:
|
||||
record_to_update.pid = val
|
||||
|
||||
# Delete the old preferences and categories
|
||||
session.query(Preferences).filter(Preferences.name.in_(prefs),
|
||||
Preferences.cid.in_(category_ids)
|
||||
).delete(synchronize_session=False)
|
||||
|
||||
session.query(PreferenceCategory).filter(
|
||||
PreferenceCategory.name.in_(['editor', 'Editor'])).delete(
|
||||
synchronize_session=False)
|
||||
|
||||
session.commit()
|
||||
|
||||
|
||||
def downgrade():
|
||||
# pgAdmin only upgrades, downgrade not implemented.
|
||||
pass
|
||||
@@ -7,47 +7,44 @@
|
||||
],
|
||||
"license": "PostgreSQL",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.10.2",
|
||||
"@babel/core": "^7.28.0",
|
||||
"@babel/eslint-parser": "^7.26.5",
|
||||
"@babel/eslint-plugin": "^7.26.10",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.10.1",
|
||||
"@babel/plugin-syntax-jsx": "^7.16.0",
|
||||
"@babel/plugin-transform-runtime": "^7.27.4",
|
||||
"@babel/preset-env": "^7.27.2",
|
||||
"@babel/preset-env": "^7.28.0",
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@emotion/memoize": "^0.9.0",
|
||||
"@emotion/react": "^11.11.3",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@emotion/utils": "^1.0.0",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/dom": "10.4.1",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "16.3.0",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/jest": "^29.5.4",
|
||||
"@types/jest": "^30.0.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"axios-mock-adapter": "^2.1.0",
|
||||
"babel-loader": "^10.0.0",
|
||||
"browserify": "^17.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"copy-webpack-plugin": "^13.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"cross-env": "^10.0.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||
"eslint": "^9.29.0",
|
||||
"eslint-plugin-jest": "^28.14.0",
|
||||
"eslint": "^9.33.0",
|
||||
"eslint-plugin-jest": "^29.0.1",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"exports-loader": "^5.0.0",
|
||||
"globals": "^16.1.0",
|
||||
"globals": "^16.3.0",
|
||||
"html-react-parser": "^5.2.0",
|
||||
"image-minimizer-webpack-plugin": "^4.0.2",
|
||||
"imagemin": "^9.0.0",
|
||||
"imagemin-mozjpeg": "^10.0.0",
|
||||
"imagemin-optipng": "^8.0.0",
|
||||
"image-minimizer-webpack-plugin": "^4.1.3",
|
||||
"imports-loader": "^5.0.0",
|
||||
"jest": "^29.6.4",
|
||||
"jest-environment-jsdom": "^29.6.4",
|
||||
"jest": "^30.0.2",
|
||||
"jest-environment-jsdom": "^30.0.4",
|
||||
"loader-utils": "^3.2.1",
|
||||
"mini-css-extract-plugin": "^2.9.2",
|
||||
"postcss-loader": "^8.1.1",
|
||||
@@ -57,35 +54,38 @@
|
||||
"shim-loader": "^1.0.1",
|
||||
"style-loader": "^4.0.0",
|
||||
"stylis": "^4.3.4",
|
||||
"svgo": "^3.3.2",
|
||||
"svgo": "^4.0.0",
|
||||
"svgo-loader": "^4.0.0",
|
||||
"terser-webpack-plugin": "^5.3.12",
|
||||
"typescript": "^5.4.5",
|
||||
"typescript-eslint": "^8.28.0",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript-eslint": "^8.35.0",
|
||||
"url-loader": "^4.1.1",
|
||||
"webfonts-loader": "^8.0.1",
|
||||
"webpack": "^5.99.9",
|
||||
"webpack": "^5.101.0",
|
||||
"webpack-bundle-analyzer": "^4.8.0",
|
||||
"webpack-cli": "^6.0.1",
|
||||
"yarn-audit-html": "7.3.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"sharp": "^0.34.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
||||
"@babel/preset-react": "^7.12.13",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-sql": "^6.9.0",
|
||||
"@codemirror/lang-sql": "^6.9.1",
|
||||
"@date-io/core": "^3.0.0",
|
||||
"@date-io/date-fns": "3.x",
|
||||
"@emotion/sheet": "^1.0.1",
|
||||
"@fortawesome/fontawesome-free": "latest",
|
||||
"@mui/icons-material": "^7.1.1",
|
||||
"@mui/material": "^7.1.0",
|
||||
"@mui/x-date-pickers": "^8.5.2",
|
||||
"@mui/icons-material": "^7.3.1",
|
||||
"@mui/material": "^7.3.1",
|
||||
"@mui/x-date-pickers": "^8.10.0",
|
||||
"@nozbe/microfuzz": "^1.0.0",
|
||||
"@projectstorm/react-diagrams": "^7.0.4",
|
||||
"@simonwep/pickr": "^1.5.1",
|
||||
"@szhsin/react-menu": "^4.4.1",
|
||||
"@tanstack/react-query": "5.80.7",
|
||||
"@tanstack/react-query": "^5.84.2",
|
||||
"@tanstack/react-table": "^8.16.0",
|
||||
"@tanstack/react-virtual": "^3.13.6",
|
||||
"@types/classnames": "^2.3.4",
|
||||
@@ -98,7 +98,7 @@
|
||||
"ajv": "^8.8.2",
|
||||
"anti-trojan-source": "^1.4.0",
|
||||
"aspen-decorations": "^1.0.2",
|
||||
"axios": "^1.10.0",
|
||||
"axios": "^1.11.0",
|
||||
"babelify": "~10.0.0",
|
||||
"bignumber.js": "^9.2.0",
|
||||
"brace": "^0.11.1",
|
||||
@@ -109,7 +109,7 @@
|
||||
"convert-units": "^2.3.4",
|
||||
"date-fns": "^4.1.0",
|
||||
"diff-arrays-of-objects": "^1.1.8",
|
||||
"hotkeys-js": "^3.13.3",
|
||||
"hotkeys-js": "^3.13.15",
|
||||
"html-to-image": "1.11.11",
|
||||
"immutability-helper": "^3.0.0",
|
||||
"insert-if": "^1.1.0",
|
||||
@@ -123,7 +123,7 @@
|
||||
"notistack": "^3.0.1",
|
||||
"papaparse": "^5.5.2",
|
||||
"path-fx": "^2.0.0",
|
||||
"postcss": "^8.5.2",
|
||||
"postcss": "^8.5.6",
|
||||
"rc-dock": "^3.3.2",
|
||||
"react": "^18.2.0",
|
||||
"react-arborist": "^3.2.0",
|
||||
@@ -153,7 +153,7 @@
|
||||
"valid-filename": "^4.0.0",
|
||||
"vanilla-jsoneditor": "^3.3.1",
|
||||
"wkx": "^0.5.0",
|
||||
"zustand": "^5.0.5"
|
||||
"zustand": "^5.0.7"
|
||||
},
|
||||
"resolutions": {
|
||||
"rc-resize-observer": "1.4.0"
|
||||
@@ -173,7 +173,7 @@
|
||||
"test:js-coverage": "yarn run test:js-once --collect-coverage",
|
||||
"test:feature": "yarn run bundle && python regression/runtests.py --pkg feature_tests",
|
||||
"test": "yarn run test:js-once && yarn run bundle && python regression/runtests.py",
|
||||
"pep8": "pycodestyle --config=../.pycodestyle ../docs && pycodestyle --config=../.pycodestyle ../pkg && pycodestyle --config=../.pycodestyle ../tools && pycodestyle --config=../.pycodestyle ../web",
|
||||
"pep8": "pycodestyle --config=../.pycodestyle ../docs ../pkg ../tools ../web",
|
||||
"auditjs-html": "yarn audit --json | yarn run yarn-audit-html --output ../auditjs.html",
|
||||
"auditjs": "yarn audit --groups dependencies",
|
||||
"auditpy": "safety check --full-report -i 51668 -i 52495",
|
||||
|
||||
@@ -15,8 +15,8 @@ import sys
|
||||
if sys.version_info <= (3, 9):
|
||||
import select
|
||||
|
||||
if sys.version_info < (3, 8):
|
||||
raise RuntimeError('This application must be run under Python 3.8 '
|
||||
if sys.version_info < (3, 9):
|
||||
raise RuntimeError('This application must be run under Python 3.9 '
|
||||
'or later.')
|
||||
import builtins
|
||||
import os
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
if sys.version_info < (3, 8):
|
||||
raise Exception('This application must be run under Python 3.8 or later.')
|
||||
if sys.version_info < (3, 9):
|
||||
raise Exception('This application must be run under Python 3.9 or later.')
|
||||
|
||||
import builtins
|
||||
|
||||
|
||||
@@ -109,6 +109,26 @@ class OAuth2Authentication(BaseAuthentication):
|
||||
OAuth2Authentication.oauth2_config[
|
||||
oauth2_config['OAUTH2_NAME']] = oauth2_config
|
||||
|
||||
# Build client_kwargs with defaults
|
||||
client_kwargs = {
|
||||
'scope': oauth2_config.get(
|
||||
'OAUTH2_SCOPE', 'email profile'),
|
||||
'verify': oauth2_config.get(
|
||||
'OAUTH2_SSL_CERT_VERIFICATION', True)
|
||||
}
|
||||
|
||||
# Override with PKCE parameters if provided
|
||||
if 'OAUTH2_CHALLENGE_METHOD' in oauth2_config and \
|
||||
'OAUTH2_RESPONSE_TYPE' in oauth2_config:
|
||||
# Merge PKCE kwargs with defaults
|
||||
pkce_kwargs = {
|
||||
'code_challenge_method': oauth2_config[
|
||||
'OAUTH2_CHALLENGE_METHOD'],
|
||||
'response_type': oauth2_config[
|
||||
'OAUTH2_RESPONSE_TYPE']
|
||||
}
|
||||
client_kwargs.update(pkce_kwargs)
|
||||
|
||||
OAuth2Authentication.oauth2_clients[
|
||||
oauth2_config['OAUTH2_NAME']
|
||||
] = OAuth2Authentication.oauth_obj.register(
|
||||
@@ -118,10 +138,7 @@ class OAuth2Authentication(BaseAuthentication):
|
||||
access_token_url=oauth2_config['OAUTH2_TOKEN_URL'],
|
||||
authorize_url=oauth2_config['OAUTH2_AUTHORIZATION_URL'],
|
||||
api_base_url=oauth2_config['OAUTH2_API_BASE_URL'],
|
||||
client_kwargs={'scope': oauth2_config.get(
|
||||
'OAUTH2_SCOPE', 'email profile'),
|
||||
'verify': oauth2_config.get(
|
||||
'OAUTH2_SSL_CERT_VERIFICATION', True)},
|
||||
client_kwargs=client_kwargs,
|
||||
server_metadata_url=oauth2_config.get(
|
||||
'OAUTH2_SERVER_METADATA_URL', None)
|
||||
)
|
||||
|
||||
@@ -44,7 +44,7 @@ import config
|
||||
from pgadmin import current_blueprint
|
||||
from pgadmin.authenticate import get_logout_url
|
||||
from pgadmin.authenticate.mfa.utils import is_mfa_enabled
|
||||
from pgadmin.settings import get_setting
|
||||
from pgadmin.settings import get_setting, get_layout
|
||||
from pgadmin.utils import PgAdminModule
|
||||
from pgadmin.utils.ajax import make_json_response, internal_server_error, \
|
||||
bad_request
|
||||
@@ -52,6 +52,8 @@ from pgadmin.utils.csrf import pgCSRFProtect
|
||||
from pgadmin.utils.preferences import Preferences
|
||||
from pgadmin.browser.register_browser_preferences import \
|
||||
register_browser_preferences
|
||||
from pgadmin.browser.register_editor_preferences import \
|
||||
register_editor_preferences
|
||||
from pgadmin.utils.master_password import validate_master_password, \
|
||||
set_masterpass_check_text, cleanup_master_password, get_crypt_key, \
|
||||
set_crypt_key, process_masterpass_disabled, \
|
||||
@@ -87,6 +89,7 @@ class BrowserModule(PgAdminModule):
|
||||
|
||||
def register_preferences(self):
|
||||
register_browser_preferences(self)
|
||||
register_editor_preferences(self)
|
||||
|
||||
def get_exposed_url_endpoints(self):
|
||||
"""
|
||||
@@ -474,7 +477,8 @@ def get_shared_storage_list():
|
||||
@pgCSRFProtect.exempt
|
||||
@pga_login_required
|
||||
def utils():
|
||||
layout = get_setting('Browser/Layout', default='')
|
||||
layout = get_layout()
|
||||
|
||||
snippets = []
|
||||
|
||||
prefs = Preferences.module('paths')
|
||||
|
||||
@@ -325,7 +325,7 @@ def register_browser_preferences(self):
|
||||
self.preference.register(
|
||||
'keyboard_shortcuts',
|
||||
'sub_menu_delete',
|
||||
gettext('Delete object'),
|
||||
gettext('Drop object'),
|
||||
'keyboardshortcut',
|
||||
{
|
||||
'alt': True,
|
||||
|
||||
@@ -0,0 +1,375 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2025, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
from flask_babel import gettext
|
||||
from pgadmin.utils.preferences import Preferences
|
||||
from pgadmin.utils.constants import PREF_LABEL_OPTIONS,\
|
||||
PREF_LABEL_KEYBOARD_SHORTCUTS, PREF_LABEL_SQL_FORMATTING
|
||||
from pgadmin.utils import SHORTCUT_FIELDS as shortcut_fields
|
||||
|
||||
UPPER_CASE_STR = gettext('Upper case')
|
||||
LOWER_CASE_STR = gettext('Lower case')
|
||||
PRESERVE_STR = gettext('Preserve')
|
||||
|
||||
|
||||
def register_editor_preferences(self, migration_gettext=None):
|
||||
"""
|
||||
Registers the editor preferences
|
||||
"""
|
||||
# migration_getttext is used once in case of migration
|
||||
# In that case gettext will be used from migration_gettext
|
||||
# instead of flask_babel gettext.
|
||||
from flask_babel import gettext
|
||||
if migration_gettext is not None:
|
||||
gettext = migration_gettext
|
||||
|
||||
self.editor_preference = Preferences(
|
||||
'editor', gettext('Editor')
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'keyboard_shortcuts',
|
||||
'find',
|
||||
gettext('Find'),
|
||||
'keyboardshortcut',
|
||||
{
|
||||
'alt': False,
|
||||
'shift': False,
|
||||
'control': True,
|
||||
'ctrl_is_meta': True,
|
||||
'key': {
|
||||
'key_code': 70,
|
||||
'char': 'f'
|
||||
}
|
||||
},
|
||||
category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,
|
||||
fields=shortcut_fields
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'keyboard_shortcuts',
|
||||
'replace',
|
||||
gettext('Replace'),
|
||||
'keyboardshortcut',
|
||||
{
|
||||
'alt': True,
|
||||
'shift': False,
|
||||
'control': True,
|
||||
'ctrl_is_meta': True,
|
||||
'key': {
|
||||
'key_code': 70,
|
||||
'char': 'f'
|
||||
}
|
||||
},
|
||||
category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,
|
||||
fields=shortcut_fields
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'keyboard_shortcuts',
|
||||
'goto_line_col',
|
||||
gettext('Go to line/column'),
|
||||
'keyboardshortcut',
|
||||
{
|
||||
'alt': False,
|
||||
'shift': False,
|
||||
'control': True,
|
||||
'ctrl_is_meta': True,
|
||||
'key': {
|
||||
'key_code': 76,
|
||||
'char': 'l'
|
||||
}
|
||||
},
|
||||
category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,
|
||||
fields=shortcut_fields
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'keyboard_shortcuts',
|
||||
'comment',
|
||||
gettext('Toggle comment'),
|
||||
'keyboardshortcut',
|
||||
{
|
||||
'alt': False,
|
||||
'shift': False,
|
||||
'control': True,
|
||||
'ctrl_is_meta': True,
|
||||
'key': {
|
||||
'key_code': 191,
|
||||
'char': '/'
|
||||
}
|
||||
},
|
||||
category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,
|
||||
fields=shortcut_fields
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'keyboard_shortcuts',
|
||||
'format_sql',
|
||||
gettext('Format SQL'),
|
||||
'keyboardshortcut',
|
||||
{
|
||||
'alt': False,
|
||||
'shift': False,
|
||||
'control': True,
|
||||
'ctrl_is_meta': True,
|
||||
'key': {
|
||||
'key_code': 75,
|
||||
'char': 'k'
|
||||
}
|
||||
},
|
||||
category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,
|
||||
fields=shortcut_fields
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'options', 'plain_editor_mode',
|
||||
gettext("Plain text mode?"), 'boolean', False,
|
||||
category_label=PREF_LABEL_OPTIONS,
|
||||
help_str=gettext(
|
||||
'When set to True, keywords won\'t be highlighted and code '
|
||||
'folding will be disabled. Plain text mode will improve editor '
|
||||
'performance with large files.'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'options', 'code_folding',
|
||||
gettext("Code folding?"), 'boolean', True,
|
||||
category_label=PREF_LABEL_OPTIONS,
|
||||
help_str=gettext(
|
||||
'Enable or disable code folding. In plain text mode, this will '
|
||||
'have no effect as code folding is always disabled in that mode. '
|
||||
'Disabling will improve editor performance with large files.'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'options', 'cursor_blink_rate',
|
||||
gettext("Cursor blink rate"), 'options', 'medium',
|
||||
category_label=PREF_LABEL_OPTIONS,
|
||||
options=[{'label': gettext('None'), 'value': 'none'},
|
||||
{'label': gettext('Slow'), 'value': 'slow'},
|
||||
{'label': gettext('Medium'), 'value': 'medium'},
|
||||
{'label': gettext('Fast'), 'value': 'fast'}],
|
||||
control_props={
|
||||
'allowClear': False,
|
||||
'creatable': False,
|
||||
},
|
||||
help_str=gettext(
|
||||
'Adjust the speed at which the text cursor blinks within '
|
||||
'the editors.'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'options', 'wrap_code',
|
||||
gettext("Line wrapping?"), 'boolean', False,
|
||||
category_label=PREF_LABEL_OPTIONS,
|
||||
help_str=gettext(
|
||||
'Specifies whether or not to wrap SQL code in the editor.'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'options', 'insert_pair_brackets',
|
||||
gettext("Insert bracket pairs?"), 'boolean', True,
|
||||
category_label=PREF_LABEL_OPTIONS,
|
||||
help_str=gettext(
|
||||
'Specifies whether or not to insert paired brackets in the '
|
||||
'editor.'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'options', 'highlight_selection_matches',
|
||||
gettext("Highlight selection matches?"), 'boolean', True,
|
||||
category_label=PREF_LABEL_OPTIONS,
|
||||
help_str=gettext(
|
||||
'Specifies whether or not to highlight matched selected text '
|
||||
'in the editor.'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'options', 'brace_matching',
|
||||
gettext("Brace matching?"), 'boolean', True,
|
||||
category_label=PREF_LABEL_OPTIONS,
|
||||
help_str=gettext(
|
||||
'Specifies whether or not to highlight matched braces '
|
||||
'in the editor.'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'options', 'sql_font_size',
|
||||
gettext("Font size"), 'numeric', '1',
|
||||
min_val=0.1,
|
||||
max_val=10,
|
||||
category_label=PREF_LABEL_OPTIONS,
|
||||
help_str=gettext(
|
||||
'The font size to use for the SQL text boxes and editors. '
|
||||
'The value specified is in "em" units, in which 1 is the '
|
||||
'default relative font size. For example, to increase the '
|
||||
'font size by 20 percent use a value of 1.2, or to reduce '
|
||||
'by 20 percent, use a value of 0.8. Minimum 0.1, maximum 10.'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'options', 'sql_font_ligatures',
|
||||
gettext("Font ligatures?"), 'boolean',
|
||||
False, category_label=PREF_LABEL_OPTIONS,
|
||||
help_str=gettext(
|
||||
'If set to true, ligatures will be enabled in SQL text boxes '
|
||||
'and editors provided the configured font family supports them.'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'options', 'sql_font_family',
|
||||
gettext("Font family"), 'text', 'Source Code Pro',
|
||||
category_label=PREF_LABEL_OPTIONS,
|
||||
help_str=gettext(
|
||||
'Specify the font family to be used for all SQL editors. '
|
||||
'The specified font should already be installed on your system. '
|
||||
'If the font is not found, the editor will fall back to the '
|
||||
'default font, Source Code Pro.'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'options', 'indent_new_line',
|
||||
gettext("Auto-indent new line?"), 'boolean', True,
|
||||
category_label=PREF_LABEL_OPTIONS,
|
||||
help_str=gettext(
|
||||
'Specifies whether the newly added line using enter key should '
|
||||
'be auto-indented or not'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'sql_formatting', 'keyword_case',
|
||||
gettext("Keyword case"), 'radioModern', 'upper',
|
||||
options=[{'label': UPPER_CASE_STR, 'value': 'upper'},
|
||||
{'label': LOWER_CASE_STR, 'value': 'lower'},
|
||||
{'label': PRESERVE_STR, 'value': 'preserve'}],
|
||||
category_label=PREF_LABEL_SQL_FORMATTING,
|
||||
help_str=gettext(
|
||||
'Convert keywords to upper, lower, or preserve casing.'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'sql_formatting', 'identifier_case',
|
||||
gettext("Identifier case"), 'radioModern', 'upper',
|
||||
options=[{'label': UPPER_CASE_STR, 'value': 'upper'},
|
||||
{'label': LOWER_CASE_STR, 'value': 'lower'},
|
||||
{'label': PRESERVE_STR, 'value': 'preserve'}],
|
||||
category_label=PREF_LABEL_SQL_FORMATTING,
|
||||
help_str=gettext(
|
||||
'Convert identifiers to upper, lower, or preserve casing.'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'sql_formatting', 'function_case',
|
||||
gettext("Function case"), 'radioModern', 'upper',
|
||||
options=[{'label': UPPER_CASE_STR, 'value': 'upper'},
|
||||
{'label': LOWER_CASE_STR, 'value': 'lower'},
|
||||
{'label': PRESERVE_STR, 'value': 'preserve'}],
|
||||
category_label=PREF_LABEL_SQL_FORMATTING,
|
||||
help_str=gettext(
|
||||
'Convert function names to upper, lower, or preserve casing.'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'sql_formatting', 'data_type_case',
|
||||
gettext("Data type case"), 'radioModern', 'upper',
|
||||
options=[{'label': UPPER_CASE_STR, 'value': 'upper'},
|
||||
{'label': LOWER_CASE_STR, 'value': 'lower'},
|
||||
{'label': PRESERVE_STR, 'value': 'preserve'}],
|
||||
category_label=PREF_LABEL_SQL_FORMATTING,
|
||||
help_str=gettext(
|
||||
'Convert data types to upper, lower, or preserve casing.'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'sql_formatting', 'spaces_around_operators',
|
||||
gettext("Spaces around operators?"), 'boolean', True,
|
||||
category_label=PREF_LABEL_SQL_FORMATTING,
|
||||
help_str=gettext('If set to True, spaces are used around all '
|
||||
'operators.')
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'sql_formatting', 'tab_size',
|
||||
gettext("Tab size"), 'integer', 4,
|
||||
min_val=2,
|
||||
max_val=8,
|
||||
category_label=PREF_LABEL_SQL_FORMATTING,
|
||||
help_str=gettext(
|
||||
'The number of spaces per tab. Minimum 2, maximum 8.'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'sql_formatting', 'use_spaces',
|
||||
gettext("Use spaces?"), 'boolean', False,
|
||||
category_label=PREF_LABEL_SQL_FORMATTING,
|
||||
help_str=gettext(
|
||||
'Specifies whether or not to insert spaces instead of tabs '
|
||||
'when the tab key or auto-indent are used.'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'sql_formatting', 'expression_width',
|
||||
gettext("Expression Width"), 'integer', 50,
|
||||
category_label=PREF_LABEL_SQL_FORMATTING,
|
||||
help_str=gettext(
|
||||
'maximum number of characters in parenthesized expressions to be '
|
||||
'kept on single line.'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'sql_formatting', 'logical_operator_new_line',
|
||||
gettext("Logical operator new line"), 'radioModern', 'before',
|
||||
options=[{'label': gettext('Before'), 'value': 'before'},
|
||||
{'label': gettext('After'), 'value': 'after'}],
|
||||
category_label=PREF_LABEL_SQL_FORMATTING,
|
||||
help_str=gettext(
|
||||
'Decides newline placement before or after logical operators '
|
||||
'(AND, OR, XOR).'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'sql_formatting', 'lines_between_queries',
|
||||
gettext("Lines between queries"), 'integer', 1,
|
||||
min_val=0,
|
||||
max_val=5,
|
||||
category_label=PREF_LABEL_SQL_FORMATTING,
|
||||
help_str=gettext(
|
||||
'Decides how many empty lines to leave between SQL statements. '
|
||||
'If zero it puts no new line.'
|
||||
)
|
||||
)
|
||||
|
||||
self.editor_preference.register(
|
||||
'sql_formatting', 'new_line_before_semicolon',
|
||||
gettext("New line before semicolon?"), 'boolean', False,
|
||||
category_label=PREF_LABEL_SQL_FORMATTING,
|
||||
help_str=gettext(
|
||||
'Whether to place query separator (;) on a separate line.'
|
||||
)
|
||||
)
|
||||
@@ -31,7 +31,7 @@ from config import PG_DEFAULT_DRIVER
|
||||
from pgadmin.model import db, Server, ServerGroup, User, SharedServer
|
||||
from pgadmin.utils.driver import get_driver
|
||||
from pgadmin.utils.master_password import get_crypt_key
|
||||
from pgadmin.utils.exception import CryptKeyMissing
|
||||
from pgadmin.utils.exception import CryptKeyMissing, ConnectionLost
|
||||
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
|
||||
from pgadmin.browser.server_groups.servers.utils import \
|
||||
(is_valid_ipaddress, get_replication_type, convert_connection_parameter,
|
||||
@@ -43,6 +43,7 @@ from sqlalchemy.orm.attributes import flag_modified
|
||||
from pgadmin.utils.preferences import Preferences
|
||||
from .... import socketio as sio
|
||||
from pgadmin.utils import get_complete_file_path
|
||||
from pgadmin.settings.utils import with_object_filters
|
||||
|
||||
|
||||
def has_any(data, keys):
|
||||
@@ -220,8 +221,25 @@ class ServerModule(sg.ServerGroupPluginModule):
|
||||
|
||||
return servers
|
||||
|
||||
def has_tag(self, server, object_filters):
|
||||
try:
|
||||
# No tags filter, show all
|
||||
if len(object_filters['tags']) == 0:
|
||||
return True
|
||||
|
||||
# No tags on server, don't show
|
||||
if server.tags is None or len(server.tags) == 0:
|
||||
return False
|
||||
|
||||
# Check if any of the tag exists
|
||||
return any([t['text'] in object_filters['tags']
|
||||
for t in server.tags])
|
||||
except Exception as _:
|
||||
return True
|
||||
|
||||
@with_object_filters
|
||||
@pga_login_required
|
||||
def get_nodes(self, gid):
|
||||
def get_nodes(self, gid, object_filters):
|
||||
"""Return a JSON document listing the server groups for the user"""
|
||||
|
||||
hide_shared_server = get_preferences()
|
||||
@@ -241,6 +259,10 @@ class ServerModule(sg.ServerGroupPluginModule):
|
||||
wal_paused = None
|
||||
server_type = 'pg'
|
||||
user_info = None
|
||||
|
||||
if not self.has_tag(server, object_filters):
|
||||
continue
|
||||
|
||||
try:
|
||||
manager = driver.connection_manager(server.id)
|
||||
conn = manager.connection()
|
||||
@@ -627,12 +649,18 @@ class ServerNode(PGChildNodeView):
|
||||
in_recovery = None
|
||||
wal_paused = None
|
||||
if connected:
|
||||
status, result, in_recovery, wal_paused =\
|
||||
recovery_state(conn, manager.version)
|
||||
if not status:
|
||||
try:
|
||||
status, result, in_recovery, wal_paused =\
|
||||
recovery_state(conn, manager.version)
|
||||
|
||||
if not status:
|
||||
connected = False
|
||||
manager.release()
|
||||
errmsg = "{0} : {1}".format(server.name, result)
|
||||
|
||||
except ConnectionLost:
|
||||
connected = False
|
||||
manager.release()
|
||||
errmsg = "{0} : {1}".format(server.name, result)
|
||||
|
||||
return make_json_response(
|
||||
result=self.blueprint.generate_browser_node(
|
||||
@@ -744,6 +772,8 @@ class ServerNode(PGChildNodeView):
|
||||
'port': 'port',
|
||||
'db': 'maintenance_db',
|
||||
'username': 'username',
|
||||
'password': 'password',
|
||||
'save_password': 'save_password',
|
||||
'gid': 'servergroup_id',
|
||||
'comment': 'comment',
|
||||
'role': 'role',
|
||||
@@ -877,9 +907,16 @@ class ServerNode(PGChildNodeView):
|
||||
sharedserver):
|
||||
|
||||
idx = 0
|
||||
|
||||
crypt_key_present, crypt_key = get_crypt_key()
|
||||
if not crypt_key_present:
|
||||
raise CryptKeyMissing
|
||||
|
||||
for arg in config_param_map:
|
||||
if arg in data:
|
||||
value = data[arg]
|
||||
if arg == 'password':
|
||||
value = encrypt(data[arg], crypt_key)
|
||||
# sqlite3 do not have boolean type so we need to convert
|
||||
# it manually to integer
|
||||
if 'shared' in data and not data['shared']:
|
||||
@@ -911,7 +948,7 @@ class ServerNode(PGChildNodeView):
|
||||
)
|
||||
|
||||
@pga_login_required
|
||||
def list(self, gid):
|
||||
def list(self, gid, object_filters):
|
||||
"""
|
||||
Return list of attributes of all servers.
|
||||
"""
|
||||
@@ -1020,6 +1057,8 @@ class ServerNode(PGChildNodeView):
|
||||
'host': server.host,
|
||||
'port': server.port,
|
||||
'db': server.maintenance_db,
|
||||
'password': server.password,
|
||||
'save_password': server.save_password,
|
||||
'shared': server.shared if config.SERVER_MODE else None,
|
||||
'shared_username': server.shared_username
|
||||
if config.SERVER_MODE else None,
|
||||
|
||||
@@ -59,11 +59,13 @@ define('pgadmin.node.cast', [
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Cast...'),
|
||||
data: {action: 'create'},
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
},{
|
||||
name: 'create_cast', node: 'cast', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Cast...'),
|
||||
data: {action: 'create'},
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
}]);
|
||||
|
||||
},
|
||||
|
||||
@@ -52,11 +52,13 @@ define('pgadmin.node.event_trigger', [
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Event Trigger...'),
|
||||
data: {action: 'create'},
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
},{
|
||||
name: 'create_event_trigger', node: 'event_trigger', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Event Trigger...'),
|
||||
data: {action: 'create'},
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
},{
|
||||
name: 'create_event_trigger', node: 'database', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
|
||||
@@ -3,5 +3,5 @@ CREATE EVENT TRIGGER {{ conn|qtIdent(data.name) }} ON {{data.eventname}}
|
||||
{% if data.when %}
|
||||
WHEN TAG IN ({{data.when}})
|
||||
{% endif %}
|
||||
EXECUTE PROCEDURE {{data.eventfunname}}();
|
||||
EXECUTE FUNCTION {{data.eventfunname}}();
|
||||
{% endif %}
|
||||
@@ -12,7 +12,7 @@ CREATE EVENT TRIGGER {{ conn|qtIdent(data.name) if data.name else conn|qtIdent(o
|
||||
{% if data.when or o_data.when %}
|
||||
WHEN TAG IN ({{ data.when if data.when else o_data.when }})
|
||||
{% endif %}
|
||||
EXECUTE PROCEDURE {{ data.eventfunname if data.eventfunname else o_data.eventfunname }}();
|
||||
EXECUTE FUNCTION {{ data.eventfunname if data.eventfunname else o_data.eventfunname }}();
|
||||
|
||||
ALTER EVENT TRIGGER {{ conn|qtIdent(o_data.name) }}
|
||||
OWNER TO {{ conn|qtIdent(o_data.eventowner) }};
|
||||
|
||||
@@ -50,8 +50,8 @@ def create_event_trigger(server, db_name, schema_name, func_name,
|
||||
set_isolation_level(connection, 0)
|
||||
pg_cursor = connection.cursor()
|
||||
pg_cursor.execute('''CREATE EVENT TRIGGER "%s" ON DDL_COMMAND_END
|
||||
EXECUTE PROCEDURE "%s"."%s"()''' % (trigger_name, schema_name,
|
||||
func_name))
|
||||
EXECUTE FUNCTION "%s"."%s"()''' % (trigger_name, schema_name,
|
||||
func_name))
|
||||
set_isolation_level(connection, old_isolation_level)
|
||||
connection.commit()
|
||||
# Get 'oid' from newly created event trigger
|
||||
|
||||
@@ -77,11 +77,13 @@ define('pgadmin.node.extension', [
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Extension...'),
|
||||
data: {action: 'create'},
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
},{
|
||||
name: 'create_extension', node: 'extension', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Extension...'),
|
||||
data: {action: 'create'},
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
},{
|
||||
name: 'create_extension', node: 'database', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
|
||||
@@ -56,11 +56,13 @@ define('pgadmin.node.foreign_data_wrapper', [
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Foreign Data Wrapper...'),
|
||||
data: {action: 'create'},
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
},{
|
||||
name: 'create_foreign_data_wrapper', node: 'foreign_data_wrapper', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Foreign Data Wrapper...'),
|
||||
data: {action: 'create'},
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
},{
|
||||
name: 'create_foreign_data_wrapper', node: 'database', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
|
||||
@@ -61,11 +61,13 @@ define('pgadmin.node.language', [
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Language...'),
|
||||
data: {action: 'create'},
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
},{
|
||||
name: 'create_language', node: 'language', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Language...'),
|
||||
data: {action: 'create'},
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
}]);
|
||||
},
|
||||
|
||||
|
||||
@@ -62,11 +62,13 @@ define('pgadmin.node.publication', [
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Publication...'),
|
||||
data: {action: 'create'},
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
},{
|
||||
name: 'create_publication', node: 'publication', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Publication...'),
|
||||
data: {action: 'create'},
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
}]);
|
||||
},
|
||||
|
||||
|
||||
@@ -52,11 +52,13 @@ define('pgadmin.node.collation', [
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Collation...'),
|
||||
data: {action: 'create', check: true}, enable: 'canCreate',
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
},{
|
||||
name: 'create_collation', node: 'collation', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Collation...'),
|
||||
data: {action: 'create', check: true}, enable: 'canCreate',
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
},{
|
||||
name: 'create_collation', node: 'schema', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
@@ -77,7 +79,8 @@ define('pgadmin.node.collation', [
|
||||
{
|
||||
owner: pgBrowser.serverInfo[treeNodeInfo.server._id].user.name,
|
||||
schema: ('schema' in treeNodeInfo)? treeNodeInfo.schema.label : ''
|
||||
}
|
||||
},
|
||||
treeNodeInfo
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ import gettext from 'sources/gettext';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
|
||||
export default class CollationSchema extends BaseUISchema {
|
||||
constructor(fieldOptions = {},initValues={}) {
|
||||
constructor(fieldOptions = {}, initValues={}, nodeInfo={}) {
|
||||
super({
|
||||
name: undefined,
|
||||
oid: undefined,
|
||||
@@ -30,6 +30,7 @@ export default class CollationSchema extends BaseUISchema {
|
||||
this.schemaList = fieldOptions.schemaList;
|
||||
this.ownerList = fieldOptions.rolesList;
|
||||
this.collationsList = fieldOptions.collationsList;
|
||||
this.nodeInfo = nodeInfo;
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
@@ -86,16 +87,24 @@ export default class CollationSchema extends BaseUISchema {
|
||||
deps: ['locale', 'lc_collate', 'lc_type'],
|
||||
},
|
||||
{
|
||||
id: 'provider', label: gettext('Locale Provider'),
|
||||
id: 'provider', label: gettext('Provider'),
|
||||
editable: false, type: 'select',mode: ['create'], group: gettext('Definition'),
|
||||
readonly: function (state) { return !obj.isNew(state); },
|
||||
options: [{
|
||||
label: gettext('icu'),
|
||||
value: 'icu',
|
||||
}, {
|
||||
label: gettext('libc'),
|
||||
value: 'libc',
|
||||
}],
|
||||
options: function() {
|
||||
let options = [{
|
||||
label: gettext('icu'),
|
||||
value: 'icu',
|
||||
}, {
|
||||
label: gettext('libc'),
|
||||
value: 'libc',
|
||||
}];
|
||||
if(obj.getServerVersion() >= 170000) {
|
||||
options.push({
|
||||
label: gettext('builtin'), value: 'builtin',
|
||||
});
|
||||
}
|
||||
return Promise.resolve(options);
|
||||
},
|
||||
min_version: 110000,
|
||||
deps: ['copy_collation'],
|
||||
depChange: (state)=>{
|
||||
@@ -110,7 +119,7 @@ export default class CollationSchema extends BaseUISchema {
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'provider', label: gettext('Locale Provider'),
|
||||
id: 'provider', label: gettext('Provider'),
|
||||
type: 'text',mode: ['properties', 'edit'], group: gettext('Definition'),
|
||||
readonly: true,
|
||||
min_version: 110000,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
SELECT c.oid, c.collname AS name, c.collcollate AS lc_collate, c.collctype AS lc_type,
|
||||
pg_catalog.pg_get_userbyid(c.collowner) AS owner, c.collisdeterministic AS is_deterministic, c.collversion AS version,
|
||||
c.collprovider AS provider, des.description, n.nspname AS schema
|
||||
|
||||
CASE WHEN c.collprovider = 'i' THEN 'icu' ELSE 'libc' END provider,
|
||||
des.description, n.nspname AS schema
|
||||
FROM pg_catalog.pg_collation c
|
||||
JOIN pg_catalog.pg_namespace n ON n.oid=c.collnamespace
|
||||
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=c.oid AND des.classoid='pg_collation'::regclass)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
SELECT c.oid, c.collname AS name, COALESCE(c.collcollate, c.colliculocale) AS lc_collate,
|
||||
COALESCE(c.collctype, c.colliculocale) AS lc_type, c.collisdeterministic AS is_deterministic, c.collversion AS version,
|
||||
c.collprovider AS provider,
|
||||
CASE WHEN c.collprovider = 'i' THEN 'icu' ELSE 'libc' END provider,
|
||||
pg_catalog.pg_get_userbyid(c.collowner) AS owner, description, n.nspname AS schema
|
||||
FROM pg_catalog.pg_collation c
|
||||
JOIN pg_catalog.pg_namespace n ON n.oid=c.collnamespace
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
SELECT c.oid, c.collname AS name, COALESCE(c.collcollate, c.colliculocale) AS lc_collate,
|
||||
COALESCE(c.collctype, c.colliculocale) AS lc_type, c.collisdeterministic AS is_deterministic, c.collversion AS version,
|
||||
c.collprovider AS provider, c.collicurules AS rules,
|
||||
CASE WHEN c.collprovider = 'i' THEN 'icu' ELSE 'libc' END provider, c.collicurules AS rules,
|
||||
pg_catalog.pg_get_userbyid(c.collowner) AS owner, description, n.nspname AS schema
|
||||
FROM pg_catalog.pg_collation c
|
||||
JOIN pg_catalog.pg_namespace n ON n.oid=c.collnamespace
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
SELECT c.oid, c.collname AS name, COALESCE(c.collcollate, c.colllocale) AS lc_collate,
|
||||
COALESCE(c.collctype, c.colllocale) AS lc_type, c.collisdeterministic AS is_deterministic, c.collversion AS version,
|
||||
c.collprovider AS provider, c.collicurules AS rules,
|
||||
CASE WHEN c.collprovider = 'i' THEN 'icu' WHEN c.collprovider = 'b' THEN 'builtin'
|
||||
ELSE 'libc' END provider, c.collicurules AS rules,
|
||||
pg_catalog.pg_get_userbyid(c.collowner) AS owner, description, n.nspname AS schema
|
||||
FROM pg_catalog.pg_collation c
|
||||
JOIN pg_catalog.pg_namespace n ON n.oid=c.collnamespace
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
-- DROP COLLATION IF EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#a";
|
||||
|
||||
CREATE COLLATION IF NOT EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#a"
|
||||
(LC_COLLATE = 'C', LC_CTYPE = 'C', PROVIDER = 'c', DETERMINISTIC = true);
|
||||
(LC_COLLATE = 'C', LC_CTYPE = 'C', PROVIDER = 'libc', DETERMINISTIC = true);
|
||||
|
||||
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#a"
|
||||
OWNER TO <OWNER>;
|
||||
@@ -3,7 +3,7 @@
|
||||
-- DROP COLLATION IF EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#";
|
||||
|
||||
CREATE COLLATION IF NOT EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#"
|
||||
(LC_COLLATE = 'C', LC_CTYPE = 'C', PROVIDER = 'c', DETERMINISTIC = true);
|
||||
(LC_COLLATE = 'C', LC_CTYPE = 'C', PROVIDER = 'libc', DETERMINISTIC = true);
|
||||
|
||||
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#"
|
||||
OWNER TO <OWNER>;
|
||||
@@ -3,7 +3,7 @@
|
||||
-- DROP COLLATION IF EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b";
|
||||
|
||||
CREATE COLLATION IF NOT EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
|
||||
(LC_COLLATE = 'locale', LC_CTYPE = 'locale', PROVIDER = 'i', DETERMINISTIC = true, VERSION = '1');
|
||||
(LC_COLLATE = 'locale', LC_CTYPE = 'locale', PROVIDER = 'icu', DETERMINISTIC = true, VERSION = '1');
|
||||
|
||||
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
|
||||
OWNER TO <OWNER>;
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Collation: Cl1_$%{}[]()&*^!@"'`\/#b;
|
||||
|
||||
-- DROP COLLATION IF EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b";
|
||||
|
||||
CREATE COLLATION IF NOT EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
|
||||
(LC_COLLATE = 'C', LC_CTYPE = 'C', PROVIDER = 'libc', DETERMINISTIC = true, VERSION = '1');
|
||||
|
||||
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
|
||||
OWNER TO <OWNER>;
|
||||
|
||||
COMMENT ON COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
|
||||
IS 'libc';
|
||||
@@ -33,12 +33,12 @@
|
||||
"name": "Drop Collation",
|
||||
"endpoint": "NODE-collation.delete_id",
|
||||
"data": {
|
||||
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b"
|
||||
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "create",
|
||||
"name": "Create Collation with extra parameters",
|
||||
"name": "Create Collation with icu provider",
|
||||
"endpoint": "NODE-collation.obj",
|
||||
"sql_endpoint": "NODE-collation.sql_id",
|
||||
"data": {
|
||||
@@ -53,6 +53,40 @@
|
||||
},
|
||||
"store_object_id": true,
|
||||
"expected_sql_file": "create_collation_with_extra_params.sql"
|
||||
},
|
||||
{
|
||||
"type": "delete",
|
||||
"name": "Drop Collation",
|
||||
"endpoint": "NODE-collation.delete_id",
|
||||
"data": {
|
||||
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "create",
|
||||
"name": "Create Collation with libc provider",
|
||||
"endpoint": "NODE-collation.obj",
|
||||
"sql_endpoint": "NODE-collation.sql_id",
|
||||
"data": {
|
||||
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b",
|
||||
"schema": "testschema",
|
||||
"owner": "postgres",
|
||||
"description": "libc",
|
||||
"locale": "C",
|
||||
"provider": "libc",
|
||||
"is_deterministic": true,
|
||||
"version": "1"
|
||||
},
|
||||
"store_object_id": true,
|
||||
"expected_sql_file": "create_collation_with_libc.sql"
|
||||
},
|
||||
{
|
||||
"type": "delete",
|
||||
"name": "Drop Collation",
|
||||
"endpoint": "NODE-collation.delete_id",
|
||||
"data": {
|
||||
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Collation: Cl1_$%{}[]()&*^!@"'`\/#a;
|
||||
|
||||
-- DROP COLLATION IF EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#a";
|
||||
|
||||
CREATE COLLATION IF NOT EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#a"
|
||||
(LC_COLLATE = 'C', LC_CTYPE = 'C', PROVIDER = 'libc', DETERMINISTIC = true);
|
||||
|
||||
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#a"
|
||||
OWNER TO <OWNER>;
|
||||
|
||||
COMMENT ON COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#a"
|
||||
IS 'Description for alter';
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Collation: Cl1_$%{}[]()&*^!@"'`\/#;
|
||||
|
||||
-- DROP COLLATION IF EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#";
|
||||
|
||||
CREATE COLLATION IF NOT EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#"
|
||||
(LC_COLLATE = 'C', LC_CTYPE = 'C', PROVIDER = 'libc', DETERMINISTIC = true);
|
||||
|
||||
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#"
|
||||
OWNER TO <OWNER>;
|
||||
|
||||
COMMENT ON COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#"
|
||||
IS 'Description';
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Collation: Cl1_$%{}[]()&*^!@"'`\/#b;
|
||||
|
||||
-- DROP COLLATION IF EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b";
|
||||
|
||||
CREATE COLLATION IF NOT EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
|
||||
(LC_COLLATE = 'C', LC_CTYPE = 'C', PROVIDER = 'builtin', DETERMINISTIC = true, VERSION = '1');
|
||||
|
||||
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
|
||||
OWNER TO <OWNER>;
|
||||
|
||||
COMMENT ON COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
|
||||
IS 'builtin';
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Collation: Cl1_$%{}[]()&*^!@"'`\/#b;
|
||||
|
||||
-- DROP COLLATION IF EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b";
|
||||
|
||||
CREATE COLLATION IF NOT EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
|
||||
(LC_COLLATE = 'locale', LC_CTYPE = 'locale', PROVIDER = 'icu', DETERMINISTIC = true, VERSION = '1');
|
||||
|
||||
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
|
||||
OWNER TO <OWNER>;
|
||||
|
||||
COMMENT ON COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
|
||||
IS 'Description for extra params';
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Collation: Cl1_$%{}[]()&*^!@"'`\/#b;
|
||||
|
||||
-- DROP COLLATION IF EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b";
|
||||
|
||||
CREATE COLLATION IF NOT EXISTS testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
|
||||
(LC_COLLATE = 'C', LC_CTYPE = 'C', PROVIDER = 'libc', DETERMINISTIC = true);
|
||||
|
||||
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
|
||||
OWNER TO <OWNER>;
|
||||
|
||||
COMMENT ON COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#b"
|
||||
IS 'libc';
|
||||
@@ -0,0 +1,5 @@
|
||||
COMMENT ON COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#"
|
||||
IS 'Description for alter';
|
||||
|
||||
ALTER COLLATION testschema."Cl1_$%{}[]()&*^!@""'`\/#"
|
||||
RENAME TO "Cl1_$%{}[]()&*^!@""'`\/#a";
|
||||
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"scenarios": [
|
||||
{
|
||||
"type": "create",
|
||||
"name": "Create Collation",
|
||||
"endpoint": "NODE-collation.obj",
|
||||
"sql_endpoint": "NODE-collation.sql_id",
|
||||
"data": {
|
||||
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#",
|
||||
"schema": "testschema",
|
||||
"copy_collation": "pg_catalog.\"C\"",
|
||||
"description": "Description"
|
||||
},
|
||||
"store_object_id": true,
|
||||
"expected_sql_file": "create_collation.sql"
|
||||
},
|
||||
{
|
||||
"type": "alter",
|
||||
"name": "Alter Collation",
|
||||
"endpoint": "NODE-collation.obj_id",
|
||||
"sql_endpoint": "NODE-collation.sql_id",
|
||||
"msql_endpoint": "NODE-collation.msql_id",
|
||||
"data": {
|
||||
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#a",
|
||||
"schema": "testschema",
|
||||
"description": "Description for alter"
|
||||
},
|
||||
"expected_sql_file": "alter_collation.sql",
|
||||
"expected_msql_file": "msql_collation.sql"
|
||||
},
|
||||
{
|
||||
"type": "delete",
|
||||
"name": "Drop Collation",
|
||||
"endpoint": "NODE-collation.delete_id",
|
||||
"data": {
|
||||
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "create",
|
||||
"name": "Create Collation with icu provider",
|
||||
"endpoint": "NODE-collation.obj",
|
||||
"sql_endpoint": "NODE-collation.sql_id",
|
||||
"data": {
|
||||
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b",
|
||||
"schema": "testschema",
|
||||
"owner": "postgres",
|
||||
"description": "Description for extra params",
|
||||
"locale": "locale",
|
||||
"provider": "icu",
|
||||
"is_deterministic": true,
|
||||
"version": "1"
|
||||
},
|
||||
"store_object_id": true,
|
||||
"expected_sql_file": "create_collation_with_extra_params.sql"
|
||||
},
|
||||
{
|
||||
"type": "delete",
|
||||
"name": "Drop Collation",
|
||||
"endpoint": "NODE-collation.delete_id",
|
||||
"data": {
|
||||
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "create",
|
||||
"name": "Create Collation with libc provider",
|
||||
"endpoint": "NODE-collation.obj",
|
||||
"sql_endpoint": "NODE-collation.sql_id",
|
||||
"data": {
|
||||
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b",
|
||||
"schema": "testschema",
|
||||
"owner": "postgres",
|
||||
"description": "libc",
|
||||
"locale": "C",
|
||||
"provider": "libc",
|
||||
"is_deterministic": true
|
||||
},
|
||||
"store_object_id": true,
|
||||
"expected_sql_file": "create_collation_with_libc.sql"
|
||||
},
|
||||
{
|
||||
"type": "delete",
|
||||
"name": "Drop Collation",
|
||||
"endpoint": "NODE-collation.delete_id",
|
||||
"data": {
|
||||
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "create",
|
||||
"name": "Create Collation with builtin provider",
|
||||
"endpoint": "NODE-collation.obj",
|
||||
"sql_endpoint": "NODE-collation.sql_id",
|
||||
"data": {
|
||||
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b",
|
||||
"schema": "testschema",
|
||||
"owner": "postgres",
|
||||
"description": "builtin",
|
||||
"locale": "C",
|
||||
"provider": "builtin",
|
||||
"is_deterministic": true,
|
||||
"version": "1"
|
||||
},
|
||||
"store_object_id": true,
|
||||
"expected_sql_file": "create_collation_with_builtin.sql"
|
||||
},
|
||||
{
|
||||
"type": "delete",
|
||||
"name": "Drop Collation",
|
||||
"endpoint": "NODE-collation.delete_id",
|
||||
"data": {
|
||||
"name": "Cl1_$%{}[]()&*^!@\"'`\\/#b"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -53,6 +53,7 @@ define('pgadmin.node.domain_constraints', [
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 5, label: gettext('Domain Constraint...'),
|
||||
data: {action: 'create', check: true}, enable: 'canCreate',
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
},{
|
||||
name: 'create_domain_constraints', node: 'domain_constraints', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
@@ -63,6 +64,7 @@ define('pgadmin.node.domain_constraints', [
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 5, label: gettext('Domain Constraint...'),
|
||||
data: {action: 'create', check: false}, enable: 'canCreate',
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -55,11 +55,13 @@ define('pgadmin.node.domain', [
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Domain...'),
|
||||
data: {action: 'create', check: true}, enable: 'canCreate',
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
},{
|
||||
name: 'create_domain', node: 'domain', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Domain...'),
|
||||
data: {action: 'create', check: true}, enable: 'canCreate',
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
},{
|
||||
name: 'create_domain', node: 'schema', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
|
||||
@@ -60,11 +60,13 @@ define('pgadmin.node.foreign_table', ['pgadmin.tables.js/enable_disable_triggers
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Foreign Table...'),
|
||||
data: {action: 'create', check: true}, enable: 'canCreate',
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
},{
|
||||
name: 'create_foreign_table', node: 'foreign_table', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Foreign Table...'),
|
||||
data: {action: 'create', check: true}, enable: 'canCreate',
|
||||
shortcut_preference: ['browser', 'sub_menu_create'],
|
||||
},{
|
||||
name: 'create_foreign_table', node: 'schema', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
|
||||
@@ -5,5 +5,5 @@ SELECT
|
||||
FROM
|
||||
pg_catalog.pg_constraint
|
||||
WHERE
|
||||
conrelid={{foid}}::oid
|
||||
conrelid={{foid}}::oid AND contype != 'n'
|
||||
ORDER by conname;
|
||||