Implemented runtime using NWjs to open pgAdmin4 in a standalone window

instead of the system tray and web browser. Used NWjs to get rid of QT
and C++. Fixes #5967

Use cheroot as the default production server for pgAdmin4. Fixes #5017
This commit is contained in:
Akshay Joshi 2021-01-29 13:38:27 +05:30
parent a0271c7656
commit 102ffd141c
392 changed files with 3388 additions and 7599 deletions

3
.gitignore vendored
View File

@ -19,11 +19,12 @@
/redhat-build
/src-build
/win-build
/win-temp
_build
/build-*
pgadmin4.log
pkg/win32/installer.iss
pkg/mac/codesign.conf
pkg/mac/codesign.confx
pkg/mac/framework.conf
runtime/.qmake.cache
runtime/.qmake.stash

File diff suppressed because it is too large Load Diff

206
Make.bat
View File

@ -3,6 +3,7 @@ SETLOCAL
SET WD=%CD%
SET "BUILDROOT=%WD%\win-build"
SET "TMPDIR=%WD%\win-temp"
SET "DISTROOT=%WD%\dist"
SET CMDOPTIONS=""
@ -18,19 +19,13 @@ IF "%1" == "clean" (
EXIT /B %ERRORLEVEL%
)
set "ARCHITECTURE=x64"
if "%Platform%" == "X86" (
set "ARCHITECTURE=x86"
)
REM Main build sequence
CALL :SET_ENVIRONMENT
CALL :VALIDATE_ENVIRONMENT || EXIT /B 1
CALL :CLEAN || EXIT /B 1
CALL :CREATE_VIRTUAL_ENV || EXIT /B 1
CALL :CREATE_RUNTIME_ENV || EXIT /B 1
CALL :CREATE_PYTHON_ENV || EXIT /B 1
CALL :CLEANUP_ENV || EXIT /B 1
CALL :CREATE_RUNTIME_ENV || EXIT /B 1
CALL :CREATE_INSTALLER || EXIT /B 1
CALL :SIGN_INSTALLER || EXIT /B 1
@ -42,7 +37,10 @@ REM Main build sequence Ends
ECHO Removing build directory...
IF EXIST "%BUILDROOT%" RD "%BUILDROOT%" /S /Q > nul || EXIT /B 1
ECHO Removing temp build directory...
ECHO Removing tmp directory...
IF EXIST "%TMPDIR%" RD "%TMPDIR%" /S /Q > nul || EXIT /B 1
ECHO Removing installer build directory...
IF EXIST "%WD%\pkg\win32\Output" rd "%WD%\pkg\win32\Output" /S /Q > nul || EXIT /B 1
ECHO Removing installer configuration script...
@ -54,7 +52,6 @@ REM Main build sequence Ends
:SET_ENVIRONMENT
ECHO Configuring the environment...
IF "%PGADMIN_PYTHON_DIR%" == "" SET "PGADMIN_PYTHON_DIR=C:\Python38"
IF "%PGADMIN_QT_DIR%" == "" SET "PGADMIN_QT_DIR=C:\Qt\5.14.2\msvc2017_64"
IF "%PGADMIN_KRB5_DIR%" == "" SET "PGADMIN_KRB5_DIR=C:\Program Files\MIT\Kerberos"
IF "%PGADMIN_POSTGRES_DIR%" == "" SET "PGADMIN_POSTGRES_DIR=C:\Program Files (x86)\PostgreSQL\12"
IF "%PGADMIN_INNOTOOL_DIR%" == "" SET "PGADMIN_INNOTOOL_DIR=C:\Program Files (x86)\Inno Setup 6"
@ -62,7 +59,7 @@ REM Main build sequence Ends
IF "%PGADMIN_SIGNTOOL_DIR%" == "" SET "PGADMIN_SIGNTOOL_DIR=C:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x64"
REM Set REDIST_NAME (the filename)
set "VCREDIST_FILE=vcredist_%ARCHITECTURE%.exe"
set "VCREDIST_FILE=vcredist_x64.exe"
REM Set additional variables we need
FOR /F "tokens=3" %%a IN ('findstr /C:"APP_RELEASE =" %WD%\web\config.py') DO SET APP_MAJOR=%%a
@ -74,13 +71,13 @@ REM Main build sequence Ends
FOR /F "tokens=2* DELims='" %%a IN ('findstr /C:"APP_NAME =" web\config.py') DO SET APP_NAME=%%a
FOR /f "tokens=1 DELims=." %%G IN ('%PGADMIN_PYTHON_DIR%/python.exe -c "print('%APP_NAME%'.lower().replace(' ', ''))"') DO SET APP_SHORTNAME=%%G
SET APP_VERSION=%APP_MAJOR%.%APP_MINOR%
SET INSTALLERNAME=%APP_SHORTNAME%-%APP_MAJOR%.%APP_MINOR%-%APP_VERSION_SUFFIX%-%ARCHITECTURE%.exe
IF "%APP_VERSION_SUFFIX%" == "" SET INSTALLERNAME=%APP_SHORTNAME%-%APP_MAJOR%.%APP_MINOR%-%ARCHITECTURE%.exe
SET INSTALLERNAME=%APP_SHORTNAME%-%APP_MAJOR%.%APP_MINOR%-%APP_VERSION_SUFFIX%-x64.exe
IF "%APP_VERSION_SUFFIX%" == "" SET INSTALLERNAME=%APP_SHORTNAME%-%APP_MAJOR%.%APP_MINOR%-x64.exe
REM get Python version for the runtime build ex. 2.7.1 will be 27
FOR /f "tokens=1 DELims=." %%G IN ('%PGADMIN_PYTHON_DIR%/python.exe -c "import sys; print(sys.version.split(' ')[0])"') DO SET PYTHON_MAJOR=%%G
FOR /f "tokens=2 DELims=." %%G IN ('%PGADMIN_PYTHON_DIR%/python.exe -c "import sys; print(sys.version.split(' ')[0])"') DO SET PYTHON_MINOR=%%G
SET "PYTHON_VERSION=%PYTHON_MAJOR%%PYTHON_MINOR%"
FOR /f "tokens=3 DELims=." %%G IN ('%PGADMIN_PYTHON_DIR%/python.exe -c "import sys; print(sys.version.split(' ')[0])"') DO SET PYTHON_REVISION=%%G
EXIT /B 0
@ -94,10 +91,8 @@ REM Main build sequence Ends
ECHO Installer name: %INSTALLERNAME%
ECHO.
ECHO Python directory: %PGADMIN_PYTHON_DIR%
ECHO Python DLL: %PGADMIN_PYTHON_DIR%\Python%PYTHON_VERSION%.dll
ECHO Python version: %PYTHON_MAJOR%.%PYTHON_MINOR%
ECHO Python version: %PYTHON_MAJOR%.%PYTHON_MINOR%.%PYTHON_REVISION%
ECHO.
ECHO Qt directory: %PGADMIN_QT_DIR%
ECHO KRB5 directory: %PGADMIN_KRB5_DIR%
ECHO PostgreSQL directory: %PGADMIN_POSTGRES_DIR%
ECHO.
@ -125,36 +120,18 @@ REM Main build sequence Ends
EXIT /B 1
)
IF NOT EXIST "%PGADMIN_QT_DIR%" (
ECHO !PGADMIN_QT_DIR! does not exist.
ECHO Please install Qt and set the PGADMIN_QT_DIR environment variable.
EXIT /B 1
)
IF NOT EXIST "%PGADMIN_KRB5_DIR%" (
ECHO !PGADMIN_KRB5_DIR! does not exist.
ECHO Please install MIT Kerberos for Windows and set the PGADMIN_KRB5_DIR environment variable.
EXIT /B 1
)
IF NOT EXIST "%PGADMIN_QT_DIR%\bin\qmake.exe" (
ECHO !QMAKE! does not exist.
ECHO Please install Qt and set the PGADMIN_QT_DIR environment variable.
EXIT /B 1
)
IF NOT EXIST "%PGADMIN_PYTHON_DIR%" (
ECHO !PGADMIN_PYTHON_DIR! does not exist.
ECHO Please install Python and set the PGADMIN_PYTHON_DIR environment variable.
EXIT /B 1
)
IF NOT EXIST "%PGADMIN_PYTHON_DIR%\Python%PYTHON_VERSION%.dll" (
ECHO !PGADMIN_PYTHON_DIR!\Python!PYTHON_VERSION!.dll does not exist.
ECHO Please check your Python installation is complete.
EXIT /B 1
)
IF NOT EXIST "%PGADMIN_POSTGRES_DIR%" (
ECHO !PGADMIN_POSTGRES_DIR! does not exist.
ECHO Please install PostgreSQL and set the PGADMIN_POSTGRES_DIR environment variable.
@ -174,47 +151,75 @@ REM Main build sequence Ends
:CREATE_VIRTUAL_ENV
ECHO Creating virtual environment...
IF NOT EXIST "%BUILDROOT%" MKDIR "%BUILDROOT%"
CD "%BUILDROOT%"
IF NOT EXIST "%TMPDIR%" MKDIR "%TMPDIR%"
CD "%TMPDIR%"
REM Note that we must use virtualenv.exe here, as the venv module doesn't allow python.exe to relocate.
"%PGADMIN_PYTHON_DIR%\Scripts\virtualenv.exe" venv
XCOPY /S /I /E /H /Y "%PGADMIN_PYTHON_DIR%\DLLs" "%BUILDROOT%\venv\DLLs" > nul || EXIT /B 1
XCOPY /S /I /E /H /Y "%PGADMIN_PYTHON_DIR%\Lib" "%BUILDROOT%\venv\Lib" > nul || EXIT /B 1
XCOPY /S /I /E /H /Y "%PGADMIN_PYTHON_DIR%\DLLs" "%TMPDIR%\venv\DLLs" > nul || EXIT /B 1
XCOPY /S /I /E /H /Y "%PGADMIN_PYTHON_DIR%\Lib" "%TMPDIR%\venv\Lib" > nul || EXIT /B 1
ECHO Activating virtual environment - %BUILDROOT%\venv...
CALL "%BUILDROOT%\venv\Scripts\activate" || EXIT /B 1
ECHO Activating virtual environment - %TMPDIR%\venv...
CALL "%TMPDIR%\venv\Scripts\activate" || EXIT /B 1
ECHO Installing dependencies...
CALL pip install -r "%WD%\requirements.txt" || EXIT /B 1
CALL pip install sphinx || EXIT /B 1
REM If this is Python 3.6+, we need to remove the hack above or it will break qmake. Sigh.
IF %PYTHON_VERSION% GEQ 36 SET CL=
CALL pip install --upgrade pip
CALL pip install --only-binary=cryptography -r "%WD%\requirements.txt" || EXIT /B 1
CD %WD%
EXIT /B 0
:CREATE_PYTHON_ENV
ECHO Staging Python...
MKDIR "%BUILDROOT%\python\Lib" || EXIT /B 1
ECHO Downloading embedded Python...
REM Get the python embeddable and extract it to %BUILDROOT%\python
CD "%TMPDIR%
%PGADMIN_PYTHON_DIR%\python -c "import sys; from urllib.request import urlretrieve; urlretrieve('https://www.python.org/ftp/python/' + sys.version.split(' ')[0] + '/python-' + sys.version.split(' ')[0] + '-embed-amd64.zip', 'python-embedded.zip')" || EXIT /B 1
%PGADMIN_PYTHON_DIR%\python -c "import zipfile; z = zipfile.ZipFile('python-embedded.zip', 'r'); z.extractall('../win-build/python/')" || EXIT /B 1
ECHO Copying site-packages...
XCOPY /S /I /E /H /Y "%TMPDIR%\venv\Lib\site-packages" "%BUILDROOT%\python\Lib\site-packages" > nul || EXIT /B 1
REM NOTE: There is intentionally no space after "site" in the line below, to prevent Python barfing if there's one in the file
ECHO import site>> "%BUILDROOT%\python\python%PYTHON_MAJOR%%PYTHON_MINOR%._pth"
ECHO Staging Kerberos components...
COPY "%PGADMIN_KRB5_DIR%\bin\kinit.exe" "%BUILDROOT%\python" > nul || EXIT /B 1
COPY "%PGADMIN_KRB5_DIR%\bin\krb5_64.dll" "%BUILDROOT%\python" > nul || EXIT /B 1
COPY "%PGADMIN_KRB5_DIR%\bin\comerr64.dll" "%BUILDROOT%\python" > nul || EXIT /B 1
COPY "%PGADMIN_KRB5_DIR%\bin\k5sprt64.dll" "%BUILDROOT%\python" > nul || EXIT /B 1
COPY "%PGADMIN_KRB5_DIR%\bin\gssapi64.dll" "%BUILDROOT%\python" > nul || EXIT /B 1
ECHO Cleaning up unnecessary .pyc and .pyo files...
FOR /R "%BUILDROOT%\python" %%f in (*.pyc *.pyo) do DEL /q "%%f" 1> nul 2>&1
ECHO Removing tests...
FOR /R "%BUILDROOT%\python\Lib" %%f in (test tests) do RD /Q /S "%%f" 1> nul 2>&1
EXIT /B 0
:CREATE_RUNTIME_ENV
IF NOT EXIST "%BUILDROOT%" MKDIR "%BUILDROOT%"
MKDIR "%BUILDROOT%\runtime"
CD "%WD%\web"
ECHO Installing javascript dependencies...
CALL yarn install || EXIT /B 1
ECHO Bundling javascript...
CALL yarn run bundle || EXIT /B 1
ECHO Removing webpack caches...
RD /Q /S "%WD%\web\pgadmin\static\js\generated\.cache" 1> nul 2>&1
ECHO Copying web directory...
XCOPY /S /I /E /H /Y "%WD%\web" "%BUILDROOT%\web" > nul || EXIT /B 1
ECHO Installing javascript dependencies...
CD "%BUILDROOT%\web"
CALL yarn install || EXIT /B 1
ECHO Bundling javascript...
CALL yarn run bundle || EXIT /B 1
ECHO Cleaning up unnecessary .pyc and .pyo files...
FOR /R "%BUILDROOT%\web" %%f in (*.pyc *.pyo) do DEL /q "%%f" 1> nul 2>&1
ECHO Removing tests, Python caches and node modules...
@ -240,60 +245,34 @@ REM Main build sequence Ends
ECHO } >> "%BUILDROOT%\web\config_distro.py"
ECHO Building docs...
CALL pip install sphinx || EXIT /B 1
MKDIR "%BUILDROOT%\docs\en_US\html"
CD "%WD%\docs\en_US"
CALL "%BUILDROOT%\venv\Scripts\python.exe" build_code_snippet.py || EXIT /B 1
CALL "%BUILDROOT%\venv\Scripts\sphinx-build.exe" "%WD%\docs\en_US" "%BUILDROOT%\docs\en_US\html" || EXIT /B 1
CALL "%TMPDIR%\venv\Scripts\python.exe" build_code_snippet.py || EXIT /B 1
CALL "%TMPDIR%\venv\Scripts\sphinx-build.exe" "%WD%\docs\en_US" "%BUILDROOT%\docs\en_US\html" || EXIT /B 1
ECHO Removing Sphinx
CALL pip uninstall -y sphinx Pygments alabaster colorama docutils imagesize requests snowballstemmer
ECHO Staging runtime components...
XCOPY /S /I /E /H /Y "%WD%\runtime\assets" "%BUILDROOT%\runtime\assets" > nul || EXIT /B 1
XCOPY /S /I /E /H /Y "%WD%\runtime\src" "%BUILDROOT%\runtime\src" > nul || EXIT /B 1
ECHO Assembling runtime environment...
CD "%WD%\runtime"
COPY "%WD%\runtime\package.json" "%BUILDROOT%\runtime\" > nul || EXIT /B 1
CD "%BUILDROOT%\runtime\"
CALL yarn install --production=true || EXIT /B 1
ECHO Running qmake...
CALL set "PGADMIN_PYTHON_DIR=%PGADMIN_PYTHON_DIR%" && "%PGADMIN_QT_DIR%\bin\qmake.exe" || EXIT /B 1
ECHO Downloading NWjs to %TMPDIR%...
CALL yarn --cwd "%TMPDIR%" add nw || EXIT /B
ECHO Cleaning the build directory...
CALL nmake clean || EXIT /B 1
XCOPY /S /I /E /H /Y "%TMPDIR%\node_modules\nw\nwjs\*" "%BUILDROOT%\runtime" > nul || EXIT /B 1
MOVE "%BUILDROOT%\runtime\nw.exe" "%BUILDROOT%\runtime\pgAdmin4.exe"
ECHO Running make...
CALL nmake || EXIT /B 1
ECHO Staging pgAdmin4.exe...
COPY "%WD%\runtime\release\pgAdmin4.exe" "%BUILDROOT%\runtime" > nul || EXIT /B 1
ECHO Staging Qt components...
COPY "%PGADMIN_QT_DIR%\bin\Qt5Core.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
COPY "%PGADMIN_QT_DIR%\bin\Qt5Gui.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
COPY "%PGADMIN_QT_DIR%\bin\Qt5Widgets.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
COPY "%PGADMIN_QT_DIR%\bin\Qt5Network.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
COPY "%PGADMIN_QT_DIR%\bin\Qt5Svg.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
MKDIR "%BUILDROOT%\runtime\platforms" > nul || EXIT /B 1
COPY "%PGADMIN_QT_DIR%\plugins\platforms\qwindows.dll" "%BUILDROOT%\runtime\platforms" > nul || EXIT /B 1
MKDIR "%BUILDROOT%\runtime\imageformats" > nul || EXIT /B 1
COPY "%PGADMIN_QT_DIR%\plugins\imageformats\qsvg.dll" "%BUILDROOT%\runtime\imageformats" > nul || EXIT /B 1
ECHO [Paths] > "%BUILDROOT%\runtime\qt.conf"
ECHO Plugins=plugins >> "%BUILDROOT%\runtime\qt.conf"
ECHO Staging Kerberos components...
IF "%ARCHITECTURE%" == "x64" (
COPY "%PGADMIN_KRB5_DIR%\bin\kinit.exe" "%BUILDROOT%\runtime" > nul || EXIT /B 1
COPY "%PGADMIN_KRB5_DIR%\bin\krb5_64.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
COPY "%PGADMIN_KRB5_DIR%\bin\comerr64.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
COPY "%PGADMIN_KRB5_DIR%\bin\k5sprt64.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
COPY "%PGADMIN_KRB5_DIR%\bin\gssapi64.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
)
ECHO Replacing executable icon...
CALL yarn --cwd "%TMPDIR%" add winresourcer || EXIT /B
"%TMPDIR%\node_modules\winresourcer\bin\Resourcer.exe" -op:upd -src:"%BUILDROOT%\runtime\pgAdmin4.exe" -type:Icongroup -name:IDR_MAINFRAME -file:"%WD%\pkg\win32\Resources\pgAdmin4.ico"
ECHO Staging PostgreSQL components...
COPY "%PGADMIN_POSTGRES_DIR%\bin\libpq.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
IF "%ARCHITECTURE%" == "x64" (
COPY "%PGADMIN_POSTGRES_DIR%\bin\libcrypto-1_1-x64.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
COPY "%PGADMIN_POSTGRES_DIR%\bin\libssl-1_1-x64.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
) ELSE (
COPY "%PGADMIN_POSTGRES_DIR%\bin\libcrypto-1_1.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
COPY "%PGADMIN_POSTGRES_DIR%\bin\libssl-1_1.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
)
COPY "%PGADMIN_POSTGRES_DIR%\bin\libcrypto-1_1-x64.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
COPY "%PGADMIN_POSTGRES_DIR%\bin\libssl-1_1-x64.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
IF EXIST "%PGADMIN_POSTGRES_DIR%\bin\libintl-*.dll" COPY "%PGADMIN_POSTGRES_DIR%\bin\libintl-*.dll" "%BUILDROOT%\runtime" > nul
IF EXIST "%PGADMIN_POSTGRES_DIR%\bin\libiconv-*.dll" COPY "%PGADMIN_POSTGRES_DIR%\bin\libiconv-*.dll" "%BUILDROOT%\runtime" > nul
COPY "%PGADMIN_POSTGRES_DIR%\bin\zlib.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
@ -308,22 +287,6 @@ REM Main build sequence Ends
CD %WD%
EXIT /B 0
:CREATE_PYTHON_ENV
ECHO Staging Python...
COPY %PGADMIN_PYTHON_DIR%\python%PYTHON_VERSION%.dll "%BUILDROOT%\runtime" > nul || EXIT /B 1
COPY %PGADMIN_PYTHON_DIR%\python.exe "%BUILDROOT%\runtime" > nul || EXIT /B 1
COPY %PGADMIN_PYTHON_DIR%\pythonw.exe "%BUILDROOT%\runtime" > nul || EXIT /B 1
ECHO Cleaning up unnecessary .pyc and .pyo files...
FOR /R "%BUILDROOT%\venv" %%f in (*.pyc *.pyo) do DEL /q "%%f" 1> nul 2>&1
ECHO Removing tests...
FOR /R "%BUILDROOT%\venv\Lib" %%f in (test tests) do RD /Q /S "%%f" 1> nul 2>&1
ECHO Removing TCL...
RD /Q /S "%BUILDROOT%\venv\tcl" 1> nul 2>&1
EXIT /B 0
:CREATE_INSTALLER
@ -339,12 +302,7 @@ REM Main build sequence Ends
CALL "%PGADMIN_PYTHON_DIR%\python" "%WD%\pkg\win32\replace.py" "-i" "%WD%\pkg\win32\installer.iss.in" "-o" "%WD%\pkg\win32\installer.iss.in_stage1" "-s" MYAPP_NAME -r """%APP_NAME%"""
CALL "%PGADMIN_PYTHON_DIR%\python" "%WD%\pkg\win32\replace.py" "-i" "%WD%\pkg\win32\installer.iss.in_stage1" "-o" "%WD%\pkg\win32\installer.iss.in_stage2" "-s" MYAPP_FULLVERSION -r """%APP_VERSION%"""
CALL "%PGADMIN_PYTHON_DIR%\python" "%WD%\pkg\win32\replace.py" "-i" "%WD%\pkg\win32\installer.iss.in_stage2" "-o" "%WD%\pkg\win32\installer.iss.in_stage3" "-s" MYAPP_VERSION -r """v%APP_MAJOR%"""
SET ARCMODE=
IF "%ARCHITECTURE%" == "x64" (
set ARCMODE="x64"
)
CALL "%PGADMIN_PYTHON_DIR%\python" "%WD%\pkg\win32\replace.py" "-i" "%WD%\pkg\win32\installer.iss.in_stage3" "-o" "%WD%\pkg\win32\installer.iss.in_stage4" "-s" MYAPP_ARCHITECTURESMODE -r """%ARCMODE%"""
CALL "%PGADMIN_PYTHON_DIR%\python" "%WD%\pkg\win32\replace.py" "-i" "%WD%\pkg\win32\installer.iss.in_stage3" "-o" "%WD%\pkg\win32\installer.iss.in_stage4" "-s" MYAPP_ARCHITECTURESMODE -r """x64"""
CALL "%PGADMIN_PYTHON_DIR%\python" "%WD%\pkg\win32\replace.py" "-i" "%WD%\pkg\win32\installer.iss.in_stage4" "-o" "%WD%\pkg\win32\installer.iss" "-s" MYAPP_VCDIST -r """%PGADMIN_VCREDIST_DIRNAME%\%VCREDIST_FILE%"""
ECHO Cleaning up...
@ -377,14 +335,6 @@ REM Main build sequence Ends
EXIT /B 0
:CLEANUP_ENV
ECHO Cleaning the build environment...
RD "%BUILDROOT%\venv\Include" /S /Q 1> nul 2>&1
DEL /s "%BUILDROOT%\venv\pip-selfcheck.json" 1> nul 2>&1
EXIT /B 0
:USAGE
ECHO Invalid command line options.
ECHO Usage: "Make.bat [clean]"

View File

@ -20,7 +20,7 @@ APP_REVISION := $(shell grep ^APP_REVISION web/config.py | awk -F"=" '{print $$N
# Include only platform-independent builds in all
all: docs pip src runtime
appbundle: docs
appbundle:
./pkg/mac/build.sh
install-node:

84
README
View File

@ -17,59 +17,32 @@ utilised.
Although developed using web technologies, pgAdmin 4 can be deployed either on
a web server using a browser, or standalone on a workstation. The runtime/
subdirectory contains a QT based runtime application intended to allow this -
it is essentially a Python application server that runs in the system tray
and allows the user to connect to the application using their web browser.
subdirectory contains an NWjs based runtime application intended to allow this,
which will execute the Python server and display the UI.
Building the Runtime
--------------------
To build the runtime, the following packages must be installed:
- QT 5 (Use the VC++ build on Windows, not MinGW).
- Python 3.4+
- NodeJS 12+
- Yarn
An environment variable named PGADMIN_PYTHON_DIR must be set to the directory
in which Python has been installed, for example:
Change into the runtime directory, and run "yarn install". This will install the
dependencies required.
- /usr
- /usr/local/python-3.8
- C:\Python38
In order to use the runtime in a development environment, you'll need to copy
dev_config.json.in file to dev_config.json, and edit the paths to the Python
executable and pgAdmin.py file, otherwise the runtime will use the default
paths it would expect to find in the standard package for your platform.
Assuming both qmake is in the path:
You can then execute the runtime by running something like:
dpage@hal:~/git/pgadmin4$ cd runtime
dpage@hal:~/git/pgadmin4/runtime$ export PGADMIN_PYTHON_DIR=/opt/local
dpage@hal:~/git/pgadmin4/runtime$ qmake
Project MESSAGE: ==================================
Project MESSAGE: Configuring the pgAdmin 4 runtime.
Project MESSAGE: ==================================
Project MESSAGE: Qt version: 5
Project MESSAGE: Platform: macOS
Project MESSAGE: Python executable: /opt/local/bin/python3
Project MESSAGE: Python version: 3.8 (38)
Project MESSAGE: Python config: /opt/local/bin/python3-config
Project MESSAGE: CXXFLAGS: -pipe -stdlib=libc++ -I/opt/local/Library/Frameworks/Python.framework/Versions/3.8/include/python3.8 -I/opt/local/Library/Frameworks/Python.framework/Versions/3.8/include/python3.8
Project MESSAGE: LDFLAGS: -stdlib=libc++ -L/opt/local/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/config-3.8-darwin -ldl -framework CoreFoundation
Project MESSAGE: LIBS: -lpython3.8 -ldl -framework CoreFoundation
dpage@hal:~/git/pgadmin4/runtime$ make
...
node_modules/nw/nwjs/nw .
To build the runtime in debug mode, use the option below with qmake:
$ qmake CONFIG+=debug
or on macOS:
To build the runtime in release mode, use the option below with qmake:
$ qmake CONFIG+=release
By default, the runtime application will be built in release mode.
On Linux, an executable called 'pgAdmin4' will be built, on Windows,
'pgAdmin4.exe', and on Mac OS X, an app bundle called pgAdmin4.app will be
created.
You can also use Qt Creator to build, develop and debug the runtime. Simply
open the $PGADMIN4_SRC/runtime/pgAdmin4.pro project file in Qt Creator and
configure the project with a supported version of Qt when prompted.
node_modules/nw/nwjs/nwjs.app/Contents/MacOS/nwjs .
Create Database Migrations
--------------------------
@ -92,7 +65,7 @@ Configuring the Python Environment
----------------------------------
In order to run the Python code, a suitable runtime environment is required.
Python version 3.4 and later are currently supported. It is recommended that a
Python version 3.5 and later are currently supported. It is recommended that a
Python Virtual Environment is setup for this purpose, rather than using the
system Python environment. On Linux and Mac systems, the process is fairly
simple - adapt as required for your distribution:
@ -231,33 +204,6 @@ entries to make them available in preferences.
The name of the theme is derived from the directory name. Underscores (_) and
hyphens (-) will be replaced with spaces and the result will be camel cased.
Configuring the Runtime
-----------------------
The pgAdmin 4 Runtime maintains it's own Python Path to avoid conflicts with
packages or other issues in the system Python installation. It will also search
a number of known locations for the pgAdmin4.py file needed to run pgAdmin
(including relative locations in a source code tree), however you can specify
an alternate path if needed.
If either a working environment or pgAdmin4.py cannot be found at startup, the
runtime will prompt for the locations. Alternatively, you can click the try
icon and select the Configuration option to open the configuration dialogue.
On a Linux/Mac system, the Python Path will typically consist of a single path
to the virtual environment's site-packages directory, e.g.
/Users/<USERNAME>/.virtualenvs/pgadmin4/lib/python3.8/site-packages
On Windows, multiple paths are likely to be required, e.g.
C:\Users\dpage\.virtualenvs\pgadmin4\Lib\site-packages;C:\Users\dpage\.virtualenvs\pgadmin4\Lib;C:\Users\dpage\.virtualenvs\pgadmin4\Lib\lib-tk;C:\Users\dpage\.virtualenvs\pgadmin4\DLLs
If you wish to specify a specific copy of the Python code to run, you can set
the Application Path to a directory containing pgAdmin4.py, e.g.
/Users/<USERNAME>/git/pgadmin4-test/web/
Building the documentation
--------------------------

View File

@ -7,14 +7,14 @@
The bulk of pgAdmin is a Python web application written using the Flask framework
on the backend, and HTML5 with CSS3, Bootstrap and jQuery on the front end. A
desktop runtime is also included for users that prefer a desktop application to
a web application, which is written in C++ using the QT framework.
a web application, which is written using NWjs (Node Webkit).
Runtime
*******
The runtime is essentially a Python webserver and browser in a box. Found in the
**/runtime** directory in the source tree, it is a relatively simple QT
application that is most easily modified using the **QT Creator** application.
The runtime is based on NWjs which integrates a browser and the Python server
creating a standalone application. The source code can be found in the
**/runtime** directory in the source tree.
Web Application
***************
@ -213,4 +213,4 @@ divided each module in small chunks as much as possible. Not all javascript
modules are required to be loaded (i.e. loading a javascript module for
database will make sense only when a server node is loaded completely.) Please
look at the the javascript files node.js, browser.js, menu.js, panel.js, etc for
better understanding of the code.
better understanding of the code.

View File

@ -109,54 +109,6 @@ names.
.. note:: From version 3.0 onwards, new or refactored code should be written using
ES6 features and conventions.
C++
***
C++ code is used in the desktop runtime for the application, primarily with the
QT framework and an embedded Python interpreter. Note the use of hanging braces,
which may be omitted if on a single statement is present:
.. code-block:: c++
// Ping the application server to see if it's alive
bool PingServer(QUrl url)
{
QNetworkAccessManager manager;
QEventLoop loop;
QNetworkReply *reply;
QVariant redirectUrl;
url.setPath("/utils/ping");
do
{
reply = manager.get(QNetworkRequest(url));
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
url = redirectUrl.toUrl();
if (!redirectUrl.isNull())
delete reply;
} while (!redirectUrl.isNull());
if (reply->error() != QNetworkReply::NoError)
return false;
QString response = reply->readAll();
if (response != "PING")
{
qDebug() << "Failed to connect, server response: " << response;
return false;
}
return true;
}
Python
******

View File

@ -8,13 +8,8 @@ pgAdmin may be deployed as a desktop application by configuring the application
to run in desktop mode and then utilising the desktop runtime to host the
program on a supported Windows, Mac OS X or Linux installation.
The desktop runtime is a system-tray application that when launched, runs the
pgAdmin server and launches a web browser to render the user interface. If
additional instances of pgAdmin are launched, a new browser tab will be opened
and be served by the existing instance of the server in order to minimise system
resource utilisation. Clicking the icon in the system tray will present a menu
offering options to open a new pgAdmin window, configure the runtime, view the
server log and shut down the server.
The desktop runtime is a standalone application that when launched, runs the
pgAdmin server and opens a window to render the user interface.
.. note:: Pre-compiled and configured installation packages are available for
a number of platforms. These packages should be used by end-users whereever
@ -42,19 +37,26 @@ Runtime
*******
When executed, the runtime will automatically try to execute the pgAdmin Python
application. If execution fails, it will prompt you to adjust the Python Path
to include the directories containing the pgAdmin code as well as any additional
Python dependencies. You can enter a list of paths by separating them with a
semi-colon character, for example:
application. If execution fails, it will prompt you with error message
displaying a *Configure* button at the bottom. You can configure a fixed port
number to avoid clashes of the default random port number with other
applications and a connection timeout if desired.
.. code-block:: bash
If the error is related to Python Path or pgAdmin Python file then you need to
create a file named 'dev_config.json' and specify the following entries:
/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/;/Users/dpage/python-libs/
{
"pythonPath": <PATH OF THE PYTHON BINARY> For Example: "../../venv/bin/python3",
"pgadminFile": <PATH OF THE pgAdmin4.py> For Example: "../web/pgAdmin4.py"
}
The configuration settings are stored using the QSettings class in Qt, which
will use an INI file on Unix systems (~/.config/pgadmin/pgadmin4.conf),
a plist file on Mac OS X (~/Library/Preferences/org.pgadmin.pgadmin4.plist),
and the registry on Windows (HKEY_CURRENT_USER\\Software\\pgadmin\\pgadmin4).
Note that the dev_config.py file should only be required by developers who are
working outside of a standard installation.
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).
The configuration settings:
@ -65,18 +67,10 @@ The configuration settings:
+--------------------------+--------------------+---------------------------------------------------------------+
| Key | Type | Purpose |
+==========================+====================+===============================================================+
| ApplicationPath | String | The directory containing pgAdmin4.py |
+--------------------------+--------------------+---------------------------------------------------------------+
| BrowserCommand | String | An alternate command to run instead of the default browser. |
+--------------------------+--------------------+---------------------------------------------------------------+
| ConnectionTimeout | Integer | The number of seconds to wait for application server startup. |
+--------------------------+--------------------+---------------------------------------------------------------+
| FixedPort | Boolean | Use a fixed network port number rather than a random one. |
+--------------------------+--------------------+---------------------------------------------------------------+
| OpenTabAtStartup | Boolean | Open a browser tab at startup. |
+--------------------------+--------------------+---------------------------------------------------------------+
| PortNumber | Integer | The port number to use, if using a fixed port. |
+--------------------------+--------------------+---------------------------------------------------------------+
| PythonPath | String | The Python module search path |
| ConnectionTimeout | Integer | The number of seconds to wait for application server startup. |
+--------------------------+--------------------+---------------------------------------------------------------+

View File

@ -9,10 +9,12 @@ This release contains a number of bug fixes and new features since the release o
New features
************
| `Issue #5967 <https://redmine.postgresql.org/issues/5967>`_ - Implemented runtime using NWjs to open pgAdmin4 in a standalone window instead of the system tray and web browser.
Housekeeping
************
| `Issue #5017 <https://redmine.postgresql.org/issues/5017>`_ - Use cheroot as the default production server for pgAdmin4.
Bug fixes
*********
@ -25,8 +27,3 @@ Bug fixes
| `Issue #6177 <https://redmine.postgresql.org/issues/6177>`_ - Fixed an issue while downloading ERD images in Safari and Firefox.
| `Issue #6179 <https://redmine.postgresql.org/issues/6179>`_ - Fixed an issue where Generate SQL displayed twice in the ERD tool.
| `Issue #6180 <https://redmine.postgresql.org/issues/6180>`_ - Updated missing documentation for the 'Download Image' option in ERD.
. Documentation missing for 'Download Image' option in ERD. Fixes #6180.
2. Generate SQL displayed twice in ERD tool. Fixes #6179.
3. Zooming out too far makes the diagram vanish entirely. Fixes #6164.
4. Zoom to fit button only works if the diagram is larger than the canvas. Fixes #6163.

View File

@ -36,7 +36,7 @@ Package: ${APP_NAME}-server
Version: ${APP_LONG_VERSION}
Architecture: ${OS_ARCH}
Depends: python3, libpq5 (>= 11.0), libgssapi-krb5-2
Recommends: postgresql-client | postgresql-client-12 | postgresql-client-11 | postgresql-client-10
Recommends: postgresql-client | postgresql-client-13 | postgresql-client-12 | postgresql-client-11 | postgresql-client-10
Maintainer: pgAdmin Development Team <pgadmin-hackers@postgresql.org>
Description: The core server package for pgAdmin. 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.
EOF
@ -56,7 +56,7 @@ cat << EOF > "${DESKTOPROOT}/DEBIAN/control"
Package: ${APP_NAME}-desktop
Version: ${APP_LONG_VERSION}
Architecture: ${OS_ARCH}
Depends: ${APP_NAME}-server, libqt5gui5
Depends: ${APP_NAME}-server
Maintainer: pgAdmin Development Team <pgadmin-hackers@postgresql.org>
Description: The desktop user interface for pgAdmin. 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.
EOF

View File

@ -30,5 +30,5 @@ apt update
# Install pre-reqs
echo "Installing build pre-requisites..."
apt install -y build-essential python3-dev python3-venv python3-sphinx python3-wheel libpq-dev libffi-dev qtbase5-dev qt5-qmake nodejs yarn libkrb5-dev
apt install -y build-essential python3-dev python3-venv python3-sphinx python3-wheel libpq-dev libffi-dev nodejs yarn libkrb5-dev

View File

@ -53,7 +53,8 @@ _create_python_virtualenv() {
python3 -m venv venv
source venv/bin/activate
# Make sure we have the wheel package present
# Make sure we have the wheel package present, as well as the latest pip
pip3 install --upgrade pip
pip3 install wheel
# Install the requirements
@ -63,7 +64,7 @@ _create_python_virtualenv() {
# Use "python3" here as we want the venv path
PYMODULES_PATH=$(python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")
DIR_PYMODULES_PATH=`dirname ${PYMODULES_PATH}`
# Use /usr/bin/python3 here as we want the system path
if [ $1 == "debian" ]; then
PYSYSLIB_PATH=$(/usr/bin/python3 -c "import sys; print('%s/lib/python%d.%.d' % (sys.prefix, sys.version_info.major, sys.version_info.minor))")
@ -105,23 +106,60 @@ _create_python_virtualenv() {
}
_build_runtime() {
echo "Building the desktop runtime..."
cd ${SOURCEDIR}/runtime
if [ -f Makefile ]; then
make clean
fi
if hash qmake-qt5 2>/dev/null; then
PGADMIN_PYTHON_DIR=/usr qmake-qt5
else
PGADMIN_PYTHON_DIR=/usr qmake
fi
make
echo "Assembling the desktop runtime..."
# Get a fresh copy of nwjs.
# NOTE: The nw download servers seem to be very unreliable, so at the moment we're using wget
# in a retry loop as Yarn/Npm don't seem to like that.
# YARN:
# yarn add --cwd "${BUILDROOT}" nw
# YARN END
# WGET:
NW_VERSION=$(yarn info nw | grep latest | awk -F "'" '{ print $2}')
pushd "${BUILDROOT}" > /dev/null
while true;do
wget https://dl.nwjs.io/v${NW_VERSION}/nwjs-v${NW_VERSION}-linux-x64.tar.gz && break
rm nwjs-v${NW_VERSION}-linux-x64.tar.gz
done
tar -zxvf nwjs-v${NW_VERSION}-linux-x64.tar.gz
popd > /dev/null
# WGET END
# Copy nwjs into the staging directory
mkdir -p "${DESKTOPROOT}/usr/${APP_NAME}/bin"
cp pgAdmin4 "${DESKTOPROOT}/usr/${APP_NAME}/bin/pgadmin4"
mkdir -p "${DESKTOPROOT}/usr/${APP_NAME}/share"
cp pgAdmin4.ico "${DESKTOPROOT}/usr/${APP_NAME}/share/pgadmin4.ico"
# YARN:
# cp -r "${BUILDROOT}/node_modules/nw/nwjs"/* "${DESKTOPROOT}/usr/${APP_NAME}/bin"
# YARN END
# WGET:
cp -r "${BUILDROOT}/nwjs-v${NW_VERSION}-linux-x64"/* "${DESKTOPROOT}/usr/${APP_NAME}/bin"
# WGET END
mv "${DESKTOPROOT}/usr/${APP_NAME}/bin/nw" "${DESKTOPROOT}/usr/${APP_NAME}/bin/${APP_NAME}"
cp -r "${SOURCEDIR}/runtime/assets" "${DESKTOPROOT}/usr/${APP_NAME}/bin/assets"
cp -r "${SOURCEDIR}/runtime/src" "${DESKTOPROOT}/usr/${APP_NAME}/bin/src"
cp "${SOURCEDIR}/runtime/package.json" "${DESKTOPROOT}/usr/${APP_NAME}/bin/"
yarn --cwd "${DESKTOPROOT}/usr/${APP_NAME}/bin" install --production=true
# Create the icon
mkdir -p "${DESKTOPROOT}/usr/share/icons/hicolor/128x128/apps/"
cp "${SOURCEDIR}/pkg/linux/pgadmin4-128x128.png" "${DESKTOPROOT}/usr/share/icons/hicolor/128x128/apps/${APP_NAME}.png"
mkdir -p "${DESKTOPROOT}/usr/share/icons/hicolor/64x64/apps/"
cp "${SOURCEDIR}/pkg/linux/pgadmin4-64x64.png" "${DESKTOPROOT}/usr/share/icons/hicolor/64x64/apps/${APP_NAME}.png"
mkdir -p "${DESKTOPROOT}/usr/share/icons/hicolor/48x48/apps/"
cp "${SOURCEDIR}/pkg/linux/pgadmin4-48x48.png" "${DESKTOPROOT}/usr/share/icons/hicolor/48x48/apps/${APP_NAME}.png"
mkdir -p "${DESKTOPROOT}/usr/share/icons/hicolor/32x32/apps/"
cp "${SOURCEDIR}/pkg/linux/pgadmin4-32x32.png" "${DESKTOPROOT}/usr/share/icons/hicolor/32x32/apps/${APP_NAME}.png"
mkdir -p "${DESKTOPROOT}/usr/share/icons/hicolor/16x16/apps/"
cp "${SOURCEDIR}/pkg/linux/pgadmin4-16x16.png" "${DESKTOPROOT}/usr/share/icons/hicolor/16x16/apps/${APP_NAME}.png"
mkdir -p "${DESKTOPROOT}/usr/share/applications"
cp ../pkg/linux/pgadmin4.desktop "${DESKTOPROOT}/usr/share/applications"
cp "${SOURCEDIR}/pkg/linux/pgadmin4.desktop" "${DESKTOPROOT}/usr/share/applications"
}
_build_docs() {

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -2,7 +2,7 @@
Encoding=UTF-8
Name=pgAdmin 4
Exec=/usr/pgadmin4/bin/pgadmin4
Icon=/usr/pgadmin4/share/pgadmin4.ico
Icon=pgadmin4
Type=Application
Categories=Application;Development;
MimeType=text/html

2
pkg/mac/.gitignore vendored
View File

@ -1,4 +1,2 @@
# Global excludes across all subdirectories
codesign.conf
debug.pgadmin.Info.plist
pgadmin.Info.plist

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>__SHORT_VERSION__</string>
<key>CFBundleVersion</key>
<string>__FULL_VERSION__</string>
<key>CFBundleGetInfoString</key>
<string>Created by Qt/QMake</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleExecutable</key>
<string>__FRAMEWORK_NAME__</string>
<key>CFBundleIdentifier</key>
<string>org.pgadmin.__FRAMEWORK_NAME__</string>
<key>NOTE</key>
<string>Please, do NOT change this file -- It was generated by Qt/QMake.</string>
</dict>
</plist>

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>__SHORT_VERSION__</string>
<key>CFBundleVersion</key>
<string>__FULL_VERSION__</string>
<key>CFBundleGetInfoString</key>
<string>Created by Qt/QMake</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleExecutable</key>
<string>__FRAMEWORK_NAME__</string>
<key>CFBundleIdentifier</key>
<string>org.qt-project.Qt.__FRAMEWORK_NAME__</string>
<key>NOTE</key>
<string>Please, do NOT change this file -- It was generated by Qt/QMake.</string>
</dict>
</plist>

73
pkg/mac/Info.plist.in Normal file
View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>18G103</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>%APPNAME%</string>
<key>CFBundleExecutable</key>
<string>%APPNAME%</string>
<key>CFBundleIconFile</key>
<string>app.icns</string>
<key>CFBundleIdentifier</key>
<string>%APPID%</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>%APPNAME%</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>%APPVER%</string>
<key>CFBundleSignature</key>
<string>%APPNAME%</string>
<key>CFBundleVersion</key>
<string>4280.88</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTSDKBuild</key>
<string>10.15</string>
<key>DTSDKName</key>
<string>macosx10.15</string>
<key>DTXcode</key>
<string>1131</string>
<key>DTXcodeBuild</key>
<string>11C505</string>
<key>GPUEjectPolicy</key>
<string>wait</string>
<key>LSEnvironment</key>
<dict>
<key>MallocNanoZone</key>
<string>0</string>
<key>SYSTEM_VERSION_COMPAT</key>
<string>0</string>
</dict>
<key>LSFileQuarantineEnabled</key>
<true/>
<key>LSHasLocalizedDisplayName</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>10.10.0</string>
<key>NSAppleScriptEnabled</key>
<true/>
<key>NSPrincipalClass</key>
<string>BrowserCrApplication</string>
<key>NSRequiresAquaSystemAppearance</key>
<false/>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
<key>NSUserActivityTypes</key>
<array>
<string>NSUserActivityTypeBrowsingWeb</string>
</array>
<key>NSUserNotificationAlertStyle</key>
<string>none</string>
<key>OSAScriptingDefinition</key>
<string>scripting.sdef</string>
<key>SCMRevision</key>
<string>62f83a7521ae1f32e563795732dff0c9da1b660d-refs/heads/master@{#812354}</string>
</dict>
</plist>

View File

@ -1 +0,0 @@
APPL????

View File

@ -1,44 +1,31 @@
Building pgAdmin4.dmg on Mac OS X
=================================
Building pgAdmin4.dmg on macOS
==============================
Required Packages (Either build the sources or get them from macports or
similar):
1. Python installation
- Python 3.4+ or above from https://www.python.org/
1. Yarn & NodeJS
2. QT installation
- Qt 5 from http://www.qt.io/
3. PostgreSQL installation
- PostgreSQL 9.5 or above from http://www.postgresql.org/
2. PostgreSQL installation
- PostgreSQL 12 or above from http://www.postgresql.org/
Building:
1. If a value different from the default of /usr/local/python is required, set
the PGADMIN_PYTHON_DIR environment variable to the Python root installation
directory, e.g.
1. To bundle a different version of Python from the default of 3.9.0, set the
PGADMIN_PYTHON_VERSION environment variable, e.g:
export PGADMIN_PYTHON_DIR=/opt/local
export PGADMIN_PYTHON_VERSION=3.8.5
2. If a value different from the default of ~/Qt/5.13.2/clang_64, is required,
set the PGADMIN_QT_DIR environment variable to the QT root installation
directory, e.g.
export PGADMIN_QT_DIR=~/Qt/5.14.2/clang_64
3. If a value different from the default of /usr/local/pgsql is required, set
the PGADMIN_POSTGRES_DIR environment variable to the PostgreSQL installation
directory, e.g.
2. If a path different from the default of /usr/local/pgsql for the PostgreSQL
installation has been used, set the PGADMIN_POSTGRES_DIR environment variable
appropriately, e.g:
export PGADMIN_POSTGRES_DIR=/opt/local/pgsql
4. Copy framework.conf.in to framework.conf, and edit the values accordingly.
5. If you want to codesign the appbundle, copy codesign.conf.in to
3. If you want to codesign the appbundle, copy codesign.conf.in to
codesign.conf and set the values accordingly.
6. To build, go to pgAdmin4 source root directory and execute "make appbundle".
4. To build, go to pgAdmin4 source root directory and execute "make appbundle".
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

View File

@ -13,58 +13,160 @@ _setup_env() {
_cleanup() {
echo Cleaning up the old environment and app bundle...
rm -rf ${SOURCE_DIR}/runtime/*.app
rm -rf ${BUILD_ROOT}
rm -rf "${BUILD_ROOT}"
rm -rf "${TEMP_DIR}"
rm -f ${DIST_ROOT}/*.dmg
}
_create_venv() {
_build_runtime() {
echo "Assembling the runtime environment..."
test -d "${BUILD_ROOT}" || mkdir "${BUILD_ROOT}"
# Copy in the template application
cd "${BUILD_ROOT}"
yarn --cwd "${BUILD_ROOT}" add nw
cp -R "${BUILD_ROOT}/node_modules/nw/nwjs/nwjs.app" "${BUILD_ROOT}/"
mv "${BUILD_ROOT}/nwjs.app" "${BUNDLE_DIR}"
# Copy in the runtime code
mkdir "${BUNDLE_DIR}/Contents/Resources/app.nw/"
cp -R "${SOURCE_DIR}/runtime/assets" "${BUNDLE_DIR}/Contents/Resources/app.nw/"
cp -R "${SOURCE_DIR}/runtime/src" "${BUNDLE_DIR}/Contents/Resources/app.nw/"
cp "${SOURCE_DIR}/runtime/package.json" "${BUNDLE_DIR}/Contents/Resources/app.nw/"
# Install the runtime node_modules, then replace the package.json
yarn --cwd "${BUNDLE_DIR}/Contents/Resources/app.nw/" install --production=true
}
_create_python_env() {
echo "Creating the Python environment..."
PATH=${PGADMIN_POSTGRES_DIR}/bin:${PATH}
LD_LIBRARY_PATH=${PGADMIN_POSTGRES_DIR}/lib:${LD_LIBRARY_PATH}
test -d ${BUILD_ROOT} || mkdir ${BUILD_ROOT}
cd ${BUILD_ROOT}
git clone https://github.com/gregneagle/relocatable-python.git "${BUILD_ROOT}/relocatable_python"
PATH=$PATH:/usr/local/pgsql/bin "${BUILD_ROOT}/relocatable_python/make_relocatable_python_framework.py" --upgrade-pip --python-version ${PGADMIN_PYTHON_VERSION} --pip-requirements "${SOURCE_DIR}/requirements.txt" --destination "${BUNDLE_DIR}/Contents/Frameworks/"
${PYTHON_EXE} -m venv --copies venv
source venv/bin/activate
pip install --no-cache-dir --no-binary psycopg2 -r ${SOURCE_DIR}/requirements.txt
# Figure the source path for use when completing the venv
SOURCE_PYMODULES_PATH=$(dirname $("${PYTHON_EXE}" -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"))
# Figure the target path for use when completing the venv
# Use "python" here as we want the venv path
TARGET_PYMODULES_PATH=$(dirname $(python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"))
# Copy in the additional system python modules
cp -R ${SOURCE_PYMODULES_PATH}/* "${TARGET_PYMODULES_PATH}/"
# Link the python<version> directory to python so that the private environment path is found by the application.
ln -s "$(basename ${TARGET_PYMODULES_PATH})" "${TARGET_PYMODULES_PATH}/../python"
# Remove tests
find venv -name "test" -type d -print0 | xargs -0 rm -rf
find venv -name "tests" -type d -print0 | xargs -0 rm -rf
}
_build_runtime() {
cd ${SOURCE_DIR}/runtime
if [ -f Makefile ]; then
make clean
fi
${QMAKE}
make
cp -r pgAdmin4.app "${BUNDLE_DIR}"
# Remove some things we don't need
cd "${BUNDLE_DIR}/Contents/Frameworks/Python.framework"
find . -name test -type d -print0 | xargs -0 rm -rf
find . -name tkinter -type d -print0 | xargs -0 rm -rf
find . -name turtle.py -type f -print0 | xargs -0 rm -rf
find . -name turtledemo -type d -print0 | xargs -0 rm -rf
find . -name tcl* -type d -print0 | xargs -0 rm -rf
find . -name tk* -type d -print0 | xargs -0 rm -rf
find . -name tdbc* -type d -print0 | xargs -0 rm -rf
find . -name itcl* -type d -print0 | xargs -0 rm -rf
rm -f Versions/Current/lib/Tk.*
rm -f Versions/Current/lib/libtcl*.dylib
rm -f Versions/Current/lib/libtk*.dylib
rm -f Versions/Current/lib/tcl*.sh
rm -f Versions/Current/lib/tk*.sh
rm -rf Versions/Current/share
}
_build_docs() {
cd ${SOURCE_DIR}/docs/en_US
echo "Building the docs..."
# Create a temporary venv for the doc build, so we don't contaminate the one
# that we're going to ship.
"${BUNDLE_DIR}/Contents/Frameworks/Python.framework/Versions/Current/bin/python3" -m venv "${BUILD_ROOT}/venv"
source "${BUILD_ROOT}/venv/bin/activate"
pip3 install --upgrade pip
pip3 install -r "${SOURCE_DIR}/requirements.txt"
pip3 install sphinx
cd "${SOURCE_DIR}"
make docs
cd "${SOURCE_DIR}/docs/en_US"
test -d "${BUNDLE_DIR}/Contents/Resources/docs/en_US" || mkdir -p "${BUNDLE_DIR}/Contents/Resources/docs/en_US"
cp -r _build/html "${BUNDLE_DIR}/Contents/Resources/docs/en_US/"
# Remove some things we don't need
rm -rf "${BUNDLE_DIR}/Contents/Resources/docs/en_US/html/_sources"
rm -f "${BUNDLE_DIR}/Contents/Resources/docs/en_US/html/_static"/*.png
}
_fixup_imports() {
local TODO TODO_OLD FW_RELPATH LIB LIB_BN
echo "Fixing imports on the core appbundle..."
pushd "$1" > /dev/null
# Find all the files that may need tweaks
TODO=$(file `find . -perm +0111 -type f` | \
grep -v "Frameworks/Python.framework" | \
grep -v "Frameworks/nwjs" | \
grep -E "Mach-O 64-bit" | \
awk -F ':| ' '{ORS=" "; print $1}' | \
uniq)
# Add anything in the site-packages Python directory
TODO+=$(file `find ./Contents/Frameworks/Python.framework/Versions/Current/lib/python*/site-packages -perm +0111 -type f` | \
grep -E "Mach-O 64-bit" | \
awk -F ':| ' '{ORS=" "; print $1}' | \
uniq)
echo "Found executables: ${TODO}"
while test "${TODO}" != ""; do
TODO_OLD=${TODO} ;
TODO="" ;
for TODO_OBJ in ${TODO_OLD}; do
echo "Post-processing: ${TODO_OBJ}"
# Figure out the relative path from ${TODO_OBJ} to Contents/Frameworks
FW_RELPATH=$(echo "${TODO_OBJ}" | \
sed -n 's|^\(\.//*\)\(\([^/][^/]*/\)*\)[^/][^/]*$|\2|gp' | \
sed -n 's|[^/][^/]*/|../|gp' \
)"Contents/Frameworks"
# Find all libraries ${TODO_OBJ} depends on, but skip system libraries
for LIB in $(
otool -L ${TODO_OBJ} | \
sed -n 's|^.*[[:space:]]\([^[:space:]]*\.dylib\).*$|\1|p' | \
egrep -v '^(/usr/lib)|(/System)|@executable_path' \
); do
# Copy in any required dependencies
LIB_BN="$(basename "${LIB}")" ;
if ! test -f "Contents/Frameworks/${LIB_BN}"; then
TARGET_FILE=""
TARGET_PATH=""
echo "Adding symlink: ${LIB_BN} (because of: ${TODO_OBJ})"
cp -R "${LIB}" "Contents/Frameworks/${LIB_BN}"
if ! test -L "Contents/Frameworks/${LIB_BN}"; then
chmod 755 "Contents/Frameworks/${LIB_BN}"
else
TARGET_FILE=$(readlink "${LIB}")
TARGET_PATH=$(dirname "${LIB}")/${TARGET_FILE}
echo "Adding symlink target: ${TARGET_PATH}"
cp "${TARGET_PATH}" "Contents/Frameworks/${TARGET_FILE}"
chmod 755 "Contents/Frameworks/${TARGET_FILE}"
fi
echo "Rewriting ID in Contents/Frameworks/${LIB_BN} to ${LIB_BN}"
install_name_tool \
-id "${LIB_BN}" \
"Contents/Frameworks/${LIB_BN}" || exit 1
TODO="${TODO} ./Contents/Frameworks/${LIB_BN}"
fi
# Rewrite the dependency paths
echo "Rewriting library ${LIB} to @loader_path/${FW_RELPATH}/${LIB_BN} in ${TODO_OBJ}"
install_name_tool -change \
"${LIB}" \
"@loader_path/${FW_RELPATH}/${LIB_BN}" \
"${TODO_OBJ}" || exit 1
install_name_tool -change \
"${TARGET_PATH}" \
"@loader_path/${FW_RELPATH}/${TARGET_FILE}" \
"${TODO_OBJ}" || exit 1
done
done
done
echo "Imports updated on the core appbundle."
popd > /dev/null
}
_complete_bundle() {
echo "Completing the appbundle..."
cd ${SCRIPT_DIR}
# Copy the binary utilities into place
@ -73,129 +175,35 @@ _complete_bundle() {
cp "${PGADMIN_POSTGRES_DIR}/bin/pg_dumpall" "${BUNDLE_DIR}/Contents/SharedSupport/"
cp "${PGADMIN_POSTGRES_DIR}/bin/pg_restore" "${BUNDLE_DIR}/Contents/SharedSupport/"
cp "${PGADMIN_POSTGRES_DIR}/bin/psql" "${BUNDLE_DIR}/Contents/SharedSupport/"
# Replace the place holders with the current version
sed -e "s/PGADMIN_LONG_VERSION/${APP_LONG_VERSION}/g" -e "s/PGADMIN_SHORT_VERSION/${APP_SHORT_VERSION}/g" pgadmin.Info.plist.in > pgadmin.Info.plist
# copy Python private environment to app bundle
cp -PR ${BUILD_ROOT}/venv "${BUNDLE_DIR}/Contents/Resources/"
# Update the plist
cp Info.plist.in "${BUNDLE_DIR}/Contents/Info.plist"
sed -i '' "s/%APPNAME%/${APP_NAME}/g" "${BUNDLE_DIR}/Contents/Info.plist"
sed -i '' "s/%APPVER%/${APP_LONG_VERSION}/g" "${BUNDLE_DIR}/Contents/Info.plist"
sed -i '' "s/%APPID%/org.pgadmin.pgadmin4/g" "${BUNDLE_DIR}/Contents/Info.plist"
for FILE in "${BUNDLE_DIR}"/Contents/Resources/*.lproj/InfoPlist.strings; do
sed -i '' 's/CFBundleGetInfoString =.*/CFBundleGetInfoString = "Copyright (C) 2013 - 2021, The pgAdmin Development Team";/g' "${FILE}"
sed -i '' 's/NSHumanReadableCopyright =.*/NSHumanReadableCopyright = "Copyright (C) 2013 - 2021, The pgAdmin Development Team";/g' "${FILE}"
echo CFBundleDisplayName = \"${APP_NAME}\"\; >> "${FILE}"
done
# Remove any TCL-related files that may cause us problems
find "${BUNDLE_DIR}/Contents/Resources/venv/" -name "_tkinter*" -print0 | xargs -0 rm -f
# PkgInfo
echo APPLPGA4 > "${BUNDLE_DIR}/Contents/PkgInfo"
test -d "${BUNDLE_DIR}/Contents/Resources" || mkdir -p "${BUNDLE_DIR}/Contents/Resources"
# Create qt.conf so that app knows where the Plugins are present
cat >> "${BUNDLE_DIR}/Contents/Resources/qt.conf" << EOF
[Paths]
Plugins = PlugIns
EOF
# Icon
cp pgAdmin4.icns "${BUNDLE_DIR}/Contents/Resources/app.icns"
test -d "${BUNDLE_DIR}/Contents/Frameworks" || mkdir -p "${BUNDLE_DIR}/Contents/Frameworks"
test -d "${BUNDLE_DIR}/Contents/PlugIns/platforms" || mkdir -p "${BUNDLE_DIR}/Contents/PlugIns/platforms"
test -d "${BUNDLE_DIR}/Contents/PlugIns/imageformats" || mkdir -p "${BUNDLE_DIR}/Contents/PlugIns/imageformats"
cp -f ${PGADMIN_QT_DIR}/plugins/platforms/libqcocoa.dylib "${BUNDLE_DIR}/Contents/PlugIns/platforms"
cp -f ${PGADMIN_QT_DIR}/plugins/imageformats/libqsvg.dylib "${BUNDLE_DIR}/Contents/PlugIns/imageformats"
cp -f ${PGADMIN_POSTGRES_DIR}/lib/libpq.5.dylib "${BUNDLE_DIR}/Contents/Frameworks"
# Rename the executable
mv "${BUNDLE_DIR}/Contents/MacOS/nwjs" "${BUNDLE_DIR}/Contents/MacOS/${APP_NAME}"
local todo todo_old fw_relpath lib lib_bn
# Rename the app in package.json so the menu looks as it should
sed -i '' "s/\"name\": \"pgadmin4\"/\"name\": \"${APP_NAME}\"/g" "${BUNDLE_DIR}/Contents/Resources/app.nw/package.json"
pushd "${BUNDLE_DIR}" > /dev/null
# Import the dependencies, and rewrite any library references
_fixup_imports "${BUNDLE_DIR}"
# We skip nested apps here - those are treated specially
todo=$(file `find ./ -perm +0111 ! -type d ! -path "*.app/*" ! -name "*.app"` | grep -E "Mach-O 64-bit" | awk -F ':| ' '{ORS=" "; print $1}')
echo "Found executables: ${todo}"
while test "${todo}" != ""; do
todo_old=${todo} ;
todo="" ;
for todo_obj in ${todo_old}; do
echo "Post-processing: ${todo_obj}"
# Figure out the relative path from todo_obj to Contents/Frameworks
fw_relpath=$(echo "${todo_obj}" | sed -n 's|^\(\.//*\)\(\([^/][^/]*/\)*\)[^/][^/]*$|\2|gp' | sed -n 's|[^/][^/]*/|../|gp')"Contents/Frameworks"
fw_relpath_old=${fw_relpath}
# Find all libraries $todo_obj depends on, but skip system libraries
for lib in $(otool -L ${todo_obj} | grep "Qt\|dylib\|Frameworks\|PlugIns" | grep -v ":" | sed 's/(.*//' | egrep -v '(/usr/lib)|(/System)|@executable_path@'); do
if echo ${lib} | grep "PlugIns\|libqcocoa" > /dev/null; then
lib_loc="Contents/PlugIns/platforms"
elif echo ${lib} | grep "PlugIns\|libqsvg" > /dev/null; then
lib_loc="Contents/PlugIns/imageformats"
elif echo ${lib} | grep "Qt" > /dev/null; then
qtfw_path="$(dirname ${lib} | sed 's|.*\(Qt.*framework\)|\1|')"
lib_loc="Contents/Frameworks/${qtfw_path}"
if [ "$(basename ${todo_obj})" = "${lib}" ]; then
lib_loc="$(dirname ${todo_obj})"
qtfw_path=$(echo ${lib_loc} | sed 's/Contents\/Frameworks\///')
fi
elif echo ${lib} | grep "Python" > /dev/null; then
pyfw_path="$(dirname ${lib} | sed 's|.*\(Python.*framework\)|\1|')"
lib_loc="Contents/Frameworks/${pyfw_path}"
if [ "$(basename ${todo_obj})" = "${lib}" ]; then
lib_loc="$(dirname ${todo_obj})"
pyfw_path=$(echo ${lib_loc} | sed 's/Contents\/Frameworks\///')
fi
else
lib_loc="Contents/Frameworks"
fi
lib_bn="$(basename "${lib}")" ;
if ! test -f "${lib_loc}/${lib_bn}"; then
target_file=""
target_path=""
echo "Adding symlink: ${lib_bn} (because of: ${todo_obj})"
# Copy the QT and Python framework
if echo ${lib} | grep Qt > /dev/null ; then
test -d ${lib_loc} || mkdir -p ${lib_loc}
echo Copying -R ${PGADMIN_QT_DIR}/lib/${qtfw_path}/${lib_bn} to ${lib_loc}/
cp ${PGADMIN_QT_DIR}/lib/${qtfw_path}/${lib_bn} ${lib_loc}/
elif echo ${lib} | grep Python > /dev/null ; then
test -d ${lib_loc} || mkdir -p ${lib_loc}
cp -R "${lib}" "${lib_loc}/${lib_bn}"
else
cp -R "${lib}" "${lib_loc}/${lib_bn}"
fi
if ! test -L "${lib_loc}/${lib_bn}"; then
chmod 755 "${lib_loc}/${lib_bn}"
else
target_file=$(readlink "${lib}")
target_path=$(dirname "${lib}")/${target_file}
echo "Adding symlink target: ${target_path}"
cp "${target_path}" "${lib_loc}/${target_file}"
chmod 755 "${lib_loc}/${target_file}"
fi
echo "Rewriting ID in ${lib_loc}/${lib_bn} to ${lib_bn}"
install_name_tool -id "${lib_bn}" "${lib_loc}/${lib_bn}"
todo="${todo} ./${lib_loc}/${lib_bn}"
fi
if echo ${lib} | grep Qt > /dev/null ; then
fw_relpath="${fw_relpath}/${qtfw_path}"
fi
if echo ${lib} | grep Python > /dev/null ; then
fw_relpath="${fw_relpath}/${pyfw_path}"
fi
chmod +w ${todo_obj}
echo "Rewriting library ${lib} to @loader_path/${fw_relpath}/${lib_bn} in ${todo_obj}"
install_name_tool -change "${lib}" "@loader_path/${fw_relpath}/${lib_bn}" "${todo_obj}"
install_name_tool -change "${target_path}" "@loader_path/${fw_relpath}/${target_file}" "${todo_obj}"
fw_relpath="${fw_relpath_old}"
done
done
done
# Fix the rpaths for psycopg module
find "${BUNDLE_DIR}/Contents/Resources/venv/" -name _psycopg.so -print0 | xargs -0 install_name_tool -change libpq.5.dylib @loader_path/../../../../../../Frameworks/libpq.5.dylib
find "${BUNDLE_DIR}/Contents/Resources/venv/" -name _psycopg.so -print0 | xargs -0 install_name_tool -change libssl.1.0.0.dylib @loader_path/../../../../../../Frameworks/libssl.1.0.0.dylib
find "${BUNDLE_DIR}/Contents/Resources/venv/" -name _psycopg.so -print0 | xargs -0 install_name_tool -change libcrypto.1.0.0.dylib @loader_path/../../../../../../Frameworks/libcrypto.1.0.0.dylib
echo "App completed: ${BUNDLE_DIR}"
popd > /dev/null
pushd ${SOURCE_DIR}/web > /dev/null
# Build node modules
pushd "${SOURCE_DIR}/web" > /dev/null
yarn install
yarn run bundle
@ -203,7 +211,7 @@ EOF
popd > /dev/null
# copy the web directory to the bundle as it is required by runtime
cp -r ${SOURCE_DIR}/web "${BUNDLE_DIR}/Contents/Resources/"
cp -r "${SOURCE_DIR}/web" "${BUNDLE_DIR}/Contents/Resources/"
cd "${BUNDLE_DIR}/Contents/Resources/web"
rm -f pgadmin4.db config_local.*
rm -rf karma.conf.js package.json node_modules/ regression/ tools/ pgadmin/static/js/generated/.cache
@ -226,66 +234,6 @@ EOF
find "${BUNDLE_DIR}" -name "*.pyc" -print0 | xargs -0 rm -f
}
_framework_config() {
# Get the config
source ${SCRIPT_DIR}/framework.conf
echo Reorganising the framework structure...
# Create "Current" and "Current/Resources" inside each of the framework dirs
find "${BUNDLE_DIR}/Contents/Frameworks"/*framework -type d -name "Versions" | while read -r framework_dir; do
pushd "${framework_dir}" > /dev/null
# Create framework 'Current' soft link
VERSION_NUMBER=`ls -1`
ln -s ${VERSION_NUMBER} Current
# Create "Resources" subdirectory
if [ ! -d Current/Resources ]; then
mkdir Current/Resources
fi
popd > /dev/null
done
# Stuff for Qt framework files only
find "${BUNDLE_DIR}/Contents/Frameworks" -type d -name "Qt*framework" | while read -r framework_dir; do
pushd "${framework_dir}" > /dev/null
# Create soft link to the framework binary
ln -s Versions/Current/Qt*
# Create soft link to the framework Resources dir
ln -s Versions/Current/Resources
# Create the Info.plist files
MYNAME=`ls -1 Qt*`
if [ -f Resources/Info.plist ]; then
chmod +w Resources/Info.plist
fi
sed 's/__SHORT_VERSION__/${QT_SHORT_VERSION}/' "${SCRIPT_DIR}/Info.plist-template_Qt5" | sed 's/__FULL_VERSION__/${QT_FULL_VERSION}/' | sed "s/__FRAMEWORK_NAME__/${MYNAME}/" > "Resources/Info.plist"
popd > /dev/null
done
# Same thing, but specific to the Python framework dir
find "${BUNDLE_DIR}/Contents/Frameworks" -type d -name "P*framework" | while read -r framework_dir; do
pushd "${framework_dir}" > /dev/null
# Create soft link to the framework binary
ln -s Versions/Current/Py*
# Create soft link to the framework Resources dir
ln -s Versions/Current/Resources
# Create the Info.plist file
MYNAME=`ls -1 Py*`
sed 's/__SHORT_VERSION__/${PYTHON_SHORT_VERSION}/' "${SCRIPT_DIR}/Info.plist-template_Python" | sed 's/__FULL_VERSION__/${PYTHON_FULL_VERSION}/' | sed "s/__FRAMEWORK_NAME__/${MYNAME}/" > "Resources/Info.plist"
popd > /dev/null
done
}
_codesign_binaries() {
if [ ${CODESIGN} -eq 0 ]; then
return
@ -300,17 +248,22 @@ _codesign_binaries() {
echo "Developer Bundle Identifier not found in codesign.conf" >&2
fi
# Create the entitlements file
cp "${SCRIPT_DIR}/entitlements.plist.in" "${BUILD_ROOT}/entitlements.plist"
TEAM_ID=$(echo ${DEVELOPER_ID} | awk -F"[()]" '{print $2}')
sed -i '' "s/%TEAMID%/${TEAM_ID}/g" "${BUILD_ROOT}/entitlements.plist"
echo Signing ${BUNDLE_DIR} binaries...
IFS=$'\n'
for i in $(find "${BUNDLE_DIR}" -type f -perm +111 -exec file "{}" \; | grep -E "Mach-O executable|Mach-O 64-bit executable|Mach-O 64-bit bundle" | awk -F":| \\\(" '{print $1}' | uniq)
for i in $(find "${BUNDLE_DIR}" -type f -perm +111 -exec file "{}" \; | grep -v "(for architecture" | grep -E "Mach-O executable|Mach-O 64-bit executable|Mach-O 64-bit bundle" | awk -F":" '{print $1}' | uniq)
do
codesign --deep --force --verify --verbose --timestamp --options runtime -i "${DEVELOPER_BUNDLE_ID}" --sign "${DEVELOPER_ID}" "$i"
codesign --deep --force --verify --verbose --timestamp --options runtime --entitlements "${BUILD_ROOT}/entitlements.plist" -i "${DEVELOPER_BUNDLE_ID}" --sign "${DEVELOPER_ID}" "$i"
done
echo Signing ${BUNDLE_DIR} libraries...
for i in $(find "${BUNDLE_DIR}" -type f -name "*.dylib*")
do
codesign --deep --force --verify --verbose --timestamp --options runtime -i "${DEVELOPER_BUNDLE_ID}" --sign "${DEVELOPER_ID}" "$i"
codesign --deep --force --verify --verbose --timestamp --options runtime --entitlements "${BUILD_ROOT}/entitlements.plist" -i "${DEVELOPER_BUNDLE_ID}" --sign "${DEVELOPER_ID}" "$i"
done
}
@ -321,44 +274,32 @@ _codesign_bundle() {
# Sign the .app
echo Signing ${BUNDLE_DIR}...
codesign --deep --force --verify --verbose --timestamp --options runtime -i "${DEVELOPER_BUNDLE_ID}" --sign "${DEVELOPER_ID}" "${BUNDLE_DIR}"
# Verify it worked
echo Verifying the signature...
codesign --verify --verbose --deep --force "${BUNDLE_DIR}"
echo ${BUNDLE_DIR} successfully signed.
codesign --deep --force --verify --verbose --timestamp --options runtime --entitlements "${BUILD_ROOT}/entitlements.plist" -i "${DEVELOPER_BUNDLE_ID}" --sign "${DEVELOPER_ID}" "${BUNDLE_DIR}"
}
_create_dmg() {
# move to the directory where we want to create the DMG
test -d ${DIST_ROOT} || mkdir ${DIST_ROOT}
cd ${DIST_ROOT}
DMG_LICENCE=./../pkg/mac/licence.rtf
DMG_VOLUME_NAME=${APP_NAME}
DMG_NAME=`echo ${DMG_VOLUME_NAME} | sed 's/ //g' | awk '{print tolower($0)}'`
DMG_IMAGE=${DMG_NAME}-${APP_LONG_VERSION}.dmg
echo "Checking out create-dmg..."
git clone https://github.com/create-dmg/create-dmg.git "${BUILD_ROOT}/create-dmg"
DMG_DIR=./${DMG_IMAGE}.src
if test -e "${DMG_DIR}"; then
echo "Directory ${DMG_DIR} already exists. Please delete it manually." >&2
exit 1
fi
echo "Cleaning up"
rm -f "${DMG_IMAGE}"
mkdir "${DMG_DIR}"
echo "Copying data into temporary directory"
cp -R "${BUNDLE_DIR}" "${DMG_DIR}"
echo "Creating image"
hdiutil create -quiet -srcfolder "$DMG_DIR" -fs HFS+ -format UDZO -volname "${DMG_VOLUME_NAME}" -ov "${DMG_IMAGE}"
rm -rf "${DMG_DIR}"
echo Attaching License to image...
python ${SCRIPT_DIR}/dmg-license.py "${DMG_IMAGE}" "${DMG_LICENCE}" -c bz2
"${BUILD_ROOT}/create-dmg/create-dmg" \
--volname "${APP_NAME}" \
--volicon "${SCRIPT_DIR}/dmg-icon.icns" \
--eula "${SCRIPT_DIR}/licence.rtf" \
--background "${SCRIPT_DIR}/dmg-background.png" \
--app-drop-link 600 220 \
--icon "${APP_NAME}.app" 200 220 \
--window-pos 200 120 \
--window-size 800 400 \
--hide-extension "${APP_NAME}.app" \
--add-file .DS_Store "${SCRIPT_DIR}/dmg.DS_Store" 5 5 \
--format UDBZ \
--skip-jenkins \
--no-internet-enable \
"${DIST_ROOT}/$(echo ${APP_NAME} | sed 's/ //g' | awk '{print tolower($0)}')-${APP_LONG_VERSION}.dmg" \
"${BUNDLE_DIR}"
}
_codesign_dmg() {
@ -366,21 +307,7 @@ _codesign_dmg() {
return
fi
DMG_VOLUME_NAME=${APP_NAME}
DMG_NAME=`echo ${DMG_VOLUME_NAME} | sed 's/ //g' | awk '{print tolower($0)}'`
DMG_IMAGE=${DIST_ROOT}/${DMG_NAME}-${APP_LONG_VERSION}.dmg
if ! test -f "${DMG_IMAGE}" ; then
echo "${DMG_IMAGE} is no disk image!" >&2
exit 1
fi
# Sign the .app
echo Signing ${DMG_IMAGE}...
codesign --deep --force --verify --verbose --timestamp --options runtime -i "${DEVELOPER_BUNDLE_ID}" --sign "${DEVELOPER_ID}" "${DMG_IMAGE}"
# Verify it worked
echo Verifying the signature...
codesign --verify --verbose --force "${DMG_IMAGE}"
echo ${DMG_IMAGE} successfully signed.
}
echo Signing disk image...
codesign --force --verify --verbose --timestamp --options runtime -i "${DEVELOPER_BUNDLE_ID}" --sign "${DEVELOPER_ID}" "${DIST_ROOT}/$(echo ${APP_NAME} | sed 's/ //g' | awk '{print tolower($0)}')-${APP_LONG_VERSION}.dmg"
}

View File

@ -12,21 +12,14 @@ trap 'if [ $? -ne 0 ]; then echo "\"${last_command}\" command filed with exit co
SCRIPT_DIR=$(cd `dirname $0` && pwd)
SOURCE_DIR=$(realpath ${SCRIPT_DIR}/../..)
BUILD_ROOT=$(realpath ${SCRIPT_DIR}/../..)/mac-build
TEMP_DIR=$(realpath ${SCRIPT_DIR}/../..)/mac-temp
DIST_ROOT=$(realpath ${SCRIPT_DIR}/../..)/dist
if [ ! -f ${SCRIPT_DIR}/framework.conf ]; then
echo
echo "Error: pkg/mac/framework.conf not found!"
echo "Copy pkg/mac/framework.conf.in to pkg/mac/framework.conf and edit as required for the current system."
echo
exit 1
fi
CODESIGN=1
if [ ! -f ${SCRIPT_DIR}/codesign.conf ]; then
echo
echo "******************************************************************"
echo "* ${SCRIPT_DIR}/codesign.conf not found. NOT signing the binaries."
echo "* pkg/mac/codesign.conf not found. NOT signing the binaries."
echo "******************************************************************"
echo
CODESIGN=0
@ -35,50 +28,24 @@ else
source ${SCRIPT_DIR}/codesign.conf
fi
if [ "x${PGADMIN_PYTHON_DIR}" == "x" ]; then
echo "PGADMIN_PYTHON_DIR not set. Setting it to the default: /usr/local/python"
export PGADMIN_PYTHON_DIR=/usr/local/python
fi
PYTHON_EXE=${PGADMIN_PYTHON_DIR}/bin/python3
# Check if Python is working and calculate PYTHON_VERSION
if ${PYTHON_EXE} -V > /dev/null 2>&1; then
PYTHON_VERSION=`${PYTHON_EXE} -V 2>&1 | awk '{print $2}' | cut -d"." -f1-2 | sed 's/\.//'`
else
echo "Error: Python installation missing!"
exit 1
fi
if [ "${PYTHON_VERSION}" -gt "38" ] && [ "${PYTHON_VERSION}" -lt "34" ]; then
echo "Python version not supported."
exit 1
fi
if [ "x${PGADMIN_QT_DIR}" == "x" ]; then
echo "PGADMIN_QT_DIR not set. Setting it to the default: ~/Qt/5.13.2/clang_64"
export PGADMIN_QT_DIR=~/Qt/5.13.2/clang_64
fi
QMAKE=${PGADMIN_QT_DIR}/bin/qmake
if ! ${QMAKE} --version > /dev/null 2>&1; then
echo "Error: qmake not found. QT installation is not present or incomplete."
exit 1
fi
if [ "x${PGADMIN_POSTGRES_DIR}" == "x" ]; then
echo "PGADMIN_POSTGRES_DIR not set. Setting it to the default: /usr/local/pgsql"
export PGADMIN_POSTGRES_DIR=/usr/local/pgsql
fi
if [ "x${PGADMIN_PYTHON_VERSION}" == "x" ]; then
echo "PGADMIN_PYTHON_VERSION not set. Setting it to the default: 3.9.0"
export PGADMIN_PYTHON_VERSION=3.9.0
fi
source ${SCRIPT_DIR}/build-functions.sh
_setup_env
_cleanup
_create_venv
_build_runtime
_create_python_env
_build_docs
_complete_bundle
_framework_config
_codesign_binaries
_codesign_bundle
_create_dmg

BIN
pkg/mac/dmg-background.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 KiB

BIN
pkg/mac/dmg-icon.icns Normal file

Binary file not shown.

View File

@ -1,166 +0,0 @@
#! /usr/bin/env python
"""
This script adds a license file to a DMG. Requires Xcode and a plain ascii text
license file.
Obviously only runs on a Mac.
Copyright (C) 2011-2013 Jared Hobbs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import os
import sys
import tempfile
import optparse
class Path(str):
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
os.unlink(self)
def mktemp(dir=None, suffix=''):
(fd, filename) = tempfile.mkstemp(dir=dir, suffix=suffix)
os.close(fd)
return Path(filename)
def main(options, args):
dmg_file, license_file = args
with mktemp('.') as tmp_file:
with open(tmp_file, 'w') as f:
f.write("""data 'TMPL' (128, "LPic") {
$"1344 6566 6175 6C74 204C 616E 6775 6167"
$"6520 4944 4457 5244 0543 6F75 6E74 4F43"
$"4E54 042A 2A2A 2A4C 5354 430B 7379 7320"
$"6C61 6E67 2049 4444 5752 441E 6C6F 6361"
$"6C20 7265 7320 4944 2028 6F66 6673 6574"
$"2066 726F 6D20 3530 3030 4457 5244 1032"
$"2D62 7974 6520 6C61 6E67 7561 6765 3F44"
$"5752 4404 2A2A 2A2A 4C53 5445"
};
data 'LPic' (5000) {
$"0000 0002 0000 0000 0000 0000 0004 0000"
};
data 'STR#' (5000, "English buttons") {
$"0006 0D45 6E67 6C69 7368 2074 6573 7431"
$"0541 6772 6565 0844 6973 6167 7265 6505"
$"5072 696E 7407 5361 7665 2E2E 2E7A 4966"
$"2079 6F75 2061 6772 6565 2077 6974 6820"
$"7468 6520 7465 726D 7320 6F66 2074 6869"
$"7320 6C69 6365 6E73 652C 2063 6C69 636B"
$"2022 4167 7265 6522 2074 6F20 6163 6365"
$"7373 2074 6865 2073 6F66 7477 6172 652E"
$"2020 4966 2079 6F75 2064 6F20 6E6F 7420"
$"6167 7265 652C 2070 7265 7373 2022 4469"
$"7361 6772 6565 2E22"
};
data 'STR#' (5002, "English") {
$"0006 0745 6E67 6C69 7368 0541 6772 6565"
$"0844 6973 6167 7265 6505 5072 696E 7407"
$"5361 7665 2E2E 2E7B 4966 2079 6F75 2061"
$"6772 6565 2077 6974 6820 7468 6520 7465"
$"726D 7320 6F66 2074 6869 7320 6C69 6365"
$"6E73 652C 2070 7265 7373 2022 4167 7265"
$"6522 2074 6F20 696E 7374 616C 6C20 7468"
$"6520 736F 6674 7761 7265 2E20 2049 6620"
$"796F 7520 646F 206E 6F74 2061 6772 6565"
$"2C20 7072 6573 7320 2244 6973 6167 7265"
$"6522 2E"
};\n\n""")
with open(license_file, 'r') as lines:
kind = 'RTF ' if license_file.lower().endswith('.rtf') \
else 'TEXT'
f.write('data \'%s\' (5000, "English") {\n' % kind)
def escape(s):
return s.strip().replace('\\', '\\\\').replace('"', '\\"')
for line in lines:
if len(line) < 1000:
f.write(' "' + escape(line) + '\\n"\n')
else:
for liner in line.split('.'):
f.write(' "' + escape(liner) + '. \\n"\n')
f.write('};\n\n')
f.write("""data 'styl' (5000, "English") {
$"0003 0000 0000 000C 0009 0014 0000 0000"
$"0000 0000 0000 0000 0027 000C 0009 0014"
$"0100 0000 0000 0000 0000 0000 002A 000C"
$"0009 0014 0000 0000 0000 0000 0000"
};\n""")
os.system('hdiutil unflatten -quiet "%s"' % dmg_file)
ret = os.system('%s -a %s -o "%s"' %
(options.rez, tmp_file, dmg_file))
os.system('hdiutil flatten -quiet "%s"' % dmg_file)
if options.compression is not None:
os.system('cp %s %s.temp.dmg' % (dmg_file, dmg_file))
os.remove(dmg_file)
if options.compression == "bz2":
os.system('hdiutil convert %s.temp.dmg -format UDBZ -o %s' %
(dmg_file, dmg_file))
elif options.compression == "gz":
os.system('hdiutil convert %s.temp.dmg -format ' % dmg_file +
'UDZO -imagekey zlib-devel=9 -o %s' % dmg_file)
os.remove('%s.temp.dmg' % dmg_file)
if ret == 0:
print("Successfully added license to '%s'" % dmg_file)
else:
print("Failed to add license to '%s'" % dmg_file)
if __name__ == '__main__':
parser = optparse.OptionParser()
parser.set_usage("""%prog <dmgFile> <licenseFile> [OPTIONS]
This program adds a software license agreement to a DMG file.
It requires Xcode and either a plain ascii text <licenseFile>
or a <licenseFile.rtf> with the RTF contents.
See --help for more details.""")
parser.add_option(
'--rez',
'-r',
action='store',
default='/Applications/Xcode.app/Contents/Developer/Tools/Rez',
help='The path to the Rez tool. Defaults to %default'
)
parser.add_option(
'--compression',
'-c',
action='store',
choices=['bz2', 'gz'],
default=None,
help='Optionally compress dmg using specified compression type. '
'Choices are bz2 and gz.'
)
options, args = parser.parse_args()
cond = len(args) != 2
if not os.path.exists(options.rez):
print('Failed to find Rez at "%s"!\n' % options.rez)
cond = True
if cond:
parser.print_usage()
sys.exit(1)
main(options, args)

BIN
pkg/mac/dmg.DS_Store Normal file

Binary file not shown.

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<false/>
<key>com.apple.security.application-groups</key>
<string>%TEAMID%.org.pgadmin.pgadmin4</string>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
</dict>
</plist>

View File

@ -1,10 +0,0 @@
# Copy this file to framework.conf, and edit the values below to reflect your
# environment (versions of Python/Qt used)
PYTHON_SHORT_VERSION=3.8
PYTHON_FULL_VERSION=3.8.2
QT_SHORT_VERSION=5.14
QT_FULL_VERSION=5.14.2

BIN
pkg/mac/pgAdmin4.icns Normal file

Binary file not shown.

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>pgAdmin4</string>
<key>CFBundleGetInfoString</key>
<string>pgAdmin4 PGADMIN_LONG_VERSION</string>
<key>CFBundleIconFile</key>
<string>pgAdmin4.icns</string>
<key>CFBundleIdentifier</key>
<string>org.postgresql.pgadmin</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>PGADMIN_SHORT_VERSION</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>PGADMIN_LONG_VERSION</string>
<key>CSResourcesFileMapped</key>
<true/>
</dict>
</plist>

View File

@ -26,6 +26,12 @@ _build_runtime
_build_docs "redhat"
_copy_code
# Get an RPM-compatible version number
RPM_VERSION=${APP_RELEASE}.${APP_REVISION}
if [ ! -z ${APP_SUFFIX} ]; then
RPM_VERSION=${RPM_VERSION}_${APP_SUFFIX}
fi
#
# Server package
#
@ -46,7 +52,7 @@ cat << EOF > "${BUILDROOT}/server.spec"
%undefine __brp_ldconfig
Name: ${APP_NAME}-server
Version: ${APP_LONG_VERSION}
Version: ${RPM_VERSION}
Release: 1%{?dist}
Summary: The core server package for pgAdmin.
License: PostgreSQL
@ -84,12 +90,12 @@ cat << EOF > "${BUILDROOT}/desktop.spec"
%undefine __brp_ldconfig
Name: ${APP_NAME}-desktop
Version: ${APP_LONG_VERSION}
Version: ${RPM_VERSION}
Release: 1%{?dist}
Summary: The desktop user interface for pgAdmin.
License: PostgreSQL
URL: https://www.pgadmin.org/
Requires: ${APP_NAME}-server, qt5-qtbase, qt5-qtbase-gui
Requires: ${APP_NAME}-server, libatomic
%description
The desktop user interface for pgAdmin. 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.
@ -99,9 +105,16 @@ The desktop user interface for pgAdmin. pgAdmin is the most popular and feature
%install
cp -rfa %{pga_build_root}/desktop/* \${RPM_BUILD_ROOT}
%post
/bin/xdg-icon-resource forceupdate
%files
/usr/pgadmin4/bin/*
/usr/pgadmin4/share/*
/usr/share/icons/hicolor/128x128/apps/*
/usr/share/icons/hicolor/64x64/apps/*
/usr/share/icons/hicolor/48x48/apps/*
/usr/share/icons/hicolor/32x32/apps/*
/usr/share/icons/hicolor/16x16/apps/*
/usr/share/applications/*
EOF
@ -124,7 +137,7 @@ cat << EOF > "${BUILDROOT}/web.spec"
%undefine __brp_ldconfig
Name: ${APP_NAME}-web
Version: ${APP_LONG_VERSION}
Version: ${RPM_VERSION}
Release: 1%{?dist}
BuildArch: noarch
Summary: The web interface for pgAdmin, hosted under Apache HTTPD.
@ -171,7 +184,7 @@ cat << EOF > "${BUILDROOT}/meta.spec"
%undefine __brp_ldconfig
Name: ${APP_NAME}
Version: ${APP_LONG_VERSION}
Version: ${RPM_VERSION}
Release: 1%{?dist}
BuildArch: noarch
Summary: Installs all required components to run pgAdmin in desktop and web modes.
@ -209,8 +222,8 @@ yumdownloader --downloadonly --destdir=$DISTROOT postgresql13-libs
#
# Get the results!
#
cp ${HOME}/rpmbuild/RPMS/${OS_ARCH}/${APP_NAME}-*${APP_LONG_VERSION}-*.${OS_ARCH}.rpm "${DISTROOT}/"
cp ${HOME}/rpmbuild/RPMS/noarch/${APP_NAME}-*${APP_LONG_VERSION}-*.noarch.rpm "${DISTROOT}/"
cp ${HOME}/rpmbuild/RPMS/${OS_ARCH}/${APP_NAME}-*${RPM_VERSION}-*.${OS_ARCH}.rpm "${DISTROOT}/"
cp ${HOME}/rpmbuild/RPMS/noarch/${APP_NAME}-*${RPM_VERSION}-*.noarch.rpm "${DISTROOT}/"
if [ ${OS_VERSION} == 7 ]; then
cp ${HOME}/rpmbuild/RPMS/${OS_ARCH}/pgadmin4-python3-mod_wsgi-4.7.1-2.el7.x86_64.rpm "${DISTROOT}/"
fi

View File

@ -35,10 +35,10 @@ echo "Installing build pre-requisites..."
yum groupinstall -y "Development Tools"
if [ ${OS_VERSION} == 7 ]; then
yum install -y expect fakeroot httpd-devel qt5-qtbase-devel postgresql12-devel python3-devel nodejs yarn rpm-build rpm-sign yum-utils krb5-devel
yum install -y expect fakeroot httpd-devel postgresql12-devel python3-devel nodejs yarn rpm-build rpm-sign yum-utils krb5-devel
pip3 install sphinx
else
yum install -y expect fakeroot qt5-qtbase-devel postgresql12-devel python3-devel python3-sphinx nodejs yarn rpm-build rpm-sign yum-utils krb5-devel
yum install -y expect fakeroot postgresql12-devel python3-devel python3-sphinx nodejs yarn rpm-build rpm-sign yum-utils krb5-devel
fi
# Setup RPM macros for signing

View File

@ -5,38 +5,29 @@ a 32bit build.
Installing build requirements
=============================
1) Install Qt 5.14.2: https://www.qt.io/download-qt-installer
Use the MSVC++ 2017 64bit option.
2) Install Visual Studio 2017 Pro: https://my.visualstudio.com/Downloads?q=Visual%20Studio%202017
1) Install Visual Studio 2017 Pro: https://my.visualstudio.com/Downloads?q=Visual%20Studio%202017
Choose the Desktop development with C++ option.
3) Install Chocolatey: https://chocolatey.org/install#individual
2) Install Chocolatey: https://chocolatey.org/install#individual
4) Install various command line tools:
3) Install various command line tools:
choco install -y bzip2 cmake diffutils gawk gnuwin32-coreutils.install gzip git html-help-workshop innosetup nodejs-lts python sed strawberryperl wget yarn
choco install -y bzip2 cmake diffutils dotnet3.5 gzip git innosetup nodejs-lts python strawberryperl wget yarn
5) Upgrade pip (this may give a permissions error that can be ignored):
4) Upgrade pip (this may give a permissions error that can be ignored):
pip install --upgrade pip
6) Install virtualenv
5) Install virtualenv
pip install virtualenv
7) Add the following paths to the system PATH:
C:\Program Files (x86)\GnuWin32\bin
C:\Program Files (x86)\HTML Help Workshop
Building dependencies
=====================
The following steps should be run from a Visual Studio 2017 64bit command
prompt (except where noted).
prompt.
1) Create a directory for the dependencies:
@ -165,9 +156,9 @@ Studio 2017 64bit command prompt. Note that the examples shown below are the
defaults for the build system, so if they match your requirements you don't
need to set them:
SET "PGADMIN_POSTGRES_DIR=C:\Program Files\PostgreSQL\12"
SET "PGADMIN_PYTHON_DIR=C:\Python38"
SET "PGADMIN_QT_DIR=C:\Qt\5.14.2\msvc2017_64"
SET "PGADMIN_POSTGRES_DIR=C:\Program Files\PostgreSQL\13"
SET "PGADMIN_PYTHON_DIR=C:\Python39"
SET "PGADMIN_KRB5_DIR=C:\jenkins\build64\krb5"
SET "PGADMIN_INNOTOOL_DIR=C:\Program Files (x86)\Inno Setup 6"
SET "PGADMIN_SIGNTOOL_DIR=C:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x64"
SET "PGADMIN_VCREDIST_DIR=C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Redist\MSVC\14.16.27012"
@ -179,4 +170,4 @@ make
If you have a code signing certificate, this will automatically be used if
found in the Windows Certificate Store to sign the installer.
3) Find the completed installer in the dist/ subdirectory of your source tree.
3) Find the completed installer in the dist/ subdirectory of your source tree.

View File

@ -33,6 +33,7 @@ ChangesEnvironment=yes
;UninstallFilesDir={app}\{#MyAppVersion}
ArchitecturesInstallIn64BitMode={#MyAppArchitecturesMode}
AllowNoIcons=yes
WizardImageFile=sidebar.bmp
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
@ -288,7 +289,7 @@ begin
end;
// This function would be called during upgrade mode
// In upgrade mode - delete venv/* for example
// In upgrade mode - delete python/* for example
procedure DelFolder(Path: string);
var
FindRec: TFindRec;
@ -340,7 +341,7 @@ begin
if (IsUpgradeMode) then
begin
DelWebfolder(ExpandConstant('{app}\web'));
DelFolder(ExpandConstant('{app}\venv'));
DelFolder(ExpandConstant('{app}\python'));
end;
end;
end;

BIN
pkg/win32/sidebar.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 KiB

View File

@ -15,6 +15,7 @@
# ignored when building a PIP Wheel.
##############################################################################
blinker==1.4
cheroot==8.5.1
Flask==1.0.2
Werkzeug>=0.15.0
Flask-Gravatar==0.5.0
@ -38,8 +39,8 @@ psycopg2>=2.8
python-dateutil>=2.8.0
SQLAlchemy>=1.3.13
Flask-Security-Too>=3.0.0,<4.0.0
bcrypt<=3.1.7
cryptography<=3.0
bcrypt>=3.1.7
cryptography>=3.0
sshtunnel>=0.1.5
ldap3>=2.5.1
Flask-BabelEx>=0.9.4

6
runtime/.eslintignore Normal file
View File

@ -0,0 +1,6 @@
generated
node_modules
vendor
templates/
templates\
ycache

53
runtime/.eslintrc.js Normal file
View File

@ -0,0 +1,53 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
module.exports = {
'env': {
'browser': true,
'es6': true,
'amd': true,
'jasmine': true,
},
'extends': [
'eslint:recommended',
],
'parserOptions': {
'ecmaVersion': 2018,
"sourceType": "module",
},
'globals': {
'_': true,
'module': true,
'process': true,
'nw': true,
'platform': true
},
'rules': {
'indent': [
'error',
2
],
'linebreak-style': 0,
'quotes': [
'error',
'single'
],
'semi': [
'error',
'always'
],
'comma-dangle': [
'error',
'always-multiline'
],
'no-console': ["error", { allow: ["warn", "error"] }],
// We need to exclude below for RegEx case
"no-useless-escape": 0,
},
};

13
runtime/.gitignore vendored
View File

@ -1,12 +1 @@
.qmake.cache
.qmake.stash
Makefile
moc_*.cpp
moc_*.h
pgAdmin4
pgAdmin4.app/
pgAdmin4.pro.user*
qrc_breeze.cpp
qrc_pgAdmin4.cpp
ui_*.h
object_script.*
dev_config.json

View File

@ -1,136 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// ConfigWindow.h - Configuration window
//
//////////////////////////////////////////////////////////////////////////
#include "pgAdmin4.h"
#include "ConfigWindow.h"
#include "ui_ConfigWindow.h"
#include <QSettings>
#include <QTcpSocket>
#include <QtWidgets>
ConfigWindow::ConfigWindow(QWidget *parent) :
QDialog(parent)
{
initConfigWindow();
}
void ConfigWindow::initConfigWindow()
{
ui = new Ui::ConfigWindow;
ui->setupUi(this);
m_needRestart = false;
setConfigValues();
}
void ConfigWindow::setConfigValues()
{
QSettings settings;
ui->browserCommandLineEdit->setText(settings.value("BrowserCommand").toString());
if(settings.value("FixedPort").toBool())
{
ui->chkFixedPort->setCheckState(Qt::Checked);
ui->spinPortNumber->setEnabled(true);
}
else
{
ui->chkFixedPort->setCheckState(Qt::Unchecked);
ui->spinPortNumber->setEnabled(false);
}
ui->spinPortNumber->setValue(settings.value("PortNumber").toInt());
if (settings.value("OpenTabAtStartup", true).toBool())
{
ui->chkOpenTabAtStartup->setCheckState(Qt::Checked);
}
else
{
ui->chkOpenTabAtStartup->setCheckState(Qt::Unchecked);
}
ui->pythonPathLineEdit->setText(settings.value("PythonPath").toString());
ui->applicationPathLineEdit->setText(settings.value("ApplicationPath").toString());
}
void ConfigWindow::on_buttonBox_accepted()
{
QSettings settings;
// Save the settings, and return true if a restart is required, otherwise false.
QString browsercommand = ui->browserCommandLineEdit->text();
bool fixedport = ui->chkFixedPort->isChecked();
int portnumber = ui->spinPortNumber->value();
bool opentabatstartup = ui->chkOpenTabAtStartup->isChecked();
QString pythonpath = ui->pythonPathLineEdit->text();
QString applicationpath = ui->applicationPathLineEdit->text();
if (fixedport && (settings.value("FixedPort").toBool() != fixedport ||
settings.value("PortNumber").toInt() != portnumber) && isPortInUse(portnumber))
{
QString error = QString(QWidget::tr("The specified fixed port is already in use. Please provide any other valid port."));
QMessageBox::critical(Q_NULLPTR, QString(QWidget::tr("Fatal Error")), error);
}
else
{
m_needRestart = (settings.value("FixedPort").toBool() != fixedport ||
settings.value("PortNumber").toInt() != portnumber ||
settings.value("PythonPath").toString() != pythonpath ||
settings.value("ApplicationPath").toString() != applicationpath);
settings.setValue("BrowserCommand", browsercommand);
settings.setValue("FixedPort", fixedport);
settings.setValue("PortNumber", portnumber);
settings.setValue("OpenTabAtStartup", opentabatstartup);
settings.setValue("PythonPath", pythonpath);
settings.setValue("ApplicationPath", applicationpath);
settings.sync();
}
emit accepted(m_needRestart);
emit closing(true);
this->close();
}
void ConfigWindow::on_buttonBox_rejected()
{
emit closing(false);
this->close();
}
void ConfigWindow::on_chkFixedPort_stateChanged(int state)
{
if (state == Qt::Checked)
ui->spinPortNumber->setEnabled(true);
else
ui->spinPortNumber->setEnabled(false);
}
bool ConfigWindow::isPortInUse(const quint16 port) const
{
QTcpSocket socket;
// Bind the socket on the specified port.
socket.bind(port, QTcpSocket::DontShareAddress);
// Returns the host port number of the local socket if available; otherwise returns 0
quint16 tmpPort = socket.localPort();
if (tmpPort == 0)
return true;
return false;
}

View File

@ -1,46 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// ConfigWindow.h - Configuration window
//
//////////////////////////////////////////////////////////////////////////
#ifndef CONFIGWINDOW_H
#define CONFIGWINDOW_H
#include <QDialog>
namespace Ui {
class ConfigWindow;
}
class ConfigWindow : public QDialog
{
Q_OBJECT
public:
explicit ConfigWindow(QWidget *parent = Q_NULLPTR);
void setConfigValues();
signals:
void accepted(bool needRestart);
void closing(bool accepted);
private slots:
void on_buttonBox_accepted();
void on_buttonBox_rejected();
void on_chkFixedPort_stateChanged(int state);
private:
Ui::ConfigWindow *ui;
bool m_needRestart;
void initConfigWindow();
bool isPortInUse(const quint16 port) const;
};
#endif // CONFIGWINDOW_H

View File

@ -1,652 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigWindow</class>
<widget class="QDialog" name="ConfigWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>415</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>800</width>
<height>415</height>
</size>
</property>
<property name="windowTitle">
<string>pgAdmin 4 Configuration</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Runtime</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="sizeConstraint">
<enum>QLayout::SetMaximumSize</enum>
</property>
<item>
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Enter a command line to be used to start the browser. If blank, the system default browser will be used. %URL% will be replaced with the appropriate URL when executing the browser.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="margin">
<number>4</number>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetMaximumSize</enum>
</property>
<item>
<widget class="QLabel" name="pythonPathLabel_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>250</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Browser Command</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="browserCommandLineEdit">
<property name="placeholderText">
<string>/usr/bin/firefox %URL%</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>By default the runtime uses a random port number to ensure it can always run successfully. If you need to use a predictable port number, you can set one here. Note that if the port is already in use, the application will be unable to start.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label_5">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>250</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Fixed Port Number?</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="chkFixedPort">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_7">
<property name="text">
<string>Port number</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinPortNumber">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>5050</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_8">
<property name="text">
<string>By default, when the pgAdmin server is started a browser tab will be automatically opened to display the user interface. Un-check this option to run the server without automatically opening the browser.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_9">
<property name="minimumSize">
<size>
<width>250</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Open a Browser Window/Tab at Startup?</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="chkOpenTabAtStartup">
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string/>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Python</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>13</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>The options below are intended for expert users only, and may not behave as expected as they modify fixed search paths and are not alternate values. Modify with care!</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>17</width>
<height>13</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QLabel" name="pythonPathLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>250</width>
<height>0</height>
</size>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Python Path</string>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="margin">
<number>0</number>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_9">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QLineEdit" name="pythonPathLineEdit">
<property name="placeholderText">
<string>/usr/pgadmin4/lib/python3.8;/usr/pgadmin4/lib/python3.8/site-packages</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Enter a PYTHONPATH if desired. Path elements should be semi-colon delimited.</string>
</property>
<property name="textFormat">
<enum>Qt::AutoText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>13</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="applicationPathLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>250</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Application Path</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLineEdit" name="applicationPathLineEdit">
<property name="placeholderText">
<string>/usr/pgadmin4/web</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Enter the path to the directory containing pgAdmin.py if desired.</string>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_8">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>60</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfigWindow</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigWindow</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>chkFixedPort</sender>
<signal>stateChanged(int)</signal>
<receiver>ConfigWindow</receiver>
<slot>on_chkFixedPort_stateChanged(int)</slot>
<hints>
<hint type="sourcelabel">
<x>202</x>
<y>213</y>
</hint>
<hint type="destinationlabel">
<x>365</x>
<y>149</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>on_chkFixedPort_stateChanged(int)</slot>
</slots>
</ui>

View File

@ -1,147 +0,0 @@
////////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// FloatingWindow.cpp - For GNOME 3.26 and above floating window will be used.
//
////////////////////////////////////////////////////////////////////////////
#include "pgAdmin4.h"
#include "FloatingWindow.h"
#include "ui_FloatingWindow.h"
#include <QMenu>
#include <QMenuBar>
FloatingWindow::FloatingWindow(QWidget *parent) :
QMainWindow(parent)
{
}
bool FloatingWindow::Init()
{
ui = new Ui::FloatingWindow;
ui->setupUi(this);
// Creating Menu
createMenu();
// Setup the icon itself. For convenience, we'll also use it for the dialogue.
#ifdef Q_OS_MAC
QIcon icon(":pgAdmin4-mac.png");
#else
QIcon icon(":pgAdmin4.png");
#endif
setWindowIcon(icon);
setWindowTitle(tr("pgAdmin"));
setFixedSize(300, 230);
setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint);
return true;
}
// Create the menu
void FloatingWindow::createMenu()
{
createActions();
m_floatingWindowMenu = menuBar()->addMenu(tr("&pgAdmin 4"));
m_floatingWindowMenu->addAction(m_newAction);
m_floatingWindowMenu->addAction(m_copyUrlAction);
m_floatingWindowMenu->addSeparator();
m_floatingWindowMenu->addAction(m_configAction);
m_floatingWindowMenu->addAction(m_logAction);
m_floatingWindowMenu->addSeparator();
m_floatingWindowMenu->addAction(m_quitAction);
}
// Create the menu actions
void FloatingWindow::createActions()
{
m_newAction = new QAction(tr("&New pgAdmin 4 window..."), this);
m_newAction->setEnabled(false);
connect(m_newAction, SIGNAL(triggered()), m_menuActions, SLOT(onNew()));
m_copyUrlAction = new QAction(tr("&Copy server URL"), this);
m_copyUrlAction->setEnabled(false);
connect(m_copyUrlAction, SIGNAL(triggered()), m_menuActions, SLOT(onCopyUrl()));
m_configAction = new QAction(tr("C&onfigure..."), this);
m_configAction->setEnabled(false);
connect(m_configAction, SIGNAL(triggered()), m_menuActions, SLOT(onConfig()));
m_logAction = new QAction(tr("&View log..."), this);
m_logAction->setEnabled(false);
connect(m_logAction, SIGNAL(triggered()), m_menuActions, SLOT(onLog()));
m_quitAction = new QAction(tr("&Shut down server"), this);
m_quitAction->setEnabled(false);
connect(m_quitAction, SIGNAL(triggered()), m_menuActions, SLOT(onQuit()));
}
void FloatingWindow::enablePostStartOptions()
{
if (m_newAction != Q_NULLPTR)
m_newAction->setEnabled(true);
if (m_copyUrlAction != Q_NULLPTR)
m_copyUrlAction->setEnabled(true);
if (m_configAction != Q_NULLPTR)
m_configAction->setEnabled(true);
if (m_logAction != Q_NULLPTR)
m_logAction->setEnabled(true);
if (m_quitAction != Q_NULLPTR)
m_quitAction->setEnabled(true);
}
void FloatingWindow::setMenuActions(MenuActions * menuActions)
{
m_menuActions = menuActions;
}
// Enable the View Log option
void FloatingWindow::enableViewLogOption()
{
if (m_logAction != Q_NULLPTR)
m_logAction->setEnabled(true);
}
// Disable the View Log option
void FloatingWindow::disableViewLogOption()
{
if (m_logAction != Q_NULLPTR)
m_logAction->setEnabled(false);
}
// Enable the configure option
void FloatingWindow::enableConfigOption()
{
if (m_configAction != Q_NULLPTR)
m_configAction->setEnabled(true);
}
// Disable the configure option
void FloatingWindow::disableConfigOption()
{
if (m_configAction != Q_NULLPTR)
m_configAction->setEnabled(false);
}
void FloatingWindow::closeEvent(QCloseEvent * event)
{
// Emit the signal to shut down the python server.
emit shutdownSignal(m_menuActions->getAppServerUrl());
event->accept();
exit(0);
}

View File

@ -1,58 +0,0 @@
////////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// FloatingWindow.h - For GNOME 3.26 and above floating window will be used.
//
////////////////////////////////////////////////////////////////////////////
#ifndef FLOATINGWINDOW_H
#define FLOATINGWINDOW_H
#include "MenuActions.h"
#include <QMainWindow>
namespace Ui {
class FloatingWindow;
}
class FloatingWindow : public QMainWindow
{
Q_OBJECT
public:
explicit FloatingWindow(QWidget *parent = Q_NULLPTR);
bool Init();
void enablePostStartOptions();
void enableViewLogOption();
void disableViewLogOption();
void enableConfigOption();
void disableConfigOption();
void setMenuActions(MenuActions * menuActions);
private:
Ui::FloatingWindow *ui;
void createMenu();
void createActions();
void closeEvent(QCloseEvent * event);
QAction *m_newAction = Q_NULLPTR;
QAction *m_copyUrlAction = Q_NULLPTR;
QAction *m_configAction = Q_NULLPTR;
QAction *m_logAction = Q_NULLPTR;
QAction *m_quitAction = Q_NULLPTR;
QMenu *m_floatingWindowMenu = Q_NULLPTR;
MenuActions *m_menuActions = Q_NULLPTR;
signals:
void shutdownSignal(QUrl);
};
#endif // FLOATINGWINDOW_H

View File

@ -1,155 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FloatingWindow</class>
<widget class="QMainWindow" name="FloatingWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>300</width>
<height>230</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>pgAdmin 4</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QWidget" name="verticalLayoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>281</width>
<height>201</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="minimumSize">
<size>
<width>100</width>
<height>100</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>100</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">border-image:url(&quot;:pgAdmin4.png&quot;);</string>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Note: Installing a system tray plugin will prevent this window being shown.</string>
</property>
<property name="textFormat">
<enum>Qt::AutoText</enum>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
<plist version="0.9">
<dict>
<!-- start of standard entries -->
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>pgAdmin 4</string>
<key>CFBundleShortVersionString</key>
<string>4.30.0</string>
<key>CFBundleVersion</key>
<string>4.30.0</string>
<key>LSMinimumSystemVersion</key>
<string>10.10</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright (C) 2013 - 2021, The pgAdmin Development Team</string>
<key>CFBundleIconFile</key>
<string>@ICON@</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleGetInfoString</key>
<string>pgAdmin 4 - PostgreSQL Tools</string>
<key>CFBundleSignature</key>
<string>@TYPEINFO@</string>
<key>CFBundleExecutable</key>
<string>@EXECUTABLE@</string>
<key>CFBundleIdentifier</key>
<string>org.pgadmin.@EXECUTABLE@</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>LSUIElement</key>
<string>1</string>
</dict>
</plist>

View File

@ -1,103 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// LogWindow.cpp - Log viewer window
//
//////////////////////////////////////////////////////////////////////////
#include "pgAdmin4.h"
#include "LogWindow.h"
#include "ui_LogWindow.h"
#include <QStandardPaths>
#include <QTime>
#include <stdio.h>
LogWindow::LogWindow(QWidget *parent) :
QDialog(parent)
{
initLogWindow();
}
void LogWindow::initLogWindow()
{
ui = new Ui::LogWindow;
ui->setupUi(this);
}
void LogWindow::LoadLog()
{
int startupLines;
int serverLines;
ui->lblStatus->setText(tr("Loading logfiles..."));
ui->lblStartupLog->setText(tr("Startup Log (%1):").arg(g_startupLogFile));
ui->lblServerLog->setText(tr("Server Log (%1):").arg(g_serverLogFile));
startupLines = this->readLog(g_startupLogFile, ui->textStartupLog);
serverLines = this->readLog(g_serverLogFile, ui->textServerLog);
ui->lblStatus->setText(QString(tr("Loaded startup log (%1 lines) and server log (%2 lines).")).arg(startupLines).arg(serverLines));
}
void LogWindow::reload()
{
this->LoadLog();
}
// Read the logfile
int LogWindow::readLog(QString logFile, QPlainTextEdit *logWidget)
{
FILE *log;
char *buffer;
long len = 0;
int i;
int lines = 0;
// Look busy!
QApplication::setOverrideCursor(Qt::WaitCursor);
this->setDisabled(true);
QCoreApplication::processEvents( QEventLoop::AllEvents, 100 );
logWidget->clear();
// Attempt to open the file
log = fopen(logFile.toUtf8().data(), "r");
if (log == Q_NULLPTR)
{
logWidget->setPlainText(QString(tr("The log file (%1) could not be opened.")).arg(g_serverLogFile));
this->setDisabled(false);
QApplication::restoreOverrideCursor();
return 0;
}
// Get the file size, and read the data
fseek(log, 0, SEEK_END);
len = ftell(log);
rewind(log);
buffer = static_cast<char *>(malloc((len + 1) * sizeof(char)));
for (i = 0; i < len; i++) {
if (fread(buffer + i, 1, 1, log) > 0 && buffer[i] == '\n')
lines++;
}
buffer[i] = 0;
fclose(log);
logWidget->setPlainText(buffer);
// And... relax
this->setDisabled(false);
QApplication::restoreOverrideCursor();
return lines;
}

View File

@ -1,40 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// LogWindow.h - Log viewer window
//
//////////////////////////////////////////////////////////////////////////
#ifndef LOGWINDOW_H
#define LOGWINDOW_H
#include <QDialog>
#include <QPlainTextEdit>
namespace Ui {
class LogWindow;
}
class LogWindow : public QDialog
{
Q_OBJECT
public:
explicit LogWindow(QWidget *parent = Q_NULLPTR);
void LoadLog();
private slots:
void reload();
private:
Ui::LogWindow *ui;
void initLogWindow();
int readLog(QString logFile, QPlainTextEdit *logWidget);
};
#endif // LOGWINDOW_H

View File

@ -1,138 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LogWindow</class>
<widget class="QDialog" name="LogWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>500</height>
</rect>
</property>
<property name="windowTitle">
<string>pgAdmin 4 Log</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="lblStartupLog">
<property name="text">
<string>Startup Log:</string>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="textStartupLog">
<property name="font">
<font>
<family>Courier</family>
</font>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="plainText">
<string notr="true"/>
</property>
<property name="centerOnScroll">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblServerLog">
<property name="text">
<string>Server Log:</string>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="textServerLog">
<property name="font">
<font>
<family>Courier</family>
</font>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="plainText">
<string notr="true"/>
</property>
<property name="centerOnScroll">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="btnReload">
<property name="text">
<string>Reload</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblStatus">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnClose">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>btnReload</sender>
<signal>clicked()</signal>
<receiver>LogWindow</receiver>
<slot>reload()</slot>
<hints>
<hint type="sourcelabel">
<x>53</x>
<y>471</y>
</hint>
<hint type="destinationlabel">
<x>399</x>
<y>249</y>
</hint>
</hints>
</connection>
<connection>
<sender>btnClose</sender>
<signal>clicked()</signal>
<receiver>LogWindow</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>731</x>
<y>471</y>
</hint>
<hint type="destinationlabel">
<x>399</x>
<y>249</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>reload()</slot>
</slots>
</ui>

View File

@ -1,63 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// Logger.cpp - Logger Utility
//
//////////////////////////////////////////////////////////////////////////
#include "pgAdmin4.h"
#include "Logger.h"
#include <QDateTime>
#include <QTextStream>
#include <QStandardPaths>
Logger* Logger::m_pThis = Q_NULLPTR;
QFile* Logger::m_Logfile = Q_NULLPTR;
Logger::Logger()
{
}
Logger::~Logger()
{
}
Logger* Logger::GetLogger()
{
if (m_pThis == Q_NULLPTR)
{
m_pThis = new Logger();
m_Logfile = new QFile;
m_Logfile->setFileName(g_startupLogFile);
m_Logfile->open(QIODevice::WriteOnly | QIODevice::Text);
m_Logfile->setPermissions(QFile::ReadOwner|QFile::WriteOwner);
}
return m_pThis;
}
void Logger::Log(const QString& sMessage) const
{
QString text = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss: ") + sMessage + "\n";
if (m_Logfile != Q_NULLPTR)
{
QTextStream out(m_Logfile);
out << text;
}
}
void Logger::ReleaseLogger()
{
if (m_pThis != Q_NULLPTR)
{
if(m_Logfile != Q_NULLPTR)
m_Logfile->close();
delete m_pThis;
m_pThis = Q_NULLPTR;
}
}

View File

@ -1,33 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// Logger.h - Logger Utility
//
//////////////////////////////////////////////////////////////////////////
#ifndef LOGGER_H
#define LOGGER_H
#include <QObject>
#include <QFile>
class Logger : public QObject
{
public:
static Logger* GetLogger();
static void ReleaseLogger();
void Log(const QString& sMessage) const;
private:
Logger();
virtual ~Logger();
static Logger* m_pThis;
static QFile *m_Logfile;
};
#endif // LOGGER_H

View File

@ -1,118 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// MenuActions.cpp - Common file for menu actions.
//
//////////////////////////////////////////////////////////////////////////
#include "pgAdmin4.h"
#include "MenuActions.h"
#include <QApplication>
#include <QClipboard>
#include <QDesktopServices>
#include <QEventLoop>
#include <QMessageBox>
#include <QProcess>
#include <QSettings>
MenuActions::MenuActions()
{
}
void MenuActions::setAppServerUrl(QString appServerUrl)
{
m_appServerUrl = appServerUrl;
}
// Create a new application browser window on user request
void MenuActions::onNew() const
{
QSettings settings;
QString cmd = settings.value("BrowserCommand").toString();
if (!cmd.isEmpty())
{
cmd.replace("%URL%", m_appServerUrl);
QProcess::startDetached(cmd);
}
else
{
if (!QDesktopServices::openUrl(m_appServerUrl))
{
QString error(QWidget::tr("Failed to open the system default web browser. Is one installed?."));
QMessageBox::critical(Q_NULLPTR, QString(QWidget::tr("Fatal Error")), error);
exit(1);
}
}
}
// Copy the application server URL to the clipboard
void MenuActions::onCopyUrl() const
{
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(m_appServerUrl);
}
// Show the config dialogue
void MenuActions::onConfig()
{
if (!m_configWindow)
m_configWindow = new ConfigWindow();
m_configWindow->setConfigValues();
m_configWindow->show();
m_configWindow->raise();
m_configWindow->activateWindow();
connect(m_configWindow, SIGNAL(accepted(bool)), this, SLOT(onConfigDone(bool)));
}
void MenuActions::onConfigDone(bool needRestart) const
{
if (needRestart && QMessageBox::Yes == QMessageBox::question(Q_NULLPTR,
tr("Shut down server?"),
tr("The pgAdmin 4 server must be restarted for changes to take effect. Do you want to shut down the server now?"),
QMessageBox::Yes | QMessageBox::No))
{
exit(0);
}
}
// Show the log window
void MenuActions::onLog()
{
QSettings settings;
if (!m_logWindow)
m_logWindow = new LogWindow();
m_logWindow->show();
m_logWindow->raise();
m_logWindow->activateWindow();
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
m_logWindow->LoadLog();
}
// Exit
void MenuActions::onQuit()
{
if (QMessageBox::Yes == QMessageBox::question(Q_NULLPTR, tr("Shut down server?"), tr("Are you sure you want to shut down the pgAdmin 4 server?"), QMessageBox::Yes | QMessageBox::No))
{
// Emit the signal to shut down the python server.
emit shutdownSignal(m_appServerUrl);
QApplication::quit();
}
}

View File

@ -1,46 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// MenuActions.h - Common file for menu actions.
//
//////////////////////////////////////////////////////////////////////////
#ifndef MENUACTIONS_H
#define MENUACTIONS_H
#include "LogWindow.h"
#include "ConfigWindow.h"
class MenuActions: public QObject
{
Q_OBJECT
public:
MenuActions();
void setAppServerUrl(QString appServerUrl);
QString getAppServerUrl() const { return m_appServerUrl; }
private:
QString m_appServerUrl = "";
LogWindow *m_logWindow = Q_NULLPTR;
ConfigWindow *m_configWindow = Q_NULLPTR;
public slots:
void onConfigDone(bool needRestart) const;
protected slots:
void onNew() const;
void onCopyUrl() const;
void onConfig();
void onLog();
void onQuit();
signals:
void shutdownSignal(QUrl);
};
#endif // MENUACTIONS_H

View File

@ -1,660 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// Runtime.cpp - Core of the runtime
//
//////////////////////////////////////////////////////////////////////////
#include "pgAdmin4.h"
#include "Runtime.h"
#include "Server.h"
#include "TrayIcon.h"
#include "MenuActions.h"
#include "ConfigWindow.h"
#include "FloatingWindow.h"
#include "Logger.h"
#ifdef Q_OS_MAC
#include "macos.h"
#endif
// Must be before QT
#include <Python.h>
#include <QtWidgets>
#include <QNetworkProxyFactory>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QTime>
Runtime::Runtime()
{
}
bool Runtime::go(int argc, char *argv[])
{
// Before starting main application, need to set 'QT_X11_NO_MITSHM=1'
// to make the runtime work with IBM PPC machines.
#if defined (Q_OS_LINUX)
QByteArray val("1");
qputenv("QT_X11_NO_MITSHM", val);
#endif
// Create the QT application
QApplication app(argc, argv);
app.setQuitOnLastWindowClosed(false);
// Setup look n feel
setupStyling(&app);
// Setup the settings management
QCoreApplication::setOrganizationName("pgadmin");
QCoreApplication::setOrganizationDomain("pgadmin.org");
QCoreApplication::setApplicationName("pgadmin4");
QSettings settings;
// Interlock
if (alreadyRunning())
exit(0);
// Proxy config
configureProxy();
// Display the spash screen
m_splash = displaySplash(&app);
// Generate a random key to authenticate the client to the server
QString key = QUuid::createUuid().toString();
key = key.mid(1, key.length() - 2);
// Create Menu Actions
MenuActions *menuActions = new MenuActions();
// Create the control object (tray icon or floating window
m_splash->showMessage(QString(QWidget::tr("Checking for system tray...")), Qt::AlignBottom | Qt::AlignCenter);
if (QSystemTrayIcon::isSystemTrayAvailable())
m_trayIcon = createTrayIcon(menuActions);
else
m_floatingWindow = createFloatingWindow(menuActions);
// Fire up the app server
const Server *server = startServerLoop(key);
// Ensure we'll cleanup
QObject::connect(server, SIGNAL(finished()), server, SLOT(deleteLater()));
atexit(cleanup);
// Generate the app server URL
QString url = QString("http://127.0.0.1:%1/?key=%2").arg(m_port).arg(key);
Logger::GetLogger()->Log(QString(QWidget::tr("Application Server URL: %1")).arg(url));
// Check the server is running
checkServer(url);
// Stash the URL for any duplicate processes to open
createAddressFile(url);
// Go!
menuActions->setAppServerUrl(url);
// Enable the shutdown server menu as server started successfully.
if (m_trayIcon != Q_NULLPTR)
m_trayIcon->enablePostStartOptions();
if (m_floatingWindow != Q_NULLPTR)
m_floatingWindow->enablePostStartOptions();
// Open the browser if needed
if (settings.value("OpenTabAtStartup", true).toBool())
openBrowserTab(url);
// Make sure the server is shutdown if the server is quit by the user
QObject::connect(menuActions, SIGNAL(shutdownSignal(QUrl)), server, SLOT(shutdown(QUrl)));
// Final cleanup
m_splash->finish(Q_NULLPTR);
if (m_floatingWindow != Q_NULLPTR)
m_floatingWindow->show();
Logger::GetLogger()->Log("Everything works fine, successfully started pgAdmin4.");
Logger::ReleaseLogger();
return app.exec();
}
// Setup the styling
void Runtime::setupStyling(QApplication *app) const
{
// Setup the styling
#ifndef Q_OS_LINUX
QFile stylesheet;
#ifdef Q_OS_WIN32
QSettings registry("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", QSettings::Registry64Format);
if (!registry.value("AppsUseLightTheme", true).toBool())
{
qDebug( "Windows Dark Mode..." );
stylesheet.setFileName(":/qdarkstyle/style.qss");
stylesheet.open(QFile::ReadOnly | QFile::Text);
QTextStream stream(&stylesheet);
app->setStyleSheet(stream.readAll());
}
#endif
#ifdef Q_OS_MAC
if (IsDarkMode())
{
qDebug( "macOS Dark Mode...");
stylesheet.setFileName(":/qdarkstyle/style.qss");
stylesheet.open(QFile::ReadOnly | QFile::Text);
QTextStream stream(&stylesheet);
app->setStyleSheet(stream.readAll());
}
#endif
#endif
// Set high DPI pixmap to display icons clear on Qt widget.
QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
}
// Check if we're already running. If we are, open a new browser tab.
bool Runtime::alreadyRunning()
{
// Create a system-wide semaphore keyed by app name, exe hash and the username
// to ensure instances are unique to the user and path
QString userName = qgetenv("USER"); // *nix
if (userName.isEmpty())
userName = qgetenv("USERNAME"); // Windows
QString semaName = QString("pgadmin4-%1-%2-sema").arg(userName).arg(getExeHash());
QString shmemName = QString("pgadmin4-%1-%2-shmem").arg(userName).arg(getExeHash());
qDebug() << "Semaphore name:" << semaName;
qDebug() << "Shared memory segment name:" << shmemName;
QSystemSemaphore sema(semaName, 1);
sema.acquire();
#ifndef Q_OS_WIN32
// We may need to clean up stale shmem segments on *nix. Attaching and detaching
// should remove the segment if it is orphaned.
QSharedMemory stale_shmem(shmemName);
if (stale_shmem.attach())
stale_shmem.detach();
#endif
m_shmem = new QSharedMemory(shmemName);
bool is_running;
if (m_shmem->attach())
is_running = true;
else
{
m_shmem->create(1);
is_running = false;
}
sema.release();
if (is_running)
{
QFile addressFile(g_addressFile);
addressFile.open(QIODevice::ReadOnly | QIODevice::Text);
QTextStream in(&addressFile);
QString url = in.readLine();
qDebug() << "Already running. Opening browser tab to: " << url << "and exiting.";
openBrowserTab(url);
return true;
}
return false;
}
void Runtime::configureProxy() const
{
// In windows and linux, it is required to set application level proxy
// because socket bind logic to find free port gives socket creation error
// when system proxy is configured. We are also setting
// "setUseSystemConfiguration"=true to use the system proxy which will
// override this application level proxy. As this bug is fixed in Qt 5.9 so
// need to set application proxy for Qt version < 5.9.
//
#if defined (Q_OS_WIN) && QT_VERSION <= 0x050800
// Give dummy URL required to find proxy server configured in windows.
QNetworkProxyQuery proxyQuery(QUrl("https://www.pgadmin.org"));
QNetworkProxy l_proxy;
QList<QNetworkProxy> listOfProxies = QNetworkProxyFactory::systemProxyForQuery(proxyQuery);
if (listOfProxies.size())
{
l_proxy = listOfProxies[0];
// If host name is not empty means proxy server is configured.
if (!l_proxy.hostName().isEmpty()) {
QNetworkProxy::setApplicationProxy(QNetworkProxy());
}
}
#endif
#if defined (Q_OS_LINUX) && QT_VERSION <= 0x050800
QByteArray proxy_env;
proxy_env = qgetenv("http_proxy");
// If http_proxy environment is defined in linux then proxy server is configured.
if (!proxy_env.isEmpty()) {
QNetworkProxy::setApplicationProxy(QNetworkProxy());
}
#endif
}
// Display the splash screen
QSplashScreen * Runtime::displaySplash(QApplication *app)
{
QSplashScreen *splash = new QSplashScreen();
splash->setPixmap(QPixmap(":/splash.png"));
splash->setWindowFlags(splash->windowFlags() | Qt::WindowStaysOnTopHint);
splash->show();
app->processEvents(QEventLoop::AllEvents);
return splash;
}
// Get the port number we're going to use
quint16 Runtime::getPort() const
{
quint16 port = 0L;
QSettings settings;
if (settings.value("FixedPort", false).toBool())
{
// Use the fixed port number
port = settings.value("PortNumber", 5050).toUInt();
}
else
{
// Find an unused port number. Essentially, we're just reserving one
// here that Flask will use when we start up the server.
QTcpSocket socket;
#if QT_VERSION >= 0x050900
socket.setProxy(QNetworkProxy::NoProxy);
#endif
socket.bind(0, QTcpSocket::ShareAddress);
port = socket.localPort();
}
return port;
}
// Create a tray icon
TrayIcon * Runtime::createTrayIcon(MenuActions *menuActions)
{
TrayIcon *trayIcon = Q_NULLPTR;
m_splash->showMessage(QString(QWidget::tr("Checking for system tray...")), Qt::AlignBottom | Qt::AlignCenter);
Logger::GetLogger()->Log("Checking for system tray...");
// Start the tray service
trayIcon = new TrayIcon();
// Set the MenuActions object to connect to slot
if (trayIcon != Q_NULLPTR)
trayIcon->setMenuActions(menuActions);
trayIcon->Init();
return trayIcon;
}
// Create a floating window
FloatingWindow * Runtime::createFloatingWindow(MenuActions *menuActions)
{
FloatingWindow *floatingWindow = Q_NULLPTR;
m_splash->showMessage(QString(QWidget::tr("System tray not found, creating floating window...")), Qt::AlignBottom | Qt::AlignCenter);
Logger::GetLogger()->Log("System tray not found, creating floating window...");
floatingWindow = new FloatingWindow();
if (floatingWindow == Q_NULLPTR)
{
QString error = QString(QWidget::tr("Unable to initialize either a tray icon or floating window."));
QMessageBox::critical(Q_NULLPTR, QString(QWidget::tr("Fatal Error")), error);
Logger::GetLogger()->Log(error);
Logger::ReleaseLogger();
exit(1);
}
// Set the MenuActions object to connect to slot
floatingWindow->setMenuActions(menuActions);
floatingWindow->Init();
return floatingWindow;
}
void Runtime::openConfigureWindow(const QString errorMsg)
{
m_splash->finish(Q_NULLPTR);
qDebug() << errorMsg;
QMessageBox::critical(Q_NULLPTR, QString(QWidget::tr("Fatal Error")), errorMsg);
Logger::GetLogger()->Log(errorMsg);
// Allow the user to tweak the configuration if needed
m_configDone = false;
QSettings settings;
bool oldFixedPort = settings.value("FixedPort", false).toBool();
ConfigWindow *dlg = new ConfigWindow();
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->show();
dlg->raise();
dlg->activateWindow();
QObject::connect(dlg, SIGNAL(closing(bool)), this, SLOT(onConfigDone(bool)));
// Wait for configuration to be completed
while (!m_configDone)
delay(100);
// Read the value of port again if user has changed.
bool newFixedPort = settings.value("FixedPort", false).toBool();
quint16 newPort = settings.value("PortNumber").toUInt();
// User hasn't changed the value of fixed port check box
// only change the value of the port
if (oldFixedPort == newFixedPort && newFixedPort && m_port != newPort)
m_port = newPort;
// User has selected the fixed port and it's old value is random port,
// so port needs to be updated.
else if (oldFixedPort != newFixedPort && newFixedPort)
m_port = newPort;
// User has deselect the fixed port and it's old value is fixed port,
// so we will have to get the random port
else if (oldFixedPort != newFixedPort && !newFixedPort)
m_port = getPort();
}
// Server startup loop
Server * Runtime::startServerLoop(QString key)
{
bool done = false;
Server *server;
// Get the port number to use
m_port = getPort();
while (!done)
{
server = startServer(key);
if (server == NULL)
{
Logger::ReleaseLogger();
QApplication::quit();
}
// Check for server startup errors
if (server->isFinished() || server->getError().length() > 0)
{
QString error = QString(QWidget::tr("An error occurred initialising the pgAdmin 4 server:\n\n%1")).arg(server->getError());
delete server;
// Enable the View Log option for diagnostics
if (m_floatingWindow)
m_floatingWindow->enableViewLogOption();
if (m_trayIcon)
m_trayIcon->enableViewLogOption();
// Open the configuration window
openConfigureWindow(error);
// Disable the View Log option again
if (m_floatingWindow)
m_floatingWindow->disableViewLogOption();
if (m_trayIcon)
m_trayIcon->disableViewLogOption();
}
else
{
// Startup appears successful
done = true;
}
}
return server;
}
// Slot called when re-configuration is done.
void Runtime::onConfigDone(bool accepted)
{
if (accepted)
m_configDone = true;
else
exit(0);
}
// Start the server
Server * Runtime::startServer(QString key)
{
Server *server;
m_splash->showMessage(QString(QWidget::tr("Starting pgAdmin4 server...")), Qt::AlignBottom | Qt::AlignCenter);
Logger::GetLogger()->Log("Starting pgAdmin4 server...");
QString msg = QString(QWidget::tr("Creating server object, port:%1, key:%2, logfile:%3")).arg(m_port).arg(key).arg(g_serverLogFile);
Logger::GetLogger()->Log(msg);
server = new Server(this, m_port, key, g_serverLogFile);
Logger::GetLogger()->Log("Initializing server...");
if (!server->Init())
{
m_splash->finish(Q_NULLPTR);
qDebug() << server->getError();
QString error = QString(QWidget::tr("An error occurred initialising the pgAdmin 4 server:\n\n%1")).arg(server->getError());
QMessageBox::critical(Q_NULLPTR, QString(QWidget::tr("Fatal Error")), error);
Logger::GetLogger()->Log(error);
Logger::ReleaseLogger();
exit(1);
}
Logger::GetLogger()->Log("Server initialized, starting server thread...");
server->start();
// This is a hack to give the server a chance to start and potentially fail. As
// the Python interpreter is a synchronous call, we can't check for proper startup
// easily in a more robust way - we have to rely on a clean startup not returning.
// It should always fail pretty quickly, and take longer to start if it succeeds, so
// we don't really get a visible delay here.
delay(1000);
return server;
}
// Check the server is running properly
void Runtime::checkServer(QString url)
{
// Read the server connection timeout from the registry or set the default timeout.
QSettings settings;
int timeout = settings.value("ConnectionTimeout", 90).toInt();
// Now the server should be up, we'll attempt to connect and get a response.
// We'll retry in a loop a few time before aborting if necessary.
QTime endTime = QTime::currentTime().addSecs(timeout);
QTime midTime1 = QTime::currentTime().addSecs(timeout/3);
QTime midTime2 = QTime::currentTime().addSecs(timeout*2/3);
bool alive = false;
bool enableOptions = false;
Logger::GetLogger()->Log("The server should be up. Attempting to connect and get a response.");
while(QTime::currentTime() <= endTime)
{
alive = pingServer(QUrl(url));
if (alive)
{
break;
}
if(QTime::currentTime() >= midTime1)
{
if (m_floatingWindow && !enableOptions)
{
m_floatingWindow->enableViewLogOption();
m_floatingWindow->enableConfigOption();
enableOptions = true;
}
if (m_trayIcon && !enableOptions)
{
m_trayIcon->enableViewLogOption();
m_trayIcon->enableConfigOption();
enableOptions = true;
}
if(QTime::currentTime() < midTime2) {
m_splash->showMessage(QString(QWidget::tr("Taking longer than usual...")), Qt::AlignBottom | Qt::AlignCenter);
}
else
{
m_splash->showMessage(QString(QWidget::tr("Almost there...")), Qt::AlignBottom | Qt::AlignCenter);
}
}
delay(200);
}
// Attempt to connect one more time in case of a long network timeout while looping
Logger::GetLogger()->Log("Attempt to connect one more time in case of a long network timeout while looping");
if (!alive && !pingServer(QUrl(url)))
{
m_splash->finish(Q_NULLPTR);
QString error(QWidget::tr("The pgAdmin 4 server could not be contacted."));
QMessageBox::critical(Q_NULLPTR, QString(QWidget::tr("Fatal Error")), error);
Logger::ReleaseLogger();
exit(1);
}
}
// Create the address file
void Runtime::createAddressFile(QString url) const
{
QFile addressFile(g_addressFile);
if (addressFile.open(QIODevice::WriteOnly))
{
addressFile.setPermissions(QFile::ReadOwner|QFile::WriteOwner);
QTextStream out(&addressFile);
out << url << endl;
}
}
// Open a browser tab
void Runtime::openBrowserTab(QString url) const
{
QSettings settings;
QString cmd = settings.value("BrowserCommand").toString();
if (!cmd.isEmpty())
{
cmd.replace("%URL%", url);
QProcess::startDetached(cmd);
}
else
{
if (!QDesktopServices::openUrl(url))
{
QString error(QWidget::tr("Failed to open the system default web browser. Is one installed?."));
QMessageBox::critical(Q_NULLPTR, QString(QWidget::tr("Fatal Error")), error);
Logger::GetLogger()->Log(error);
Logger::ReleaseLogger();
exit(1);
}
}
}
// Make a request to the Python API server
QString Runtime::serverRequest(QUrl url, QString path)
{
QNetworkAccessManager manager;
QEventLoop loop;
QNetworkReply *reply;
QVariant redirectUrl;
url.setPath(path);
QString requestUrl = url.toString();
do
{
reply = manager.get(QNetworkRequest(url));
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
url = redirectUrl.toUrl();
if (!redirectUrl.isNull())
delete reply;
} while (!redirectUrl.isNull());
if (reply->error() != QNetworkReply::NoError)
{
qDebug() << "Failed to connect to the server:" << reply->errorString() << "- request URL:" << requestUrl << ".";
return QString();
}
QString response = reply->readAll();
qDebug() << "Server response:" << response << "- request URL:" << requestUrl << ".";
return response;
}
// Ping the application server to see if it's alive
bool Runtime::pingServer(QUrl url)
{
return serverRequest(url, "/misc/ping") == "PING";
}
// Shutdown the application server
bool Runtime::shutdownServer(QUrl url)
{
return serverRequest(url, "/misc/shutdown") == "SHUTDOWN";
}
void Runtime::delay(int milliseconds) const
{
QTime endTime = QTime::currentTime().addMSecs(milliseconds);
while(QTime::currentTime() < endTime)
{
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
}
}

View File

@ -1,69 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// Runtime.h - Core of the runtime
//
//////////////////////////////////////////////////////////////////////////
#ifndef RUNTIME_H
#define RUNTIME_H
// Include the Python header here as it needs to appear before any QT
// headers anywhere in the app.
#ifdef __MINGW32__
#include <cmath>
#endif
#include <Python.h>
#include "TrayIcon.h"
#include "MenuActions.h"
#include "FloatingWindow.h"
// QT headers
#include <QtWidgets>
class Server;
class Runtime: public QObject
{
Q_OBJECT
public:
Runtime();
bool alreadyRunning();
bool go(int argc, char *argv[]);
void delay(int milliseconds) const;
bool shutdownServer(QUrl url);
private:
QSharedMemory *m_shmem;
bool m_configDone;
FloatingWindow *m_floatingWindow = Q_NULLPTR;
TrayIcon *m_trayIcon = Q_NULLPTR;
QSplashScreen *m_splash = Q_NULLPTR;
quint16 m_port = 0;
void setupStyling(QApplication *app) const;
void configureProxy() const;
QSplashScreen *displaySplash(QApplication *app);
quint16 getPort() const;
TrayIcon *createTrayIcon(MenuActions *menuActions);
FloatingWindow *createFloatingWindow(MenuActions *menuActions);
Server *startServerLoop(QString key);
Server *startServer(QString key);
void checkServer(QString url);
void createAddressFile(QString url) const;
void openBrowserTab(QString url) const;
QString serverRequest(QUrl url, QString path);
bool pingServer(QUrl url);
void openConfigureWindow(const QString errorMsg);
private slots:
void onConfigDone(bool accepted);
};
#endif // RUNTIME_H

View File

@ -1,363 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// Server.cpp - Thread in which the web server will run.
//
//////////////////////////////////////////////////////////////////////////
#include "pgAdmin4.h"
// Must be before QT
#include <Python.h>
#include "Server.h"
#include "Logger.h"
// QT headers
#include <QCoreApplication>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QMessageBox>
#include <QSettings>
static void add_to_path(QString &python_path, QString path, bool prepend=false)
{
if (!python_path.contains(path))
{
if (!prepend)
{
#if defined(Q_OS_WIN)
if (!python_path.isEmpty() && !python_path.endsWith(";"))
python_path.append(";");
#else
if (!python_path.isEmpty() && !python_path.endsWith(":"))
python_path.append(":");
#endif
python_path.append(path);
}
else
{
#if defined(Q_OS_WIN)
if (!python_path.isEmpty() && !python_path.startsWith(";"))
python_path.prepend(";");
#else
if (!python_path.isEmpty() && !python_path.startsWith(":"))
python_path.prepend(":");
#endif
python_path.prepend(path);
}
}
}
Server::Server(Runtime *runtime, quint16 port, QString key, QString logFileName):
m_runtime(runtime),
m_port(port),
m_key(key),
m_logFileName(logFileName)
{
// Initialise Python
Py_NoSiteFlag=1;
Py_NoUserSiteDirectory=1;
Py_DontWriteBytecodeFlag=1;
// Python3 requires conversion of char * to wchar_t *, so...
const char *appName = QString("pgAdmin 4").toUtf8();
const size_t cSize = strlen(appName)+1;
m_wcAppName = new wchar_t[cSize];
mbstowcs (m_wcAppName, appName, cSize);
Py_SetProgramName(m_wcAppName);
// Setup the search path
QSettings settings;
QString python_path = settings.value("PythonPath").toString();
// Get the application directory
QString app_dir = QCoreApplication::applicationDirPath();
QString path_env = qgetenv("PATH");
QString pythonHome;
QStringList path_list;
int i;
#ifdef Q_OS_MAC
// In the case we're running in a release appbundle, we need to ensure the
// bundled virtual env is included in the Python path. We include it at the
// end, so expert users can override the path, but we do not save it, because
// if users move the app bundle, we'll end up with dead entries
// Build (and canonicalise) the virtual environment path
QFileInfo venvBinPath(app_dir + "/../Resources/venv/bin");
QFileInfo venvLibPath(app_dir + "/../Resources/venv/lib/python");
QFileInfo venvDynLibPath(app_dir + "/../Resources/venv/lib/python/lib-dynload");
QFileInfo venvSitePackagesPath(app_dir + "/../Resources/venv/lib/python/site-packages");
QFileInfo venvPath(app_dir + "/../Resources/venv");
// Prepend the bin directory to the path
add_to_path(path_env, venvBinPath.canonicalFilePath(), true);
// Append the path, if it's not already there
add_to_path(python_path, venvLibPath.canonicalFilePath());
add_to_path(python_path, venvDynLibPath.canonicalFilePath());
add_to_path(python_path, venvSitePackagesPath.canonicalFilePath());
add_to_path(pythonHome, venvPath.canonicalFilePath());
#elif defined(Q_OS_WIN)
// In the case we're running in a release application, we need to ensure the
// bundled virtual env is included in the Python path. We include it at the
// end, so expert users can override the path, but we do not save it.
// Build (and canonicalise) the virtual environment path
QFileInfo venvBinPath(app_dir + "/../venv");
QFileInfo venvLibPath(app_dir + "/../venv/Lib");
QFileInfo venvDLLsPath(app_dir + "/../venv/DLLs");
QFileInfo venvSitePackagesPath(app_dir + "/../venv/Lib/site-packages");
QFileInfo venvPath(app_dir + "/../venv");
// Prepend the bin directory to the path
add_to_path(path_env, venvBinPath.canonicalFilePath(), true);
// Append paths, if they're not already there
add_to_path(python_path, venvLibPath.canonicalFilePath());
add_to_path(python_path, venvDLLsPath.canonicalFilePath());
add_to_path(python_path, venvSitePackagesPath.canonicalFilePath());
add_to_path(pythonHome, venvPath.canonicalFilePath());
#else
// Build (and canonicalise) the virtual environment path
QFileInfo venvBinPath(app_dir + "/../venv/bin");
QFileInfo venvLibPath(app_dir + "/../venv/lib/python");
QFileInfo venvDynLibPath(app_dir + "/../venv/lib/python/lib-dynload");
QFileInfo venvSitePackagesPath(app_dir + "/../venv/lib/python/site-packages");
QFileInfo venvPath(app_dir + "/../venv");
// Prepend the bin directory to the path
add_to_path(path_env, venvBinPath.canonicalFilePath(), true);
// Append the path, if it's not already there
add_to_path(python_path, venvLibPath.canonicalFilePath());
add_to_path(python_path, venvDynLibPath.canonicalFilePath());
add_to_path(python_path, venvSitePackagesPath.canonicalFilePath());
add_to_path(pythonHome, venvPath.canonicalFilePath());
#endif
qputenv("PATH", path_env.toUtf8().data());
if (python_path.length() > 0)
{
// Split the path setting into individual entries
path_list = python_path.split(";", QString::SkipEmptyParts);
python_path = QString();
// Add new additional path elements
for (i = path_list.size() - 1; i >= 0 ; --i)
{
python_path.append(path_list.at(i));
if (i > 0)
{
#if defined(Q_OS_WIN)
python_path.append(";");
#else
python_path.append(":");
#endif
}
}
qputenv("PYTHONPATH", python_path.toUtf8().data());
}
qDebug() << "Python path: " << python_path
<< "\nPython Home: " << pythonHome;
Logger::GetLogger()->Log(QString("Python Path: %1").arg(python_path));
Logger::GetLogger()->Log(QString("Python Home: %1").arg(pythonHome));
if (!pythonHome.isEmpty())
{
const char *python_home = pythonHome.toUtf8().data();
const size_t home_size = strlen(python_home) + 1;
m_wcPythonHome = new wchar_t[home_size];
mbstowcs (m_wcPythonHome, python_home, home_size);
Py_SetPythonHome(m_wcPythonHome);
}
Logger::GetLogger()->Log("Initializing Python...");
Py_Initialize();
Logger::GetLogger()->Log("Python initialized.");
// Get the current path
PyObject* sysPath = PySys_GetObject(const_cast<char *>("path"));
if (sysPath != Q_NULLPTR)
{
// Add new additional path elements
Logger::GetLogger()->Log("Adding new additional path elements");
for (i = path_list.size() - 1; i >= 0 ; --i)
{
PyList_Append(sysPath, PyUnicode_DecodeFSDefault(path_list.at(i).toUtf8().data()));
}
}
else
Logger::GetLogger()->Log("Unable to get the current path.");
// Redirect stderr
Logger::GetLogger()->Log("Redirecting stderr...");
PyObject *sys = PyImport_ImportModule("sys");
if (sys != Q_NULLPTR)
{
PyObject *err = Q_NULLPTR;
FILE *log = Q_NULLPTR;
#if defined(Q_OS_WIN)
char *logFile = m_logFileName.toUtf8().data();
size_t fileSize = strlen(logFile) + 1;
wchar_t * wcLogFileName = new wchar_t[fileSize];
mbstowcs (wcLogFileName, logFile, fileSize);
log = _wfopen(wcLogFileName, (wchar_t *)"w");
#else
log = fopen(m_logFileName.toUtf8().data(), const_cast<char *>("w"));
#endif
if (log != Q_NULLPTR)
{
int fd = fileno(log);
err = PyFile_FromFd(fd, Q_NULLPTR, const_cast<char *>("w"), -1, Q_NULLPTR, Q_NULLPTR, Q_NULLPTR, 0);
}
else
Logger::GetLogger()->Log(QString("Failed to open log file: %1").arg(m_logFileName));
#if defined(Q_OS_WIN)
if (wcLogFileName != NULL)
{
delete wcLogFileName;
wcLogFileName = NULL;
}
#endif
QFile(m_logFileName).setPermissions(QFile::ReadOwner|QFile::WriteOwner);
if (err != Q_NULLPTR)
{
PyObject_SetAttrString(sys, "stderr", err);
Logger::GetLogger()->Log("stderr redirected successfully.");
}
else
Logger::GetLogger()->Log(QString("Failed to get the file pointer of: %1 ").arg(m_logFileName));
}
else
Logger::GetLogger()->Log("Failed to import 'sys' module.");
}
Server::~Server()
{
if (m_wcAppName)
delete m_wcAppName;
if (m_wcPythonHome)
delete m_wcPythonHome;
// Shutdown Python
Py_Finalize();
}
bool Server::Init()
{
QSettings settings;
// Find the webapp
QStringList paths;
paths.append("../web/"); // Linux source tree
paths.append("../../web/"); // Windows source tree
paths.append("../../../../web/"); // Mac source tree (in a dev env)
#ifdef Q_OS_MAC
paths.append("../Resources/web/"); // Mac source tree (in a release app bundle)
#endif
paths.append(settings.value("ApplicationPath").toString()); // System configured value
paths.append(""); // Should be last!
for (int i = 0; i < paths.size(); ++i)
{
QDir dir;
if (paths[i].startsWith('/'))
dir.setPath(paths[i]);
else
dir.setPath(QCoreApplication::applicationDirPath() + "/" + paths[i]);
m_appfile = dir.canonicalPath() + "/pgAdmin4.py";
if (QFile::exists(m_appfile))
{
qDebug() << "Webapp path: " << m_appfile;
Logger::GetLogger()->Log(QString("Webapp Path: %1").arg(m_appfile));
break;
}
}
if (!QFile::exists(m_appfile))
{
Logger::GetLogger()->Log("Failed to locate pgAdmin4.py, terminating server thread.");
setError(tr("Failed to locate pgAdmin4.py, terminating server thread."));
return false;
}
return true;
}
void Server::run()
{
// Open the application code and run it.
Logger::GetLogger()->Log("Open the application code and run it.");
FILE *cp = fopen(m_appfile.toUtf8().data(), "r");
if (!cp)
{
Logger::GetLogger()->Log(QString(tr("Failed to open the application file: %1, server thread exiting.")).arg(m_appfile));
setError(QString(tr("Failed to open the application file: %1, server thread exiting.")).arg(m_appfile));
return;
}
// Set the port number and key, and force SERVER_MODE off.
Logger::GetLogger()->Log("Set the port number, key and force SERVER_MODE off");
PyRun_SimpleString(QString("PGADMIN_INT_PORT = %1").arg(m_port).toLatin1());
PyRun_SimpleString(QString("PGADMIN_INT_KEY = '%1'").arg(m_key).toLatin1());
PyRun_SimpleString(QString("SERVER_MODE = False").toLatin1());
// Run the app!
QByteArray m_appfile_utf8 = m_appfile.toUtf8();
/*
* Untrusted search path vulnerability in the PySys_SetArgv API function in Python 2.6 and earlier, and possibly later
* versions, prepends an empty string to sys.path when the argv[0] argument does not contain a path separator,
* which might allow local users to execute arbitrary code via a Trojan horse Python file in the current working directory.
* Here we have to set arguments explicitly to python interpreter. Check more details in 'PySys_SetArgv' documentation.
*/
const char *appName = m_appfile_utf8.data();
const size_t cSize = strlen(appName)+1;
wchar_t* wcAppName = new wchar_t[cSize];
mbstowcs (wcAppName, appName, cSize);
wchar_t* n_argv[] = { wcAppName };
PySys_SetArgv(1, n_argv);
Logger::GetLogger()->Log("PyRun_SimpleFile launching application server...");
if (PyRun_SimpleFile(cp, m_appfile_utf8.data()) != 0)
{
Logger::GetLogger()->Log("Failed to launch the application server, server thread exiting.");
setError(tr("Failed to launch the application server, server thread exiting."));
}
fclose(cp);
}
void Server::shutdown(QUrl url)
{
if (!m_runtime->shutdownServer(url))
setError(tr("Failed to shut down application server thread."));
QThread::quit();
QThread::wait();
while(!this->isFinished())
{
Logger::GetLogger()->Log("Waiting for server to shut down.");
m_runtime->delay(250);
}
}

View File

@ -1,56 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// Server.h - Thread in which the web server will run.
//
//////////////////////////////////////////////////////////////////////////
#ifndef SERVER_H
#define SERVER_H
#include "Runtime.h"
#include <QThread>
#include <QUrl>
class Server : public QThread
{
Q_OBJECT
public:
Server(Runtime *runtime, quint16 port, QString key, QString logFileName);
~Server();
bool Init();
QString getError() const { return m_error; }
public slots:
void shutdown(QUrl url);
protected:
void run();
private:
void setError(QString error) { m_error = error; }
QString m_appfile;
QString m_error;
Runtime *m_runtime;
quint16 m_port;
QString m_key;
QString m_logFileName;
// Application name in UTF-8 for Python
wchar_t *m_wcAppName = Q_NULLPTR;
// PythonHome for Python
wchar_t *m_wcPythonHome = Q_NULLPTR;
};
#endif // SERVER_H

View File

@ -1,143 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// TrayIcon.cpp - Manages the tray icon
//
//////////////////////////////////////////////////////////////////////////
#include "pgAdmin4.h"
#include "TrayIcon.h"
#include <QMenu>
TrayIcon::TrayIcon()
{
}
void TrayIcon::Init()
{
createTrayIcon();
if (m_trayIcon)
m_trayIcon->show();
}
// Create the tray icon
void TrayIcon::createTrayIcon()
{
createActions();
if (m_trayIconMenu)
{
delete m_trayIconMenu;
m_trayIconMenu = Q_NULLPTR;
}
m_trayIconMenu = new QMenu(this);
m_trayIconMenu->addAction(m_newAction);
m_trayIconMenu->addAction(m_copyUrlAction);
m_trayIconMenu->addSeparator();
m_trayIconMenu->addAction(m_configAction);
m_trayIconMenu->addAction(m_logAction);
m_trayIconMenu->addSeparator();
m_trayIconMenu->addAction(m_quitAction);
if (!m_trayIcon)
m_trayIcon = new QSystemTrayIcon(this);
m_trayIcon->setContextMenu(m_trayIconMenu);
// Setup the icon itself. For convenience, we'll also use it for the dialogue.
#ifdef Q_OS_MAC
QIcon icon(":pgAdmin4-mac.png");
#else
QIcon icon(":pgAdmin4.png");
#endif
m_trayIcon->setIcon(icon);
setWindowIcon(icon);
}
// Create the menu actions
void TrayIcon::createActions()
{
m_newAction = new QAction(tr("&New pgAdmin 4 window..."), this);
m_newAction->setEnabled(false);
connect(m_newAction, SIGNAL(triggered()), m_menuActions, SLOT(onNew()));
m_copyUrlAction = new QAction(tr("&Copy server URL"), this);
m_copyUrlAction->setEnabled(false);
connect(m_copyUrlAction, SIGNAL(triggered()), m_menuActions, SLOT(onCopyUrl()));
m_configAction = new QAction(tr("&Configure..."), this);
m_configAction->setEnabled(false);
connect(m_configAction, SIGNAL(triggered()), m_menuActions, SLOT(onConfig()));
m_logAction = new QAction(tr("&View log..."), this);
m_logAction->setEnabled(false);
connect(m_logAction, SIGNAL(triggered()), m_menuActions, SLOT(onLog()));
m_quitAction = new QAction(tr("&Shut down server"), this);
m_quitAction->setEnabled(false);
connect(m_quitAction, SIGNAL(triggered()), m_menuActions, SLOT(onQuit()));
}
void TrayIcon::enablePostStartOptions()
{
if (m_newAction != Q_NULLPTR)
m_newAction->setEnabled(true);
if (m_copyUrlAction != Q_NULLPTR)
m_copyUrlAction->setEnabled(true);
if (m_configAction != Q_NULLPTR)
m_configAction->setEnabled(true);
if (m_logAction != Q_NULLPTR)
m_logAction->setEnabled(true);
if (m_quitAction != Q_NULLPTR)
m_quitAction->setEnabled(true);
}
// Enable the View Log option
void TrayIcon::enableViewLogOption()
{
if (m_logAction != Q_NULLPTR)
m_logAction->setEnabled(true);
}
// Disable the View Log option
void TrayIcon::disableViewLogOption()
{
if (m_logAction != Q_NULLPTR)
m_logAction->setEnabled(false);
}
// Enable the configure option
void TrayIcon::enableConfigOption()
{
if (m_configAction != Q_NULLPTR)
m_configAction->setEnabled(true);
}
// Disable the configure option
void TrayIcon::disableConfigOption()
{
if (m_configAction != Q_NULLPTR)
m_configAction->setEnabled(false);
}
void TrayIcon::setMenuActions(MenuActions * menuActions)
{
m_menuActions = menuActions;
}

View File

@ -1,51 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// TrayIcon.h - Manages the tray icon
//
//////////////////////////////////////////////////////////////////////////
#ifndef TRAYICON_H
#define TRAYICON_H
#include "MenuActions.h"
#include <QWidget>
#include <QSystemTrayIcon>
class TrayIcon : public QWidget
{
Q_OBJECT
public:
TrayIcon();
void Init();
void enablePostStartOptions();
void enableViewLogOption();
void disableViewLogOption();
void enableConfigOption();
void disableConfigOption();
void setMenuActions(MenuActions * menuActions);
private:
void createTrayIcon();
void createActions();
QAction *m_newAction = Q_NULLPTR;
QAction *m_copyUrlAction = Q_NULLPTR;
QAction *m_configAction = Q_NULLPTR;
QAction *m_logAction = Q_NULLPTR;
QAction *m_quitAction = Q_NULLPTR;
QSystemTrayIcon *m_trayIcon = Q_NULLPTR;
QMenu *m_trayIconMenu = Q_NULLPTR;
MenuActions *m_menuActions = Q_NULLPTR;
};
#endif // TRAYICON_H

View File

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

View File

View File

Some files were not shown because too many files have changed in this diff Show More