Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60c56b3285 | ||
|
|
f251fdfdb1 | ||
|
|
09d2b7eeb0 | ||
|
|
e374edc692 | ||
|
|
1d397395f7 | ||
|
|
ed52a44b17 | ||
|
|
06fb5a45c1 | ||
|
|
d1805c5640 | ||
|
|
c04b9198c8 | ||
|
|
ec63acc4c5 | ||
|
|
f906326003 | ||
|
|
39b6f40482 | ||
|
|
e6f6a90d2f | ||
|
|
d80cc6f8f9 | ||
|
|
38ebb43bac | ||
|
|
48b4a8d448 | ||
|
|
d9b3f950ad | ||
|
|
986ba41ba9 | ||
|
|
08379d6ae0 | ||
|
|
7251c1f628 | ||
|
|
061450c8d7 | ||
|
|
b62212b8e5 | ||
|
|
282a956f4f | ||
|
|
00dbe58125 | ||
|
|
abdcd983f6 | ||
|
|
c7a6056ee3 | ||
|
|
55c5e86fe0 | ||
|
|
8d55e9a445 | ||
|
|
c1da777fba | ||
|
|
9e35c32ef2 | ||
|
|
37a164cccb | ||
|
|
c20d035f1b | ||
|
|
9a44fee646 | ||
|
|
0a14db01be | ||
|
|
53a994ba1e | ||
|
|
91ad54d17b | ||
|
|
ea5ee74205 | ||
|
|
5f22b4e2d2 | ||
|
|
758bbc7272 | ||
|
|
618e03b65f | ||
|
|
e9b97d61f8 | ||
|
|
83377d4a50 | ||
|
|
c03d21899b | ||
|
|
d03a0cdce6 | ||
|
|
f3567518d3 | ||
|
|
b30520adc0 | ||
|
|
c6af61afd3 | ||
|
|
04024c7f61 | ||
|
|
9e6ab295df | ||
|
|
5c9acc1c3d | ||
|
|
c75bba03c2 | ||
|
|
93bbadb994 | ||
|
|
42ff67c845 | ||
|
|
5376d88444 | ||
|
|
f733d124bd | ||
|
|
7ff3c4bbeb | ||
|
|
e5590797c0 | ||
|
|
8576a5e7e6 | ||
|
|
3e66b6f7be | ||
|
|
2fd2a144f1 | ||
|
|
500a5c7101 | ||
|
|
5498682934 | ||
|
|
cdeb18fcbb | ||
|
|
0de635cc4c | ||
|
|
e09af0a0e9 | ||
|
|
ade57d4ec4 | ||
|
|
fc41c795f1 | ||
|
|
7caaf2de82 | ||
|
|
09bc544e83 | ||
|
|
42a47126aa | ||
|
|
db87df8f82 | ||
|
|
15c212a2df | ||
|
|
68627ab44e | ||
|
|
800fdce316 | ||
|
|
a5557b466c | ||
|
|
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 |
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.]
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pgver: [13, 14, 15, 16, 17]
|
||||
pgver: [13, 14, 15, 16, 17, 18]
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
|
||||
@@ -73,6 +73,14 @@ jobs:
|
||||
IF %ERRORLEVEL% EQU 3010 cmd /c "exit /b 0"
|
||||
shell: cmd
|
||||
|
||||
- name: Add pg_config path to PATH
|
||||
shell: pwsh
|
||||
run: |
|
||||
$pgPath = "C:\\EPAS\\${{ matrix.pgver }}\\bin"
|
||||
Write-Output $pgPath
|
||||
Add-Content -Path $env:GITHUB_PATH -Value $pgPath
|
||||
pg_config --version
|
||||
|
||||
- name: Create the tablespace directory on Linux
|
||||
if: ${{ matrix.os == 'ubuntu-22.04' }}
|
||||
run: |
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-22.04, windows-latest]
|
||||
pgver: [13, 14, 15, 16, 17]
|
||||
pgver: [13, 14, 15, 16, 17, 18]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
@@ -64,6 +64,7 @@ jobs:
|
||||
brew install postgresql@${{ matrix.pgver }}
|
||||
echo "/opt/homebrew/opt/postgresql@${{ matrix.pgver }}/bin" >> $GITHUB_PATH
|
||||
|
||||
|
||||
- name: Uninstall PostgreSQL if already present on windows
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
run: |
|
||||
@@ -78,13 +79,21 @@ jobs:
|
||||
run: |
|
||||
FOR /f "delims=" %%F IN ('python tools\get_sb_package.py "https://www.postgresql.org/applications-v2.xml" "postgresql_${{ matrix.pgver }}" "windows-x64"') DO SET INSTALLER_EXE=%%F
|
||||
ECHO Running %INSTALLER_EXE%...
|
||||
%INSTALLER_EXE% --prefix C:\PostgreSQL\${{ matrix.pgver }} --datadir C:\PostgreSQL\${{ matrix.pgver }}\data --serverport 59${{ matrix.pgver }} --superpassword postgres --install_runtimes 0 --mode unattended --unattendedmodeui none --disable-components pgAdmin,stackbuilder --enable-components server,commandlinetools"
|
||||
%INSTALLER_EXE% --prefix C:\PostgreSQL_custom\${{ matrix.pgver }} --datadir C:\PostgreSQL_custom\${{ matrix.pgver }}\data --serverport 59${{ matrix.pgver }} --superpassword postgres --install_runtimes 0 --mode unattended --unattendedmodeui none --disable-components pgAdmin,stackbuilder --enable-components server,commandlinetools"
|
||||
choco install -y mitkerberos
|
||||
|
||||
REM Ignore error 3010 (reboot required)
|
||||
IF %ERRORLEVEL% EQU 3010 cmd /c "exit /b 0"
|
||||
shell: cmd
|
||||
|
||||
- name: Add pg_config path to PATH
|
||||
shell: pwsh
|
||||
run: |
|
||||
$pgPath = "C:\\PostgreSQL_custom\\${{ matrix.pgver }}\\bin"
|
||||
Write-Output $pgPath
|
||||
Add-Content -Path $env:GITHUB_PATH -Value $pgPath
|
||||
pg_config --version
|
||||
|
||||
- name: Create the tablespace directory on Linux
|
||||
if: ${{ matrix.os == 'ubuntu-22.04' }}
|
||||
run: |
|
||||
|
||||
@@ -120,21 +120,16 @@ RUN rm -rf /pgadmin4/docs/en_US/_build/html/_static/*.png
|
||||
# Create additional builders to get all of the PostgreSQL utilities
|
||||
#########################################################################
|
||||
|
||||
FROM postgres:12-alpine AS pg12-builder
|
||||
FROM postgres:13-alpine AS pg13-builder
|
||||
FROM postgres:14-alpine AS pg14-builder
|
||||
FROM postgres:15-alpine AS pg15-builder
|
||||
FROM postgres:16-alpine AS pg16-builder
|
||||
FROM postgres:17-alpine AS pg17-builder
|
||||
FROM postgres:18-alpine AS pg18-builder
|
||||
|
||||
FROM alpine:latest AS tool-builder
|
||||
|
||||
# Copy the PG binaries
|
||||
COPY --from=pg12-builder /usr/local/bin/pg_dump /usr/local/pgsql/pgsql-12/
|
||||
COPY --from=pg12-builder /usr/local/bin/pg_dumpall /usr/local/pgsql/pgsql-12/
|
||||
COPY --from=pg12-builder /usr/local/bin/pg_restore /usr/local/pgsql/pgsql-12/
|
||||
COPY --from=pg12-builder /usr/local/bin/psql /usr/local/pgsql/pgsql-12/
|
||||
|
||||
COPY --from=pg13-builder /usr/local/bin/pg_dump /usr/local/pgsql/pgsql-13/
|
||||
COPY --from=pg13-builder /usr/local/bin/pg_dumpall /usr/local/pgsql/pgsql-13/
|
||||
COPY --from=pg13-builder /usr/local/bin/pg_restore /usr/local/pgsql/pgsql-13/
|
||||
@@ -160,6 +155,11 @@ COPY --from=pg17-builder /usr/local/bin/pg_dumpall /usr/local/pgsql/pgsql-17/
|
||||
COPY --from=pg17-builder /usr/local/bin/pg_restore /usr/local/pgsql/pgsql-17/
|
||||
COPY --from=pg17-builder /usr/local/bin/psql /usr/local/pgsql/pgsql-17/
|
||||
|
||||
COPY --from=pg18-builder /usr/local/bin/pg_dump /usr/local/pgsql/pgsql-18/
|
||||
COPY --from=pg18-builder /usr/local/bin/pg_dumpall /usr/local/pgsql/pgsql-18/
|
||||
COPY --from=pg18-builder /usr/local/bin/pg_restore /usr/local/pgsql/pgsql-18/
|
||||
COPY --from=pg18-builder /usr/local/bin/psql /usr/local/pgsql/pgsql-18/
|
||||
|
||||
#########################################################################
|
||||
# Assemble everything into the final container.
|
||||
#########################################################################
|
||||
@@ -171,12 +171,12 @@ COPY --from=env-builder /venv /venv
|
||||
|
||||
# Copy in the tools
|
||||
COPY --from=tool-builder /usr/local/pgsql /usr/local/
|
||||
COPY --from=pg17-builder /usr/local/lib/libpq.so.5.17 /usr/lib/
|
||||
COPY --from=pg17-builder /usr/lib/libzstd.so.1.5.7 /usr/lib/
|
||||
COPY --from=pg17-builder /usr/lib/liblz4.so.1.10.0 /usr/lib/
|
||||
COPY --from=pg18-builder /usr/local/lib/libpq.so.5.18 /usr/lib/
|
||||
COPY --from=pg18-builder /usr/lib/libzstd.so.1.5.7 /usr/lib/
|
||||
COPY --from=pg18-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 && \
|
||||
RUN ln -s libpq.so.5.18 /usr/lib/libpq.so.5 && \
|
||||
ln -s libpq.so.5.18 /usr/lib/libpq.so && \
|
||||
ln -s libzstd.so.1.5.7 /usr/lib/libzstd.so.1 && \
|
||||
ln -s liblz4.so.1.10.0 /usr/lib/liblz4.so.1
|
||||
|
||||
|
||||
@@ -282,7 +282,7 @@ REM Main build sequence Ends
|
||||
|
||||
REM WGET
|
||||
FOR /f "tokens=*" %%i IN ('npm info electron version') DO SET "ELECTRON_VERSION=%%i"
|
||||
SET ELECTRON_VERSION="35.4.0"
|
||||
|
||||
:GET_NW
|
||||
wget https://github.com/electron/electron/releases/download/v%ELECTRON_VERSION%/electron-v%ELECTRON_VERSION%-win32-x64.zip -O "%TMPDIR%\electron-v%ELECTRON_VERSION%-win32-x64.zip"
|
||||
IF %ERRORLEVEL% NEQ 0 GOTO GET_NW
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -53,6 +53,7 @@ The default binary paths set in the container are as follows:
|
||||
.. code-block:: bash
|
||||
|
||||
DEFAULT_BINARY_PATHS = {
|
||||
'pg-18': '/usr/local/pgsql-18',
|
||||
'pg-17': '/usr/local/pgsql-17',
|
||||
'pg-16': '/usr/local/pgsql-16',
|
||||
'pg-15': '/usr/local/pgsql-15',
|
||||
|
||||
@@ -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
|
||||
@@ -86,6 +86,9 @@ Editing Options
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
| Icon | Behavior | Shortcut |
|
||||
+======================+===================================================================================================+================+
|
||||
| *Search table* | Click to search for a table in the diagram. Selecting a table from the search results will bring | Option/Alt + |
|
||||
| | it into view and highlight it. | Ctrl + F |
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
| *Add table* | Click this button to add a new table to the diagram. On clicking, this will open a table dialog | Option/Alt + |
|
||||
| | where you can put the table details. | Ctrl + A |
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
@@ -109,11 +112,14 @@ Table Relationship Options
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
| Icon | Behavior | Shortcut |
|
||||
+======================+===================================================================================================+================+
|
||||
| *1M* | Click this button to open a one-to-many relationship dialog to add a relationship between the | Option/Alt + |
|
||||
| *1-1* | Click this button to open a one-to-one relationship dialog to add a relationship between the | Option/Alt + |
|
||||
| | two tables. The selected table becomes the referencing table. | Ctrl + B |
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
| *1-M* | Click this button to open a one-to-many relationship dialog to add a relationship between the | Option/Alt + |
|
||||
| | two tables. The selected table becomes the referencing table and will have the *many* endpoint of | Ctrl + O |
|
||||
| | the link. | |
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
| *MM* | Click this button to open a many-to-many relationship dialog to add a relationship between the | Option/Alt + |
|
||||
| *M-M* | Click this button to open a many-to-many relationship dialog to add a relationship between the | Option/Alt + |
|
||||
| | two tables. This option will create a new table based on the selected columns for the two relating| Ctrl + M |
|
||||
| | tables and link them. | |
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
|
||||
@@ -41,6 +41,9 @@ Use the fields in the *Definition* tab to define the function:
|
||||
will change to an input text field.
|
||||
* Use the drop-down listbox next to *Language* to select the implementation
|
||||
language. The default is *sql*.
|
||||
* Use the drop-down listbox next to *Depends on extensions* to select the extension that this function
|
||||
depends on (for example, plpgsql). If set, dropping the extension will automatically drop the
|
||||
function as well.
|
||||
* Use the fields in the *Arguments* to define an argument. Click the *Add*
|
||||
icon (+) to set parameters and values for the argument:
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 340 KiB After Width: | Height: | Size: 679 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 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: 141 KiB After Width: | Height: | Size: 125 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: 39 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 72 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>/
|
||||
@@ -87,6 +87,8 @@ There are 3 ways to configure LDAP:
|
||||
"LDAP_KEY_FILE","Specifies the path to the server private key file. This parameter
|
||||
is applicable only if you are using *ldaps* as connection protocol or you have
|
||||
set *LDAP_USE_STARTTLS* parameter to *True*."
|
||||
"LDAP_CERT_VALIDATE", "Set this parameter to *False* if you want to bypass
|
||||
the TLS certificate validation. By default it is set to True."
|
||||
"LDAP_IGNORE_MALFORMED_SCHEMA", "Some flaky LDAP servers returns malformed schema.
|
||||
If this parameter set to *True*, no exception will be raised and schema is thrown away
|
||||
but authentication will be done. This parameter should remain False, as recommended."
|
||||
|
||||
@@ -81,9 +81,9 @@ following options (in alphabetical order):
|
||||
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
|
||||
| *Trigger(s)* | Click to *Disable* or *Enable* trigger(s) for the currently selected table. Options are displayed on the flyout menu. |
|
||||
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
|
||||
| *Truncate* | Click to remove all rows from a table (*Truncate*), to remove all rows from a table and its child tables |
|
||||
| | (*Truncate Cascade*) or to remove all rows from a table and automatically restart sequences owned by columns |
|
||||
| | (*Truncate Restart Identity*). Options are displayed on the flyout menu. |
|
||||
| *Truncate* | Click to remove all rows from a table/foreign tables (*Truncate*), to remove all rows from a table/foreign tables and |
|
||||
| | its child tables (*Truncate Cascade*) or to remove all rows from a table/foreign tables and automatically restart |
|
||||
| | sequences owned by columns (*Truncate Restart Identity*). Options are displayed on the flyout menu. |
|
||||
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
|
||||
| *View Data* | Click to access a context menu that provides several options for viewing data (see below). |
|
||||
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
@@ -254,6 +254,12 @@ Use the fields on the *Options* panel to manage ERD preferences.
|
||||
* Use *Cardinality Notation* to change the cardinality notation format
|
||||
used to present relationship links.
|
||||
|
||||
* Use *Format ERD Project File?* if you want to format the .pgerd ERD
|
||||
project file. It's a JSON file which will be formatted.
|
||||
|
||||
* Use *Image Download Resolution* to change the ERD image download
|
||||
resolution. Higher values will use higher memory and slower rendering.
|
||||
|
||||
* When the *SQL With DROP Table* switch is set to *True*, the SQL
|
||||
generated by the ERD Tool will add DROP table DDL before each CREATE
|
||||
table DDL.
|
||||
@@ -262,6 +268,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 +520,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
|
||||
@@ -592,34 +645,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
|
||||
********************
|
||||
|
||||
@@ -36,6 +36,9 @@ Use the fields in the *Definition* tab to define the procedure:
|
||||
|
||||
* Use the drop-down listbox next to *Language* to select a language. The default
|
||||
is *edbspl*.
|
||||
* Use the drop-down listbox next to *Depends on extensions* to select the extension that this procedure
|
||||
depends on (for example, edbspl). If set, dropping the extension will automatically drop the
|
||||
procedure as well.
|
||||
* Use the fields in the *Arguments* section to define an argument. Click *Add* to set
|
||||
parameters and values for the argument:
|
||||
* Use the drop-down listbox next to *Data type* to select a data type.
|
||||
|
||||
@@ -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,10 @@ notes for it.
|
||||
:maxdepth: 1
|
||||
|
||||
|
||||
release_notes_9_10
|
||||
release_notes_9_9
|
||||
release_notes_9_8
|
||||
release_notes_9_7
|
||||
release_notes_9_6
|
||||
release_notes_9_5
|
||||
release_notes_9_4
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
************
|
||||
Version 9.10
|
||||
************
|
||||
|
||||
Release date: 2025-11-13
|
||||
|
||||
This release contains a number of bug fixes and new features since the release of pgAdmin 4 v9.9.
|
||||
|
||||
Supported Database Servers
|
||||
**************************
|
||||
**PostgreSQL**: 13, 14, 15, 16, 17 and 18
|
||||
|
||||
**EDB Advanced Server**: 13, 14, 15, 16, 17 and 18
|
||||
|
||||
Bundled PostgreSQL Utilities
|
||||
****************************
|
||||
**psql**, **pg_dump**, **pg_dumpall**, **pg_restore**: 18.0
|
||||
|
||||
|
||||
New features
|
||||
************
|
||||
|
||||
| `Issue #4306 <https://github.com/pgadmin-org/pgadmin4/issues/4306>`_ - Added the ability to search for tables and automatically bring them into view in the ERD tool.
|
||||
| `Issue #6391 <https://github.com/pgadmin-org/pgadmin4/issues/6391>`_ - Add support of DEPENDS/NO DEPENDS ON EXTENSION for PROCEDURE.
|
||||
| `Issue #6698 <https://github.com/pgadmin-org/pgadmin4/issues/6698>`_ - Add support for setting image download resolution in the ERD tool.
|
||||
| `Issue #7885 <https://github.com/pgadmin-org/pgadmin4/issues/7885>`_ - Add support for displaying detailed Citus query plans instead of 'Custom Scan' placeholder.
|
||||
| `Issue #8912 <https://github.com/pgadmin-org/pgadmin4/issues/8912>`_ - Add support for formatting .pgerd ERD project file.
|
||||
|
||||
Housekeeping
|
||||
************
|
||||
|
||||
| `Issue #8676 <https://github.com/pgadmin-org/pgadmin4/issues/8676>`_ - Migrate pgAdmin UI to use React 19.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Issue #8504 <https://github.com/pgadmin-org/pgadmin4/issues/8504>`_ - Fixed an issue where data output column resize is not sticking in Safari.
|
||||
| `Issue #9117 <https://github.com/pgadmin-org/pgadmin4/issues/9117>`_ - Fixed an issue where Schema Diff does not ignore Tablespace for indexes.
|
||||
| `Issue #9132 <https://github.com/pgadmin-org/pgadmin4/issues/9132>`_ - Fixed an issue where the 2FA window redirected to the login page after session expiration.
|
||||
| `Issue #9233 <https://github.com/pgadmin-org/pgadmin4/issues/9233>`_ - Fixed an issue where the Select All option on the columns tab of import/export data was not working in languages other than English.
|
||||
| `Issue #9240 <https://github.com/pgadmin-org/pgadmin4/issues/9240>`_ - Fixed an issue where the Debian build process failed with a "Sphinx module not found" error when using a Python virtual environment.
|
||||
| `Issue #9281 <https://github.com/pgadmin-org/pgadmin4/issues/9281>`_ - Fixed an issue where the last used storage directory was reset to blank, leading to access denied errors during backup or restore operations.
|
||||
| `Issue #9304 <https://github.com/pgadmin-org/pgadmin4/issues/9304>`_ - Fixed an issue that prevented assigning multiple users to an RLS policy.
|
||||
| `Issue #9320 <https://github.com/pgadmin-org/pgadmin4/issues/9320>`_ - Fixed remote code execution vulnerability when restoring PLAIN-format SQL dumps in server mode (CVE-2025-12762).
|
||||
| `Issue #9323 <https://github.com/pgadmin-org/pgadmin4/issues/9323>`_ - Fixed Command injection vulnerability allowing arbitrary command execution on Windows (CVE-2025-12763).
|
||||
| `Issue #9324 <https://github.com/pgadmin-org/pgadmin4/issues/9324>`_ - Fixed LDAP authentication flow vulnerable to TLS certificate verification bypass (CVE-2025-12765).
|
||||
| `Issue #9325 <https://github.com/pgadmin-org/pgadmin4/issues/9325>`_ - Fixed LDAP injection vulnerability in LDAP authentication flow (CVE-2025-12764).
|
||||
@@ -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.
|
||||
@@ -0,0 +1,39 @@
|
||||
***********
|
||||
Version 9.8
|
||||
***********
|
||||
|
||||
Release date: 2025-09-04
|
||||
|
||||
This release contains a number of bug fixes and new features since the release of pgAdmin 4 v9.7.
|
||||
|
||||
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.5
|
||||
|
||||
|
||||
New features
|
||||
************
|
||||
|
||||
| `Issue #6396 <https://github.com/pgadmin-org/pgadmin4/issues/6396>`_ - Add menu items for truncating foreign tables.
|
||||
| `Issue #7928 <https://github.com/pgadmin-org/pgadmin4/issues/7928>`_ - Added Debian Trixie as a supported platform for pgAdmin.
|
||||
| `Issue #8891 <https://github.com/pgadmin-org/pgadmin4/issues/8891>`_ - Allow user to configure security related gunicorn parameters.
|
||||
| `Issue #9093 <https://github.com/pgadmin-org/pgadmin4/issues/9093>`_ - Change the default pgAdmin theme to System.
|
||||
|
||||
Housekeeping
|
||||
************
|
||||
|
||||
| `Issue #7448 <https://github.com/pgadmin-org/pgadmin4/issues/7448>`_ - Remove usage of BrowserFS as it is deprecated.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Issue #9090 <https://github.com/pgadmin-org/pgadmin4/issues/9090>`_ - Pin Paramiko to version 3.5.1 to fix the DSSKey error introduced in the latest release.
|
||||
| `Issue #9095 <https://github.com/pgadmin-org/pgadmin4/issues/9095>`_ - Fixed an issue where pgAdmin config migration was failing while upgrading to v9.7.
|
||||
| `Issue #9114 <https://github.com/pgadmin-org/pgadmin4/issues/9114>`_ - Fixed Cross-Origin Opener Policy (COOP) vulnerability in the OAuth 2.0 authentication flow (CVE-2025-9636).
|
||||
| `Issue #9116 <https://github.com/pgadmin-org/pgadmin4/issues/9116>`_ - Fixed an issue where editor shortcuts fail when using Option key combinations on macOS, due to macOS treating Option+Key as a different key input.
|
||||
@@ -0,0 +1,40 @@
|
||||
***********
|
||||
Version 9.9
|
||||
***********
|
||||
|
||||
Release date: 2025-10-16
|
||||
|
||||
This release contains a number of bug fixes and new features since the release of pgAdmin 4 v9.8.
|
||||
|
||||
Supported Database Servers
|
||||
**************************
|
||||
**PostgreSQL**: 13, 14, 15, 16, 17 and 18
|
||||
|
||||
**EDB Advanced Server**: 13, 14, 15, 16 and 17
|
||||
|
||||
Bundled PostgreSQL Utilities
|
||||
****************************
|
||||
**psql**, **pg_dump**, **pg_dumpall**, **pg_restore**: 17.5
|
||||
|
||||
|
||||
New features
|
||||
************
|
||||
|
||||
| `Issue #6385 <https://github.com/pgadmin-org/pgadmin4/issues/6385>`_ - Add support of DEPENDS/NO DEPENDS ON EXTENSION for ALTER FUNCTION.
|
||||
| `Issue #6394 <https://github.com/pgadmin-org/pgadmin4/issues/6394>`_ - Added "MULTIRANGE_TYPE_NAME" option while creating a Range Type.
|
||||
| `Issue #6395 <https://github.com/pgadmin-org/pgadmin4/issues/6395>`_ - Added "SUBSCRIPT" option while creating a External Type.
|
||||
| `Issue #6996 <https://github.com/pgadmin-org/pgadmin4/issues/6996>`_ - Added option to skip the password dialog when using an identity file.
|
||||
| `Issue #8932 <https://github.com/pgadmin-org/pgadmin4/issues/8932>`_ - Added 'failover' and 'two_phase' parameter support in CREATE/ALTER SUBSCRIPTION for PostgreSQL v17+.
|
||||
| `Issue #9225 <https://github.com/pgadmin-org/pgadmin4/issues/9225>`_ - Add support for initializing the pgAdmin4 Kubernetes Helm chart.
|
||||
|
||||
Housekeeping
|
||||
************
|
||||
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Issue #9098 <https://github.com/pgadmin-org/pgadmin4/issues/9098>`_ - Fixed an issue where the query tool displayed 'default' instead of 'null' for null text data in the data output.
|
||||
| `Issue #9125 <https://github.com/pgadmin-org/pgadmin4/issues/9125>`_ - Fixed an issue where the pgAdmin configuration database wasn't being created on a fresh install when an external database was used for the configuration.
|
||||
| `Issue #9157 <https://github.com/pgadmin-org/pgadmin4/issues/9157>`_ - Fixed an issue where shortcuts are not working as expected on multiple keyboard layouts.
|
||||
| `Issue #9158 <https://github.com/pgadmin-org/pgadmin4/issues/9158>`_ - Fixed an issue where saving the newly changed preferences was not reflecting on the preferences tab.
|
||||
@@ -177,6 +177,9 @@ not be able to connect directly.
|
||||
*Identity file* field to specify the location of the key file.
|
||||
* If the SSH host is expecting a password of the user name or an identity file
|
||||
if being used, use the *Password* field to specify the password.
|
||||
* Check the box next to *Prompt for password?* to to have pgAdmin prompt for
|
||||
a password if the identity file includes one. This setting applies only when
|
||||
using an identity file, which may or may not require a password.
|
||||
* Check the box next to *Save password?* to instruct pgAdmin to save the
|
||||
password for future use. Use
|
||||
:ref:`Clear SSH Tunnel Password <clear_saved_passwords>` to remove the saved
|
||||
|
||||
@@ -106,6 +106,7 @@ Use the *With* tab to define some parameters for a subscription:
|
||||
* Move the *Run as owner?* switch to *true* position to specify all replication actions are performed as the subscription owner. If *false*, replication workers will perform actions on each table as the owner of that table. The default is *false*. This option is available only on PostgreSQL 16 and above.
|
||||
* Use the *Password required?* to specify whether connections to the publisher made as a result of this subscription must use password authentication. This setting is ignored when the subscription is owned by a superuser. The default is true. Only superusers can set this value to *false*. This option is available only on PostgreSQL 16 and above.
|
||||
* Use the *Origin* to specify whether the subscription will request the publisher to only send changes that don't have an origin or send changes regardless of origin. The default is *any*. This option is available only on PostgreSQL 16 and above.
|
||||
* Use the *Failover* to specify whether the replication slots associated with the subscription are enabled to be synced to the standbys so that logical replication can be resumed from the new primary after failover. The default is false. This option is available only on PostgreSQL 17 and above.
|
||||
|
||||
Click the *SQL* tab to continue.
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -109,17 +109,17 @@ On the *Optional-1* tab:
|
||||
to select a type_modifier_output_function. It is allowed to omit the
|
||||
type_modifier_output_function, in which case the default display format is the
|
||||
stored typmod integer value enclosed in parentheses.
|
||||
* Use the drop-down listbox next to the optional *Analyze function* field to
|
||||
select a function for performing type-specific statistics collection for
|
||||
columns of the data type.
|
||||
* Use the drop-down listbox next to the optional *Subscript function* field to
|
||||
select a function for allows the data type to be subscripted in SQL commands.
|
||||
* Use the optional *Internal length* to specify a value for internal
|
||||
representation.
|
||||
* Move the *Variable?* switch to specify the internal representation is of
|
||||
variable length (VARIABLE). The default is a fixed length positive integer.
|
||||
* Specify a default value in the optional *Default* field in cases where a
|
||||
column of the data type defaults to something other than the null value.
|
||||
Specify the default with the DEFAULT key word. (A default can be overridden
|
||||
by an explicit DEFAULT clause attached to a particular column.)
|
||||
* Use the drop-down listbox next to the optional *Analyze function* field to
|
||||
select a function for performing type-specific statistics collection for
|
||||
columns of the data type.
|
||||
* Use the drop-down listbox next to the optional *Category type* field to help
|
||||
control which implicit cast will be applied in ambiguous situations.
|
||||
* Move the *Preferred?* switch to *Yes* to specify the selected category type is
|
||||
@@ -162,6 +162,9 @@ disabled.
|
||||
values to a canonical form.
|
||||
* Use the drop-down listbox next to *Sub-type diff function* to select a
|
||||
user-defined subtype_diff function.
|
||||
* Specify the optional *Multirange type name* parameter to specifies the
|
||||
name of the corresponding multirange type. If not specified, this name is
|
||||
chosen automatically.
|
||||
|
||||
If you select *Shell* in the *Type* field, the *Definition* tab displays the
|
||||
*Shell* panel:
|
||||
|
||||
@@ -5,8 +5,8 @@ Debian and Ubuntu.
|
||||
|
||||
## Supported platforms
|
||||
|
||||
* Debian 11 & 12
|
||||
* Ubuntu 22.04, 24.04 & 24.10
|
||||
* Debian 11, 12 & 13
|
||||
* Ubuntu 22.04, 24.04 & 25.04
|
||||
|
||||
## Build configuration
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ set -e
|
||||
trap 'ERRCODE=$? && if [ ${ERRCODE} -ne 0 ]; then echo "The command \"${BASH_COMMAND}\" failed in \"${FUNCNAME}\" with exit code ${ERRCODE}."; fi' EXIT
|
||||
|
||||
OS_ARCH=$(dpkg-architecture -qDEB_HOST_ARCH)
|
||||
DISTRO=$(lsb_release -cs)
|
||||
RELEASE=1
|
||||
|
||||
# Stop creating pyc files.
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
@@ -58,7 +60,7 @@ EOF
|
||||
|
||||
# Build the Debian package for the server
|
||||
chmod -R u+rwX,go+rX,go-w "${SERVERROOT}"
|
||||
fakeroot dpkg-deb --build "${SERVERROOT}" "${DISTROOT}/${APP_NAME}-server_${APP_LONG_VERSION}_${OS_ARCH}.deb"
|
||||
fakeroot dpkg-deb --build "${SERVERROOT}" "${DISTROOT}/${APP_NAME}-server_${APP_LONG_VERSION}-${RELEASE}.${DISTRO}_${OS_ARCH}.deb"
|
||||
|
||||
#
|
||||
# Desktop package
|
||||
@@ -102,7 +104,7 @@ EOF
|
||||
|
||||
# Build the Debian package for the desktop
|
||||
chmod -R u+rwX,go+rX,go-w "${DESKTOPROOT}"
|
||||
fakeroot dpkg-deb --build "${DESKTOPROOT}" "${DISTROOT}/${APP_NAME}-desktop_${APP_LONG_VERSION}_${OS_ARCH}.deb"
|
||||
fakeroot dpkg-deb --build "${DESKTOPROOT}" "${DISTROOT}/${APP_NAME}-desktop_${APP_LONG_VERSION}-${RELEASE}.${DISTRO}_${OS_ARCH}.deb"
|
||||
|
||||
#
|
||||
# Web package
|
||||
@@ -132,7 +134,7 @@ cp "${SOURCEDIR}/pkg/debian/pgadmin4.conf" "${WEBROOT}/etc/apache2/conf-availabl
|
||||
|
||||
# Build the Debian package for the web
|
||||
chmod -R u+rwX,go+rX,go-w "${WEBROOT}"
|
||||
fakeroot dpkg-deb --build "${WEBROOT}" "${DISTROOT}/${APP_NAME}-web_${APP_LONG_VERSION}_all.deb"
|
||||
fakeroot dpkg-deb --build "${WEBROOT}" "${DISTROOT}/${APP_NAME}-web_${APP_LONG_VERSION}-${RELEASE}.${DISTRO}_all.deb"
|
||||
|
||||
#
|
||||
# Meta package
|
||||
@@ -154,7 +156,7 @@ Description: Installs all required components to run pgAdmin in desktop and web
|
||||
EOF
|
||||
|
||||
# Build the Debian meta package
|
||||
fakeroot dpkg-deb --build "${METAROOT}" "${DISTROOT}/${APP_NAME}_${APP_LONG_VERSION}_all.deb"
|
||||
fakeroot dpkg-deb --build "${METAROOT}" "${DISTROOT}/${APP_NAME}_${APP_LONG_VERSION}-${RELEASE}.${DISTRO}_all.deb"
|
||||
|
||||
# Get the libpq package
|
||||
pushd "${DISTROOT}" 1> /dev/null
|
||||
|
||||
@@ -48,7 +48,8 @@ CA_FILE = '/etc/ssl/certs/ca-certificates.crt'
|
||||
LOG_FILE = '/dev/null'
|
||||
HELP_PATH = '../../docs'
|
||||
DEFAULT_BINARY_PATHS = {
|
||||
'pg': '/usr/local/pgsql-17',
|
||||
'pg': '/usr/local/pgsql-18',
|
||||
'pg-18': '/usr/local/pgsql-18',
|
||||
'pg-17': '/usr/local/pgsql-17',
|
||||
'pg-16': '/usr/local/pgsql-16',
|
||||
'pg-15': '/usr/local/pgsql-15',
|
||||
@@ -188,5 +189,5 @@ fi
|
||||
if [ -n "${PGADMIN_ENABLE_TLS}" ]; then
|
||||
exec /venv/bin/gunicorn --limit-request-line "${GUNICORN_LIMIT_REQUEST_LINE:-8190}" --timeout "${TIMEOUT}" --bind "${BIND_ADDRESS}" -w 1 --threads "${GUNICORN_THREADS:-25}" --access-logfile "${GUNICORN_ACCESS_LOGFILE:--}" --keyfile /certs/server.key --certfile /certs/server.cert -c gunicorn_config.py run_pgadmin:app
|
||||
else
|
||||
exec /venv/bin/gunicorn --limit-request-line "${GUNICORN_LIMIT_REQUEST_LINE:-8190}" --timeout "${TIMEOUT}" --bind "${BIND_ADDRESS}" -w 1 --threads "${GUNICORN_THREADS:-25}" --access-logfile "${GUNICORN_ACCESS_LOGFILE:--}" -c gunicorn_config.py run_pgadmin:app
|
||||
exec /venv/bin/gunicorn --limit-request-line "${GUNICORN_LIMIT_REQUEST_LINE:-8190}" --limit-request-fields "${GUNICORN_LIMIT_REQUEST_FIELDS:-100}" --limit-request-field_size "${GUNICORN_LIMIT_REQUEST_FIELD_SIZE:-8190}" --timeout "${TIMEOUT}" --bind "${BIND_ADDRESS}" -w 1 --threads "${GUNICORN_THREADS:-25}" --access-logfile "${GUNICORN_ACCESS_LOGFILE:--}" -c gunicorn_config.py run_pgadmin:app
|
||||
fi
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: v2
|
||||
name: pgadmin4-helm
|
||||
version: 9.9.0
|
||||
description: 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.
|
||||
home: https://pgadmin.org
|
||||
sources: ["https://github.com/pgadmin-org/pgadmin4/tree/master/helm"]
|
||||
maintainers:
|
||||
- name: Koren Peretz
|
||||
url: https://github.com/KorenP1
|
||||
icon: data:image/png;base64,AAABAAEAiooAAAEAIACANAEAFgAAACgAAACKAAAAFAEAAAEAIAAAAAAAkCkBACMuAAAjLgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAABk2cxNJRpMXeTaTKtk2gy05NoMu6TaDL7k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv2TaDLulGgy2JNoMrGTaDJ/lWYzPKpVVQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjmM5EpJpMXyTaDLdk2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMuWTaDKJk2wxGgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJJtJAeSaTF8lGgy8pNoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy+JRoMYyJYjsNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkmg0MZRoMtiTaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDLkk2g0QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACSaDJbk2gy95NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/JRnMXIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJRpMmuTaDL+k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDGHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAk2kzWpNoMv2TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gy/5NoMv+TaDL/k2gLine truncated
|
||||
appVersion: 9.9.0
|
||||
@@ -0,0 +1,41 @@
|
||||
# PgAdmin4 K8S Helm Chart
|
||||
|
||||
Its been a struggle to deploy pgadmin4 container on different restricted k8s distributions for example openshift, gke or vanilla k8s pod security standards.
|
||||
This helm chart follows best security measures and practices and compatible with all different security contexts and restrictions.
|
||||
Further explanation about the security implementation can be read here: https://korenp1.github.io
|
||||
|
||||
The helm chart also implements most pgadmin4 features, for instance, config_local.py, predefined server definitions or preferences.
|
||||
|
||||
The majority of features and values are covered in the helm chart but always can be more customable or tpl'able, open for contributions.
|
||||
|
||||
### Package && Push
|
||||
The chart should dump its version and appVersion in the Chart.yaml file every release and pushed to docker.io/dpage repository.
|
||||
`helm package . && helm push pgadmin4-helm-<VERSION>.tgz oci://docker.io/dpage`
|
||||
|
||||
### Installation Example:
|
||||
`helm install mypgadmin4 oci://docker.io/dpage/pgadmin4-helm --set ingress.enabled=true`
|
||||
|
||||
### Important Values
|
||||
| Value | Description | Default |
|
||||
| --------- | ----------- | ------- |
|
||||
| `containerPort` | Internal PgAdmin4 Port | `5050` |
|
||||
| `image.registry` | Image registry | `"docker.io"` |
|
||||
| `image.repository` | Image Repository | `"dpage/pgadmin4"` |
|
||||
| `image.tag` | Image tag (If empty, will use .Chart.AppVersion) | `""` |
|
||||
| `auth.email` | Admin Email | `"admin@pgadmin.org"` |
|
||||
| `auth.password` | Admin password (If both auth.password and auth.existingSecret are empty, the password will be randomly generated) | `""` |
|
||||
| `auth.existingSecret` | Existing secret name for admin password (If both auth.password and auth.existingSecret are empty, the password will be randomly generated) | `""` |
|
||||
| `extraEnvVars` | Extra environment variables | `[]` |
|
||||
| `config_local.enabled` | Whether to mount config_local.py file | `false` |
|
||||
| `config_local.data` | config_local.py configuration content | `""` |
|
||||
| `config_local.existingSecret` | Existing secret name containing config_local.py file | `""` |
|
||||
| `serverDefinitions.enabled` | Whether to mount servers.json | `false` |
|
||||
| `serverDefinitions.data` | Server definitions to import | `{}` |
|
||||
| `preferences.enabled` | Whether to mount preferences.json | `false` |
|
||||
| `preferences.data` | Preferences to load | `{}` |
|
||||
| `resources.*` | Allocated requests and limits resources | `{"requests": {...}, "limits": {...}}` |
|
||||
| `persistence.enabled` | PVC resource creation | `true` |
|
||||
| `service.type` | Service type | `"ClusterIP"` |
|
||||
| `service.loadBalancerIP` | Load balancer IP (Only if service.type is LoadBalancer) | `""` |
|
||||
| `ingress.enabled` | Ingress resource creation | `false` |
|
||||
| `ingress.hostname` | Ingress resource hostname | `"pgadmin4.local"` |
|
||||
@@ -0,0 +1,11 @@
|
||||
Thanks for using PgAdmin4 Helm Chart :)
|
||||
|
||||
Image: {{ template "pgadmin4.image" . }}
|
||||
|
||||
Credentials:
|
||||
Email: {{ .Values.auth.email }}
|
||||
Password: {{ printf "kubectl get secret %s -n %s -o jsonpath='{.data.%s}' | base64 -d" (include "pgadmin4.fullname" .) .Release.Namespace .Values.auth.passwordKey }}
|
||||
|
||||
Ingress: {{ ternary (tpl .Values.ingress.hostname .) "DISABLED" .Values.ingress.enabled }}
|
||||
|
||||
GOOD LUCK!
|
||||
@@ -0,0 +1,49 @@
|
||||
{{- define "pgadmin4.fullname" -}}
|
||||
{{- tpl .Values.fullname . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "pgadmin4.image" -}}
|
||||
{{- printf "%s/%s:%s" (default .Values.image.registry .Values.global.imageRegistry) .Values.image.repository (default .Chart.AppVersion .Values.image.tag | toString) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "pgadmin4.serviceAccountName" -}}
|
||||
{{- default (ternary (include "pgadmin4.fullname" .) "default" .Values.serviceAccount.create) .Values.serviceAccount.name -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "renderSecurityContext" -}}
|
||||
{{- $securityContext := omit .securityContext "enabled" -}}
|
||||
{{- if or (eq .context.Values.global.compatibility.openshift.adaptSecurityContext "force") (and (eq .context.Values.global.compatibility.openshift.adaptSecurityContext "auto") (ternary "true" "" (.context.Capabilities.APIVersions.Has "security.openshift.io/v1"))) -}}
|
||||
{{- $securityContext = omit $securityContext "fsGroup" "runAsUser" "runAsGroup" -}}
|
||||
{{- if not $securityContext.seLinuxOptions }}
|
||||
{{- $securityContext = omit $securityContext "seLinuxOptions" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- if $securityContext.privileged }}
|
||||
{{- $securityContext = omit $securityContext "capabilities" -}}
|
||||
{{- end -}}
|
||||
{{- if not .context.Values.global.compatibility.appArmor.enabled }}
|
||||
{{- $securityContext = omit $securityContext "appArmorProfile" -}}
|
||||
{{- end -}}
|
||||
{{- $securityContext | toYaml -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "tpl.preserve.variable" -}}
|
||||
{{- $obj := . }}
|
||||
{{- if kindIs "map" $obj }}
|
||||
{{- range $k, $v := $obj }}
|
||||
{{- if kindIs "string" $v }}
|
||||
{{- if regexMatch "^[0-9]+$" $v }}
|
||||
{{- $_ := set $obj $k (atoi $v) }}
|
||||
{{- else if regexMatch "(?i)^(true|false)$" $v }}
|
||||
{{- $_ := set $obj $k (eq (lower $v) "true") }}
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
{{- include "tpl.preserve.variable" $v }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if kindIs "slice" $obj }}
|
||||
{{- range $i, $v := $obj }}
|
||||
{{- include "tpl.preserve.variable" $v }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,14 @@
|
||||
{{- if .Values.preferences.enabled }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ template "pgadmin4.fullname" . }}-preferences
|
||||
{{- with .Values.commonLabels }}
|
||||
labels: {{ . | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.commonAnnotations }}
|
||||
annotations: {{ . | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
data:
|
||||
preferences.json: | {{ dict "preferences" .Values.preferences.data | toPrettyJson | nindent 4 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,16 @@
|
||||
{{- if .Values.serverDefinitions.enabled }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ template "pgadmin4.fullname" . }}-server-definitions
|
||||
{{- with .Values.commonLabels }}
|
||||
labels: {{ . | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.commonAnnotations }}
|
||||
annotations: {{ . | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
data:
|
||||
{{- $variableToPreserveTPL := tpl (dict "Servers" .Values.serverDefinitions.data | toYaml) . | fromYaml }}
|
||||
{{- include "tpl.preserve.variable" $variableToPreserveTPL }}
|
||||
servers.json: | {{ $variableToPreserveTPL | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,237 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ template "pgadmin4.fullname" . }}
|
||||
{{- with .Values.commonLabels }}
|
||||
labels: {{ . | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.commonAnnotations }}
|
||||
annotations: {{ . | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicas }}
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ default "pgadmin4" .Values.commonLabels.app }}
|
||||
{{- with omit .Values.commonLabels "app" }}
|
||||
{{- . | toYaml | nindent 6 }}
|
||||
{{- end }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: {{ default "pgadmin4" .Values.commonLabels.app }}
|
||||
{{- with omit .Values.commonLabels "app" }}
|
||||
{{- . | toYaml | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if or (not (empty .Values.commonAnnotations)) (not .Values.existingSecret) .Values.preferences.enabled .Values.serverDefinitions.enabled }}
|
||||
annotations:
|
||||
{{- with .Values.commonAnnotations }}
|
||||
{{- . | toYaml | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if not .Values.existingSecret }}
|
||||
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
|
||||
{{- end }}
|
||||
{{- if and .Values.config_local.enabled (empty .Values.config_local.existingSecret) }}
|
||||
checksum/secret-config: {{ include (print $.Template.BasePath "/secret-config.yaml") . | sha256sum }}
|
||||
{{- end }}
|
||||
{{- if .Values.serverDefinitions.enabled }}
|
||||
checksum/configmap-server-definitions: {{ include (print $.Template.BasePath "/configmap-serverDefinitions.yaml") . | sha256sum }}
|
||||
{{- end }}
|
||||
{{- if .Values.preferences.enabled }}
|
||||
checksum/configmap-preferences: {{ include (print $.Template.BasePath "/configmap-preferences.yaml") . | sha256sum }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if or .Values.global.imagePullSecrets .Values.image.pullSecrets }}
|
||||
imagePullSecrets: {{- concat .Values.global.imagePullSecrets .Values.image.pullSecrets | toYaml | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ template "pgadmin4.serviceAccountName" . }}
|
||||
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
|
||||
{{- if .Values.podSecurityContext.enabled }}
|
||||
securityContext: {{- include "renderSecurityContext" (dict "securityContext" .Values.podSecurityContext "context" .) | nindent 8 }}
|
||||
enableServiceLinks: {{ .Values.enableServiceLinks }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: empty-dir
|
||||
emptyDir: {}
|
||||
{{- if .Values.persistence.enabled }}
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ template "pgadmin4.fullname" . }}
|
||||
{{- end }}
|
||||
{{- if .Values.config_local.enabled }}
|
||||
- name: config-local
|
||||
secret:
|
||||
secretName: {{ default (printf "%s-config" (include "pgadmin4.fullname" .)) .Values.config_local.existingSecret }}
|
||||
items:
|
||||
- key: {{ .Values.config_local.configKey }}
|
||||
path: {{ .Values.config_local.configKey }}
|
||||
{{- end }}
|
||||
{{- if .Values.serverDefinitions.enabled }}
|
||||
- name: server-definitions
|
||||
configMap:
|
||||
name: {{ template "pgadmin4.fullname" . }}-server-definitions
|
||||
items:
|
||||
- key: servers.json
|
||||
path: servers.json
|
||||
{{- end }}
|
||||
{{- if .Values.preferences.enabled }}
|
||||
- name: preferences
|
||||
configMap:
|
||||
name: {{ template "pgadmin4.fullname" . }}-preferences
|
||||
items:
|
||||
- key: preferences.json
|
||||
path: preferences.json
|
||||
{{- end }}
|
||||
{{- with .Values.extraVolumes }}
|
||||
{{- . | toYaml | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: pgadmin4
|
||||
{{- if .Values.containerSecurityContext.enabled }}
|
||||
securityContext: {{- include "renderSecurityContext" (dict "securityContext" .Values.containerSecurityContext "context" .) | nindent 12 }}
|
||||
{{- end }}
|
||||
image: {{ template "pgadmin4.image" . }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
containerPort: {{ .Values.containerPort }}
|
||||
resources:
|
||||
requests:
|
||||
cpu: {{ .Values.resources.requests.cpu }}
|
||||
memory: {{ .Values.resources.requests.memory }}
|
||||
limits:
|
||||
cpu: {{ .Values.resources.limits.cpu }}
|
||||
memory: {{ .Values.resources.limits.memory }}
|
||||
env:
|
||||
- name: PGADMIN_DEFAULT_EMAIL
|
||||
value: {{ .Values.auth.email }}
|
||||
- name: PGADMIN_DEFAULT_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ default (include "pgadmin4.fullname" .) .Values.auth.existingSecret }}
|
||||
key: {{ .Values.auth.passwordKey }}
|
||||
- name: PGADMIN_LISTEN_PORT
|
||||
value: {{ .Values.containerPort | quote }}
|
||||
- name: PGADMIN_DISABLE_POSTFIX
|
||||
value: {{ .Values.disablePostfix | quote }}
|
||||
{{- with .Values.extraEnvVars }}
|
||||
{{- tpl (. | toYaml) $ | nindent 12 }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
- mountPath: /var/lib/pgadmin
|
||||
{{- if .Values.persistence.enabled }}
|
||||
name: data
|
||||
{{- else }}
|
||||
name: empty-dir
|
||||
subPath: data
|
||||
{{- end }}
|
||||
- name: empty-dir
|
||||
mountPath: /pgadmin4/config_distro.py
|
||||
subPath: config_distro.py
|
||||
- name: empty-dir
|
||||
mountPath: /usr/bin/python3
|
||||
subPath: python3
|
||||
- name: empty-dir
|
||||
mountPath: /tmp
|
||||
subPath: tmp
|
||||
- name: empty-dir
|
||||
mountPath: /var/log/pgadmin
|
||||
subPath: logs
|
||||
{{- if .Values.config_local.enabled }}
|
||||
- name: config-local
|
||||
mountPath: /pgadmin4/config_local.py
|
||||
subPath: {{ .Values.config_local.configKey }}
|
||||
{{- end }}
|
||||
{{- if .Values.serverDefinitions.enabled }}
|
||||
- name: server-definitions
|
||||
mountPath: /pgadmin4/servers.json
|
||||
subPath: servers.json
|
||||
{{- end }}
|
||||
{{- if .Values.preferences.enabled }}
|
||||
- name: preferences
|
||||
mountPath: /pgadmin4/preferences.json
|
||||
subPath: preferences.json
|
||||
{{- end }}
|
||||
{{- with .Values.extraVolumes }}
|
||||
{{- . | toYaml | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.startupProbe.enabled }}
|
||||
startupProbe: {{- omit .Values.startupProbe "enabled" | toYaml | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.readinessProbe.enabled }}
|
||||
readinessProbe: {{- omit .Values.readinessProbe "enabled" | toYaml | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.livenessProbe.enabled }}
|
||||
livenessProbe: {{- omit .Values.livenessProbe "enabled" | toYaml | nindent 12 }}
|
||||
{{- end }}
|
||||
initContainers:
|
||||
- name: modify-config-distro-py-permissions
|
||||
image: {{ template "pgadmin4.image" . }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
command: ["sh", "-x", "-c"]
|
||||
args: ['cp /pgadmin4/config_distro.py . && chmod 777 config_distro.py']
|
||||
workingDir: /emptyDir
|
||||
volumeMounts:
|
||||
- name: empty-dir
|
||||
mountPath: /emptyDir
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
securityContext:
|
||||
seLinuxOptions: {}
|
||||
runAsUser: 1001
|
||||
runAsGroup: 1001
|
||||
runAsNonRoot: true
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
{{- if .Values.global.compatibility.appArmor.enabled }}
|
||||
appArmorProfile:
|
||||
type: RuntimeDefault
|
||||
{{- end }}
|
||||
windowsOptions:
|
||||
hostProcess: false
|
||||
- name: unset-python3-cli-net-cap
|
||||
image: {{ template "pgadmin4.image" . }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
command: ["sh", "-x", "-c"]
|
||||
args: ['ls /usr/bin/python3.* | sort -V -r | head -n 1 | xargs -I {} cp {} python3']
|
||||
workingDir: /emptyDir
|
||||
volumeMounts:
|
||||
- name: empty-dir
|
||||
mountPath: /emptyDir
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
securityContext:
|
||||
seLinuxOptions: {}
|
||||
runAsUser: 1001
|
||||
runAsGroup: 1001
|
||||
runAsNonRoot: true
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
{{- if .Values.global.compatibility.appArmor.enabled }}
|
||||
appArmorProfile:
|
||||
type: RuntimeDefault
|
||||
{{- end }}
|
||||
windowsOptions:
|
||||
hostProcess: false
|
||||
@@ -0,0 +1,4 @@
|
||||
{{- range .Values.extraDeploy }}
|
||||
---
|
||||
{{ tpl (. | toYaml) $ }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,24 @@
|
||||
{{- if .Values.ingress.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ template "pgadmin4.fullname" . }}
|
||||
{{- with .Values.commonLabels }}
|
||||
labels: {{ . | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- if or .Values.ingress.annotations .Values.commonAnnotations }}
|
||||
annotations: {{ merge .Values.ingress.annotations .Values.commonAnnotations | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
rules:
|
||||
- host: {{ tpl .Values.ingress.hostname . }}
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
service:
|
||||
name: {{ template "pgadmin4.fullname" . }}
|
||||
port:
|
||||
name: http
|
||||
path: /
|
||||
pathType: Prefix
|
||||
{{- end }}
|
||||
@@ -0,0 +1,20 @@
|
||||
{{- if .Values.persistence.enabled }}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ template "pgadmin4.fullname" . }}
|
||||
{{- with .Values.commonLabels }}
|
||||
labels: {{ . | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.commonAnnotations }}
|
||||
annotations: {{ . | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if or .Values.global.defaultStorageClass .Values.persistence.storageClass }}
|
||||
storageClassName: {{ default .Values.global.defaultStorageClass .Values.persistence.storageClass }}
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.size }}
|
||||
accessModes: {{ .Values.persistence.accessModes }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,15 @@
|
||||
{{- if and .Values.config_local.enabled (empty .Values.config_local.existingSecret) }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ template "pgadmin4.fullname" . }}-config
|
||||
{{- with .Values.commonLabels }}
|
||||
labels: {{ . | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.commonAnnotations }}
|
||||
annotations: {{ . | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
type: Opaque
|
||||
stringData:
|
||||
{{ .Values.config_local.configKey }}: | {{ tpl .Values.config_local.data . | nindent 4 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,15 @@
|
||||
{{- if empty .Values.auth.existingSecret }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ template "pgadmin4.fullname" . }}
|
||||
{{- with .Values.commonLabels }}
|
||||
labels: {{ . | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.commonAnnotations }}
|
||||
annotations: {{ . | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
type: Opaque
|
||||
stringData:
|
||||
{{ .Values.auth.passwordKey }}: {{ coalesce .Values.auth.password (dig "data" .Values.auth.passwordKey "" (lookup "v1" "Secret" .Release.Namespace (include "pgadmin4.fullname" .)) | b64dec) (randAlphaNum 12) | quote }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ template "pgadmin4.fullname" . }}
|
||||
{{- with .Values.commonLabels }}
|
||||
labels: {{ . | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- if or .Values.service.annotations .Values.commonAnnotations }}
|
||||
annotations: {{ merge .Values.service.annotations .Values.commonAnnotations | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
{{- if and (eq .Values.service.type "LoadBalancer") (not (empty .Values.service.loadBalancerIP)) }}
|
||||
loadBalancerIP: {{ .Values.service.loadBalancerIP }}
|
||||
{{- end }}
|
||||
selector:
|
||||
app: {{ default "pgadmin4" .Values.commonLabels.app }}
|
||||
{{- with omit .Values.commonLabels "app" }}
|
||||
{{- . | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
targetPort: http
|
||||
port: {{ .Values.service.port }}
|
||||
@@ -0,0 +1,13 @@
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ template "pgadmin4.serviceAccountName" . }}
|
||||
{{- with .Values.commonLabels }}
|
||||
labels: {{ . | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.commonAnnotations }}
|
||||
annotations: {{ . | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,156 @@
|
||||
global:
|
||||
imageRegistry: ""
|
||||
defaultStorageClass: ""
|
||||
imagePullSecrets: []
|
||||
compatibility:
|
||||
openshift:
|
||||
adaptSecurityContext: auto
|
||||
appArmor:
|
||||
enabled: false
|
||||
|
||||
fullname: pgadmin4
|
||||
|
||||
image:
|
||||
registry: docker.io
|
||||
repository: dpage/pgadmin4
|
||||
tag: ""
|
||||
pullPolicy: IfNotPresent
|
||||
pullSecrets: []
|
||||
|
||||
commonLabels:
|
||||
app: pgadmin4
|
||||
commonAnnotations: {}
|
||||
|
||||
extraDeploy: []
|
||||
|
||||
replicas: 1
|
||||
|
||||
containerPort: 5050
|
||||
disablePostfix: true
|
||||
enableServiceLinks: false
|
||||
auth:
|
||||
email: admin@pgadmin.org
|
||||
password: ""
|
||||
existingSecret: ""
|
||||
passwordKey: password
|
||||
extraEnvVars: []
|
||||
# - name: PGADMIN_CONFIG_LOGIN_BANNER
|
||||
# value: '"Welcome to pgAdmin4 :)"'
|
||||
extraVolumes: []
|
||||
extraVolumeMounts: []
|
||||
|
||||
config_local:
|
||||
enabled: false
|
||||
existingSecret: ""
|
||||
configKey: config_local.py
|
||||
data: ""
|
||||
# data: |
|
||||
# DEBUG = True
|
||||
# JSON_LOGGER = True
|
||||
# AUTHENTICATION_SOURCES = ['oauth2', 'internal']
|
||||
|
||||
serverDefinitions:
|
||||
enabled: false
|
||||
data: {}
|
||||
# "1":
|
||||
# Name: Minimally Defined Server
|
||||
# Group: Servers
|
||||
# Port: 5432
|
||||
# Username: postgres
|
||||
# Host: myHost
|
||||
# MaintenanceDB: postgres
|
||||
# ConnectionParameters:
|
||||
# sslmode: prefer
|
||||
# connect_timeout: 10
|
||||
|
||||
preferences:
|
||||
enabled: false
|
||||
data: {}
|
||||
# misc:user_interface:theme: dark
|
||||
# file_manager:options:file_upload_size: 500
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 1Gi
|
||||
storageClass: ""
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
annotations: {}
|
||||
loadBalancerIP: ""
|
||||
|
||||
serviceAccount:
|
||||
create: true
|
||||
name: ""
|
||||
automountServiceAccountToken: false
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
hostname: pgadmin4.local
|
||||
annotations: {}
|
||||
|
||||
startupProbe:
|
||||
enabled: false
|
||||
httpGet:
|
||||
path: /misc/ping
|
||||
port: http
|
||||
initialDelaySeconds: null
|
||||
periodSeconds: null
|
||||
timeoutSeconds: null
|
||||
failureThreshold: null
|
||||
|
||||
readinessProbe:
|
||||
enabled: true
|
||||
httpGet:
|
||||
path: /misc/ping
|
||||
port: http
|
||||
initialDelaySeconds: null
|
||||
periodSeconds: null
|
||||
timeoutSeconds: null
|
||||
failureThreshold: null
|
||||
|
||||
livenessProbe:
|
||||
enabled: false
|
||||
httpGet:
|
||||
path: /misc/ping
|
||||
port: http
|
||||
initialDelaySeconds: null
|
||||
periodSeconds: null
|
||||
timeoutSeconds: null
|
||||
failureThreshold: null
|
||||
|
||||
podSecurityContext:
|
||||
enabled: true
|
||||
fsGroupChangePolicy: Always
|
||||
sysctls: []
|
||||
supplementalGroups: []
|
||||
fsGroup: 1001
|
||||
|
||||
containerSecurityContext:
|
||||
enabled: true
|
||||
seLinuxOptions: {}
|
||||
runAsUser: 1001
|
||||
runAsGroup: 1001
|
||||
runAsNonRoot: true
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
appArmorProfile:
|
||||
type: RuntimeDefault
|
||||
windowsOptions:
|
||||
|
||||
hostProcess: false
|
||||
@@ -22,7 +22,13 @@ _setup_env() {
|
||||
# Setting up the correct Python version for Ubuntu 18 and EL-8, which have
|
||||
# new Python versions installed parallel to the old ones.
|
||||
OS_VERSION=$(grep "^VERSION_ID=" /etc/os-release | awk -F "=" '{ print $2 }' | sed 's/"//g' | awk -F "." '{ print $1 }')
|
||||
SYSTEM_PYTHON_PATH='/usr/bin/python3'
|
||||
if [ -n "$VIRTUAL_ENV" ]; then
|
||||
SYSTEM_PYTHON_PATH="$VIRTUAL_ENV/bin/python3"
|
||||
echo "Detected virtual environment: $VIRTUAL_ENV"
|
||||
else
|
||||
SYSTEM_PYTHON_PATH='/usr/bin/python3'
|
||||
echo "Not in a virtual environment, using system Python path."
|
||||
fi
|
||||
PYTHON_BINARY=$("${SYSTEM_PYTHON_PATH}" -c "import sys; print('python%d.%.d' % (sys.version_info.major, sys.version_info.minor))")
|
||||
PYTHON_BINARY_WITHOUT_DOTS='python3'
|
||||
if [ "$2" == 'redhat' ] && [ "${OS_VERSION}" == "8" ]; then
|
||||
@@ -140,8 +146,6 @@ _build_runtime() {
|
||||
fi
|
||||
|
||||
ELECTRON_VERSION="$(npm info electron version)"
|
||||
# Can't use the latest as it is not compatible with few OS versions.
|
||||
ELECTRON_VERSION="35.4.0"
|
||||
|
||||
pushd "${BUILDROOT}" > /dev/null || exit
|
||||
while true;do
|
||||
@@ -205,9 +209,10 @@ _build_docs() {
|
||||
cd "${SERVERROOT}" && mkdir -p "usr/${APP_NAME}/share/docs/en_US/html"
|
||||
cd "${SOURCEDIR}/docs/en_US" || exit
|
||||
python3 build_code_snippet.py
|
||||
SYS_PYTHONPATH=$("${SYSTEM_PYTHON_PATH}" -c "import sys; print(':'.join([p for p in sys.path if p]))")
|
||||
|
||||
SYS_PYTHON_PACKS=$("${SYSTEM_PYTHON_PATH}" -c "import sys; print(':'.join([p for p in sys.path if p]))")
|
||||
# shellcheck disable=SC2153
|
||||
PYTHONPATH=$PYTHONPATH:${SYS_PYTHONPATH} python3 -msphinx . "${SERVERROOT}/usr/${APP_NAME}/share/docs/en_US/html"
|
||||
PYTHONPATH=$PYTHONPATH:${SYS_PYTHON_PACKS} python3 -msphinx . "${SERVERROOT}/usr/${APP_NAME}/share/docs/en_US/html"
|
||||
}
|
||||
|
||||
_copy_code() {
|
||||
|
||||
@@ -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.
|
||||
@@ -1,5 +1,11 @@
|
||||
# shellcheck shell=bash
|
||||
|
||||
# uname -m returns "x86_64" on Intel, but we need "x64"
|
||||
ARCH="x64"
|
||||
if [ "$(uname -m)" == "arm64" ]; then
|
||||
ARCH="arm64"
|
||||
fi
|
||||
|
||||
_setup_env() {
|
||||
FUNCS_DIR=$(cd "$(dirname "$0")" && pwd)/../..
|
||||
APP_RELEASE=$(grep "^APP_RELEASE" "${FUNCS_DIR}/web/version.py" | cut -d"=" -f2 | sed 's/ //g')
|
||||
@@ -11,7 +17,7 @@ _setup_env() {
|
||||
APP_LONG_VERSION=${APP_LONG_VERSION}-${APP_SUFFIX}
|
||||
fi
|
||||
BUNDLE_DIR="${BUILD_ROOT}/${APP_NAME}.app"
|
||||
DMG_NAME="${DIST_ROOT}"/$(echo "${APP_NAME}" | sed 's/ //g' | awk '{print tolower($0)}')-"${APP_LONG_VERSION}-$(uname -m).dmg"
|
||||
DMG_NAME="${DIST_ROOT}/$(echo "${APP_NAME}" | sed 's/ //g' | awk '{print tolower($0)}')-${APP_LONG_VERSION}-${ARCH}.dmg"
|
||||
PYTHON_OS_VERSION="11"
|
||||
}
|
||||
|
||||
@@ -27,21 +33,14 @@ _build_runtime() {
|
||||
|
||||
test -d "${BUILD_ROOT}" || mkdir "${BUILD_ROOT}"
|
||||
# Get a fresh copy of electron
|
||||
# uname -m returns "x86_64" on Intel, but we need "x64"
|
||||
ELECTRON_ARCH="x64"
|
||||
if [ "$(uname -m)" == "arm64" ]; then
|
||||
ELECTRON_ARCH="arm64"
|
||||
fi
|
||||
|
||||
ELECTRON_VERSION="$(npm info electron version)"
|
||||
ELECTRON_VERSION="35.4.0"
|
||||
|
||||
pushd "${BUILD_ROOT}" > /dev/null || exit
|
||||
while true;do
|
||||
wget "https://github.com/electron/electron/releases/download/v${ELECTRON_VERSION}/electron-v${ELECTRON_VERSION}-darwin-${ELECTRON_ARCH}.zip" && break
|
||||
rm "electron-v${ELECTRON_VERSION}-darwin-${ELECTRON_ARCH}.zip"
|
||||
wget "https://github.com/electron/electron/releases/download/v${ELECTRON_VERSION}/electron-v${ELECTRON_VERSION}-darwin-${ARCH}.zip" && break
|
||||
rm "electron-v${ELECTRON_VERSION}-darwin-${ARCH}.zip"
|
||||
done
|
||||
unzip "electron-v${ELECTRON_VERSION}-darwin-${ELECTRON_ARCH}.zip"
|
||||
unzip "electron-v${ELECTRON_VERSION}-darwin-${ARCH}.zip"
|
||||
popd > /dev/null || exit
|
||||
# WGET END
|
||||
|
||||
@@ -385,6 +384,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 +443,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 +466,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 +483,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
|
||||
@@ -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.
|
||||
|
||||
@@ -38,6 +38,7 @@ WizardImageFile=sidebar.bmp
|
||||
MinVersion={#MinimumWindowsVer}
|
||||
SignTool=pgAdminSigntool
|
||||
SignedUninstaller=yes
|
||||
CloseApplications=yes
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
@@ -12,14 +12,14 @@
|
||||
# relevant packages.
|
||||
###############################################################################
|
||||
|
||||
Authlib==1.6.*
|
||||
azure-identity==1.23.0
|
||||
Authlib==1.6.5
|
||||
azure-identity==1.25.1
|
||||
azure-mgmt-rdbms==10.1.0
|
||||
azure-mgmt-resource==24.0.0
|
||||
azure-mgmt-subscription==3.1.1
|
||||
bcrypt==4.3.*
|
||||
boto3==1.39.*
|
||||
cryptography==45.0.*
|
||||
bcrypt==5.0.*
|
||||
boto3==1.40.*
|
||||
cryptography==46.0.*
|
||||
Flask-Babel==4.0.*
|
||||
Flask-Compress==1.*
|
||||
Flask-Login==0.*
|
||||
@@ -33,25 +33,27 @@ Flask-SQLAlchemy==3.1.*
|
||||
Flask-WTF==1.2.*
|
||||
Flask==3.1.*
|
||||
google-api-python-client==2.*
|
||||
google-auth-oauthlib==1.2.2
|
||||
gssapi==1.9.*
|
||||
google-auth-oauthlib==1.2.3
|
||||
gssapi==1.10.*
|
||||
jsonformatter~=0.3.4
|
||||
keyring==25.*
|
||||
ldap3==2.*
|
||||
libgravatar==1.0.*
|
||||
paramiko==3.5.1
|
||||
passlib==1.*
|
||||
psutil==7.0.*
|
||||
psycopg[c]==3.2.9
|
||||
psutil==7.1.*
|
||||
psycopg[c]==3.2.12
|
||||
pyotp==2.*
|
||||
python-dateutil==2.*
|
||||
pytz==2025.*
|
||||
# pinned for https://github.com/andfoy/pywinpty/issues/545
|
||||
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.*
|
||||
typer[all]==0.16.*
|
||||
typer[all]==0.20.*
|
||||
urllib3==1.26.*; python_version <= '3.9'
|
||||
urllib3==2.5.*; python_version > '3.9'
|
||||
user-agents==2.2.0
|
||||
|
||||
@@ -10,15 +10,15 @@
|
||||
"start": "electron .",
|
||||
"linter": "yarn run eslint -c .eslintrc.js ."
|
||||
},
|
||||
"packageManager": "yarn@3.8.7",
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"devDependencies": {
|
||||
"electron": "37.2.0",
|
||||
"eslint": "^9.30.1",
|
||||
"eslint-plugin-unused-imports": "^4.1.4"
|
||||
"electron": "39.0.0",
|
||||
"eslint": "^9.39.0",
|
||||
"eslint-plugin-unused-imports": "^4.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.10.0",
|
||||
"axios": "^1.13.1",
|
||||
"electron-context-menu": "^4.1.0",
|
||||
"electron-store": "^10.1.0"
|
||||
"electron-store": "^11.0.2"
|
||||
}
|
||||
}
|
||||
@@ -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,7 +10,7 @@
|
||||
import { app, ipcMain, dialog, BrowserWindow, shell } from 'electron';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import Buffer from 'buffer';
|
||||
import { Buffer } from 'buffer';
|
||||
import { setBadge, clearBadge, clearProgress, setProgress } from './progress.js';
|
||||
import { writeServerLog } from './misc.js';
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { app, Menu, ipcMain, BrowserWindow, globalShortcut } from 'electron';
|
||||
const isMac = process.platform == 'darwin';
|
||||
const isLinux = process.platform == 'linux';
|
||||
let mainMenu;
|
||||
let cachedMenus;
|
||||
|
||||
// Use to convert shortcut to accelerator for electron.
|
||||
function convertShortcutToAccelerator({ control, meta, shift, alt, key } = {}) {
|
||||
@@ -29,18 +30,27 @@ function convertShortcutToAccelerator({ control, meta, shift, alt, key } = {}) {
|
||||
return [...mods, k].join('+');
|
||||
}
|
||||
|
||||
function buildMenu(pgadminMenus, pgAdminMainScreen, 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,
|
||||
accelerator: convertShortcutToAccelerator(subMenuItem.shortcut),
|
||||
// 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.
|
||||
@@ -48,66 +58,94 @@ function buildMenu(pgadminMenus, pgAdminMainScreen, callbacks) {
|
||||
// The actual shortcut is already handled by pgAdmin.
|
||||
return;
|
||||
}
|
||||
pgAdminMainScreen.webContents.send('menu-click', smName);
|
||||
pgAdminMainScreen.webContents.send('menu-click', `${smName}_${deeperSubMenuItem.name}`);
|
||||
},
|
||||
submenu: subMenuItem.submenu?.map((deeperSubMenuItem)=>{
|
||||
return {
|
||||
...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 = [];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -119,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' },
|
||||
@@ -128,25 +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);
|
||||
// this is important because the shortcuts are registered multiple times
|
||||
// when the menu is set multiple times using accelerators.
|
||||
globalShortcut.unregisterAll();
|
||||
if(isMac) {
|
||||
Menu.setApplicationMenu(mainMenu);
|
||||
} else {
|
||||
pgAdminMainScreen.setMenu(mainMenu);
|
||||
}
|
||||
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,7 +481,7 @@ 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) => {
|
||||
@@ -428,6 +503,7 @@ ipcMain.handle('checkPortAvailable', async (_e, fixedPort)=>{
|
||||
});
|
||||
ipcMain.handle('openConfigure', openConfigure);
|
||||
|
||||
|
||||
app.whenReady().then(() => {
|
||||
splashWindow = new BrowserWindow({
|
||||
transparent: true,
|
||||
|
||||
@@ -32,4 +32,10 @@ contextBridge.exposeInMainWorld('electronUI', {
|
||||
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),
|
||||
//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));
|
||||
},
|
||||
});
|
||||
@@ -1,210 +1,299 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2025, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
# Get the correct version of chromedriver for the version of Chrome on the
|
||||
# machine and save it to the specified location.
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
from urllib.request import urlopen, urlretrieve
|
||||
from urllib.error import URLError
|
||||
import requests
|
||||
import zipfile
|
||||
import stat
|
||||
|
||||
|
||||
# --- Helper Functions for Auto-Detection ---
|
||||
|
||||
|
||||
def _find_chrome_executable():
|
||||
"""Find the Chrome executable path on Linux or macOS."""
|
||||
system = platform.system()
|
||||
|
||||
if system == 'Darwin':
|
||||
# macOS standard location
|
||||
paths = [
|
||||
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
|
||||
]
|
||||
elif system == 'Linux':
|
||||
# Common Linux executables (Chromium and Google Chrome)
|
||||
paths = [
|
||||
'google-chrome',
|
||||
'google-chrome-stable',
|
||||
'chromium',
|
||||
'chromium-browser',
|
||||
]
|
||||
else:
|
||||
return None
|
||||
|
||||
for path in paths:
|
||||
try:
|
||||
# Check if the command/path is callable (using 'which' or
|
||||
# direct check)
|
||||
if system == 'Linux':
|
||||
# Use 'which' for common Linux commands/paths
|
||||
result = subprocess.run(
|
||||
['which', path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
|
||||
# For macOS, or if 'which' failed/is unavailable, check direct path
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def read_command_line():
|
||||
"""Read the command line arguments.
|
||||
|
||||
Returns:
|
||||
ArgumentParser: The parsed arguments object
|
||||
"""
|
||||
The 'chrome' argument is now removed."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Get the correct version of chromedriver for the '
|
||||
'specified Chrome installation and save it.')
|
||||
if platform.system() != 'Windows':
|
||||
parser.add_argument("chrome", metavar="CHROME",
|
||||
help="the Chrome executable")
|
||||
parser.add_argument("directory", metavar="DIRECTORY",
|
||||
help="the directory in which to save chromedriver")
|
||||
description=(
|
||||
'Auto-detects Chrome version, gets the correct Chromedriver '
|
||||
'using Chrome for Testing, and saves it.'
|
||||
)
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
# Only accept the directory argument
|
||||
parser.add_argument(
|
||||
'directory',
|
||||
metavar='DIRECTORY',
|
||||
help='the directory in which to save chromedriver'
|
||||
)
|
||||
|
||||
return args
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def get_chrome_version(args):
|
||||
"""Get the Chrome version number.
|
||||
def get_chrome_version():
|
||||
"""Get the Chrome version number via OS-specific auto-detection."""
|
||||
full_version = None
|
||||
|
||||
Args:
|
||||
args: The parsed arguments object
|
||||
Returns:
|
||||
The Chrome version number
|
||||
"""
|
||||
if platform.system() == 'Windows':
|
||||
# On Windows we need to examine the resource section of the binary
|
||||
import winreg
|
||||
|
||||
def _read_registry(root, key, value):
|
||||
try:
|
||||
hkey = winreg.OpenKey(root, key)
|
||||
except Exception:
|
||||
return None
|
||||
try:
|
||||
(val, typ) = winreg.QueryValueEx(hkey, value)
|
||||
except Exception:
|
||||
winreg.CloseKey(hkey)
|
||||
return None
|
||||
|
||||
winreg.CloseKey(hkey)
|
||||
return val
|
||||
|
||||
key = r'SOFTWARE\Google\Chrome\BLBeacon'
|
||||
version_str = _read_registry(winreg.HKEY_CURRENT_USER, key, 'Version')
|
||||
|
||||
# On a 64b host, look for a 32b installation
|
||||
if not version_str:
|
||||
key = r'SOFTWARE\Wow6432Node\Google\Chrome\BLBeacon'
|
||||
version_str = _read_registry(winreg.HKEY_CURRENT_USER, key,
|
||||
'Version')
|
||||
|
||||
if not version_str:
|
||||
print('The Chrome version could not be read from the registry.')
|
||||
sys.exit(1)
|
||||
|
||||
chrome_version = '.'.join(version_str.split()[-1].split('.')[:-1])
|
||||
else:
|
||||
# On Linux/Mac we run the Chrome executable with the --version flag,
|
||||
# then parse the output.
|
||||
try:
|
||||
result = subprocess.Popen([args.chrome, '--version'],
|
||||
stdout=subprocess.PIPE)
|
||||
except FileNotFoundError:
|
||||
print('The specified Chrome executable could not be found.')
|
||||
import winreg
|
||||
|
||||
def _read_registry(root, key, value):
|
||||
try:
|
||||
with winreg.OpenKey(root, key) as hkey:
|
||||
val, _ = winreg.QueryValueEx(hkey, value)
|
||||
return val
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
keys = [
|
||||
r'SOFTWARE\Google\Chrome\BLBeacon',
|
||||
r'SOFTWARE\Wow6432Node\Google\Chrome\BLBeacon',
|
||||
]
|
||||
version_str = None
|
||||
for key in keys:
|
||||
version_str = _read_registry(
|
||||
winreg.HKEY_CURRENT_USER, key, 'Version'
|
||||
)
|
||||
if version_str:
|
||||
break
|
||||
|
||||
if not version_str:
|
||||
print(
|
||||
'Error: The Chrome version could not be read '
|
||||
'from the Windows registry.'
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
full_version = (
|
||||
version_str.split()[-1].strip()
|
||||
if version_str.split()
|
||||
else version_str.strip()
|
||||
)
|
||||
|
||||
except ImportError:
|
||||
print("Error: The 'winreg' module is required on Windows.")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Error reading Windows registry: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
version_str = result.stdout.read().decode("utf-8")
|
||||
# Check for 'Chrom' not 'Chrome' in case the user is using Chromium.
|
||||
if "Chrom" not in version_str:
|
||||
print('The specified Chrome executable output an unexpected '
|
||||
'version string: {}.'.format(version_str))
|
||||
else:
|
||||
chrome_executable = _find_chrome_executable()
|
||||
|
||||
if not chrome_executable:
|
||||
print(
|
||||
'Error: Could not find the Google Chrome or Chromium '
|
||||
'executable in common locations.'
|
||||
)
|
||||
sys.exit(1)
|
||||
# On some linux distro `chrome--version` gives output like
|
||||
# 'Google Chrome 80.0.3987.132 unknown\n'
|
||||
# so we need to check and remove the unknown string from the version
|
||||
if version_str.endswith("unknown\n"):
|
||||
version_str = version_str.strip("unknown\n").strip()
|
||||
|
||||
chrome_version = '.'.join(version_str.split()[-1].split('.')[:-1])
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[chrome_executable, '--version'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
|
||||
if chrome_version.count('.') != 2:
|
||||
print('The specified Chrome executable output an unexpected '
|
||||
'version string: {}.'.format(version_str))
|
||||
version_str = result.stdout.strip()
|
||||
if 'Chrom' not in version_str:
|
||||
print(
|
||||
'Error: Executable output unexpected version string: '
|
||||
f'{version_str}'
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
full_version = version_str.split()[-1]
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error executing '{chrome_executable} --version': {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if not full_version or full_version.count('.') < 3:
|
||||
print(f'Error: Extracted Chrome version "{full_version}" '
|
||||
f'seems invalid.')
|
||||
sys.exit(1)
|
||||
|
||||
return chrome_version
|
||||
return full_version
|
||||
|
||||
|
||||
def get_chromedriver_version(chrome_version):
|
||||
"""Get the required chromedriver version number.
|
||||
def get_system_and_platform():
|
||||
"""Get the CfT platform name (e.g., mac-arm64, linux64)."""
|
||||
system = platform.system()
|
||||
|
||||
Args:
|
||||
chrome_version: The chrome version number
|
||||
Returns:
|
||||
The chromedriver version number
|
||||
"""
|
||||
url = 'https://chromedriver.storage.googleapis.com/LATEST_RELEASE_{}' \
|
||||
.format(chrome_version)
|
||||
if system == 'Darwin':
|
||||
if platform.machine() in ('arm64', 'aarch64'):
|
||||
return 'mac-arm64'
|
||||
return 'mac-x64'
|
||||
if system == 'Linux':
|
||||
return 'linux64'
|
||||
if system == 'Windows':
|
||||
return 'win64'
|
||||
|
||||
print(f'Error: Unknown or unsupported operating system: {system}')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_chromedriver_download_url(full_chrome_version, cft_platform):
|
||||
"""Get the required Chromedriver version and download URL using CfT."""
|
||||
download_url = (
|
||||
'https://storage.googleapis.com/chrome-for-testing-public/'
|
||||
f'{full_chrome_version}/{cft_platform}/'
|
||||
f'chromedriver-{cft_platform}.zip'
|
||||
)
|
||||
return full_chrome_version, download_url
|
||||
|
||||
|
||||
def download_and_extract(url, target_directory, cft_platform):
|
||||
"""Downloads the zip, extracts chromedriver, and sets permissions."""
|
||||
print(f'Downloading from: {url}')
|
||||
|
||||
temp_zip_path = os.path.join(target_directory, 'chromedriver_temp.zip')
|
||||
try:
|
||||
response = requests.get(url, stream=True)
|
||||
response.raise_for_status()
|
||||
with open(temp_zip_path, 'wb') as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
f.write(chunk)
|
||||
except Exception as e:
|
||||
print(f'Error during download: {e}')
|
||||
sys.exit(1)
|
||||
|
||||
print('Extracting chromedriver...')
|
||||
|
||||
chromedriver_in_zip_dir = f'chromedriver-{cft_platform}/'
|
||||
|
||||
if cft_platform.startswith('win'):
|
||||
chromedriver_in_zip = chromedriver_in_zip_dir + 'chromedriver.exe'
|
||||
chromedriver_target_name = 'chromedriver.exe'
|
||||
else:
|
||||
chromedriver_in_zip = chromedriver_in_zip_dir + 'chromedriver'
|
||||
chromedriver_target_name = 'chromedriver'
|
||||
|
||||
target_path = os.path.join(target_directory, chromedriver_target_name)
|
||||
found = False
|
||||
|
||||
try:
|
||||
fp = urlopen(url)
|
||||
except URLError as e:
|
||||
print('The chromedriver catalog URL could not be accessed: {}'
|
||||
.format(e))
|
||||
with zipfile.ZipFile(temp_zip_path, 'r') as z:
|
||||
for name in z.namelist():
|
||||
if name == chromedriver_in_zip:
|
||||
with z.open(name) as source, open(
|
||||
target_path, 'wb'
|
||||
) as target:
|
||||
target.write(source.read())
|
||||
found = True
|
||||
break
|
||||
except Exception as e:
|
||||
print(f'Error during extraction: {e}')
|
||||
sys.exit(1)
|
||||
finally:
|
||||
os.remove(temp_zip_path)
|
||||
|
||||
if not found:
|
||||
print(f"Error: '{chromedriver_in_zip}' not found in archive.")
|
||||
sys.exit(1)
|
||||
|
||||
version = fp.read().decode("utf-8").strip()
|
||||
fp.close()
|
||||
if not cft_platform.startswith('win'):
|
||||
print('Setting executable permissions.')
|
||||
os.chmod(
|
||||
target_path,
|
||||
stat.S_IRWXU |
|
||||
stat.S_IRGRP |
|
||||
stat.S_IXGRP |
|
||||
stat.S_IROTH |
|
||||
stat.S_IXOTH
|
||||
)
|
||||
|
||||
return version
|
||||
print(f'\nSuccess! Chromedriver downloaded and saved to: {target_path}')
|
||||
|
||||
|
||||
def get_system():
|
||||
"""Get the system name as known to chromedriver
|
||||
# --- Core Logic ---
|
||||
|
||||
Returns:
|
||||
The system name
|
||||
"""
|
||||
if platform.system() == 'Darwin':
|
||||
return 'mac64'
|
||||
elif platform.system() == 'Linux':
|
||||
return 'linux64'
|
||||
elif platform.system() == 'Windows':
|
||||
return 'win32'
|
||||
else:
|
||||
print("Unknown or unsupported operating system: {}"
|
||||
.format(platform.system()))
|
||||
|
||||
def main():
|
||||
"""The core structure of the app."""
|
||||
try:
|
||||
import requests # noqa: F401
|
||||
except ImportError:
|
||||
print(
|
||||
"Error: The 'requests' library is required. "
|
||||
"Please install it with 'pip install requests'"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
args = read_command_line()
|
||||
|
||||
"""The core structure of the app."""
|
||||
if not os.path.isdir(args.directory):
|
||||
print(
|
||||
f'Error: The specified output directory "{args.directory}" '
|
||||
'could not be accessed.'
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Read the command line options
|
||||
args = read_command_line()
|
||||
cft_platform = get_system_and_platform()
|
||||
current_system = platform.system()
|
||||
print(f'Detected OS: {current_system} | '
|
||||
f'Target CfT Platform: {cft_platform}')
|
||||
|
||||
chrome_version = get_chrome_version(args)
|
||||
full_chrome_version = get_chrome_version()
|
||||
print(f'Detected Chrome Version: {full_chrome_version}')
|
||||
|
||||
# Check the directory exists
|
||||
if not os.path.isdir(args.directory):
|
||||
print('The specified output directory could not be accessed.')
|
||||
sys.exit(1)
|
||||
chromedriver_version, download_url = get_chromedriver_download_url(
|
||||
full_chrome_version, cft_platform
|
||||
)
|
||||
print(f'Downloading chromedriver v{chromedriver_version}...')
|
||||
|
||||
chromedriver_version = get_chromedriver_version(chrome_version)
|
||||
download_and_extract(download_url, args.directory, cft_platform)
|
||||
|
||||
system = get_system()
|
||||
|
||||
url = 'https://chromedriver.storage.googleapis.com/{}/chromedriver_{}.zip' \
|
||||
.format(chromedriver_version, system)
|
||||
|
||||
print('Downloading chromedriver v{} for Chrome v{} on {}...'
|
||||
.format(chromedriver_version, chrome_version, system))
|
||||
|
||||
try:
|
||||
file, headers = urlretrieve(url)
|
||||
except URLError as e:
|
||||
print('The chromedriver download URL could not be accessed: {}'
|
||||
.format(e))
|
||||
sys.exit(1)
|
||||
|
||||
# Unzip chromedriver
|
||||
print('Extracting chromedriver...')
|
||||
|
||||
found = False
|
||||
fp = open(file, 'rb')
|
||||
z = zipfile.ZipFile(fp)
|
||||
for name in z.namelist():
|
||||
if (system == 'win32' and name == 'chromedriver.exe') or \
|
||||
(system != 'win32' and name == 'chromedriver'):
|
||||
z.extract(name, args.directory)
|
||||
found = True
|
||||
fp.close()
|
||||
|
||||
if not found:
|
||||
print("chromedriver could not be found in the downloaded archive: {}"
|
||||
.format(file))
|
||||
sys.exit(1)
|
||||
|
||||
# Set the permissions
|
||||
if system == 'mac64' or system == 'linux64':
|
||||
os.chmod(os.path.join(args.directory, 'chromedriver'), 0o755)
|
||||
|
||||
print('Chromedriver downloaded to: {}'.format(args.directory))
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -92,7 +92,9 @@ module.exports = [
|
||||
'no-import-assign': 'off',
|
||||
...reactjs.configs.recommended.rules,
|
||||
...reactjs.configs['jsx-runtime'].rules,
|
||||
'react/jsx-uses-react': 'error',
|
||||
'react/prop-types': 'off',
|
||||
'react/react-in-jsx-scope': 0,
|
||||
'react/jsx-uses-react': 0,
|
||||
'react/jsx-uses-vars': 'error',
|
||||
'no-unused-vars': 'off',
|
||||
'unused-imports/no-unused-imports': 'error',
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
{
|
||||
"presets": [["@babel/preset-env", {"modules": "commonjs", "useBuiltIns": "usage", "corejs": 3}], "@babel/preset-react", "@babel/preset-typescript"],
|
||||
"plugins": ["@babel/plugin-proposal-class-properties", "@babel/proposal-object-rest-spread", "@babel/plugin-transform-runtime"]
|
||||
"presets": [["@babel/preset-env", {"modules": "commonjs", "useBuiltIns": "usage", "corejs": 3}], ["@babel/preset-react", {
|
||||
"runtime": "automatic"
|
||||
}], "@babel/preset-typescript"],
|
||||
"plugins": ["@babel/plugin-proposal-class-properties", "@babel/proposal-object-rest-spread"]
|
||||
}
|
||||
@@ -129,6 +129,17 @@ WEB_SERVER = 'Python'
|
||||
# See https://tools.ietf.org/html/rfc7034 for more info.
|
||||
X_FRAME_OPTIONS = "SAMEORIGIN"
|
||||
|
||||
|
||||
# The Cross-Origin-Opener-Policy allows a website to control whether
|
||||
# a new top-level document, opened using Window.open() or by navigating
|
||||
# to a new page, is opened in the same browsing context group (BCG)
|
||||
# or in a new browsing context group.
|
||||
# Set to 'unsafe-none', 'same-origin-allow-popups', 'same-origin',
|
||||
# or 'noopener-allow-popups'
|
||||
|
||||
CROSS_ORIGIN_OPENER_POLICY = "same-origin"
|
||||
|
||||
|
||||
# The Content-Security-Policy header allows you to restrict how resources
|
||||
# such as JavaScript, CSS, or pretty much anything that the browser loads.
|
||||
# see https://content-security-policy.com/#source_list for more info
|
||||
@@ -743,6 +754,9 @@ LDAP_CA_CERT_FILE = ''
|
||||
LDAP_CERT_FILE = ''
|
||||
LDAP_KEY_FILE = ''
|
||||
|
||||
# TLS/SSL certificate Validation (True/False),
|
||||
# Make it false if certificate validation is not required.
|
||||
LDAP_CERT_VALIDATE = True
|
||||
##########################################################################
|
||||
|
||||
# Some flaky LDAP servers returns malformed schema. If True, no exception
|
||||
|
||||
@@ -2,7 +2,8 @@ const webpackShimAlias = require('./webpack.shim').resolveAlias;
|
||||
|
||||
const webpackAliasToJestModules = ()=>{
|
||||
const ret = {
|
||||
'\\.svg': '<rootDir>/regression/javascript/__mocks__/svg.js'
|
||||
'\\.svg\\?svgr$': '<rootDir>/regression/javascript/__mocks__/svg.js',
|
||||
'react-dom/server': 'react-dom/server.edge',
|
||||
};
|
||||
Object.keys(webpackShimAlias).forEach((an)=>{
|
||||
// eg - sources: ./pgadmin/static/js/ to '^sources/(.*)$': '<rootDir>/pgadmin/static/js/$1'
|
||||
@@ -51,7 +52,7 @@ module.exports = {
|
||||
],
|
||||
'testEnvironment': 'jsdom',
|
||||
'transformIgnorePatterns': [
|
||||
'[/\\\\]node_modules[/\\\\](?!react-dnd|dnd-core|@react-dnd|react-resize-detector).+\\.(js|jsx|mjs|cjs|ts|tsx)$',
|
||||
'[/\\\\]node_modules[/\\\\](?!react-dnd|dnd-core|@react-dnd|react-resize-detector|react-data-grid).+\\.(js|jsx|mjs|cjs|ts|tsx)$',
|
||||
'^.+\\.module\\.(css|sass|scss)$'
|
||||
]
|
||||
};
|
||||
@@ -1,5 +1,13 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2025, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
"""empty message
|
||||
"""
|
||||
|
||||
Revision ID: e24b1c4def17
|
||||
Revises: c62bcc14c3d6
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
##########################################################################
|
||||
#
|
||||
# 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
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.orm.session import Session
|
||||
from pgadmin.model import Preferences, ModulePreference, PreferenceCategory
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'e6ed5dac37c2'
|
||||
down_revision = 'e24b1c4def17'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
|
||||
session = Session(bind=op.get_bind())
|
||||
old_pref_categories = ['keyboard_shortcuts', 'editor', 'Editor']
|
||||
new_pref_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']
|
||||
preference_map = {
|
||||
'keyboard_shortcuts': [
|
||||
'find', 'replace', 'goto_line_col', 'comment', 'format_sql'
|
||||
],
|
||||
'options': [
|
||||
'plain_editor_mode', 'code_folding', 'cursor_blink_rate',
|
||||
'wrap_code', 'insert_pair_brackets', 'highlight_selection_matches',
|
||||
'brace_matching', 'sql_font_size', 'sql_font_ligatures',
|
||||
'sql_font_family', 'indent_new_line'
|
||||
],
|
||||
'sql_formatting': [
|
||||
'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 = {}
|
||||
|
||||
# get metadata from current connection
|
||||
meta = sa.MetaData()
|
||||
# define table representation
|
||||
meta.reflect(op.get_bind(), only=('user_preferences', 'module_preference',
|
||||
'preference_category', 'preferences'))
|
||||
module_pref_table = sa.Table('module_preference', meta)
|
||||
|
||||
module_id = session.query(ModulePreference).filter_by(
|
||||
name='editor').first()
|
||||
|
||||
# Insert the 'editor' module in module_preference table
|
||||
if not module_id:
|
||||
op.execute(
|
||||
module_pref_table.insert().values(name='editor')
|
||||
)
|
||||
|
||||
module_id = session.query(ModulePreference).filter_by(
|
||||
name='editor').first().id
|
||||
|
||||
# Insert the new preference categories in preference_category table
|
||||
pref_category_table = sa.Table('preference_category', meta)
|
||||
|
||||
op.bulk_insert(pref_category_table, [
|
||||
{'mid': module_id, 'name': 'keyboard_shortcuts'},
|
||||
{'mid': module_id, 'name': 'sql_formatting'},
|
||||
{'mid': module_id, 'name': 'options'},
|
||||
])
|
||||
|
||||
# Insert the new preferences in preferences table
|
||||
prefs_table = sa.Table('preferences', meta)
|
||||
|
||||
for category in new_pref_categories:
|
||||
category_id = session.query(PreferenceCategory
|
||||
).filter_by(name=category,
|
||||
mid=module_id).first().id
|
||||
|
||||
op.bulk_insert(
|
||||
prefs_table,
|
||||
[
|
||||
{'cid':category_id, 'name': pref_name}
|
||||
for pref_name in preference_map[category]
|
||||
]
|
||||
)
|
||||
|
||||
# Migrate the preferences from 'sqleditor' module to 'editor'
|
||||
category_data = session.query(ModulePreference, PreferenceCategory,).join(
|
||||
PreferenceCategory).filter(
|
||||
ModulePreference.name == 'sqleditor',
|
||||
PreferenceCategory.name.in_(old_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_pref_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
|
||||
|
||||
user_pref_table = sa.Table('user_preferences', meta)
|
||||
|
||||
# Update the user preferences with new preference ids
|
||||
for key, val in pref_map.items():
|
||||
op.execute(
|
||||
user_pref_table.update().where(
|
||||
user_pref_table.c.pid == key).values(pid=val)
|
||||
)
|
||||
|
||||
# Delete the old preferences and categories
|
||||
session.query(Preferences).filter(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
|
||||
@@ -0,0 +1,42 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2025, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
"""
|
||||
Revision ID: efbbe5d5862f
|
||||
Revises: e6ed5dac37c2
|
||||
Create Date: 2025-09-29 18:40:30.248908
|
||||
|
||||
"""
|
||||
from alembic import op, context
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'efbbe5d5862f'
|
||||
down_revision = 'e6ed5dac37c2'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
with op.batch_alter_table(
|
||||
"server",
|
||||
table_kwargs={'sqlite_autoincrement': True}) as batch_op:
|
||||
batch_op.add_column(sa.Column('tunnel_prompt_password',
|
||||
sa.Integer(), server_default='0'))
|
||||
with op.batch_alter_table(
|
||||
"sharedserver",
|
||||
table_kwargs={'sqlite_autoincrement': True}) as batch_op:
|
||||
batch_op.add_column(sa.Column('tunnel_prompt_password',
|
||||
sa.Integer(), server_default='0'))
|
||||
|
||||
|
||||
def downgrade():
|
||||
# pgAdmin only upgrades, downgrade not implemented.
|
||||
pass
|
||||
@@ -7,21 +7,21 @@
|
||||
],
|
||||
"license": "PostgreSQL",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.28.0",
|
||||
"@babel/core": "^7.28.3",
|
||||
"@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/plugin-transform-runtime": "^7.28.3",
|
||||
"@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/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@emotion/utils": "^1.0.0",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/dom": "10.4.1",
|
||||
"@testing-library/jest-dom": "^6.7.0",
|
||||
"@testing-library/react": "16.3.0",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/jest": "^30.0.0",
|
||||
@@ -30,65 +30,68 @@
|
||||
"babel-loader": "^10.0.0",
|
||||
"browserify": "^17.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"copy-webpack-plugin": "^13.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"copy-webpack-plugin": "^13.0.1",
|
||||
"cross-env": "^10.0.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||
"eslint": "^9.30.1",
|
||||
"eslint": "^9.39.0",
|
||||
"eslint-plugin-jest": "^29.0.1",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"eslint-plugin-unused-imports": "^4.2.0",
|
||||
"exports-loader": "^5.0.0",
|
||||
"globals": "^16.3.0",
|
||||
"globals": "^16.5.0",
|
||||
"html-react-parser": "^5.2.0",
|
||||
"image-minimizer-webpack-plugin": "^4.1.3",
|
||||
"image-minimizer-webpack-plugin": "^4.1.4",
|
||||
"imports-loader": "^5.0.0",
|
||||
"jest": "^30.0.2",
|
||||
"jest-environment-jsdom": "^29.6.4",
|
||||
"jest": "^30.2.0",
|
||||
"jest-environment-jsdom": "^30.2.0",
|
||||
"loader-utils": "^3.2.1",
|
||||
"mini-css-extract-plugin": "^2.9.2",
|
||||
"postcss-loader": "^8.1.1",
|
||||
"process": "^0.11.10",
|
||||
"prop-types": "^15.7.2",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"sharp": "^0.34.3",
|
||||
"shim-loader": "^1.0.1",
|
||||
"style-loader": "^4.0.0",
|
||||
"stylis": "^4.3.4",
|
||||
"svgo": "^4.0.0",
|
||||
"svgo-loader": "^4.0.0",
|
||||
"terser-webpack-plugin": "^5.3.12",
|
||||
"typescript": "^5.4.5",
|
||||
"typescript-eslint": "^8.35.0",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript-eslint": "^8.44.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.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
||||
"@babel/preset-react": "^7.12.13",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-sql": "^6.9.0",
|
||||
"@codemirror/lang-sql": "^6.10.0",
|
||||
"@date-io/core": "^3.0.0",
|
||||
"@date-io/date-fns": "3.x",
|
||||
"@emotion/sheet": "^1.0.1",
|
||||
"@fluentui/keyboard-key": "^0.4.23",
|
||||
"@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.4",
|
||||
"@mui/material": "^7.3.4",
|
||||
"@mui/x-date-pickers": "^8.16.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.81.5",
|
||||
"@tanstack/react-query": "^5.90.6",
|
||||
"@tanstack/react-table": "^8.16.0",
|
||||
"@tanstack/react-virtual": "^3.13.6",
|
||||
"@types/classnames": "^2.3.4",
|
||||
"@types/react": "^18.0.2",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/addon-search": "^0.15.0",
|
||||
"@xterm/addon-web-links": "^0.11.0",
|
||||
@@ -96,18 +99,17 @@
|
||||
"ajv": "^8.8.2",
|
||||
"anti-trojan-source": "^1.4.0",
|
||||
"aspen-decorations": "^1.0.2",
|
||||
"axios": "^1.10.0",
|
||||
"axios": "^1.13.1",
|
||||
"babelify": "~10.0.0",
|
||||
"bignumber.js": "^9.2.0",
|
||||
"brace": "^0.11.1",
|
||||
"browserfs": "^1.4.3",
|
||||
"chart.js": "^4.4.7",
|
||||
"chartjs-plugin-zoom": "^2.0.1",
|
||||
"codemirror": "^6.0.1",
|
||||
"convert-units": "^2.3.4",
|
||||
"date-fns": "^4.1.0",
|
||||
"diff-arrays-of-objects": "^1.1.8",
|
||||
"hotkeys-js": "^3.13.14",
|
||||
"hotkeys-js": "^3.13.15",
|
||||
"html-to-image": "1.11.11",
|
||||
"immutability-helper": "^3.0.0",
|
||||
"insert-if": "^1.1.0",
|
||||
@@ -122,21 +124,21 @@
|
||||
"papaparse": "^5.5.2",
|
||||
"path-fx": "^2.0.0",
|
||||
"postcss": "^8.5.6",
|
||||
"rc-dock": "^3.3.2",
|
||||
"react": "^18.2.0",
|
||||
"rc-dock": "^4.0.0-alpha.2",
|
||||
"react": "^19.0.0",
|
||||
"react-arborist": "^3.2.0",
|
||||
"react-aspen": "^1.1.0",
|
||||
"react-checkbox-tree": "^1.7.2",
|
||||
"react-data-grid": "https://github.com/pgadmin-org/react-data-grid.git#3dfc2ca01a046d55c1c7a45392dcec104815dc76",
|
||||
"react-data-grid": "https://github.com/pgadmin-org/react-data-grid.git#8ddaa7bed99c8329e39b8ff37ba156f7fc8e85d4",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-draggable": "^4.4.6",
|
||||
"react-dropzone": "^14.2.1",
|
||||
"react-frame-component": "^5.2.6",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"react-new-window": "^1.0.1",
|
||||
"react-resize-detector": "^12.1.0",
|
||||
"react-resize-detector": "^12.3.0",
|
||||
"react-rnd": "^10.5.1",
|
||||
"react-select": "^5.7.2",
|
||||
"react-timer-hook": "^4.0.1",
|
||||
@@ -144,14 +146,14 @@
|
||||
"snapsvg-cjs": "^0.0.6",
|
||||
"socket.io-client": "^4.5.0",
|
||||
"split.js": "^1.5.10",
|
||||
"sql-formatter": "^15.6.2",
|
||||
"sql-formatter": "^15.6.10",
|
||||
"uplot": "^1.6.32",
|
||||
"uplot-react": "^1.1.4",
|
||||
"use-resize-observer": "^9.1.0",
|
||||
"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"
|
||||
@@ -171,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",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import url_for from 'sources/url_for';
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { Box, Grid, InputLabel } from '@mui/material';
|
||||
import { InputSQL } from '../../../static/js/components/FormComponents';
|
||||
import getApiInstance from '../../../static/js/api_instance';
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import gettext from 'sources/gettext';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import AboutComponent from './AboutComponent';
|
||||
|
||||