Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
173acd46a5 | ||
|
|
c39d257bf6 | ||
|
|
b3490ba3de | ||
|
|
4e5d8fe8e2 | ||
|
|
c4de5474a4 | ||
|
|
ebb5e3fe65 | ||
|
|
644624eabe | ||
|
|
9dbb74367d | ||
|
|
c07e52b9de | ||
|
|
78d55c7cc0 | ||
|
|
81752bb998 | ||
|
|
fb4afce629 | ||
|
|
6e8ebbd375 | ||
|
|
5c0ea0c012 | ||
|
|
ce7679c4bd | ||
|
|
b36d5d153b | ||
|
|
9e65c971a3 | ||
|
|
71919a4ce9 | ||
|
|
402dfd07db | ||
|
|
bf548dda3f | ||
|
|
2894c6cd14 | ||
|
|
3b69f92d70 | ||
|
|
82bd94e696 | ||
|
|
04328b76ba | ||
|
|
08b78fb6a1 | ||
|
|
23364464c2 | ||
|
|
d774a0ff67 | ||
|
|
5437a8adab | ||
|
|
6d52f2b911 | ||
|
|
2ca11c84e8 | ||
|
|
f561c0cee6 | ||
|
|
321b445a7e | ||
|
|
7ff85903e4 | ||
|
|
8b9c50ee5c | ||
|
|
4b6e9b031c | ||
|
|
e691885881 | ||
|
|
caf181d5e3 | ||
|
|
d90136d93d | ||
|
|
9a00a8a60b | ||
|
|
a588c2994d | ||
|
|
13704a4f34 | ||
|
|
912db8913f | ||
|
|
969a2961fe | ||
|
|
6b80523872 | ||
|
|
c16c593322 | ||
|
|
6a7339bbe4 | ||
|
|
41a20e0252 | ||
|
|
2dd075161d | ||
|
|
91075cc3f5 | ||
|
|
26c7d76d4f | ||
|
|
2185ea302d | ||
|
|
32defc91cc | ||
|
|
37bde4aaf4 | ||
|
|
1f76d207f2 | ||
|
|
8eab534c95 | ||
|
|
0eb3008426 | ||
|
|
dd187b9122 | ||
|
|
15252b0a26 | ||
|
|
aa95ff9f57 | ||
|
|
dad0d690db | ||
|
|
2fa849dc26 | ||
|
|
f340cd044b | ||
|
|
991f667f9f | ||
|
|
307637b969 | ||
|
|
91595593d3 | ||
|
|
12186a0fd6 | ||
|
|
9fdf68d559 | ||
|
|
8851cb0704 | ||
|
|
a936f708de | ||
|
|
dfa892d2a2 | ||
|
|
6f0eafb223 | ||
|
|
90a45557b9 | ||
|
|
2790d01c31 | ||
|
|
4d23bb24a5 | ||
|
|
361f7077fd | ||
|
|
dcd163ac02 | ||
|
|
ee37be56f9 | ||
|
|
f1ef7b0346 | ||
|
|
0ee50ac248 | ||
|
|
ea4dbd31e8 | ||
|
|
155768a2d7 | ||
|
|
da8da7bb49 | ||
|
|
5786c17569 | ||
|
|
941dea99f5 | ||
|
|
ab5f35da8d | ||
|
|
db4bb22cc6 | ||
|
|
2cd58efcdd | ||
|
|
ed8d1cde00 | ||
|
|
b7121bb2d2 | ||
|
|
3364350b01 | ||
|
|
4b421550d3 | ||
|
|
020d9bb018 | ||
|
|
eb189844e7 | ||
|
|
20c42d878b | ||
|
|
036bd66638 | ||
|
|
f02f91cbf1 | ||
|
|
34b59da526 | ||
|
|
277bf85645 | ||
|
|
991fe3d797 | ||
|
|
4009ac3108 | ||
|
|
92e6a47713 | ||
|
|
e53c2b1a5d | ||
|
|
e536408ce2 | ||
|
|
f74448a1b2 | ||
|
|
9b5a6e2bb7 | ||
|
|
3fef078ec8 | ||
|
|
74af8976c9 | ||
|
|
ecded18333 | ||
|
|
5452bff49e | ||
|
|
05ae330030 | ||
|
|
94e1820ccd | ||
|
|
49b318c39e | ||
|
|
4ac064de42 | ||
|
|
2ef71cf749 | ||
|
|
d96f453e7f | ||
|
|
0b47d1dbf6 | ||
|
|
d728ee7436 | ||
|
|
1a192109d6 | ||
|
|
024cfd2fab | ||
|
|
a25bb2b7db | ||
|
|
fd3337024f | ||
|
|
ded446fe8a | ||
|
|
a9d964b5ca | ||
|
|
9c3925e448 | ||
|
|
6016a5ee98 | ||
|
|
490999f528 | ||
|
|
ceb0e39e14 | ||
|
|
3bed332439 | ||
|
|
c21ea3c342 | ||
|
|
84ecffa4d9 | ||
|
|
9bff6c7157 | ||
|
|
5c4c09f237 | ||
|
|
f706f7ed0c | ||
|
|
fb12b2e2fc | ||
|
|
badbbade08 |
No files matched your search
@@ -31,8 +31,8 @@ bundle-dev:
|
||||
linter:
|
||||
cd web && yarn run linter
|
||||
|
||||
check: install-node bundle linter
|
||||
cd web && pycodestyle --config=.pycodestyle . && yarn run karma start --single-run && python regression/runtests.py
|
||||
check: install-node bundle linter check-pep8
|
||||
cd web && yarn run karma start --single-run && python regression/runtests.py
|
||||
|
||||
check-pep8:
|
||||
pycodestyle --config=.pycodestyle docs/
|
||||
@@ -43,6 +43,9 @@ check-pep8:
|
||||
check-python:
|
||||
cd web && python regression/runtests.py --exclude feature_tests
|
||||
|
||||
check-resql:
|
||||
cd web && python regression/runtests.py --pkg resql --exclude feature_tests
|
||||
|
||||
check-feature: install-node bundle
|
||||
cd web && python regression/runtests.py --pkg feature_tests
|
||||
|
||||
|
||||
@@ -33,8 +33,9 @@ Click the *Definition* tab to continue.
|
||||
Use the fields in the *Definition* tab to define the check constraint:
|
||||
|
||||
* Provide the expression that a row must satisfy in the *Check* field.
|
||||
* Move the *No Inherit?* switch to the *Yes* position to specify this constraint
|
||||
is automatically inherited by a table's children. The default is *No*.
|
||||
* Move the *No Inherit?* switch to the *Yes* position to specify that this
|
||||
constraint is not automatically inherited by a table's children. The default
|
||||
is *No*, meaning that the constraint will be inherited by any children.
|
||||
* Move the *Don't validate?* switch to the *No* position to skip validation of
|
||||
existing data; the constraint may not hold for all rows in the table. The
|
||||
default is *Yes*.
|
||||
|
||||
@@ -24,6 +24,13 @@ dialog, right-click on the *Servers* node of the tree control, and select
|
||||
|
||||
server_dialog
|
||||
|
||||
A master password is required to secure and later unlock saved server passwords.
|
||||
It is set by the user and can be disabled using config.
|
||||
|
||||
.. toctree::
|
||||
|
||||
master_password
|
||||
|
||||
After defining a server connection, right-click on the server name, and select
|
||||
*Connect to server* to authenticate with the server, and start using pgAdmin to
|
||||
manage objects that reside on the server.
|
||||
|
||||
@@ -88,7 +88,6 @@ instances:
|
||||
This is the working directory in which pgAdmin stores session data, user files,
|
||||
configuration files, and it's configuration database. Mapping this directory
|
||||
onto the host machine gives you an easy way to maintain configuration between
|
||||
|
||||
invocations of the container.
|
||||
|
||||
*/pgadmin4/config_local.py*
|
||||
@@ -101,7 +100,9 @@ required.
|
||||
|
||||
If this file is mapped, server definitions found in it will be loaded at launch
|
||||
time. This allows connection information to be pre-loaded into the instance of
|
||||
pgAdmin in the container.
|
||||
pgAdmin in the container. Note that server definitions are only loaded on first
|
||||
launch, i.e. when the configuration database is created, and not on subsequent
|
||||
launches using the same configuration database.
|
||||
|
||||
*/certs/server.cert*
|
||||
|
||||
|
||||
@@ -45,11 +45,15 @@ are as follows:
|
||||
|
||||
* ``config_distro.py``: This file is read after ``config.py`` and is intended for
|
||||
packagers to change any settings that are required for their pgAdmin distribution.
|
||||
This may typically include certain paths and file locations.
|
||||
This may typically include certain paths and file locations. This file is optional,
|
||||
and may be created by packagers in the same directory as ``config.py`` if
|
||||
needed.
|
||||
|
||||
* ``config_local.py``: This file is read after ``config_distro.py`` and is intended
|
||||
for end users to change any default or packaging specific settings that they may
|
||||
wish to adjust to meet local preferences or standards.
|
||||
wish to adjust to meet local preferences or standards.This file is optional,
|
||||
and may be created by users in the same directory as ``config.py`` if
|
||||
needed.
|
||||
|
||||
.. note:: If the SERVER_MODE setting is changed in ``config_distro.py`` or ``config_local.py``,
|
||||
you will most likely need to re-set the LOG_FILE, SQLITE_PATH, SESSION_DB_PATH
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 261 KiB After Width: | Height: | Size: 247 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 111 KiB |
@@ -73,49 +73,49 @@ When using the syntax-highlighting SQL editors, the following shortcuts are avai
|
||||
:class: longtable
|
||||
:widths: 2 2 3
|
||||
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Shortcut (Windows/Linux) | Shortcut (Mac) | Function |
|
||||
+==========================+==================+=====================================+
|
||||
| Alt+Left | Option+Left | Move to the beginning of the line |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Alt+Right | Option+Right | Move to the end of the line |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+Alt+Left | Cmd+Option+Left | Move left one word |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+Alt+Right | Cmd+Option+Right | Move right one word |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+/ | Cmd+/ | Comment selected code (Inline) |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+. | Cmd+. | Uncomment selected code (Inline) |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+Shift+/ | Cmd+Shift+/ | Comment/Uncomment code (Block) |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+A | Cmd+A | Select all |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+C | Cmd+C | Copy selected text to the clipboard |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+R | Cmd+R | Redo last edit un-done |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+V | Cmd+V | Paste text from the clipboard |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+Z | Cmd+Z | Undo last edit |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Tab | Tab | Indent selected text |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Shift+Tab | Shift+Tab | Un-indent selected text |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Alt+G | Alt+G | Jump (to line:column) |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+Space | Ctrl+Space | Auto-complete |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+F | Cmd+F | Find |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+G | Cmd+G | Find next |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+Shift+G | Cmd+Shift+G | Find previous |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
| Ctrl+Shift+F | Cmd+Shift+F | Replace |
|
||||
+--------------------------+------------------+-------------------------------------+
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Shortcut (Windows/Linux) | Shortcut (Mac) | Function |
|
||||
+==========================+======================+=====================================+
|
||||
| Alt + Left | Option + Left | Move to the beginning of the line |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Alt + Right | Option + Right | Move to the end of the line |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + Alt + Left | Cmd + Option + Left | Move left one word |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + Alt + Right | Cmd + Option + Right | Move right one word |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + / | Cmd + / | Comment selected code (Inline) |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + . | Cmd + . | Uncomment selected code (Inline) |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + Shift + / | Cmd + Shift + / | Comment/Uncomment code (Block) |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + a | Cmd + a | Select all |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + c | Cmd + c | Copy selected text to the clipboard |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + r | Cmd + r | Redo last edit un-done |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + v | Cmd + v | Paste text from the clipboard |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + z | Cmd + z | Undo last edit |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Tab | Tab | Indent selected text |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Shift + Tab | Shift + Tab | Un-indent selected text |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Alt + g | Option + g | Jump (to line:column) |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + Space | Ctrl + Space | Auto-complete |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + f | Cmd + f | Find |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + g | Cmd + g | Find next |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + Shift + g | Cmd + Shift + g | Find previous |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
| Ctrl + Shift + f | Cmd + Shift + f | Replace |
|
||||
+--------------------------+----------------------+-------------------------------------+
|
||||
|
||||
Query Tool
|
||||
**********
|
||||
@@ -133,7 +133,7 @@ When using the Query Tool, the following shortcuts are available:
|
||||
+--------------------------+--------------------+-----------------------------------+
|
||||
| F7 | F7 | EXPLAIN query |
|
||||
+--------------------------+--------------------+-----------------------------------+
|
||||
| Shift+F7 | Shift+F7 | EXPLAIN ANALYZE query |
|
||||
| Shift + F7 | Shift + F7 | EXPLAIN ANALYZE query |
|
||||
+--------------------------+--------------------+-----------------------------------+
|
||||
| F8 | F8 | Execute query to CSV file |
|
||||
+--------------------------+--------------------+-----------------------------------+
|
||||
@@ -190,11 +190,11 @@ When using the Debugger, the following shortcuts are available:
|
||||
+--------------------------+--------------------+-----------------------------------+
|
||||
| <accesskey> + s | <accesskey> + s | Stop |
|
||||
+--------------------------+--------------------+-----------------------------------+
|
||||
| Alt + Shift + q | Alt + Shift + q | Enter or Edit values in Grid |
|
||||
| Alt + Shift + q | Option + Shift + q | Enter or Edit values in Grid |
|
||||
+--------------------------+--------------------+-----------------------------------+
|
||||
|
||||
Inner Panel Navigation
|
||||
**********************
|
||||
Inner Tab and Panel Navigation
|
||||
******************************
|
||||
|
||||
When using the Query Tool and Debugger, the following shortcuts are available
|
||||
for inner panel navigation:
|
||||
@@ -203,13 +203,15 @@ for inner panel navigation:
|
||||
:class: longtable
|
||||
:widths: 2 2 3
|
||||
|
||||
+--------------------------+---------------------------+------------------------------+
|
||||
| Shortcut (Windows/Linux) | Shortcut (Mac) | Function |
|
||||
+==========================+===========================+==============================+
|
||||
| Alt + Shift + Right Arrow| Alt + Shift + Right Arrow | Move to next inner panel |
|
||||
+--------------------------+---------------------------+------------------------------+
|
||||
| Alt + Shift + Left Arrow | Alt + Shift + Left Arrow | Move to previous inner panel |
|
||||
+--------------------------+---------------------------+------------------------------+
|
||||
+--------------------------+---------------------------+------------------------------------+
|
||||
| Shortcut (Windows/Linux) | Shortcut (Mac) | Function |
|
||||
+==========================+===========================+====================================+
|
||||
| Alt + Shift + ] | Alt + Shift + ] | Move to next tab within a panel |
|
||||
+--------------------------+---------------------------+------------------------------------+
|
||||
| Alt + Shift + [ | Alt + Shift + [ | Move to previous tab within a panel|
|
||||
+--------------------------+---------------------------+------------------------------------+
|
||||
| Alt + Shift + Tab | Alt + Shift + Tab | Move between inner panels |
|
||||
+--------------------------+---------------------------+------------------------------------+
|
||||
|
||||
Access Key
|
||||
**********
|
||||
@@ -221,14 +223,14 @@ default access keys for supported browsers.
|
||||
:class: longtable
|
||||
:widths: 2 1 1 1
|
||||
|
||||
+-------------------+------------+------------+------------+
|
||||
| | Windows | Linux | Mac |
|
||||
+===================+============+============+============+
|
||||
| Internet Explorer | Alt | Alt | |
|
||||
+-------------------+------------+------------+------------+
|
||||
| Chrome | Alt | Alt | Ctrl+Alt |
|
||||
+-------------------+------------+------------+------------+
|
||||
| Firefox | Alt+Shift | Alt+Shift | Ctrl+Alt |
|
||||
+-------------------+------------+------------+------------+
|
||||
| Safari | Alt | | Ctrl+Alt |
|
||||
+-------------------+------------+------------+------------+
|
||||
+-------------------+-------------+-------------+---------------+
|
||||
| | Windows | Linux | Mac |
|
||||
+===================+=============+=============+===============+
|
||||
| Internet Explorer | Alt | Alt | |
|
||||
+-------------------+-------------+-------------+---------------+
|
||||
| Chrome | Alt | Alt | Ctrl + Option |
|
||||
+-------------------+-------------+-------------+---------------+
|
||||
| Firefox | Alt + Shift | Alt + Shift | Ctrl + Option |
|
||||
+-------------------+-------------+-------------+---------------+
|
||||
| Safari | Alt | | Ctrl + Option |
|
||||
+-------------------+-------------+-------------+---------------+
|
||||
@@ -16,6 +16,7 @@ Use the fields in the *Login* dialog to authenticate your connection:
|
||||
field.
|
||||
* Provide your password in the *Password* field.
|
||||
* Click the *Login* button to securely log into pgAdmin.
|
||||
* Please note that, if the pgAdmin server is restarted then you will be logged out. You need to re-login to continue.
|
||||
|
||||
Recovering a Lost Password
|
||||
**************************
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
.. _master_password:
|
||||
|
||||
************************
|
||||
`Master Password`:index:
|
||||
************************
|
||||
|
||||
A master password is required to secure and later unlock the saved server
|
||||
passwords. This is applicable only for desktop mode users.
|
||||
|
||||
* You are prompted to enter the master password when you open the window for
|
||||
the first time after starting the application.
|
||||
* Once you set the master password, all the existing saved passwords will be
|
||||
re-encrypted using the master password.
|
||||
* The server passwords which are saved in the SQLite DB file are encrypted and
|
||||
decrypted using the master password.
|
||||
|
||||
.. image:: images/master_password_set.png
|
||||
:alt: Set master password
|
||||
:align: center
|
||||
|
||||
* You can disable the master password by setting the configuration parameter
|
||||
*MASTER_PASSWORD_REQUIRED=False*. See :ref:`Desktop Deployment <desktop_deployment>`
|
||||
for more information.
|
||||
|
||||
.. note:: If the master password is disabled, then all the saved passwords will
|
||||
be removed.
|
||||
|
||||
.. warning:: If the master password is disabled, then the saved passwords will
|
||||
be encrypted using a key which is derived from information within the
|
||||
configuration database. Use of a master password ensures that the encryption
|
||||
key does not need to be stored anywhere, and thus prevents possible access
|
||||
to server credentials if the configuration database becomes available to an
|
||||
attacker.
|
||||
|
||||
It is **strongly** recommended that you use the master password if you use
|
||||
the *Save Password* option.
|
||||
|
||||
* The master password is not stored anywhere on the physical storage. It is
|
||||
temporarily stored in the application memory and it does not get saved when
|
||||
the application is restarted.
|
||||
* You are prompted to enter the master password when pgAdmin server is
|
||||
restarted.
|
||||
|
||||
.. image:: images/master_password_enter.png
|
||||
:alt: Enter master password
|
||||
:align: center
|
||||
|
||||
|
||||
* If you forget the master password, you can use the *Reset Master Password*
|
||||
button to reset the password.
|
||||
|
||||
.. image:: images/master_password_reset.png
|
||||
:alt: Reset master password
|
||||
:align: center
|
||||
|
||||
.. warning:: Resetting the master password will also remove all saved passwords
|
||||
and close all existing established connections.
|
||||
@@ -25,6 +25,9 @@ Use the *File* menu to access the following options:
|
||||
+-------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| *Reset Layout* | If you have modified the workspace, click to restore the default layout. |
|
||||
+-------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
| *Lock Layout* | Click to open a submenu to select the level for locking the UI layout |
|
||||
| | This can also be changed from browser display :ref:`preferences <preferences>` |
|
||||
+-------------------------+---------------------------------------------------------------------------------------------------------+
|
||||
|
||||
The Object Menu
|
||||
***************
|
||||
|
||||
@@ -38,10 +38,6 @@ Use the fields on the *Display* panel to specify general display preferences:
|
||||
attempt to catch browser close or refresh events and prompt before allowing
|
||||
them to continue.
|
||||
|
||||
* When the *Show system objects?* switch is set to *True*, the client will
|
||||
display system objects such as system schemas (for example, *pg_temp*) or
|
||||
system columns (for example, *xmin* or *ctid*) in the tree control.
|
||||
|
||||
* When the *Enable browser tree animation?* switch is set to *True*, the client
|
||||
will display the animated tree control otherwise it will be unanimated.
|
||||
|
||||
@@ -49,6 +45,23 @@ Use the fields on the *Display* panel to specify general display preferences:
|
||||
the client will display the animated dialogues/notifications otherwise it
|
||||
will be unanimated.
|
||||
|
||||
* Use the *Lock layout* field to lock the UI layout at different levels. This
|
||||
can also be changed from File menu on the :ref:`menu bar <menu_bar>`
|
||||
|
||||
+---------------------+-------------------------------------------------------------------+
|
||||
| Option | Action |
|
||||
+=====================+===================================================================+
|
||||
| *None* | No locking. Every panel is resizable and dockable. |
|
||||
+---------------------+-------------------------------------------------------------------+
|
||||
| *Prevent docking* | This will disable the docking/undocking of the panels |
|
||||
+---------------------+-------------------------------------------------------------------+
|
||||
| *Full* | This will disable resizing, docking/undocking of the panels |
|
||||
+---------------------+-------------------------------------------------------------------+
|
||||
|
||||
* When the *Show system objects?* switch is set to *True*, the client will
|
||||
display system objects such as system schemas (for example, *pg_temp*) or
|
||||
system columns (for example, *xmin* or *ctid*) in the tree control.
|
||||
|
||||
Use the fields on the *Keyboard shortcuts* panel to configure shortcuts for the
|
||||
main window navigation:
|
||||
|
||||
|
||||
@@ -11,6 +11,11 @@ notes for it.
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
release_notes_4_9
|
||||
release_notes_4_8
|
||||
release_notes_4_7
|
||||
release_notes_4_6
|
||||
release_notes_4_5
|
||||
release_notes_4_4
|
||||
release_notes_4_3
|
||||
release_notes_4_2
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
***********
|
||||
Version 4.5
|
||||
***********
|
||||
|
||||
Release date: 2019-04-10
|
||||
|
||||
This release contains a number of new features and fixes reported since the
|
||||
release of pgAdmin4 4.4.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Bug #2214 <https://redmine.postgresql.org/issues/2214>`_ - Fixed 'Change Password' issue for SCRAM authentication.
|
||||
| `Bug #3656 <https://redmine.postgresql.org/issues/3656>`_ - Ensure that two consecutive SELECT statements should work properly.
|
||||
| `Bug #4131 <https://redmine.postgresql.org/issues/4131>`_ - Relabel the Save button on the datagrid text editor to avoid confusion with the actual Save button that updates the database.
|
||||
| `Bug #4142 <https://redmine.postgresql.org/issues/4142>`_ - Added recommended ESLinter checks.
|
||||
| `Bug #4143 <https://redmine.postgresql.org/issues/4143>`_ - Ensure that pgAdmin4 should work properly with psycopg2 v2.8
|
||||
@@ -0,0 +1,33 @@
|
||||
***********
|
||||
Version 4.6
|
||||
***********
|
||||
|
||||
Release date: 2019-05-02
|
||||
|
||||
This release contains a number of new features and fixes reported since the
|
||||
release of pgAdmin4 4.5
|
||||
|
||||
Features
|
||||
********
|
||||
|
||||
| `Feature #4165 <https://redmine.postgresql.org/issues/4165>`_ - Depend on psycopg2-binary in the Python wheel, rather than psycopg2.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Bug #2392 <https://redmine.postgresql.org/issues/2392>`_ - Ensure that on clicking Delete button should not delete rows immediately from the database server, it should be deleted when Save button will be clicked.
|
||||
| `Bug #3327 <https://redmine.postgresql.org/issues/3327>`_ - Ensure that newly added row in backgrid should be visible.
|
||||
| `Bug #3582 <https://redmine.postgresql.org/issues/3582>`_ - Ensure that JSON strings as comments should be added properly for all the objects.
|
||||
| `Bug #3605 <https://redmine.postgresql.org/issues/3605>`_ - Fix an issue where Deleting N number of rows makes first N number of rows disable.
|
||||
| `Bug #3938 <https://redmine.postgresql.org/issues/3938>`_ - Added support for Default Partition.
|
||||
| `Bug #4087 <https://redmine.postgresql.org/issues/4087>`_ - Fix an issue where 'GRANT UPDATE' sql should be displayed for default sequence privileges.
|
||||
| `Bug #4101 <https://redmine.postgresql.org/issues/4101>`_ - Ensure that confirmation dialog should be popped up before reload of query tool or debugger if it is opened in a new browser tab.
|
||||
| `Bug #4104 <https://redmine.postgresql.org/issues/4104>`_ - Ensure that record should be add/edited for root partition table with primary keys.
|
||||
| `Bug #4121 <https://redmine.postgresql.org/issues/4121>`_ - Fixed alignment issue of columns in definition section of Index node.
|
||||
| `Bug #4134 <https://redmine.postgresql.org/issues/4134>`_ - Fixed 'Location cannot be empty' error when open Tablespace properties.
|
||||
| `Bug #4138 <https://redmine.postgresql.org/issues/4138>`_ - Fix an issue where the dropdown becomes misaligned/displaced.
|
||||
| `Bug #4154 <https://redmine.postgresql.org/issues/4154>`_ - Ensure the treeview shows all sequences except those used to implement IDENTITY columns (which can be edited as part of the column). Show all if Show System Objects is enabled.
|
||||
| `Bug #4160 <https://redmine.postgresql.org/issues/4160>`_ - Fixed 'Increment value cannot be empty' error for existing tables.
|
||||
| `Bug #4161 <https://redmine.postgresql.org/issues/4161>`_ - Ensure that parameters of procedures for EPAS server 10 and below should be set/reset properly.
|
||||
| `Bug #4163 <https://redmine.postgresql.org/issues/4163>`_ - Prevent duplicate columns being included in reverse engineered SQL for tables.
|
||||
| `Bug #4182 <https://redmine.postgresql.org/issues/4182>`_ - Ensure sanity of the permissions on the storage and session directories and the config database.
|
||||
@@ -0,0 +1,34 @@
|
||||
***********
|
||||
Version 4.7
|
||||
***********
|
||||
|
||||
Release date: 2019-05-30
|
||||
|
||||
This release contains a number of bug fixes since the release of pgAdmin4 4.6.
|
||||
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Bug #3377 <https://redmine.postgresql.org/issues/3377>`_ - In server mode, update all the saved server credentials when user password is changed.
|
||||
| `Bug #3885 <https://redmine.postgresql.org/issues/3885>`_ - Fix the responsive layout of the main menu bar.
|
||||
| `Bug #4162 <https://redmine.postgresql.org/issues/4162>`_ - Fix syntax error when adding more than one column to the existing table.
|
||||
| `Bug #4164 <https://redmine.postgresql.org/issues/4164>`_ - Fix file browser path issue which occurs when client is on Windows and server is on Mac/Linux.
|
||||
| `Bug #4184 <https://redmine.postgresql.org/issues/4184>`_ - Added Master Password to increase the security of saved passwords.
|
||||
| `Bug #4194 <https://redmine.postgresql.org/issues/4194>`_ - Fix accessibility issue for menu navigation.
|
||||
| `Bug #4208 <https://redmine.postgresql.org/issues/4208>`_ - Update the UI logo.
|
||||
| `Bug #4217 <https://redmine.postgresql.org/issues/4217>`_ - Fixed CSRF security vulnerability issue, per Alvin Lindstam
|
||||
| `Bug #4218 <https://redmine.postgresql.org/issues/4218>`_ - Properly assign dropdownParent in Select2 controls.
|
||||
| `Bug #4219 <https://redmine.postgresql.org/issues/4219>`_ - Ensure popper.js is installed when needed.
|
||||
| `Bug #4227 <https://redmine.postgresql.org/issues/4227>`_ - Fixed Tab key navigation for Maintenance dialog.
|
||||
| `Bug #4244 <https://redmine.postgresql.org/issues/4244>`_ - Fix Tab key issue for Toggle switch controls and button on the dialog footer in Safari browser.
|
||||
| `Bug #4245 <https://redmine.postgresql.org/issues/4245>`_ - Ensure that element should get highlighted when they get focus on using Tab key.
|
||||
| `Bug #4246 <https://redmine.postgresql.org/issues/4246>`_ - Fixed console error when subnode control is used in panels.
|
||||
| `Bug #4261 <https://redmine.postgresql.org/issues/4261>`_ - Stop using application/x-javascript as a mime type and use the RFC-compliant application/javascript instead.
|
||||
| `Bug #4262 <https://redmine.postgresql.org/issues/4262>`_ - Fixed error on displaying table properties of a table partitioned by list having a default partition.
|
||||
| `Bug #4263 <https://redmine.postgresql.org/issues/4263>`_ - Fix handling of JSON in the Query Tool with NULL elements.
|
||||
| `Bug #4269 <https://redmine.postgresql.org/issues/4269>`_ - Fix navigation of switch cells in grids.
|
||||
| `Bug #4275 <https://redmine.postgresql.org/issues/4275>`_ - Clarify wording for the NO INHERIT option on constraints, per Michel Feinstein.
|
||||
| `Bug #4276 <https://redmine.postgresql.org/issues/4276>`_ - Relax the permission check on the directory containing the config database, as it may fail in some environments such as OpenShift.
|
||||
| `Bug #4278 <https://redmine.postgresql.org/issues/4278>`_ - Prevent Backgrid Password cells from losing focus if the browser opens an autocomplete list.
|
||||
| `Bug #4284 <https://redmine.postgresql.org/issues/4284>`_ - Fix syntax error when creating a table with a serial column.
|
||||
@@ -0,0 +1,19 @@
|
||||
***********
|
||||
Version 4.8
|
||||
***********
|
||||
|
||||
Release date: 2019-06-03
|
||||
|
||||
This release contains a number of bug fixes and new features since the release of pgAdmin4 4.7.
|
||||
|
||||
New features
|
||||
************
|
||||
|
||||
| `Feature #2653 <https://redmine.postgresql.org/issues/2653>`_ - Allow the UI layout to be fully locked or to prevent docking changes.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Bug #4169 <https://redmine.postgresql.org/issues/4169>`_ - Omit the geometry viewer in the Query Tool from layout saving.
|
||||
| `Bug #4307 <https://redmine.postgresql.org/issues/4307>`_ - Improve the performance of explain plan by embedding the images only when downloading it.
|
||||
| `Bug #4308 <https://redmine.postgresql.org/issues/4308>`_ - Fix the issue of accessing the SQL for Views and Materialized Views. Regression of pluralisation of folder names.
|
||||
@@ -0,0 +1,44 @@
|
||||
***********
|
||||
Version 4.9
|
||||
***********
|
||||
|
||||
Release date: 2019-06-27
|
||||
|
||||
This release contains a number of bug fixes and new features since the release of pgAdmin4 4.8.
|
||||
|
||||
New features
|
||||
************
|
||||
|
||||
| `Feature #3174 <https://redmine.postgresql.org/issues/3174>`_ - Visually distinguish simple tables from tables that are inherited and from which other tables are inherited.
|
||||
| `Feature #4202 <https://redmine.postgresql.org/issues/4202>`_ - Add a framework for testing reversed engineered SQL and CRUD API endpoints.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Bug #3994 <https://redmine.postgresql.org/issues/3994>`_ - Fix issue where the dependencies tab for inherited tables/foreign keys shows partial text.
|
||||
| `Bug #4036 <https://redmine.postgresql.org/issues/4036>`_ - Allow editing of data where a primary key column includes a % sign in the value.
|
||||
| `Bug #4171 <https://redmine.postgresql.org/issues/4171>`_ - Fix issue where reverse engineered SQL was failing for foreign tables, if it had "=" in the options.
|
||||
| `Bug #4195 <https://redmine.postgresql.org/issues/4195>`_ - Fix keyboard navigation in "inner" tabsets such as the Query Tool and Debugger.
|
||||
| `Bug #4228 <https://redmine.postgresql.org/issues/4228>`_ - Ensure the correct label is used in panel headers when viewing filtered rows.
|
||||
| `Bug #4253 <https://redmine.postgresql.org/issues/4253>`_ - Fix issue where new column should be created with Default value.
|
||||
| `Bug #4283 <https://redmine.postgresql.org/issues/4283>`_ - Initial support for PostgreSQL 12.
|
||||
| `Bug #4288 <https://redmine.postgresql.org/issues/4288>`_ - Initial support for PostgreSQL 12.
|
||||
| `Bug #4290 <https://redmine.postgresql.org/issues/4290>`_ - Initial support for PostgreSQL 12.
|
||||
| `Bug #4255 <https://redmine.postgresql.org/issues/4255>`_ - Prevent the geometry viewer grabbing key presses when not in focus under Firefox, IE and Edge.
|
||||
| `Bug #4306 <https://redmine.postgresql.org/issues/4306>`_ - Prevent the "Please login to access this page" message displaying multiple times.
|
||||
| `Bug #4310 <https://redmine.postgresql.org/issues/4310>`_ - Ensure that the Return key can be used to submit the Master Password dialogue.
|
||||
| `Bug #4317 <https://redmine.postgresql.org/issues/4317>`_ - Ensure that browser auto-fill doesn't cause Help pages to be opened unexpectedly.
|
||||
| `Bug #4320 <https://redmine.postgresql.org/issues/4320>`_ - Fix issue where SSH tunnel connection using password is failing, it's regression of Master Password.
|
||||
| `Bug #4329 <https://redmine.postgresql.org/issues/4329>`_ - Fix an initialisation error when two functions with parameters are debugged in parallel.
|
||||
| `Bug #4343 <https://redmine.postgresql.org/issues/4343>`_ - Fix issue where property dialog of column should open properly for EPAS v12.
|
||||
| `Bug #4345 <https://redmine.postgresql.org/issues/4345>`_ - Capitalize the word 'export' used in Import/Export module.
|
||||
| `Bug #4349 <https://redmine.postgresql.org/issues/4349>`_ - Ensure strings are properly encoded in the Query History.
|
||||
| `Bug #4350 <https://redmine.postgresql.org/issues/4350>`_ - Ensure we include the CSRF token when uploading files.
|
||||
| `Bug #4357 <https://redmine.postgresql.org/issues/4357>`_ - Fix connection restoration issue when pgAdmin server is restarted and the page is refreshed.
|
||||
| `Bug #4360 <https://redmine.postgresql.org/issues/4360>`_ - Ensure the debugger control buttons are only enabled once initialisation is complete.
|
||||
| `Bug #4362 <https://redmine.postgresql.org/issues/4362>`_ - Remove additional "SETOF" included when generating CREATE scripts for trigger functions.
|
||||
| `Bug #4365 <https://redmine.postgresql.org/issues/4365>`_ - Fix help links for backup globals and backup server.
|
||||
| `Bug #4367 <https://redmine.postgresql.org/issues/4367>`_ - Fix an XSS issue seen in View/Edit data mode if a column name includes HTML.
|
||||
| `Bug #4378 <https://redmine.postgresql.org/issues/4378>`_ - Ensure Python escaping matched JS escaping and fix a minor XSS issue in the Query Tool that required superuser access to trigger.
|
||||
| `Bug #4380 <https://redmine.postgresql.org/issues/4380>`_ - Ensure that both columns and partitions can be edited at the same time in the table dialog.
|
||||
| `Bug #4386 <https://redmine.postgresql.org/issues/4386>`_ - Fix an XSS issue when username contains XSS vulnerable text.
|
||||
@@ -68,11 +68,15 @@ pgAdmin. These are as follows:
|
||||
|
||||
* ``config_distro.py``: This file is read after ``config.py`` and is intended for
|
||||
packagers to change any settings that are required for their pgAdmin distribution.
|
||||
This may typically include certain paths and file locations.
|
||||
This may typically include certain paths and file locations. This file is optional,
|
||||
and may be created by packagers in the same directory as ``config.py`` if
|
||||
needed.
|
||||
|
||||
* ``config_local.py``: This file is read after ``config_distro.py`` and is intended
|
||||
for end users to change any default or packaging specific settings that they may
|
||||
wish to adjust to meet local preferences or standards.
|
||||
wish to adjust to meet local preferences or standards. This file is optional,
|
||||
and may be created by administrators in the same directory as ``config.py`` if
|
||||
needed.
|
||||
|
||||
Python
|
||||
------
|
||||
|
||||
@@ -264,8 +264,9 @@ Use the fields in the *Definition* tab to define the check constraint:
|
||||
|
||||
* Provide the expression that a row must satisfy in the *Check* field. This
|
||||
field is required.
|
||||
* Move the *No Inherit?* switch to the *Yes* position to specify this constraint
|
||||
is automatically inherited by a table's children. The default is *No*.
|
||||
* Move the *No Inherit?* switch to the *Yes* position to specify that this
|
||||
constraint is not automatically inherited by a table's children. The default
|
||||
is *No*, meaning that the constraint will be inherited by any children.
|
||||
* Move the *Don't validate?* switch to the *No* position to skip validation of
|
||||
existing data; the constraint may not hold for all rows in the table. The
|
||||
default is *Yes*.
|
||||
@@ -442,6 +443,7 @@ icon (+) to add each partition:
|
||||
* Move the *Operation* switch to *attach* to attach the partition, by default it
|
||||
is *create*.
|
||||
* Use the *Name* field to add the name of the partition.
|
||||
* If partition type is Range or List then *Default* field will be enabled.
|
||||
* If partition type is Range then *From* and *To* fields will be enabled.
|
||||
* If partition type is List then *In* field will be enabled.
|
||||
* If partition type is Hash then *Modulus* and *Remainder* fields will be
|
||||
@@ -523,4 +525,4 @@ three columns and a primary key constraint on the *category_id* column.
|
||||
* Click the *Info* button (i) to access online help.
|
||||
* Click the *Save* button to save work.
|
||||
* Click the *Cancel* button to exit without saving work.
|
||||
* Click the *Reset* button to restore configuration parameters.
|
||||
* Click the *Reset* button to restore configuration parameters.
|
||||
@@ -22,8 +22,8 @@ RUN yarn install --cache-folder ./ycache --verbose && \
|
||||
FROM python:3.7-alpine3.9 as docs-builder
|
||||
|
||||
# Install only dependencies absolutely required for documentation building
|
||||
RUN apk add --no-cache make && \
|
||||
pip install --no-cache-dir sphinx flask_security flask_paranoid python-dateutil flask_sqlalchemy flask_gravatar flask_migrate simplejson
|
||||
RUN apk add --no-cache make build-base openssl-dev libffi-dev && \
|
||||
pip install --no-cache-dir sphinx flask_security flask_paranoid python-dateutil flask_sqlalchemy flask_gravatar flask_migrate simplejson cryptography
|
||||
|
||||
COPY ./pgadmin4/ /pgadmin4
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ for src in "${DMG_SOURCES}"; do
|
||||
done
|
||||
|
||||
echo "Creating image"
|
||||
${HDIUTIL} create -quiet -srcfolder "$DMG_DIR" -format UDZO -volname "${DMG_VOLUME_NAME}" -ov "${DMG_IMAGE}" || exit 1
|
||||
${HDIUTIL} create -quiet -srcfolder "$DMG_DIR" -fs HFS+ -format UDZO -volname "${DMG_VOLUME_NAME}" -ov "${DMG_IMAGE}" || exit 1
|
||||
rm -rf "${DMG_DIR}" || exit 1
|
||||
|
||||
echo "Attaching License to image"
|
||||
|
||||
@@ -37,6 +37,11 @@ with open(req_file, 'r') as reqf:
|
||||
for index, req in enumerate(required):
|
||||
if ";" in req or req.startswith("#") or req == "":
|
||||
required.remove(req)
|
||||
continue
|
||||
|
||||
# Ensure the Wheel will use psycopg2-binary, not the source distro
|
||||
if 'psycopg2' in req:
|
||||
required[index] = req.replace('psycopg2', 'psycopg2-binary')
|
||||
|
||||
# Get the app version
|
||||
if sys.version_info[:2] >= (3, 3):
|
||||
|
||||
@@ -21,7 +21,7 @@ sqlparse==0.2.4
|
||||
WTForms==2.2.1
|
||||
Flask-Paranoid==0.2.0
|
||||
psutil==5.5.1
|
||||
psycopg2>=2.7.7
|
||||
psycopg2>=2.8
|
||||
python-dateutil>=2.8.0
|
||||
htmlmin==0.1.12
|
||||
Flask-HTMLmin==1.5.0
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
<key>CFBundleName</key>
|
||||
<string>pgAdmin 4</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.4.0</string>
|
||||
<string>4.9.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>4.4.0</string>
|
||||
<string>4.9.0</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.10</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
VERSION = 4.4.0.0
|
||||
VERSION = 4.9.0.0
|
||||
QMAKE_TARGET_COMPANY = "The pgAdmin Development Team"
|
||||
QMAKE_TARGET_PRODUCT = "pgAdmin 4"
|
||||
QMAKE_TARGET_DESCRIPTION = "pgAdmin 4 Desktop Runtime"
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
# Get the correct version of chromedriver for the version of Chrome on the
|
||||
# machine and save it to the specified location.
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.request
|
||||
import zipfile
|
||||
|
||||
|
||||
def read_command_line():
|
||||
"""Read the command line arguments.
|
||||
|
||||
Returns:
|
||||
ArgumentParser: The parsed arguments object
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Get the correct version of chromedriver for the '
|
||||
'specified Chrome installation and save it.')
|
||||
if platform.system() != 'Windows':
|
||||
parser.add_argument("chrome", metavar="CHROME",
|
||||
help="the Chrome executable")
|
||||
parser.add_argument("directory", metavar="DIRECTORY",
|
||||
help="the directory in which to save chromedriver")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def get_chrome_version(args):
|
||||
"""Get the Chrome version number.
|
||||
|
||||
Args:
|
||||
args: The parsed arguments object
|
||||
Returns:
|
||||
The Chrome version number
|
||||
"""
|
||||
if platform.system() == 'Windows':
|
||||
# On Windows we need to examine the resource section of the binary
|
||||
import winreg
|
||||
|
||||
def _read_registry(root, key, value):
|
||||
try:
|
||||
hkey = winreg.OpenKey(root, key)
|
||||
except Exception as e:
|
||||
return None
|
||||
try:
|
||||
(val, typ) = winreg.QueryValueEx(hkey, value)
|
||||
except Exception as e:
|
||||
winreg.CloseKey(hkey)
|
||||
return None
|
||||
|
||||
winreg.CloseKey(hkey)
|
||||
return val
|
||||
|
||||
key = r'SOFTWARE\Google\Chrome\BLBeacon'
|
||||
version_str = _read_registry(winreg.HKEY_CURRENT_USER, key, 'Version')
|
||||
|
||||
# On a 64b host, look for a 32b installation
|
||||
if not version_str:
|
||||
key = r'SOFTWARE\Wow6432Node\Google\Chrome\BLBeacon'
|
||||
version_str = _read_registry(winreg.HKEY_CURRENT_USER, key,
|
||||
'Version')
|
||||
|
||||
if not version_str:
|
||||
print('The Chrome version could not be read from the registry.')
|
||||
sys.exit(1)
|
||||
|
||||
chrome_version = '.'.join(version_str.split()[-1].split('.')[:-1])
|
||||
else:
|
||||
# On Linux/Mac we run the Chrome executable with the --version flag,
|
||||
# then parse the output.
|
||||
try:
|
||||
result = subprocess.run([args.chrome, '--version'],
|
||||
stdout=subprocess.PIPE)
|
||||
except FileNotFoundError as e:
|
||||
print('The specified Chrome executable could not be found.')
|
||||
sys.exit(1)
|
||||
|
||||
version_str = result.stdout.decode("utf-8").strip()
|
||||
# Check for 'Chrom' not 'Chrome' in case the user is using Chromium.
|
||||
if "Chrom" not in version_str:
|
||||
print('The specified Chrome executable output an unexpected '
|
||||
'version string: {}.'.format(version_str))
|
||||
sys.exit(1)
|
||||
|
||||
chrome_version = '.'.join(version_str.split()[-1].split('.')[:-1])
|
||||
|
||||
if chrome_version.count('.') != 2:
|
||||
print('The specified Chrome executable output an unexpected '
|
||||
'version string: {}.'.format(version_str))
|
||||
sys.exit(1)
|
||||
|
||||
return chrome_version
|
||||
|
||||
|
||||
def get_chromedriver_version(chrome_version):
|
||||
"""Get the required chromedriver version number.
|
||||
|
||||
Args:
|
||||
chrome_version: The chrome version number
|
||||
Returns:
|
||||
The chromedriver version number
|
||||
"""
|
||||
url = 'https://chromedriver.storage.googleapis.com/LATEST_RELEASE_{}' \
|
||||
.format(chrome_version)
|
||||
|
||||
try:
|
||||
fp = urllib.request.urlopen(url)
|
||||
except urllib.error.URLError as e:
|
||||
print('The chromedriver catalog URL could not be accessed: {}'
|
||||
.format(e))
|
||||
sys.exit(1)
|
||||
|
||||
version = fp.read().decode("utf-8").strip()
|
||||
fp.close()
|
||||
|
||||
return version
|
||||
|
||||
|
||||
def get_system():
|
||||
"""Get the system name as known to chromedriver
|
||||
|
||||
Returns:
|
||||
The system name
|
||||
"""
|
||||
if platform.system() == 'Darwin':
|
||||
return 'mac64'
|
||||
elif platform.system() == 'Linux':
|
||||
return 'linux64'
|
||||
elif platform.system() == 'Windows':
|
||||
return 'win32'
|
||||
else:
|
||||
print("Unknown or unsupported operating system: {}"
|
||||
.format(platform.system()))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
"""The core structure of the app."""
|
||||
|
||||
# Read the command line options
|
||||
args = read_command_line()
|
||||
|
||||
chrome_version = get_chrome_version(args)
|
||||
|
||||
# Check the directory exists
|
||||
if not os.path.isdir(args.directory):
|
||||
print('The specified output directory could not be accessed.')
|
||||
sys.exit(1)
|
||||
|
||||
chromedriver_version = get_chromedriver_version(chrome_version)
|
||||
|
||||
system = get_system()
|
||||
|
||||
url = 'https://chromedriver.storage.googleapis.com/{}/chromedriver_{}.zip' \
|
||||
.format(chromedriver_version, system)
|
||||
|
||||
print('Downloading chromedriver v{} for Chrome v{} on {}...'
|
||||
.format(chromedriver_version, chrome_version, system))
|
||||
|
||||
try:
|
||||
file, headers = urllib.request.urlretrieve(url)
|
||||
except urllib.error.URLError as e:
|
||||
print('The chromedriver download URL could not be accessed: {}'
|
||||
.format(e))
|
||||
sys.exit(1)
|
||||
|
||||
# Unzip chromedriver
|
||||
print('Extracting chromedriver...')
|
||||
|
||||
found = False
|
||||
fp = open(file, 'rb')
|
||||
z = zipfile.ZipFile(fp)
|
||||
for name in z.namelist():
|
||||
if (system == 'win32' and name == 'chromedriver.exe') or \
|
||||
(system != 'win32' and name == 'chromedriver'):
|
||||
z.extract(name, args.directory)
|
||||
found = True
|
||||
fp.close()
|
||||
|
||||
if not found:
|
||||
print("chromedriver could not be found in the downloaded archive: {}"
|
||||
.format(file))
|
||||
sys.exit(1)
|
||||
|
||||
# Set the permissions
|
||||
if system == 'mac64' or system == 'linux64':
|
||||
os.chmod(os.path.join(args.directory, 'chromedriver'), 0o755)
|
||||
|
||||
print('Chromedriver downloaded to: {}'.format(args.directory))
|
||||
@@ -14,6 +14,9 @@ module.exports = {
|
||||
'amd': true,
|
||||
'jasmine': true,
|
||||
},
|
||||
'extends': [
|
||||
'eslint:recommended',
|
||||
],
|
||||
'parserOptions': {
|
||||
'ecmaVersion': 2018,
|
||||
'sourceType': 'module',
|
||||
@@ -43,5 +46,7 @@ module.exports = {
|
||||
'always-multiline'
|
||||
],
|
||||
'no-console': ["error", { allow: ["warn", "error"] }],
|
||||
// We need to exclude below for RegEx case
|
||||
"no-useless-escape": 0,
|
||||
},
|
||||
};
|
||||
@@ -50,7 +50,7 @@ APP_ICON = 'pg-icon'
|
||||
|
||||
# Application version number components
|
||||
APP_RELEASE = 4
|
||||
APP_REVISION = 4
|
||||
APP_REVISION = 9
|
||||
|
||||
# Application version suffix, e.g. 'beta1', 'dev'. Usually an empty string
|
||||
# for GA releases.
|
||||
@@ -59,7 +59,7 @@ APP_SUFFIX = ''
|
||||
# Numeric application version for upgrade checks. Should be in the format:
|
||||
# [X]XYYZZ, where X is the release version, Y is the revision, with a leading
|
||||
# zero if needed, and Z represents the suffix, with a leading zero if needed
|
||||
APP_VERSION_INT = 40400
|
||||
APP_VERSION_INT = 40900
|
||||
|
||||
# DO NOT CHANGE!
|
||||
# The application version string, constructed from the components
|
||||
@@ -123,6 +123,10 @@ if (not hasattr(builtins, 'SERVER_MODE')) or builtins.SERVER_MODE is None:
|
||||
else:
|
||||
SERVER_MODE = builtins.SERVER_MODE
|
||||
|
||||
# HTTP headers to search for CSRF token when it is not provided in the form.
|
||||
# Default is ['X-CSRFToken', 'X-CSRF-Token']
|
||||
WTF_CSRF_HEADERS = ['X-pgA-CSRFToken']
|
||||
|
||||
# User ID (email address) to use for the default user in desktop mode.
|
||||
# The default should be fine here, as it's not exposed in the app.
|
||||
DESKTOP_USER = 'pgadmin4@pgadmin.org'
|
||||
@@ -141,9 +145,6 @@ DEFAULT_SERVER = '127.0.0.1'
|
||||
# environment by the runtime
|
||||
DEFAULT_SERVER_PORT = 5050
|
||||
|
||||
# Enable CSRF protection?
|
||||
CSRF_ENABLED = True
|
||||
|
||||
# Enable X-Frame-Option protection.
|
||||
# Set to one of "SAMEORIGIN", "ALLOW-FROM origin" or "" to disable.
|
||||
# Note that "DENY" is NOT supported (and will be silently ignored).
|
||||
@@ -418,6 +419,12 @@ SUPPORT_SSH_TUNNEL = True
|
||||
# Set to False to disable password saving.
|
||||
ALLOW_SAVE_TUNNEL_PASSWORD = False
|
||||
|
||||
##########################################################################
|
||||
# Master password is used to encrypt/decrypt saved server passwords
|
||||
# Applicable for desktop mode only
|
||||
##########################################################################
|
||||
MASTER_PASSWORD_REQUIRED = True
|
||||
|
||||
##########################################################################
|
||||
# Local config settings
|
||||
##########################################################################
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
"""empty message
|
||||
|
||||
Revision ID: 35f29b1701bd
|
||||
Revises: ec1cac3399c9
|
||||
Create Date: 2019-04-26 16:38:08.368471
|
||||
|
||||
"""
|
||||
import base64
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pgadmin.model import db, Server
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '35f29b1701bd'
|
||||
down_revision = 'ec1cac3399c9'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
db.engine.execute("ALTER TABLE user RENAME TO user_old")
|
||||
|
||||
db.engine.execute("""
|
||||
CREATE TABLE user (
|
||||
id INTEGER NOT NULL,
|
||||
email VARCHAR(256) NOT NULL,
|
||||
password VARCHAR(256),
|
||||
active BOOLEAN NOT NULL,
|
||||
confirmed_at DATETIME,
|
||||
masterpass_check VARCHAR(256),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE (email),
|
||||
CHECK (active IN (0, 1))
|
||||
);
|
||||
""")
|
||||
|
||||
db.engine.execute("""
|
||||
INSERT INTO user (
|
||||
id, email, password, active, confirmed_at
|
||||
) SELECT
|
||||
id, email, password, active, confirmed_at
|
||||
FROM user_old""")
|
||||
|
||||
db.engine.execute("DROP TABLE user_old")
|
||||
|
||||
|
||||
def downgrade():
|
||||
pass
|
||||
@@ -31,7 +31,10 @@ def upgrade():
|
||||
Security(app, user_datastore, register_blueprint=False)
|
||||
else:
|
||||
app.config['SECURITY_PASSWORD_SALT'] = current_salt
|
||||
users = User.query.all()
|
||||
|
||||
users = User.query.with_entities(
|
||||
User.id, User.email, User.password, User.active, User.confirmed_at)\
|
||||
.all()
|
||||
# This will upgrade the plaintext password of all the user as per the
|
||||
# SECURITY_PASSWORD_HASH.
|
||||
for user in users:
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
"bootstrap4-toggle": "3.4.0",
|
||||
"bowser": "2.1.2",
|
||||
"browserify": "~16.2.3",
|
||||
"codemirror": "^5.44.0",
|
||||
"codemirror": "^5.47.0",
|
||||
"css-loader": "2.1.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"dropzone": "^5.5.1",
|
||||
@@ -67,15 +67,14 @@
|
||||
"immutability-helper": "^3.0.0",
|
||||
"imports-loader": "^0.8.0",
|
||||
"ip-address": "^5.8.9",
|
||||
"jquery": "3.3.1",
|
||||
"jquery": "^3.4.0",
|
||||
"jquery-contextmenu": "^2.8.0",
|
||||
"jquery-ui": "^1.12.1",
|
||||
"json-bignumber": "^1.0.1",
|
||||
"leaflet": "^1.4.0",
|
||||
"leaflet": "^1.5.1",
|
||||
"moment": "^2.24.0",
|
||||
"moment-timezone": "^0.5.23",
|
||||
"mousetrap": "^1.6.3",
|
||||
"prop-types": "^15.7.2",
|
||||
"requirejs": "~2.3.6",
|
||||
"select2": "^4.0.6-rc.1",
|
||||
"shim-loader": "^1.0.1",
|
||||
@@ -90,7 +89,7 @@
|
||||
"underscore": "^1.9.1",
|
||||
"underscore.string": "^3.3.5",
|
||||
"watchify": "~3.11.1",
|
||||
"webcabin-docker": "git+https://github.com/EnterpriseDB/wcDocker/#e191a1063c111d5542ff26a7c163940be180333d",
|
||||
"webcabin-docker": "git+https://github.com/EnterpriseDB/wcDocker/#f3c79e4b9a9e76a8e34736fc33473e65c524050c",
|
||||
"wkx": "^0.4.6"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -23,20 +23,21 @@ from flask_login import user_logged_in, user_logged_out
|
||||
from flask_mail import Mail
|
||||
from flask_paranoid import Paranoid
|
||||
from flask_security import Security, SQLAlchemyUserDatastore, current_user
|
||||
from flask_security.utils import login_user
|
||||
from flask_security.utils import login_user, logout_user
|
||||
from werkzeug.datastructures import ImmutableDict
|
||||
from werkzeug.local import LocalProxy
|
||||
from werkzeug.utils import find_modules
|
||||
|
||||
from pgadmin.model import db, Role, Server, ServerGroup, \
|
||||
User, Keys, Version, SCHEMA_VERSION as CURRENT_SCHEMA_VERSION
|
||||
from pgadmin.utils import PgAdminModule, driver
|
||||
from pgadmin.utils import PgAdminModule, driver, KeyManager
|
||||
from pgadmin.utils.preferences import Preferences
|
||||
from pgadmin.utils.session import create_session_interface, pga_unauthorised
|
||||
from pgadmin.utils.versioned_template_loader import VersionedTemplateLoader
|
||||
from datetime import timedelta
|
||||
from pgadmin.setup import get_version, set_version
|
||||
from pgadmin.utils.ajax import internal_server_error
|
||||
from pgadmin.utils.csrf import pgCSRFProtect
|
||||
|
||||
|
||||
# If script is running under python3, it will not have the xrange function
|
||||
@@ -235,6 +236,8 @@ def create_app(app_name=None):
|
||||
os.environ['PGADMIN_TESTING_MODE'] == '1'
|
||||
):
|
||||
config.SQLITE_PATH = config.TEST_SQLITE_PATH
|
||||
config.MASTER_PASSWORD_REQUIRED = False
|
||||
config.UPGRADE_CHECK_ENABLED = False
|
||||
|
||||
# Ensure the various working directories exist
|
||||
from pgadmin.setup import create_app_data_directory, db_upgrade
|
||||
@@ -338,6 +341,9 @@ def create_app(app_name=None):
|
||||
set_version(CURRENT_SCHEMA_VERSION)
|
||||
db.session.commit()
|
||||
|
||||
if os.name != 'nt':
|
||||
os.chmod(config.SQLITE_PATH, 0o600)
|
||||
|
||||
Mail(app)
|
||||
|
||||
import pgadmin.utils.paths as paths
|
||||
@@ -364,7 +370,10 @@ def create_app(app_name=None):
|
||||
'CSRF_SESSION_KEY': config.CSRF_SESSION_KEY,
|
||||
'SECRET_KEY': config.SECRET_KEY,
|
||||
'SECURITY_PASSWORD_SALT': config.SECURITY_PASSWORD_SALT,
|
||||
'SESSION_COOKIE_DOMAIN': config.SESSION_COOKIE_DOMAIN
|
||||
'SESSION_COOKIE_DOMAIN': config.SESSION_COOKIE_DOMAIN,
|
||||
# CSRF Token expiration till session expires
|
||||
'WTF_CSRF_TIME_LIMIT': getattr(config, 'CSRF_TIME_LIMIT', None),
|
||||
'WTF_CSRF_METHODS': ['GET', 'POST', 'PUT', 'DELETE'],
|
||||
}))
|
||||
|
||||
security.init_app(app, user_datastore)
|
||||
@@ -567,12 +576,23 @@ def create_app(app_name=None):
|
||||
def force_session_write(app, user):
|
||||
session.force_write = True
|
||||
|
||||
@user_logged_in.connect_via(app)
|
||||
def store_crypt_key(app, user):
|
||||
# in desktop mode, master password is used to encrypt/decrypt
|
||||
# and is stored in the keyManager memory
|
||||
if config.SERVER_MODE:
|
||||
if 'password' in request.form:
|
||||
current_app.keyManager.set(request.form['password'])
|
||||
|
||||
@user_logged_out.connect_via(app)
|
||||
def current_user_cleanup(app, user):
|
||||
from config import PG_DEFAULT_DRIVER
|
||||
from pgadmin.utils.driver import get_driver
|
||||
from flask import current_app
|
||||
|
||||
# remove key
|
||||
current_app.keyManager.reset()
|
||||
|
||||
for mdl in current_app.logout_hooks:
|
||||
try:
|
||||
mdl.on_logout(user)
|
||||
@@ -623,6 +643,15 @@ def create_app(app_name=None):
|
||||
abort(401)
|
||||
login_user(user)
|
||||
|
||||
# if the server is restarted the in memory key will be lost
|
||||
# but the user session may still be active. Logout the user
|
||||
# to get the key again when login
|
||||
if config.SERVER_MODE and current_user.is_authenticated:
|
||||
if current_app.keyManager.get() is None and \
|
||||
request.endpoint not in ('security.login',
|
||||
'security.logout'):
|
||||
logout_user()
|
||||
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
if 'key' in request.args:
|
||||
@@ -703,8 +732,16 @@ def create_app(app_name=None):
|
||||
current_app.logger.error(e, exc_info=True)
|
||||
return e
|
||||
|
||||
# Intialize the key manager
|
||||
app.keyManager = KeyManager()
|
||||
|
||||
##########################################################################
|
||||
# Protection against CSRF attacks
|
||||
##########################################################################
|
||||
with app.app_context():
|
||||
pgCSRFProtect.init_app(app)
|
||||
|
||||
##########################################################################
|
||||
# All done!
|
||||
##########################################################################
|
||||
|
||||
return app
|
||||
@@ -18,7 +18,7 @@ from socket import error as SOCKETErrorException
|
||||
|
||||
import six
|
||||
from flask import current_app, render_template, url_for, make_response, \
|
||||
flash, Response, request, after_this_request, redirect
|
||||
flash, Response, request, after_this_request, redirect, session
|
||||
from flask_babelex import gettext
|
||||
from flask_gravatar import Gravatar
|
||||
from flask_login import current_user, login_required
|
||||
@@ -37,9 +37,14 @@ from pgadmin import current_blueprint
|
||||
from pgadmin.settings import get_setting
|
||||
from pgadmin.utils import PgAdminModule
|
||||
from pgadmin.utils.ajax import make_json_response
|
||||
from pgadmin.utils.csrf import pgCSRFProtect
|
||||
from pgadmin.utils.preferences import Preferences
|
||||
from pgadmin.utils.menu import MenuItem
|
||||
from pgadmin.browser.register_browser_preferences import \
|
||||
register_browser_preferences
|
||||
from pgadmin.utils.master_password import validate_master_password, \
|
||||
set_masterpass_check_text, cleanup_master_password, get_crypt_key, \
|
||||
set_crypt_key, process_masterpass_disabled
|
||||
|
||||
try:
|
||||
import urllib.request as urlreq
|
||||
@@ -211,6 +216,40 @@ class BrowserModule(PgAdminModule):
|
||||
scripts.extend(module.get_own_javascripts())
|
||||
return scripts
|
||||
|
||||
def get_own_menuitems(self):
|
||||
return {
|
||||
'file_items': [
|
||||
MenuItem(
|
||||
name='mnu_locklayout',
|
||||
module='pgAdmin.Browser',
|
||||
label=gettext('Lock Layout'),
|
||||
priority=999,
|
||||
menu_items=[MenuItem(
|
||||
name='mnu_lock_none',
|
||||
module='pgAdmin.Browser',
|
||||
callback='mnu_lock_none',
|
||||
priority=0,
|
||||
label=gettext('None'),
|
||||
checked=True
|
||||
), MenuItem(
|
||||
name='mnu_lock_docking',
|
||||
module='pgAdmin.Browser',
|
||||
callback='mnu_lock_docking',
|
||||
priority=1,
|
||||
label=gettext('Prevent Docking'),
|
||||
checked=False
|
||||
), MenuItem(
|
||||
name='mnu_lock_full',
|
||||
module='pgAdmin.Browser',
|
||||
callback='mnu_lock_full',
|
||||
priority=2,
|
||||
label=gettext('Full Lock'),
|
||||
checked=False
|
||||
)]
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
def register_preferences(self):
|
||||
register_browser_preferences(self)
|
||||
|
||||
@@ -219,7 +258,11 @@ class BrowserModule(PgAdminModule):
|
||||
Returns:
|
||||
list: a list of url endpoints exposed to the client.
|
||||
"""
|
||||
return ['browser.index', 'browser.nodes']
|
||||
return ['browser.index', 'browser.nodes',
|
||||
'browser.check_master_password',
|
||||
'browser.set_master_password',
|
||||
'browser.reset_master_password',
|
||||
'browser.lock_layout']
|
||||
|
||||
|
||||
blueprint = BrowserModule(MODULE_NAME, __name__)
|
||||
@@ -478,6 +521,7 @@ class BrowserPluginModule(PgAdminModule):
|
||||
|
||||
|
||||
@blueprint.route("/")
|
||||
@pgCSRFProtect.exempt
|
||||
@login_required
|
||||
def index():
|
||||
"""Render and process the main browser window."""
|
||||
@@ -561,6 +605,7 @@ def index():
|
||||
|
||||
|
||||
@blueprint.route("/js/utils.js")
|
||||
@pgCSRFProtect.exempt
|
||||
@login_required
|
||||
def utils():
|
||||
layout = get_setting('Browser/Layout', default='')
|
||||
@@ -623,61 +668,37 @@ def utils():
|
||||
pg_libpq_version=pg_libpq_version,
|
||||
support_ssh_tunnel=config.SUPPORT_SSH_TUNNEL
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'})
|
||||
200, {'Content-Type': 'application/javascript'})
|
||||
|
||||
|
||||
@blueprint.route("/js/endpoints.js")
|
||||
@pgCSRFProtect.exempt
|
||||
def exposed_urls():
|
||||
return make_response(
|
||||
render_template('browser/js/endpoints.js'),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
200, {'Content-Type': 'application/javascript'}
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route("/js/error.js")
|
||||
@pgCSRFProtect.exempt
|
||||
@login_required
|
||||
def error_js():
|
||||
return make_response(
|
||||
render_template('browser/js/error.js', _=gettext),
|
||||
200, {'Content-Type': 'application/x-javascript'})
|
||||
|
||||
|
||||
@blueprint.route("/js/node.js")
|
||||
@login_required
|
||||
def node_js():
|
||||
prefs = Preferences.module('paths')
|
||||
|
||||
pg_help_path_pref = prefs.preference('pg_help_path')
|
||||
pg_help_path = pg_help_path_pref.get()
|
||||
|
||||
edbas_help_path_pref = prefs.preference('edbas_help_path')
|
||||
edbas_help_path = edbas_help_path_pref.get()
|
||||
|
||||
return make_response(
|
||||
render_template('browser/js/node.js',
|
||||
pg_help_path=pg_help_path,
|
||||
edbas_help_path=edbas_help_path,
|
||||
_=gettext
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'})
|
||||
200, {'Content-Type': 'application/javascript'})
|
||||
|
||||
|
||||
@blueprint.route("/js/messages.js")
|
||||
@pgCSRFProtect.exempt
|
||||
def messages_js():
|
||||
return make_response(
|
||||
render_template('browser/js/messages.js', _=gettext),
|
||||
200, {'Content-Type': 'application/x-javascript'})
|
||||
|
||||
|
||||
@blueprint.route("/js/collection.js")
|
||||
@login_required
|
||||
def collection_js():
|
||||
return make_response(
|
||||
render_template('browser/js/collection.js', _=gettext),
|
||||
200, {'Content-Type': 'application/x-javascript'})
|
||||
200, {'Content-Type': 'application/javascript'})
|
||||
|
||||
|
||||
@blueprint.route("/browser.css")
|
||||
@pgCSRFProtect.exempt
|
||||
@login_required
|
||||
def browser_css():
|
||||
"""Render and return CSS snippets from the nodes and modules."""
|
||||
@@ -703,6 +724,148 @@ def get_nodes():
|
||||
return make_json_response(data=nodes)
|
||||
|
||||
|
||||
def form_master_password_response(existing=True, present=False, errmsg=None):
|
||||
content_new = (
|
||||
gettext("Set Master Password"),
|
||||
"<br/>".join([
|
||||
gettext("Please set a master password for pgAdmin."),
|
||||
gettext("This will be used to secure and later unlock saved "
|
||||
"passwords and other credentials.")])
|
||||
)
|
||||
content_existing = (
|
||||
gettext("Unlock Saved Passwords"),
|
||||
"<br/>".join([
|
||||
gettext("Please enter your master password."),
|
||||
gettext("This is required to unlock saved passwords and "
|
||||
"reconnect to the database server(s).")])
|
||||
)
|
||||
|
||||
return make_json_response(data={
|
||||
'present': present,
|
||||
'title': content_existing[0] if existing else content_new[0],
|
||||
'content': render_template(
|
||||
'browser/master_password.html',
|
||||
content_text=content_existing[1] if existing else content_new[1],
|
||||
errmsg=errmsg
|
||||
),
|
||||
'reset': existing
|
||||
})
|
||||
|
||||
|
||||
@blueprint.route("/master_password", endpoint="check_master_password",
|
||||
methods=["GET"])
|
||||
def check_master_password():
|
||||
"""
|
||||
Checks if the master password is available in the memory
|
||||
This password will be used to encrypt/decrypt saved server passwords
|
||||
"""
|
||||
return make_json_response(data=get_crypt_key()[0])
|
||||
|
||||
|
||||
@blueprint.route("/master_password", endpoint="reset_master_password",
|
||||
methods=["DELETE"])
|
||||
def reset_master_password():
|
||||
"""
|
||||
Removes the master password and remove all saved passwords
|
||||
This password will be used to encrypt/decrypt saved server passwords
|
||||
"""
|
||||
cleanup_master_password()
|
||||
return make_json_response(data=get_crypt_key()[0])
|
||||
|
||||
|
||||
@blueprint.route("/master_password", endpoint="set_master_password",
|
||||
methods=["POST"])
|
||||
def set_master_password():
|
||||
"""
|
||||
Set the master password and store in the memory
|
||||
This password will be used to encrypt/decrypt saved server passwords
|
||||
"""
|
||||
|
||||
data = None
|
||||
|
||||
if hasattr(request.data, 'decode'):
|
||||
data = request.data.decode('utf-8')
|
||||
|
||||
if data != '':
|
||||
data = json.loads(data)
|
||||
|
||||
# Master password is not applicable for server mode
|
||||
if not config.SERVER_MODE and config.MASTER_PASSWORD_REQUIRED:
|
||||
|
||||
if data != '' and data.get('password', '') != '':
|
||||
# if master pass is set previously
|
||||
if current_user.masterpass_check is not None:
|
||||
if not validate_master_password(data.get('password')):
|
||||
return form_master_password_response(
|
||||
existing=True,
|
||||
present=False,
|
||||
errmsg=gettext("Incorrect master password")
|
||||
)
|
||||
|
||||
# store the master pass in the memory
|
||||
set_crypt_key(data.get('password'))
|
||||
|
||||
if current_user.masterpass_check is None:
|
||||
# master check is not set, which means the server password
|
||||
# data is old and is encrypted with old key
|
||||
# Re-encrypt with new key
|
||||
|
||||
from pgadmin.browser.server_groups.servers.utils \
|
||||
import reencrpyt_server_passwords
|
||||
reencrpyt_server_passwords(
|
||||
current_user.id, current_user.password,
|
||||
data.get('password'))
|
||||
|
||||
# set the encrypted sample text with the new
|
||||
# master pass
|
||||
set_masterpass_check_text(data.get('password'))
|
||||
|
||||
elif not get_crypt_key()[0] and \
|
||||
current_user.masterpass_check is not None:
|
||||
return form_master_password_response(
|
||||
existing=True,
|
||||
present=False,
|
||||
)
|
||||
elif not get_crypt_key()[0]:
|
||||
return form_master_password_response(
|
||||
existing=False,
|
||||
present=False,
|
||||
)
|
||||
|
||||
# if master password is disabled now, but was used once then
|
||||
# remove all the saved passwords
|
||||
process_masterpass_disabled()
|
||||
|
||||
if config.SERVER_MODE and current_user.masterpass_check is None:
|
||||
|
||||
crypt_key = get_crypt_key()[1]
|
||||
from pgadmin.browser.server_groups.servers.utils \
|
||||
import reencrpyt_server_passwords
|
||||
reencrpyt_server_passwords(
|
||||
current_user.id, current_user.password, crypt_key)
|
||||
|
||||
set_masterpass_check_text(crypt_key)
|
||||
|
||||
return form_master_password_response(
|
||||
present=True,
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route("/lock_layout", endpoint="lock_layout", methods=["PUT"])
|
||||
def lock_layout():
|
||||
data = None
|
||||
|
||||
if hasattr(request.data, 'decode'):
|
||||
data = request.data.decode('utf-8')
|
||||
|
||||
if data != '':
|
||||
data = json.loads(data)
|
||||
|
||||
blueprint.lock_layout.set(data['value'])
|
||||
|
||||
return make_json_response()
|
||||
|
||||
|
||||
# Only register route if SECURITY_CHANGEABLE is set to True
|
||||
# We can't access app context here so cannot
|
||||
# use app.config['SECURITY_CHANGEABLE']
|
||||
@@ -711,6 +874,7 @@ def get_nodes():
|
||||
if hasattr(config, 'SECURITY_CHANGEABLE') and config.SECURITY_CHANGEABLE:
|
||||
@blueprint.route("/change_password", endpoint="change_password",
|
||||
methods=['GET', 'POST'])
|
||||
@pgCSRFProtect.exempt
|
||||
@login_required
|
||||
def change_password():
|
||||
"""View function which handles a change password request."""
|
||||
@@ -760,6 +924,15 @@ if hasattr(config, 'SECURITY_CHANGEABLE') and config.SECURITY_CHANGEABLE:
|
||||
if request.json is None and not has_error:
|
||||
after_this_request(_commit)
|
||||
do_flash(*get_message('PASSWORD_CHANGE'))
|
||||
|
||||
old_key = get_crypt_key()[1]
|
||||
set_crypt_key(form.new_password.data, False)
|
||||
|
||||
from pgadmin.browser.server_groups.servers.utils \
|
||||
import reencrpyt_server_passwords
|
||||
reencrpyt_server_passwords(
|
||||
current_user.id, old_key, form.new_password.data)
|
||||
|
||||
return redirect(get_url(_security.post_change_view) or
|
||||
get_url(_security.post_login_view))
|
||||
|
||||
@@ -794,6 +967,7 @@ if hasattr(config, 'SECURITY_RECOVERABLE') and config.SECURITY_RECOVERABLE:
|
||||
|
||||
@blueprint.route("/reset_password", endpoint="forgot_password",
|
||||
methods=['GET', 'POST'])
|
||||
@pgCSRFProtect.exempt
|
||||
@anonymous_user_required
|
||||
def forgot_password():
|
||||
"""View function that handles a forgotten password request."""
|
||||
@@ -860,10 +1034,10 @@ if hasattr(config, 'SECURITY_RECOVERABLE') and config.SECURITY_RECOVERABLE:
|
||||
methods=['GET', 'POST'],
|
||||
endpoint='reset_password'
|
||||
)
|
||||
@pgCSRFProtect.exempt
|
||||
@anonymous_user_required
|
||||
def reset_password(token):
|
||||
"""View function that handles a reset password request."""
|
||||
|
||||
expired, invalid, user = reset_password_token_status(token)
|
||||
|
||||
if invalid:
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
##########################################################################
|
||||
from flask_babelex import gettext
|
||||
|
||||
LOCK_LAYOUT_LEVEL = {
|
||||
'PREVENT_DOCKING': 'docking',
|
||||
'FULL': 'full',
|
||||
'NONE': 'none'
|
||||
}
|
||||
|
||||
|
||||
def register_browser_preferences(self):
|
||||
self.show_system_objects = self.preference.register(
|
||||
@@ -58,6 +64,21 @@ def register_browser_preferences(self):
|
||||
)
|
||||
)
|
||||
|
||||
self.lock_layout = self.preference.register(
|
||||
'display', 'lock_layout',
|
||||
gettext('Lock Layout'), 'radioModern', LOCK_LAYOUT_LEVEL['NONE'],
|
||||
category_label=gettext('Display'), options=[
|
||||
{'label': gettext('None'), 'value': LOCK_LAYOUT_LEVEL['NONE']},
|
||||
{'label': gettext('Prevent Docking'),
|
||||
'value': LOCK_LAYOUT_LEVEL['PREVENT_DOCKING']},
|
||||
{'label': gettext('Full Lock'),
|
||||
'value': LOCK_LAYOUT_LEVEL['FULL']},
|
||||
],
|
||||
help_str=gettext(
|
||||
'Lock the UI layout at different levels'
|
||||
)
|
||||
)
|
||||
|
||||
self.table_row_count_threshold = self.preference.register(
|
||||
'properties', 'table_row_count_threshold',
|
||||
gettext("Count rows if estimated less than"), 'integer', 2000,
|
||||
|
||||
@@ -26,6 +26,8 @@ import config
|
||||
from config import PG_DEFAULT_DRIVER
|
||||
from pgadmin.model import db, Server, ServerGroup, User
|
||||
from pgadmin.utils.driver import get_driver
|
||||
from pgadmin.utils.master_password import get_crypt_key
|
||||
from pgadmin.utils.exception import CryptKeyMissing
|
||||
|
||||
|
||||
def has_any(data, keys):
|
||||
@@ -117,9 +119,16 @@ class ServerModule(sg.ServerGroupPluginModule):
|
||||
driver = get_driver(PG_DEFAULT_DRIVER)
|
||||
|
||||
for server in servers:
|
||||
manager = driver.connection_manager(server.id)
|
||||
conn = manager.connection()
|
||||
connected = conn.connected()
|
||||
connected = False
|
||||
manager = None
|
||||
try:
|
||||
manager = driver.connection_manager(server.id)
|
||||
conn = manager.connection()
|
||||
connected = conn.connected()
|
||||
except CryptKeyMissing:
|
||||
# show the nodes at least even if not able to connect.
|
||||
pass
|
||||
|
||||
in_recovery = None
|
||||
wal_paused = None
|
||||
|
||||
@@ -723,6 +732,11 @@ class ServerNode(PGChildNodeView):
|
||||
request.data, encoding='utf-8'
|
||||
)
|
||||
|
||||
# Get enc key
|
||||
crypt_key_present, crypt_key = get_crypt_key()
|
||||
if not crypt_key_present:
|
||||
raise CryptKeyMissing
|
||||
|
||||
# Some fields can be provided with service file so they are optional
|
||||
if 'service' in data and not data['service']:
|
||||
required_args.extend([
|
||||
@@ -807,7 +821,7 @@ class ServerNode(PGChildNodeView):
|
||||
# login with password
|
||||
have_password = True
|
||||
password = data['password']
|
||||
password = encrypt(password, current_user.password)
|
||||
password = encrypt(password, crypt_key)
|
||||
elif 'passfile' in data and data["passfile"] != '':
|
||||
passfile = data['passfile']
|
||||
setattr(server, 'passfile', passfile)
|
||||
@@ -817,7 +831,7 @@ class ServerNode(PGChildNodeView):
|
||||
have_tunnel_password = True
|
||||
tunnel_password = data['tunnel_password']
|
||||
tunnel_password = \
|
||||
encrypt(tunnel_password, current_user.password)
|
||||
encrypt(tunnel_password, crypt_key)
|
||||
|
||||
status, errmsg = conn.connect(
|
||||
password=password,
|
||||
@@ -930,7 +944,7 @@ class ServerNode(PGChildNodeView):
|
||||
"servers/supported_servers.js",
|
||||
server_types=ServerType.types()
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
200, {'Content-Type': 'application/javascript'}
|
||||
)
|
||||
|
||||
def connect_status(self, gid, sid):
|
||||
@@ -998,6 +1012,11 @@ class ServerNode(PGChildNodeView):
|
||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
||||
conn = manager.connection()
|
||||
|
||||
# Get enc key
|
||||
crypt_key_present, crypt_key = get_crypt_key()
|
||||
if not crypt_key_present:
|
||||
raise CryptKeyMissing
|
||||
|
||||
# If server using SSH Tunnel
|
||||
if server.use_ssh_tunnel:
|
||||
if 'tunnel_password' not in data:
|
||||
@@ -1014,12 +1033,12 @@ class ServerNode(PGChildNodeView):
|
||||
# Encrypt the password before saving with user's login
|
||||
# password key.
|
||||
try:
|
||||
tunnel_password = encrypt(tunnel_password, user.password) \
|
||||
tunnel_password = encrypt(tunnel_password, crypt_key) \
|
||||
if tunnel_password is not None else \
|
||||
server.tunnel_password
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
return internal_server_error(errormsg=e.message)
|
||||
return internal_server_error(errormsg=str(e))
|
||||
|
||||
if 'password' not in data:
|
||||
conn_passwd = getattr(conn, 'password', None)
|
||||
@@ -1038,11 +1057,11 @@ class ServerNode(PGChildNodeView):
|
||||
# Encrypt the password before saving with user's login
|
||||
# password key.
|
||||
try:
|
||||
password = encrypt(password, user.password) \
|
||||
password = encrypt(password, crypt_key) \
|
||||
if password is not None else server.password
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
return internal_server_error(errormsg=e.message)
|
||||
return internal_server_error(errormsg=str(e))
|
||||
|
||||
# Check do we need to prompt for the database server or ssh tunnel
|
||||
# password or both. Return the password template in case password is
|
||||
@@ -1235,6 +1254,7 @@ class ServerNode(PGChildNodeView):
|
||||
"""
|
||||
try:
|
||||
data = json.loads(request.form['data'], encoding='utf-8')
|
||||
crypt_key = get_crypt_key()[1]
|
||||
|
||||
# Fetch Server Details
|
||||
server = Server.query.filter_by(id=sid).first()
|
||||
@@ -1292,7 +1312,7 @@ class ServerNode(PGChildNodeView):
|
||||
|
||||
# Check against old password only if no pgpass file
|
||||
if not is_passfile:
|
||||
decrypted_password = decrypt(manager.password, user.password)
|
||||
decrypted_password = decrypt(manager.password, crypt_key)
|
||||
|
||||
if isinstance(decrypted_password, bytes):
|
||||
decrypted_password = decrypted_password.decode()
|
||||
@@ -1328,7 +1348,7 @@ class ServerNode(PGChildNodeView):
|
||||
|
||||
# Store password in sqlite only if no pgpass file
|
||||
if not is_passfile:
|
||||
password = encrypt(data['newPassword'], user.password)
|
||||
password = encrypt(data['newPassword'], crypt_key)
|
||||
# Check if old password was stored in pgadmin4 sqlite database.
|
||||
# If yes then update that password.
|
||||
if server.password is not None and config.ALLOW_SAVE_PASSWORD:
|
||||
@@ -1472,6 +1492,7 @@ class ServerNode(PGChildNodeView):
|
||||
|
||||
def get_response_for_password(self, server, status, prompt_password=False,
|
||||
prompt_tunnel_password=False, errmsg=None):
|
||||
|
||||
if server.use_ssh_tunnel:
|
||||
return make_json_response(
|
||||
success=0,
|
||||
@@ -1498,7 +1519,7 @@ class ServerNode(PGChildNodeView):
|
||||
server_label=server.name,
|
||||
username=server.username,
|
||||
errmsg=errmsg,
|
||||
_=gettext
|
||||
_=gettext,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -166,7 +166,6 @@ class DatabaseView(PGChildNodeView):
|
||||
def wrap(f):
|
||||
@wraps(f)
|
||||
def wrapped(self, *args, **kwargs):
|
||||
|
||||
self.manager = get_driver(
|
||||
PG_DEFAULT_DRIVER
|
||||
).connection_manager(
|
||||
@@ -805,7 +804,12 @@ class DatabaseView(PGChildNodeView):
|
||||
data = {}
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
# comments should be taken as is because if user enters a
|
||||
# json comment it is parsed by loads which should not happen
|
||||
if k in ('comments',):
|
||||
data[k] = v
|
||||
else:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
status, res = self.get_sql(gid, sid, data, did)
|
||||
|
||||
@@ -208,7 +208,7 @@ class CastView(PGChildNodeView):
|
||||
).connection_manager(kwargs['sid'])
|
||||
self.conn = self.manager.connection(did=kwargs['did'])
|
||||
# Set template path for the SQL scripts
|
||||
self.template_path = 'cast/sql/#{0}#'.format(self.manager.version)
|
||||
self.template_path = 'casts/sql/#{0}#'.format(self.manager.version)
|
||||
|
||||
return f(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Cast: money -> bigint
|
||||
|
||||
-- DROP CAST (money AS bigint);
|
||||
|
||||
CREATE CAST (money AS bigint)
|
||||
WITHOUT FUNCTION
|
||||
AS IMPLICIT;
|
||||
|
||||
COMMENT ON CAST (money AS bigint) IS 'Cast from money to bigint';
|
||||
@@ -0,0 +1,7 @@
|
||||
-- Cast: money -> bigint
|
||||
|
||||
-- DROP CAST (money AS bigint);
|
||||
|
||||
CREATE CAST (money AS bigint)
|
||||
WITHOUT FUNCTION
|
||||
AS IMPLICIT;
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"scenarios": [
|
||||
{
|
||||
"type": "create",
|
||||
"name": "Create IMPLICIT Cast",
|
||||
"endpoint": "NODE-cast.obj",
|
||||
"sql_endpoint": "NODE-cast.sql_id",
|
||||
"data": {
|
||||
"castcontext": "IMPLICIT",
|
||||
"encoding": "UTF8",
|
||||
"name": "money->bigint",
|
||||
"srctyp": "money",
|
||||
"trgtyp": "bigint"
|
||||
},
|
||||
"expected_sql_file": "create_implicit_cast.sql"
|
||||
},
|
||||
{
|
||||
"type": "alter",
|
||||
"name": "Alter IMPLICIT Cast",
|
||||
"endpoint": "NODE-cast.obj_id",
|
||||
"sql_endpoint": "NODE-cast.sql_id",
|
||||
"data": {
|
||||
"description": "Cast from money to bigint"
|
||||
},
|
||||
"expected_sql_file": "alter_implicit_cast.sql"
|
||||
},
|
||||
{
|
||||
"type": "delete",
|
||||
"name": "Drop IMPLICIT Cast",
|
||||
"endpoint": "NODE-cast.delete_id",
|
||||
"data": {
|
||||
"name": "money->bigint"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "create",
|
||||
"name":"Create EXPLICIT Cast",
|
||||
"endpoint": "NODE-cast.obj",
|
||||
"sql_endpoint": "NODE-cast.sql_id",
|
||||
"data": {
|
||||
"castcontext": "EXPLICIT",
|
||||
"encoding": "UTF8",
|
||||
"name": "money->bigint",
|
||||
"srctyp": "money",
|
||||
"trgtyp": "bigint"
|
||||
},
|
||||
"expected_sql": "-- Cast: money -> bigint\n\n-- DROP CAST (money AS bigint);\n\nCREATE CAST (money AS bigint)\n\tWITHOUT FUNCTION;"
|
||||
},
|
||||
{
|
||||
"type": "alter",
|
||||
"name": "Alter EXPLICIT Cast",
|
||||
"endpoint": "NODE-cast.obj_id",
|
||||
"sql_endpoint": "NODE-cast.sql_id",
|
||||
"data": {
|
||||
"description": "Cast from money to bigint"
|
||||
},
|
||||
"expected_sql": "-- Cast: money -> bigint\n\n-- DROP CAST (money AS bigint);\n\nCREATE CAST (money AS bigint)\n\tWITHOUT FUNCTION;\n\nCOMMENT ON CAST (money AS bigint) IS 'Cast from money to bigint';"
|
||||
},
|
||||
{
|
||||
"type": "delete",
|
||||
"name": "Drop EXPLICIT Cast",
|
||||
"endpoint": "NODE-cast.delete_id",
|
||||
"data": {
|
||||
"name": "money->bigint"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -555,7 +555,12 @@ class ForeignDataWrapperView(PGChildNodeView):
|
||||
data = {}
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
# comments should be taken as is because if user enters a
|
||||
# json comment it is parsed by loads which should not happen
|
||||
if k in ('description',):
|
||||
data[k] = v
|
||||
else:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
try:
|
||||
|
||||
@@ -116,10 +116,6 @@ class ForeignServerView(PGChildNodeView):
|
||||
* __init__(**kwargs)
|
||||
- Method is used to initialize the ForeignServerView and it's base view.
|
||||
|
||||
* module_js()
|
||||
- This property defines (if javascript) exists for this node.
|
||||
Override this property for your own logic
|
||||
|
||||
* check_precondition()
|
||||
- This function will behave as a decorator which will checks
|
||||
database connection before running view, it will also attaches
|
||||
@@ -192,23 +188,9 @@ class ForeignServerView(PGChildNodeView):
|
||||
'msql': [{'get': 'msql'}, {'get': 'msql'}],
|
||||
'stats': [{'get': 'statistics'}],
|
||||
'dependency': [{'get': 'dependencies'}],
|
||||
'dependent': [{'get': 'dependents'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}]
|
||||
'dependent': [{'get': 'dependents'}]
|
||||
})
|
||||
|
||||
def module_js(self):
|
||||
"""
|
||||
This property defines (if javascript) exists for this node.
|
||||
Override this property for your own logic.
|
||||
"""
|
||||
return make_response(
|
||||
render_template(
|
||||
"foreign_servers/js/foreign_servers.js",
|
||||
_=gettext
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
)
|
||||
|
||||
def check_precondition(f):
|
||||
"""
|
||||
This function will behave as a decorator which will checks
|
||||
@@ -578,7 +560,12 @@ class ForeignServerView(PGChildNodeView):
|
||||
data = {}
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
# comments should be taken as is because if user enters a
|
||||
# json comment it is parsed by loads which should not happen
|
||||
if k in ('description',):
|
||||
data[k] = v
|
||||
else:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
try:
|
||||
|
||||
@@ -132,10 +132,6 @@ class UserMappingView(PGChildNodeView):
|
||||
* __init__(**kwargs)
|
||||
- Method is used to initialize the UserMappingView and it's base view.
|
||||
|
||||
* module_js()
|
||||
- This property defines (if javascript) exists for this node.
|
||||
Override this property for your own logic
|
||||
|
||||
* check_precondition()
|
||||
- This function will behave as a decorator which will checks
|
||||
database connection before running view, it will also attaches
|
||||
@@ -209,23 +205,9 @@ class UserMappingView(PGChildNodeView):
|
||||
'msql': [{'get': 'msql'}, {'get': 'msql'}],
|
||||
'stats': [{'get': 'statistics'}],
|
||||
'dependency': [{'get': 'dependencies'}],
|
||||
'dependent': [{'get': 'dependents'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}]
|
||||
'dependent': [{'get': 'dependents'}]
|
||||
})
|
||||
|
||||
def module_js(self):
|
||||
"""
|
||||
This property defines (if javascript) exists for this node.
|
||||
Override this property for your own logic.
|
||||
"""
|
||||
return make_response(
|
||||
render_template(
|
||||
"user_mappings/js/user_mappings.js",
|
||||
_=gettext
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
)
|
||||
|
||||
def check_precondition(f):
|
||||
"""
|
||||
This function will behave as a decorator which will checks
|
||||
@@ -558,7 +558,12 @@ class LanguageView(PGChildNodeView):
|
||||
data = {}
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
# comments should be taken as is because if user enters a
|
||||
# json comment it is parsed by loads which should not happen
|
||||
if k in ('description',):
|
||||
data[k] = v
|
||||
else:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
try:
|
||||
|
||||
@@ -237,7 +237,7 @@ class SchemaView(PGChildNodeView):
|
||||
self.manager = None
|
||||
self.conn = None
|
||||
self.template_path = None
|
||||
self.template_initial = 'schema'
|
||||
self.template_initial = 'schemas'
|
||||
|
||||
@staticmethod
|
||||
def ppas_template_path(ver):
|
||||
@@ -724,7 +724,12 @@ It may have been removed by another user.
|
||||
data = dict()
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
# comments should be taken as is because if user enters a
|
||||
# json comment it is parsed by loads which should not happen
|
||||
if k in ('description',):
|
||||
data[k] = v
|
||||
else:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
|
||||
|
||||
@@ -128,8 +128,7 @@ class CatalogObjectView(PGChildNodeView):
|
||||
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
|
||||
'sql': [{'get': 'sql'}],
|
||||
'dependency': [{'get': 'dependencies'}],
|
||||
'dependent': [{'get': 'dependents'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}]
|
||||
'dependent': [{'get': 'dependents'}]
|
||||
})
|
||||
|
||||
def check_precondition(f):
|
||||
|
||||
@@ -155,8 +155,7 @@ class CatalogObjectColumnsView(PGChildNodeView):
|
||||
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
|
||||
'sql': [{'get': 'sql'}],
|
||||
'dependency': [{'get': 'dependencies'}],
|
||||
'dependent': [{'get': 'dependents'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}]
|
||||
'dependent': [{'get': 'dependents'}]
|
||||
})
|
||||
|
||||
def check_precondition(f):
|
||||
|
||||
@@ -171,7 +171,6 @@ class CollationView(PGChildNodeView):
|
||||
'stats': [{'get': 'statistics'}],
|
||||
'dependency': [{'get': 'dependencies'}],
|
||||
'dependent': [{'get': 'dependents'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}],
|
||||
'get_collations': [{'get': 'get_collation'},
|
||||
{'get': 'get_collation'}]
|
||||
})
|
||||
@@ -193,7 +192,7 @@ class CollationView(PGChildNodeView):
|
||||
self.conn = self.manager.connection(did=kwargs['did'])
|
||||
# Set the template path for the SQL scripts
|
||||
self.template_path = compile_template_path(
|
||||
'collation/sql/',
|
||||
'collations/sql/',
|
||||
self.manager.server_type,
|
||||
self.manager.version
|
||||
)
|
||||
@@ -602,7 +601,12 @@ class CollationView(PGChildNodeView):
|
||||
data = dict()
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
# comments should be taken as is because if user enters a
|
||||
# json comment it is parsed by loads which should not happen
|
||||
if k in ('description',):
|
||||
data[k] = v
|
||||
else:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
|
||||
|
||||
@@ -11,6 +11,5 @@ from pgadmin.utils.route import BaseTestGenerator
|
||||
|
||||
|
||||
class CollationTestGenerator(BaseTestGenerator):
|
||||
|
||||
def generate_tests(self):
|
||||
def runTest(self):
|
||||
return
|
||||
@@ -95,9 +95,6 @@ class DomainView(PGChildNodeView, DataTypeReader):
|
||||
- Works as a decorator.
|
||||
Validating request on the request of create, update and modified SQL.
|
||||
|
||||
* module_js():
|
||||
- Load JS file (domains.js) for this module.
|
||||
|
||||
* check_precondition(f):
|
||||
- Works as a decorator.
|
||||
- Checks database connection status.
|
||||
@@ -168,7 +165,6 @@ class DomainView(PGChildNodeView, DataTypeReader):
|
||||
'stats': [{'get': 'statistics'}],
|
||||
'dependency': [{'get': 'dependencies'}],
|
||||
'dependent': [{'get': 'dependents'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}],
|
||||
'get_types': [{'get': 'types'}, {'get': 'types'}],
|
||||
'get_collations': [
|
||||
{'get': 'get_collations'},
|
||||
@@ -246,18 +242,6 @@ class DomainView(PGChildNodeView, DataTypeReader):
|
||||
|
||||
return wrap
|
||||
|
||||
def module_js(self):
|
||||
"""
|
||||
Load JS file (domains.js) for this module.
|
||||
"""
|
||||
return make_response(
|
||||
render_template(
|
||||
"domains/js/domains.js",
|
||||
_=gettext
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
)
|
||||
|
||||
def check_precondition(f):
|
||||
"""
|
||||
Works as a decorator.
|
||||
|
||||
@@ -109,9 +109,6 @@ class ForeignTableView(PGChildNodeView, DataTypeReader):
|
||||
- Works as a decorator.
|
||||
Validating request on the request of create, update and modified SQL.
|
||||
|
||||
* module_js():
|
||||
- Overrides this property to define javascript for Foreign Table node.
|
||||
|
||||
* check_precondition(f):
|
||||
- Works as a decorator.
|
||||
- Checks database connection status.
|
||||
@@ -204,7 +201,6 @@ class ForeignTableView(PGChildNodeView, DataTypeReader):
|
||||
'stats': [{'get': 'statistics'}],
|
||||
'dependency': [{'get': 'dependencies'}],
|
||||
'dependent': [{'get': 'dependents'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}],
|
||||
'get_collations': [
|
||||
{'get': 'get_collations'},
|
||||
{'get': 'get_collations'}
|
||||
@@ -319,18 +315,6 @@ class ForeignTableView(PGChildNodeView, DataTypeReader):
|
||||
|
||||
return wrap
|
||||
|
||||
def module_js(self):
|
||||
"""
|
||||
Load JS file (foreign_tables.js) for this module.
|
||||
"""
|
||||
return make_response(
|
||||
render_template(
|
||||
"foreign_tables/js/foreign_tables.js",
|
||||
_=gettext
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
)
|
||||
|
||||
def check_precondition(f):
|
||||
"""
|
||||
Works as a decorator.
|
||||
@@ -1208,7 +1192,9 @@ class ForeignTableView(PGChildNodeView, DataTypeReader):
|
||||
|
||||
if db_variables is not None:
|
||||
for row in db_variables:
|
||||
var_name, var_value = row.split("=")
|
||||
# The value may contain equals in string, split on
|
||||
# first equals only
|
||||
var_name, var_value = row.split("=", 1)
|
||||
|
||||
var_dict = {'option': var_name, 'value': var_value}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
SELECT
|
||||
oid as conoid, conname, contype, pg_get_constraintdef(oid, true) as consrc,
|
||||
connoinherit, convalidated, conislocal
|
||||
FROM
|
||||
pg_constraint
|
||||
WHERE
|
||||
conrelid={{foid}}::oid
|
||||
ORDER by conname;
|
||||
@@ -108,10 +108,6 @@ class FtsConfigurationView(PGChildNodeView):
|
||||
- Method is used to initialize the FtsConfigurationView and it's base
|
||||
view.
|
||||
|
||||
* module_js()
|
||||
- This property defines (if javascript) exists for this node.
|
||||
Override this property for your own logic
|
||||
|
||||
* check_precondition()
|
||||
- This function will behave as a decorator which will checks
|
||||
database connection before running view, it will also attaches
|
||||
@@ -200,7 +196,6 @@ class FtsConfigurationView(PGChildNodeView):
|
||||
'stats': [{'get': 'statistics'}],
|
||||
'dependency': [{'get': 'dependencies'}],
|
||||
'dependent': [{'get': 'dependents'}],
|
||||
'module.js': [{}, {}, {'get': 'module_js'}],
|
||||
'parsers': [{'get': 'parsers'},
|
||||
{'get': 'parsers'}],
|
||||
'copyConfig': [{'get': 'copyConfig'},
|
||||
@@ -215,18 +210,6 @@ class FtsConfigurationView(PGChildNodeView):
|
||||
self.manager = None
|
||||
super(FtsConfigurationView, self).__init__(**kwargs)
|
||||
|
||||
def module_js(self):
|
||||
"""
|
||||
Load JS file (fts_configuration.js) for this module.
|
||||
"""
|
||||
return make_response(
|
||||
render_template(
|
||||
"fts_configuration/js/fts_configuration.js",
|
||||
_=_
|
||||
),
|
||||
200, {'Content-Type': 'application/x-javascript'}
|
||||
)
|
||||
|
||||
def check_precondition(f):
|
||||
"""
|
||||
This function will behave as a decorator which will checks
|
||||
@@ -242,7 +225,7 @@ class FtsConfigurationView(PGChildNodeView):
|
||||
kwargs['sid'])
|
||||
self.conn = self.manager.connection(did=kwargs['did'])
|
||||
# Set the template path for the SQL scripts
|
||||
self.template_path = 'fts_configuration/sql/#{0}#'.format(
|
||||
self.template_path = 'fts_configurations/sql/#{0}#'.format(
|
||||
self.manager.version)
|
||||
|
||||
return f(*args, **kwargs)
|
||||
@@ -612,7 +595,12 @@ class FtsConfigurationView(PGChildNodeView):
|
||||
data = {}
|
||||
for k, v in request.args.items():
|
||||
try:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
# comments should be taken as is because if user enters a
|
||||
# json comment it is parsed by loads which should not happen
|
||||
if k in ('description',):
|
||||
data[k] = v
|
||||
else:
|
||||
data[k] = json.loads(v, encoding='utf-8')
|
||||
except ValueError:
|
||||
data[k] = v
|
||||
|
||||
|
||||