Compare commits

...
45 Commits
Author SHA1 Message Date
Aditya Toshniwal a7fd7d67b3 1. Intermittently failing test cases.
2. Dialog vertical position should be more on the upper side.
3. File not supported error is not showing.

refs #7313
2022-07-25 11:48:27 +05:30
Akshay Joshi 96c360e728 Update version for release. 2022-07-25 09:33:11 +05:30
Akshay Joshi 3e3f06c55d Fixed an issue where Autocomplete did not work after pressing CTRL/CMD + Space
for the second time and autocomplete on keypress is off. Fixes #7563
2022-07-22 18:49:46 +05:30
Akshay Joshi 1745aaaa0a Update Dependencies 2022-07-22 16:11:01 +05:30
Akshay Joshi c67ed7d7d2 Update message catalogs. 2022-07-22 16:09:43 +05:30
Yogesh Mahajan f6d8f9f5c5 Fixed feature tests. 2022-07-22 16:07:25 +05:30
Akshay Joshi a4c12555cb Fixed docker build. 2022-07-22 13:04:24 +05:30
Aditya Toshniwal 8d1868305d Fixed Jasmine test cases.
refs #7313
2022-07-21 15:45:51 +05:30
Akshay Joshi 2cdee919c1 Fixed audit issues. 2022-07-21 13:44:19 +05:30
Yogesh Mahajan 3f8c36789f Fixed copy command error on Windows in Import/Export data.
refs #7428
2022-07-21 13:07:42 +05:30
Akshay Joshi 631a08189a 1) Improved autocomplete loading indicator.
2) Avoid too many ajax requests per key press.

refs #4488
2022-07-20 18:02:45 +05:30
Aditya Toshniwal 4808df5e95 1) Port the file/storage manager to React. Fixes #7313
2) Allow users to delete files/folders from the storage manager. Fixes #4607
3) Allow users to search within the file/storage manager. Fixes #7389
4) Fixed an issue where new folders cannot be created in the save dialog. Fixes #7524
2022-07-19 15:27:47 +05:30
Akshay Joshi 4585597388 Added option to trigger autocomplete on key press in the query tool. Fixes #4488 2022-07-19 11:55:37 +05:30
Yogesh Mahajan 8f73956d1e Fixed API test cases.
refs #7428
2022-07-18 18:15:46 +05:30
Yogesh Mahajan 8f2bda2309 1) Preserve the settings set by the user in the import/export data dialog. Fixes #7428
2) Fixed the JSON editor issue of hiding the first record. Fixes #7520
2022-07-18 10:54:03 +05:30
Aditya Toshniwal 9dde195af4 Ensure that notices should not disappear from the messages tab. Fixes #7512 2022-07-14 18:05:36 +05:30
Aditya Toshniwal 9468c80a78 Fixed an issue where comments on indexes are not displayed. Fixes #7508 2022-07-14 18:01:31 +05:30
Aditya Toshniwal 1fecf70123 Ensure that geometry should be shown for all the selected cells. Fixes #7519 2022-07-14 17:38:42 +05:30
Akshay Joshi a11f42b3e0 Fixed audit issues. 2022-07-14 11:24:17 +05:30
Yogesh Mahajan 683528b81c Fixed feature tests. 2022-07-12 11:10:23 +05:30
Akshay Joshi 02b0f35442 1) Port change password dialog to React. Fixes #7341
2) Port named restore point dialog to React. Fixes #7546
2022-07-11 13:39:09 +05:30
Yogesh Mahajan 1a5e60c44f Fixed feature tests. 2022-07-08 18:25:00 +05:30
Akshay Joshi a16e3523bd Ensure that the splash screen can be moved. Fixes # 7471 2022-07-07 14:11:36 +05:30
Akshay Joshi 50c930f5fa Make the controls on the Graph Visualiser collapsible and reorganize the controls. 2022-07-07 14:10:10 +05:30
Akshay Joshi 9cdcad7a88 1) Keep the selected columns for the X and Y-axis.
2) Lighten the color of the stacked line chart.
2022-07-06 18:58:15 +05:30
Yogesh Mahajan 59f5c0d955 Added support for Azure PostgreSQL deployment in server mode. Fixes #7522 2022-07-06 11:43:49 +05:30
Nikhil Mohite 64e6700228 Changed the default position of the dialog. 2022-07-05 18:46:49 +05:30
Akshay Joshi 7414394ceb Fixed the following issues in the Graph Visualiser:
1) Change all theme's color codes.
 2) Bar graph for the first and last labels on the X-axis does not display all the bars.
 3) Make the graphs always start from zero on the Y-axis.
2022-07-05 18:44:20 +05:30
Akshay Joshi 8b62cd1f04 Added support for visualise the graph using a Pie chart in the query tool. Fixes #7487 2022-07-05 10:47:17 +05:30
Aditya Toshniwal 239e3bb6f1 Some more theme related fixes. 2022-07-04 14:26:33 +05:30
Nikhil Mohite b283c0ba18 Port Master Password dialog to React. Fixes #7342 2022-07-04 12:16:23 +05:30
Pradip Parkale e59471d87d Ensure that dashboard graph API is not called after the panel has been closed. Fixes #7518 2022-07-04 12:03:18 +05:30
Akshay Joshi 6bebce7015 Fixed PEP8 2022-07-04 11:15:38 +05:30
Akshay Joshi eb65ab6b4b Fixed no module named 'setup' error. 2022-07-04 10:33:12 +05:30
Akshay Joshi 3a69d50458 Change the logic to call setup_db if the database doesn't exist. 2022-07-01 18:12:00 +05:30
Aditya Toshniwal 9c45a62b53 Fixed jasmine test cases introduced by the theme patch. 2022-07-01 15:01:51 +05:30
Fahar Abbas ed59584fe4 Fixed typo error for Statistics on the table header. Fixes #7523 2022-07-01 14:58:45 +05:30
Akshay Joshi bf7de8e7a5 Fixed an issue where the create_app() function was called twice if an SQLite database file was not present.
Remove the exec call to run the 'setup.py' instead call the setup database function directly.
2022-07-01 14:50:12 +05:30
Nikhil Mohite 9c745db413 Enable the start debugging button once execution is completed. Fixes #7517 2022-07-01 12:41:06 +05:30
Aditya Toshniwal 7d0ed90796 Fixed API test cases.
refs #7492
2022-07-01 12:07:43 +05:30
Fred Escallier b0681f55e9 Update French translation. 2022-07-01 12:06:34 +05:30
Aditya Toshniwal 86b30b3c83 Theme component usage has been fixed, a component should never be wrapped in multiple Theme components. 2022-06-30 15:18:20 +05:30
Akshay Joshi b92e2fcfc9 Added support for visualizing the graphs using Stacked Line, Bar, and Stacked Bar charts in the query tool. Fixes #7486 2022-06-30 12:38:48 +05:30
Akshay Joshi b1f6664292 Added some logging for start-up time and changed the ping time from every second to 250 milliseconds. 2022-06-30 11:11:30 +05:30
Aditya Toshniwal cb635f6706 Removing dynamic module loading and replacing it with static loading. Fixes #7492
Gets rid of all occurrences and usage of get_own_javascripts since it is no longer used.
2022-06-30 11:06:50 +05:30
241 changed files with 22176 additions and 21127 deletions

No files matched your search

+30 -8
View File
@@ -195,6 +195,7 @@ aspen-core 1.0.5
aspen-decorations 1.1.1 MIT git+https://github.com/neeksandhu/aspen-decorations.git
aspen-tree-model 1.0.5 MIT git+https://github.com/neeksandhu/aspen-tree-model.git
async 2.6.4 MIT https://github.com/caolan/async.git
attr-accept 2.2.2 MIT https://github.com/react-dropzone/attr-accept.git
axios 0.21.4 MIT https://github.com/axios/axios.git
babel-plugin-styled-components 1.13.3 MIT https://github.com/styled-components/babel-plugin-styled-components.git
babel-plugin-syntax-jsx 6.18.0 MIT https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-jsx
@@ -239,6 +240,7 @@ commander 7.2.0
concat-map 0.0.1 MIT git://github.com/substack/node-concat-map.git
context-menu 2.0.0 MIT https://github.com/chipto/context-menu.git
convert-source-map 1.8.0 MIT git://github.com/thlorenz/convert-source-map.git
convert-units 2.3.4 MIT git://github.com/ben-ng/convert-units.git
core-js 3.19.1 MIT https://github.com/zloirock/core-js.git
css-color-names 1.0.1 MIT git://github.com/bahamas10/css-color-names.git
css-line-break 2.0.1 MIT git+ssh://git@github.com/niklasvh/css-line-break.git
@@ -255,8 +257,7 @@ csstype 2.6.18
csstype 3.0.9 MIT https://github.com/frenic/csstype
dagre 0.8.5 MIT https://github.com/dagrejs/dagre.git
date-fns 2.25.0 MIT https://github.com/date-fns/date-fns
debug 4.3.2 MIT git://github.com/visionmedia/debug.git
debug 4.3.3 MIT git://github.com/debug-js/debug.git
debug 4.3.4 MIT git://github.com/debug-js/debug.git
decamelize 5.0.1 MIT https://github.com/sindresorhus/decamelize.git
decamelize 1.2.0 MIT https://github.com/sindresorhus/decamelize.git
decamelize-keys 1.1.0 MIT https://github.com/dsblv/decamelize-keys.git
@@ -266,7 +267,6 @@ dir-glob 3.0.1
dom-align 1.12.2 MIT git@github.com:yiminghe/dom-align.git
dom-helpers 5.2.1 MIT git+https://github.com/react-bootstrap/dom-helpers.git
dom-serializer 1.3.2 MIT git://github.com/cheeriojs/dom-renderer.git
dropzone 5.9.3 MIT https://github.com/dropzone/dropzone.git
emojis-list 3.0.0 MIT git+https://github.com/kikobeats/emojis-list.git
engine.io-client 6.0.2 MIT https://github.com/socketio/engine.io-client.git
engine.io-parser 5.0.3 MIT git@github.com:socketio/engine.io-parser.git
@@ -277,6 +277,7 @@ fast-deep-equal 3.1.3
fast-glob 3.2.11 MIT https://github.com/mrmlnc/fast-glob.git
fast-json-stable-stringify 2.1.0 MIT git://github.com/epoberezkin/fast-json-stable-stringify.git
fast-memoize 2.5.2 MIT git+https://github.com/caiogondim/fast-memoize.git
file-selector 0.6.0 MIT https://github.com/react-dropzone/file-selector.git
filename-reserved-regex 2.0.0 MIT https://github.com/sindresorhus/filename-reserved-regex.git
fill-range 7.0.1 MIT https://github.com/jonschlinkert/fill-range.git
find-up 5.0.0 MIT https://github.com/sindresorhus/find-up.git
@@ -315,7 +316,7 @@ is-unc-path 1.0.0
javascript-natural-sort 0.7.1 MIT https://github.com/Bill4Time/javascript-natural-sort.git
jquery 3.6.0 MIT https://github.com/jquery/jquery.git
jquery-contextmenu 2.9.2 MIT git://github.com/swisnl/jQuery-contextMenu.git
jquery-ui 1.13.0 MIT git://github.com/jquery/jquery-ui.git
jquery-ui 1.13.2 MIT git://github.com/jquery/jquery-ui.git
js-tokens 4.0.0 MIT https://github.com/lydell/js-tokens.git
jsbn 1.1.0 MIT https://github.com/andyperlitch/jsbn.git
jsesc 2.5.2 MIT https://github.com/mathiasbynens/jsesc.git
@@ -343,7 +344,26 @@ loader-utils 2.0.2
loader-utils 1.4.0 MIT https://github.com/webpack/loader-utils.git
locate-path 6.0.0 MIT https://github.com/sindresorhus/locate-path.git
lodash 4.17.21 MIT https://github.com/lodash/lodash.git
lodash._basebind 2.3.0 MIT https://github.com/lodash/lodash-cli.git
lodash._basecreate 2.3.0 MIT https://github.com/lodash/lodash-cli.git
lodash._basecreatecallback 2.3.0 MIT https://github.com/lodash/lodash-cli.git
lodash._basecreatewrapper 2.3.0 MIT https://github.com/lodash/lodash-cli.git
lodash._createwrapper 2.3.0 MIT https://github.com/lodash/lodash-cli.git
lodash._objecttypes 2.3.0 MIT https://github.com/lodash/lodash-cli.git
lodash._renative 2.3.0 MIT https://github.com/lodash/lodash-cli.git
lodash._setbinddata 2.3.0 MIT https://github.com/lodash/lodash-cli.git
lodash._shimkeys 2.3.0 MIT https://github.com/lodash/lodash-cli.git
lodash._slice 2.3.0 MIT https://github.com/lodash/lodash-cli.git
lodash.bind 2.3.0 MIT https://github.com/lodash/lodash-cli.git
lodash.foreach 2.3.0 MIT https://github.com/lodash/lodash-cli.git
lodash.forown 2.3.0 MIT https://github.com/lodash/lodash-cli.git
lodash.identity 2.3.0 MIT https://github.com/lodash/lodash-cli.git
lodash.isfunction 2.3.0 MIT https://github.com/lodash/lodash-cli.git
lodash.isobject 2.3.0 MIT https://github.com/lodash/lodash-cli.git
lodash.keys 2.3.0 MIT https://github.com/lodash/lodash-cli.git
lodash.memoize 4.1.2 MIT https://github.com/lodash/lodash.git
lodash.noop 2.3.0 MIT https://github.com/lodash/lodash-cli.git
lodash.support 2.3.0 MIT https://github.com/lodash/lodash-cli.git
lodash.uniq 4.5.0 MIT https://github.com/lodash/lodash.git
loose-envify 1.4.0 MIT git://github.com/zertosh/loose-envify.git
lunr 0.7.2 MIT https://github.com/olivernn/lunr.js.git
@@ -364,9 +384,8 @@ ml-array-min 1.2.2
ml-array-rescale 1.3.6 MIT git+https://github.com/mljs/array.git
ml-matrix 6.8.0 MIT https://github.com/mljs/matrix.git
mobius1-selectr 2.4.13 MIT git+https://github.com/Mobius1/Selectr.git
moment 2.29.3 MIT https://github.com/moment/moment.git
moment 2.29.4 MIT https://github.com/moment/moment.git
moment-timezone 0.5.34 MIT https://github.com/moment/moment-timezone.git
moment-timezone 0.5.33 MIT https://github.com/moment/moment-timezone.git
ms 2.1.2 MIT https://github.com/zeit/ms.git
nanoid 3.3.2 MIT https://github.com/ai/nanoid.git
nanoid 3.2.0 MIT https://github.com/ai/nanoid.git
@@ -425,6 +444,7 @@ postcss-svgo 5.0.3
postcss-unique-selectors 5.0.1 MIT https://github.com/cssnano/cssnano.git
postcss-value-parser 4.1.0 MIT https://github.com/TrySound/postcss-value-parser.git
prop-types 15.7.2 MIT https://github.com/facebook/prop-types.git
prop-types 15.8.1 MIT https://github.com/facebook/prop-types.git
punycode 2.1.1 MIT https://github.com/bestiejs/punycode.js.git
queue-microtask 1.2.3 MIT git://github.com/feross/queue-microtask.git
quick-lru 5.1.1 MIT https://github.com/sindresorhus/quick-lru.git
@@ -449,6 +469,7 @@ react-dom 16.14.0
react-dom 17.0.2 MIT https://github.com/facebook/react.git
react-draggable 4.4.4 MIT https://github.com/react-grid-layout/react-draggable.git
react-draggable 4.4.3 MIT https://github.com/mzabriskie/react-draggable.git
react-dropzone 14.2.1 MIT https://github.com/react-dropzone/react-dropzone.git
react-input-autosize 3.0.0 MIT https://github.com/JedWatson/react-input-autosize.git
react-is 16.13.1 MIT https://github.com/facebook/react.git
react-is 17.0.2 MIT https://github.com/facebook/react.git
@@ -545,6 +566,7 @@ postcss-modules-scope 3.0.0
postcss-modules-values 4.0.0 ISC git+https://github.com/css-modules/postcss-modules-values.git
react-timer-hook 3.0.5 ISC git+https://github.com/amrlabib/react-timer-hook.git
semver 7.3.5 ISC https://github.com/npm/node-semver
semver 7.3.7 ISC https://github.com/npm/node-semver.git
semver 6.3.0 ISC https://github.com/npm/node-semver
vanilla-picker 2.12.1 ISC https://github.com/Sphinxxxx/vanilla-picker.git
yallist 4.0.0 ISC git+https://github.com/isaacs/yallist.git
@@ -594,9 +616,9 @@ pako 1.0.11
precond 0.2.3 MIT* https://github.com/MathieuTurcotte/node-precond.git
webcabin-docker 2.2.5 MIT* Unknown
spdx-exceptions 2.3.0 CC-BY-3.0 https://github.com/kemitchell/spdx-exceptions.json.git
tablesorter 2.31.3 (MIT OR GPL-2.0) git://github.com/Mottie/tablesorter.git
tslib 2.4.0 0BSD https://github.com/Microsoft/tslib.git
tslib 2.3.0 0BSD https://github.com/Microsoft/tslib.git
type-fest 1.4.0 (MIT OR CC0-1.0) https://github.com/sindresorhus/type-fest.git
518 dependencies listed.
540 dependencies listed.
+2 -2
View File
@@ -19,7 +19,7 @@ the restore point to add* to provide a descriptive name for the restore point.
For more information about using a restore point as a recovery target, please
see the
`PostgreSQL documentation <https://www.postgresql.org/docs/current/recovery-target-settings.html#RECOVERY-TARGET-NAME>`_.
`PostgreSQL documentation <https://www.postgresql.org/docs/current/runtime-config-wal.html#RUNTIME-CONFIG-WAL-RECOVERY-TARGET>`_.
* Click the *OK* button to save the restore point.
* Click the *Cancel* button to exit without saving work.
* Click the *Cancel* button to exit without saving work.
+5 -2
View File
@@ -36,5 +36,8 @@ Use the *Change Password* dialog to change your password:
* Enter the desired password for in the *New Password* field.
* Re-enter the new password in the *Confirm Password* field.
Click the *OK* button to change your password; click *Cancel* to exit the dialog
without changing your password.
Click the *Save* button to change your password.
Click the *Close* button to exit without changing your password.
Click the *Reset* button to reset the values.
-2
View File
@@ -10,8 +10,6 @@ To deploy a PostgreSQL server on the Azure cloud, follow the below steps.
:alt: Cloud Deployment
:align: center
**Note:** This feature is currently available in Desktop mode only.
Once you launch the tool, select the Azure PostgreSQL option.
Click on the *Next* button to proceed further.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

+29 -31
View File
@@ -8,39 +8,36 @@ Use the *Import/Export data* dialog to copy data from a table to a file, or copy
data from a file into a table.
The *Import/Export data* dialog organizes the import/export of data through the
*Options* and *Columns* tabs.
*General*, *Options* and *Columns* tabs.
.. image:: images/import_export_general.png
:alt: Import Export data dialog general tab
:align: center
Use the fields in the *General* tab to specify import and export preferences:
* Move the *Import/Export* switch to the *Import* position to specify that the
server should import data to a table from a file. The default is *Import*.
* Enter the name of the source or target file in the *Filename* field.
Optionally, select the *Browser* icon (ellipsis) to the right to navigate
into a directory and select a file.
* Use the drop-down listbox in the *Format* field to specify the file type.
Select:
* *binary* for a .bin file.
* *csv* for a .csv file.
* *text* for a .txt file.
* Use the drop-down listbox in the *Encoding* field to specify the type of
character encoding.
.. image:: images/import_export_options.png
:alt: Import Export data dialog options tab
:align: center
Use the fields in the *Options* tab to specify import and export preferences:
* Move the *Import/Export* switch to the *Import* position to specify that the
server should import data to a table from a file. The default is *Export*.
* Use the fields in the *File Info* field box to specify information about the
source or target file:
* Enter the name of the source or target file in the *Filename* field.
Optionally, select the *Browser* icon (ellipsis) to the right to navigate
into a directory and select a file.
* Use the drop-down listbox in the *Format* field to specify the file type.
Select:
* *binary* for a .bin file.
* *csv* for a .csv file.
* *text* for a .txt file.
* Use the drop-down listbox in the *Encoding* field to specify the type of
character encoding.
.. image:: images/import_export_miscellaneous.png
:alt: Import Export data dialog miscellaneous tab
:align: center
* Use the fields in the *Miscellaneous* field box to specify additional
information:
* Use the fields in the *Options* tab to specify additional information:
* Move the *OID* switch to the *Yes* position to include the *OID* column.
The *OID* is a system-assigned value that may not be modified. The default
@@ -57,6 +54,8 @@ Use the fields in the *Options* tab to specify import and export preferences:
be a single quote or a double quote.
* Specify a character that should appear before a data character that matches
the *QUOTE* value in the *Escape* field.
* Use the *NULL Strings* field to specify a string that will represent a null
value within the source or target file.
Click the *Columns* tab to continue.
@@ -71,9 +70,8 @@ or exported:
columns from the drop-down listbox. To delete a selection, click the *x* to
the left of the column name. Click an empty spot inside the field to access
the drop-down list.
* Use the *NULL Strings* field to specify a string that will represent a null
value within the source or target file.
* If enabled, click inside the *Not null columns* field to select one or more
* If enabled, click inside the *NOT NULL columns* field to select one or more
columns that will not be checked for a NULL value. To delete a column, click
the *x* to the left of the column name.
+3
View File
@@ -290,6 +290,9 @@ preferences for the Query Editor tool.
Use the fields on the *Auto Completion* panel to set the auto completion options.
* When the *Autocomplete on key press* switch is set to *True* then autocomplete
will be available on key press along with CTRL/CMD + Space. If it is set to
*False* then autocomplete is only activated when CTRL/CMD + Space is pressed.
* When the *Keywords in uppercase* switch is set to *True* then keywords are
shown in upper case.
+58 -12
View File
@@ -331,13 +331,21 @@ Graph Visualiser Panel
**********************
Click the Graph Visualiser button in the toolbar to generate the *Graphs* of
the query results. The graph visualiser currently supports only Line Charts,
but more charts (Bar, Stacked Bar, Pie...) will be added soon.
the query results. The graph visualiser supports Line Charts, Stacked Line Charts,
Bar Charts, Stacked Bar Charts, and Pie Charts.
.. image:: images/query_graph_visualiser_panel.png
:alt: Query tool graph visualiser panel
:align: center
* Graph Type
Choose the type of the graph that you would like to generate.
.. image:: images/query_graph_type.png
:alt: Query tool graph visualiser graph type
:align: center
* X Axis
Choose the column whose value you wish to display on X-axis from the *X Axis*
@@ -358,15 +366,6 @@ from the drop-down menu to select all the columns.
:alt: Query tool graph visualiser yaxis
:align: center
* Graph Type
Choose the type of the graph that you would like to generate. Currently only
*Line Charts* option is there, but more charts will be added soon.
.. image:: images/query_graph_type.png
:alt: Query tool graph visualiser graph type
:align: center
* Download and Zoom button
Zooming is performed by clicking and selecting an area over the chart with the
@@ -382,7 +381,8 @@ Click the *Download* button on the button bar to download the chart.
Line Chart
==========
The *Line Chart* can be generated by selecting the X-axis and the Y-axis and
The *Line Chart* can be generated by selecting the 'Line Chart'
from the Graph Type drop-down, selecting the X-axis and the Y-axis, and
clicking on the 'Generate' button. Below is an example of a chart of employee
names and their salaries.
@@ -393,6 +393,52 @@ names and their salaries.
Set *Use different data point styles?* option to true in the :ref:`preferences`,
to show data points in a different style on each graph lines.
Stacked Line Chart
==================
The *Stacked Line Chart* can be generated by selecting the 'Stacked Line Chart'
from the Graph Type drop-down, selecting the X-axis and the Y-axis, and
clicking on the 'Generate' button.
.. image:: images/query_stacked_line_chart.png
:alt: Query tool graph visualiser stacked line chart
:align: center
Bar Chart
==========
The *Bar Chart* can be generated by selecting the 'Bar Chart'
from the Graph Type drop-down, selecting the X-axis and the Y-axis, and
clicking on the 'Generate' button.
.. image:: images/query_bar_chart.png
:alt: Query tool graph visualiser bar chart
:align: center
Stacked Bar Chart
=================
The *Stacked Bar Chart* can be generated by selecting the 'Stacked Bar Chart'
from the Graph Type drop-down, selecting the X-axis and the Y-axis, and
clicking on the 'Generate' button.
.. image:: images/query_stacked_bar_chart.png
:alt: Query tool graph visualiser stacked bar chart
:align: center
Pie Chart
=========
The *Pie Chart* can be generated by selecting the 'Pie Chart'
from the Graph Type drop-down, selecting the Label and Value, and
clicking on the 'Generate' button.
.. image:: images/query_pie_chart.png
:alt: Query tool graph visualiser pie chart
:align: center
Connection Status
*****************
+1
View File
@@ -11,6 +11,7 @@ notes for it.
.. toctree::
:maxdepth: 1
release_notes_6_12
release_notes_6_11
release_notes_6_10
release_notes_6_9
+40
View File
@@ -0,0 +1,40 @@
************
Version 6.12
************
Release date: 2022-07-28
This release contains a number of bug fixes and new features since the release of pgAdmin 4 v6.11.
New features
************
| `Issue #4488 <https://redmine.postgresql.org/issues/4488>`_ - Added option to trigger autocomplete on key press in the query tool.
| `Issue #4607 <https://redmine.postgresql.org/issues/4607>`_ - Allow users to delete files/folders from the storage manager.
| `Issue #7389 <https://redmine.postgresql.org/issues/7389>`_ - Allow users to search within the file/storage manager.
| `Issue #7486 <https://redmine.postgresql.org/issues/7486>`_ - Added support for visualizing the graphs using Stacked Line, Bar, and Stacked Bar charts in the query tool.
| `Issue #7487 <https://redmine.postgresql.org/issues/7487>`_ - Added support for visualise the graph using a Pie chart in the query tool.
Housekeeping
************
| `Issue #7313 <https://redmine.postgresql.org/issues/7313>`_ - Port the file/storage manager to React.
| `Issue #7341 <https://redmine.postgresql.org/issues/7341>`_ - Port change password dialog to React.
| `Issue #7342 <https://redmine.postgresql.org/issues/7342>`_ - Port Master Password dialog to React.
| `Issue #7492 <https://redmine.postgresql.org/issues/7492>`_ - Removing dynamic module loading and replacing it with static loading.
| `Issue #7546 <https://redmine.postgresql.org/issues/7546>`_ - Port named restore point dialog to React.
Bug fixes
*********
| `Issue #7428 <https://redmine.postgresql.org/issues/7428>`_ - Preserve the settings set by the user in the import/export data dialog.
| `Issue #7471 <https://redmine.postgresql.org/issues/7471>`_ - Ensure that the splash screen can be moved.
| `Issue #7508 <https://redmine.postgresql.org/issues/7508>`_ - Fixed an issue where comments on indexes are not displayed.
| `Issue #7512 <https://redmine.postgresql.org/issues/7512>`_ - Ensure that notices should not disappear from the messages tab.
| `Issue #7517 <https://redmine.postgresql.org/issues/7517>`_ - Enable the start debugging button once execution is completed.
| `Issue #7518 <https://redmine.postgresql.org/issues/7518>`_ - Ensure that dashboard graph API is not called after the panel has been closed.
| `Issue #7519 <https://redmine.postgresql.org/issues/7519>`_ - Ensure that geometry should be shown for all the selected cells.
| `Issue #7520 <https://redmine.postgresql.org/issues/7520>`_ - Fixed the JSON editor issue of hiding the first record.
| `Issue #7522 <https://redmine.postgresql.org/issues/7522>`_ - Added support for Azure PostgreSQL deployment in server mode.
| `Issue #7523 <https://redmine.postgresql.org/issues/7523>`_ - Fixed typo error for Statistics on the table header.
| `Issue #7524 <https://redmine.postgresql.org/issues/7524>`_ - Fixed an issue where new folders cannot be created in the save dialog.
+33 -27
View File
@@ -20,48 +20,54 @@ Use icons on the top of the *Storage Manager* window to manage storage:
Use the ``Home`` icon |home| to return to the home directory.
.. |home| image:: images/home.png
.. |home| image:: images/sm_home.png
Use the ``Up Arrow`` icon |uparrow| to return to the previous directory.
.. |uparrow| image:: images/uparrow.png
.. |uparrow| image:: images/sm_go_back.png
Use the ``Refresh`` icon |refresh| to display the most-recent files available.
.. |refresh| image:: images/refresh.png
.. |refresh| image:: images/sm_refresh.png
Select the ``Download`` icon |download| to download the selected file.
.. |download| image:: images/download.png
Select the ``Delete`` icon |delete| to delete the selected file or folder.
.. |delete| image:: images/delete.png
Select the ``Edit`` icon |edit| to rename a file or folder.
.. |edit| image:: images/edit.png
Use the ``Upload`` icon |upload| to upload a file.
.. |upload| image:: images/upload.png
.. |download| image:: images/sm_download.png
Use the ``New Folder`` icon |folder| to add a new folder.
.. |folder| image:: images/folder.png
Use the ``Grid View`` icon |gridview| to display all the files and folders in a grid view.
.. |gridview| image:: images/gridview.png
Use the ``Table View`` icon |tableview| to display all the files and folders in a list view.
.. |tableview| image:: images/tableview.png
Click on the check box next to *Show hidden files and folders* at the bottom of the window to view hidden files and folders.
.. |folder| image:: images/sm_new_folder.png
Use the *Format* drop down list to select the format of the files to be displayed; choose from *sql*, *csv*, or *All Files*.
Other Options
*********************
.. image:: images/sm_options.png
:alt: Other options
:align: center
.. table::
:class: longtable
:widths: 1 5
+----------------------+---------------------------------------------------------------------------------------------------+
| Menu | Behavior |
+======================+===================================================================================================+
| *Rename* | Click the *Rename* option to rename a file/folder. |
+----------------------+---------------------------------------------------------------------------------------------------+
| *Delete* | Click the *Delete* option to rename a file/folder. |
+----------------------+---------------------------------------------------------------------------------------------------+
| *Upload* | Click the *Upload* option to upload multiple files to the current folder. |
+----------------------+---------------------------------------------------------------------------------------------------+
| *List View* | Click the *List View* option to to display all the files and folders in a list view. |
+----------------------+---------------------------------------------------------------------------------------------------+
| *Grid View* | Click the *Grid View* option to to display all the files and folders in a grid view. |
+----------------------+---------------------------------------------------------------------------------------------------+
| *Show Hidden Files* | Click the *Show Hidden Files* option to view hidden files and folders. |
+----------------------+---------------------------------------------------------------------------------------------------+
You can also download backup files through *Storage Manager* at the successful completion of the backups taken through :ref:`Backup Dialog <backup_dialog>`, :ref:`Backup Global Dialog <backup_globals_dialog>`, or :ref:`Backup Server Dialog <backup_server_dialog>`.
At the successful completion of a backup, click on the icon to open the current backup file in *Storage Manager* on the *process watcher* window.
+1
View File
@@ -7,6 +7,7 @@
"license": "PostgreSQL",
"chromium-args": "--disable-popup-blocking --disable-gpu",
"user-agent": "Nwjs:%nwver-%osinfo-%chromium_ver",
"nodejs": true,
"window": {
"width": 750,
"height": 600,
+1 -1
View File
@@ -296,7 +296,7 @@ const setZoomLevelForAllWindows = () => {
for (let arr_val of winArray) {
arr_val.zoomLevel = pgAdminWindowObject.zoomLevel;
}
})
});
};
// This function used to zoom in the pgAdmin window.
+54 -25
View File
@@ -18,6 +18,7 @@ let startPageUrl = null;
let serverCheckUrl = null;
let serverPort = 5050;
let appStartTime = (new Date()).getTime();
let docsURLSubStrings = ['www.enterprisedb.com', 'www.postgresql.org', 'www.pgadmin.org', 'help/help'];
@@ -84,12 +85,15 @@ function startDesktopMode() {
misc.writeServerLog('--------------------------------------------------------\n');
// Spawn the process to start pgAdmin4 server.
let spawnStartTime = (new Date).getTime();
pgadminServerProcess = spawn(path.resolve(pythonPath), ['-s', path.resolve(pgadminFile)]);
pgadminServerProcess.on('error', function(err) {
// Log the error into the log file if process failed to launch
misc.writeServerLog('Failed to launch pgAdmin4. Error:');
misc.writeServerLog(err);
});
let spawnEndTime = (new Date).getTime();
misc.writeServerLog('Total spawn time to start the pgAdmin4 server: ' + (spawnEndTime - spawnStartTime)/1000 + ' Sec');
pgadminServerProcess.stdout.setEncoding('utf8');
pgadminServerProcess.stdout.on('data', (chunk) => {
@@ -149,6 +153,7 @@ function startDesktopMode() {
let pingInProgress = false;
// ping pgAdmin server every 1 second.
let pingStartTime = (new Date).getTime();
let intervalID = setInterval(function() {
// If ping request is already send and response is not
// received no need to send another request.
@@ -162,6 +167,12 @@ function startDesktopMode() {
misc.setProcessObject(pgadminServerProcess);
clearInterval(intervalID);
let appEndTime = (new Date).getTime();
misc.writeServerLog('------------------------------------------');
misc.writeServerLog('Total time taken to ping pgAdmin4 server: ' + (appEndTime - pingStartTime)/1000 + ' Sec');
misc.writeServerLog('------------------------------------------');
misc.writeServerLog('Total launch time of pgAdmin4: ' + (appEndTime - appStartTime)/1000 + ' Sec');
misc.writeServerLog('------------------------------------------');
launchPgAdminWindow();
}).catch(() => {
pingInProgress = false;
@@ -195,7 +206,7 @@ function startDesktopMode() {
});
pingInProgress = true;
}, 1000);
}, 250);
}
// This function is used to hide the splash screen and create/launch
@@ -232,32 +243,32 @@ function launchPgAdminWindow() {
// set up handler for new-win-policy event.
// Set the width and height for the new window.
pgadminWindow.on('new-win-policy', function(frame, url, policy) {
if(!frame) {
let openDocsInBrowser = misc.ConfigureStore.get('openDocsInBrowser', true);
let isDocURL = false;
docsURLSubStrings.forEach(function(key) {
if(url.indexOf(key) >= 0) {
isDocURL = true;
}
});
if (openDocsInBrowser && isDocURL) {
// Do not open the window
policy.ignore();
// Open URL in the external browser.
nw.Shell.openExternal(url);
} else {
policy.setNewWindowManifest({
'icon': '../../assets/pgAdmin4.png',
'frame': true,
'position': 'center',
'min_width': 640,
'min_height': 480,
'width': pgadminWindow.width,
'height': pgadminWindow.height,
});
if(!frame) {
let openDocsInBrowser = misc.ConfigureStore.get('openDocsInBrowser', true);
let isDocURL = false;
docsURLSubStrings.forEach(function(key) {
if(url.indexOf(key) >= 0) {
isDocURL = true;
}
});
if (openDocsInBrowser && isDocURL) {
// Do not open the window
policy.ignore();
// Open URL in the external browser.
nw.Shell.openExternal(url);
} else {
policy.setNewWindowManifest({
'icon': '../../assets/pgAdmin4.png',
'frame': true,
'position': 'center',
'min_width': 640,
'min_height': 480,
'width': pgadminWindow.width,
'height': pgadminWindow.height,
});
}
}
});
pgadminWindow.on('loaded', function() {
@@ -289,6 +300,24 @@ function launchPgAdminWindow() {
let gui = require('nw.gui');
let splashWindow = gui.Window.get();
// Enable dragging on the splash screen.
let isDragging = false;
let dragOrigin = {x:0, y:0};
document.mouseleave = ()=> isDragging = false;
document.onmouseup = ()=> isDragging = false;
document.onmousedown = (e)=> {
isDragging = true;
dragOrigin.x = e.x;
dragOrigin.y = e.y;
};
document.onmousemove = (e)=> {
if (isDragging) {
splashWindow.moveTo(e.screenX - dragOrigin.x, e.screenY - dragOrigin.y);
}
};
// Always clear the cache before starting the application.
nw.App.clearCache();
+2 -2
View File
@@ -43,7 +43,7 @@ APP_ICON = 'pg-icon'
# Application version number components
APP_RELEASE = 6
APP_REVISION = 11
APP_REVISION = 12
# Application version suffix, e.g. 'beta1', 'dev'. Usually an empty string
# for GA releases.
@@ -52,7 +52,7 @@ APP_SUFFIX = ''
# Numeric application version for upgrade checks. Should be in the format:
# [X]XYYZZ, where X is the release version, Y is the revision, with a leading
# zero if needed, and Z represents the suffix, with a leading zero if needed
APP_VERSION_INT = 61100
APP_VERSION_INT = 61200
# DO NOT CHANGE!
# The application version string, constructed from the components
+5 -5
View File
@@ -70,7 +70,7 @@
"webpack": "^5.21.2",
"webpack-bundle-analyzer": "^4.4.0",
"webpack-cli": "^4.5.0",
"yarn-audit-html": "^3.0.1"
"yarn-audit-html": "^4.0.0"
},
"dependencies": {
"@babel/plugin-proposal-class-properties": "^7.10.4",
@@ -111,12 +111,12 @@
"closest": "^0.0.1",
"codemirror": "^5.59.2",
"context-menu": "^2.0.0",
"convert-units": "^2.3.4",
"css-loader": "^5.0.1",
"cssnano": "^5.0.2",
"dagre": "^0.8.4",
"date-fns": "^2.24.0",
"diff-arrays-of-objects": "^1.1.8",
"dropzone": "^5.9.3",
"html2canvas": "^1.0.0-rc.7",
"immutability-helper": "^3.0.0",
"imports-loader": "^2.0.0",
@@ -124,7 +124,7 @@
"ip-address": "^7.1.0",
"jquery": "^3.6.0",
"jquery-contextmenu": "^2.9.2",
"jquery-ui": "^1.13.0",
"jquery-ui": "^1.13.2",
"json-bignumber": "^1.0.1",
"jsoneditor": "^9.5.4",
"jsoneditor-react": "^3.1.1",
@@ -132,7 +132,7 @@
"leaflet": "^1.5.1",
"lodash": "4.*",
"ml-matrix": "^6.5.0",
"moment": "^2.29.3",
"moment": "^2.29.4",
"moment-timezone": "^0.5.34",
"mousetrap": "^1.6.3",
"notificar": "^1.0.1",
@@ -150,6 +150,7 @@
"react-data-grid": "git+https://github.com/adityatoshniwal/react-data-grid.git/#8d9bc16ddd7c419acfbbd1c1cc2b70eb9f5b453c",
"react-dom": "^17.0.1",
"react-draggable": "^4.4.4",
"react-dropzone": "^14.2.1",
"react-leaflet": "^3.2.2",
"react-rnd": "^10.3.5",
"react-router-dom": "^6.2.2",
@@ -165,7 +166,6 @@
"socket.io-client": "^4.0.0",
"split.js": "^1.5.10",
"styled-components": "^5.2.1",
"tablesorter": "^2.31.2",
"tempusdominus-bootstrap-4": "^5.1.2",
"tempusdominus-core": "^5.19.3",
"underscore": "^1.13.1",
+6 -8
View File
@@ -14,7 +14,6 @@ to start a web server."""
import sys
if sys.version_info < (3, 4):
raise RuntimeError('This application must be run under Python 3.4 '
'or later.')
@@ -37,8 +36,8 @@ else:
builtins.SERVER_MODE = None
import config
import setup
from pgadmin import create_app, socketio
from pgadmin.utils import u_encode, fs_encoding, file_quote
from pgadmin.utils.constants import INTERNAL
# Get the config database schema version. We store this in pgadmin.model
# as it turns out that putting it in the config files isn't a great idea
@@ -83,13 +82,9 @@ class ReverseProxied(object):
config.SETTINGS_SCHEMA_VERSION = SCHEMA_VERSION
# Check if the database exists. If it does not, create it.
setup_db_required = False
if not os.path.isfile(config.SQLITE_PATH):
setup_py = os.path.join(
os.path.dirname(os.path.realpath(u_encode(__file__, fs_encoding))),
'setup.py'
)
exec(open(file_quote(setup_py), 'r').read())
setup_db_required = True
##########################################################################
# Create the app and configure it. It is created outside main so that
@@ -99,6 +94,9 @@ app = create_app()
app.debug = False
app.config['sessions'] = dict()
if setup_db_required:
setup.setup_db(app)
if config.SERVER_MODE:
app.wsgi_app = ReverseProxied(app.wsgi_app)
+39 -40
View File
@@ -8,12 +8,11 @@
##########################################################################
""" Azure PostgreSQL provider """
from azure.mgmt.rdbms.postgresql_flexibleservers import \
PostgreSQLManagementClient
from azure.mgmt.rdbms.postgresql_flexibleservers.models import Sku, SkuTier, \
CreateMode, Storage, Server, FirewallRule, HighAvailability
from azure.identity import AzureCliCredential, InteractiveBrowserCredential, \
from azure.identity import AzureCliCredential, DeviceCodeCredential, \
AuthenticationRecord
from azure.mgmt.resource import ResourceManagementClient
from azure.core.exceptions import ResourceNotFoundError
@@ -37,12 +36,13 @@ class AzureProvider(AbsProvider):
self._client_secret = None
self._subscription_id = None
self._default_region = None
self._use_interactive_browser_credential = False
self._interactive_browser_credential = False
self._available_capabilities = None
self._credentials = None
self._authentication_record_json = None
self._cli_credentials = None
self.azure_cred_cache_name = None
self._azure_cred_cache_name = None
self._azure_cred_cache_location = None
# Get the credentials
if 'AUTHENTICATION_RECORD_JSON' in os.environ:
@@ -56,7 +56,7 @@ class AzureProvider(AbsProvider):
self._tenant_id = os.environ['AZURE_TENANT_ID']
if 'AUTH_TYPE' in os.environ:
self._use_interactive_browser_credential = False \
self._interactive_browser_credential = False \
if os.environ['AUTH_TYPE'] == 'azure_cli_credential' else True
if 'AZURE_DATABASE_PASSWORD' in os.environ:
@@ -150,49 +150,48 @@ class AzureProvider(AbsProvider):
##########################################################################
def _get_azure_credentials(self):
try:
if self._use_interactive_browser_credential:
if self._authentication_record_json is None:
_credentials = self._azure_interactive_browser_credential()
_auth_record_ = _credentials.authenticate()
self._authentication_record_json = \
_auth_record_.serialize()
else:
deserialized_auth_record = AuthenticationRecord.\
deserialize(self._authentication_record_json)
_credentials = \
self._azure_interactive_browser_credential(
deserialized_auth_record)
if self._interactive_browser_credential:
_credentials = self._azure_interactive_auth()
else:
if self._cli_credentials is None:
self._cli_credentials = AzureCliCredential()
_credentials = self._cli_credentials
_credentials = self._azure_cli_auth()
except Exception as e:
return False, str(e)
return True, _credentials
def _azure_interactive_browser_credential(
self, deserialized_auth_record=None):
if deserialized_auth_record:
_credential = InteractiveBrowserCredential(
def _azure_cli_auth(self):
if self._cli_credentials is None:
self._cli_credentials = AzureCliCredential()
return self._cli_credentials
def _azure_interactive_auth(self):
if self._authentication_record_json is None:
_interactive_credential = DeviceCodeCredential(
tenant_id=self._tenant_id,
timeout=180,
_cache=load_persistent_cache(
TokenCachePersistenceOptions(
name=self._azure_cred_cache_name,
allow_unencrypted_storage=True,
cache_location=self._azure_cred_cache_location)),
authentication_record=deserialized_auth_record)
else:
_credential = InteractiveBrowserCredential(
tenant_id=self._tenant_id,
timeout=180,
_cache=load_persistent_cache(
TokenCachePersistenceOptions(
name=self._azure_cred_cache_name,
allow_unencrypted_storage=True,
cache_location=self._azure_cred_cache_location))
prompt_callback=None,
_cache=load_persistent_cache(TokenCachePersistenceOptions(
name=self._azure_cred_cache_name,
allow_unencrypted_storage=True,
cache_location=self._azure_cred_cache_location)
)
)
return _credential
_auth_record = _interactive_credential.authenticate()
self._authentication_record_json = _auth_record.serialize()
else:
deserialized_auth_record = AuthenticationRecord.deserialize(
self._authentication_record_json)
_interactive_credential = DeviceCodeCredential(
tenant_id=self._tenant_id,
timeout=180,
prompt_callback=None,
_cache=load_persistent_cache(TokenCachePersistenceOptions(
name=self._azure_cred_cache_name,
allow_unencrypted_storage=True,
cache_location=self._azure_cred_cache_location)
),
authentication_record=deserialized_auth_record
)
return _interactive_credential
def _get_azure_client(self, type):
""" Create/cache/return an Azure client object """
+2 -1
View File
@@ -713,7 +713,8 @@ def create_app(app_name=None):
##########################################################################
# Load plugin modules
##########################################################################
for module in app.find_submodules('pgadmin'):
from .submodules import get_submodules
for module in get_submodules():
app.logger.info('Registering blueprint module: %s' % module)
if app.blueprints.get(module.name) is None:
app.register_blueprint(module)
-7
View File
@@ -41,13 +41,6 @@ class AboutModule(PgAdminModule):
]
}
def get_own_javascripts(self):
return [{
'name': 'pgadmin.about',
'path': url_for('about.index') + 'about',
'when': None
}]
def get_exposed_url_endpoints(self):
return ['about.index']
+24 -1
View File
@@ -22,8 +22,31 @@ class: MultiFactorAuthRegistry
An registry factory for the multi-factor authentication methods.
"""
@classmethod
def load_modules(cls, app=None):
submodules = []
from . import authenticator as module
submodules.append(module)
from . import email as module
submodules.append(module)
from . import utils as module
submodules.append(module)
from . import views as module
submodules.append(module)
for module in submodules:
if "init_app" in module.__dict__.keys():
module.__dict__["init_app"](app)
MultiFactorAuthRegistry = create_registry_metaclass(
'MultiFactorAuthRegistry', __package__, decorate_as_module=True
'MultiFactorAuthRegistry', __package__, load_modules=load_modules,
decorate_as_module=True
)
+28 -1
View File
@@ -13,6 +13,33 @@
from pgadmin.utils.dynamic_registry import create_registry_metaclass
@classmethod
def load_modules(cls, app=None):
submodules = []
from . import internal as module
submodules.append(module)
from . import kerberos as module
submodules.append(module)
from . import ldap as module
submodules.append(module)
from . import mfa as module
submodules.append(module)
from . import oauth2 as module
submodules.append(module)
from . import webserver as module
submodules.append(module)
for module in submodules:
if "init_app" in module.__dict__.keys():
module.__dict__["init_app"](app)
AuthSourceRegistry = create_registry_metaclass(
"AuthSourceRegistry", __package__, decorate_as_module=True
"AuthSourceRegistry", __package__, load_modules=load_modules,
decorate_as_module=True
)
+22 -178
View File
@@ -102,103 +102,6 @@ class BrowserModule(PgAdminModule):
stylesheets.append(url_for(endpoint, filename=filename))
return stylesheets
def get_own_javascripts(self):
scripts = list()
scripts.append({
'name': 'alertify',
'path': url_for(
'static',
filename='vendor/alertifyjs/alertify' if current_app.debug
else 'vendor/alertifyjs/alertify.min'
),
'exports': 'alertify',
'preloaded': True
})
scripts.append({
'name': 'jqueryui.position',
'path': url_for(
'static',
filename='vendor/jQuery-contextMenu/jquery.ui.position' if
current_app.debug else
'vendor/jQuery-contextMenu/jquery.ui.position.min'
),
'deps': ['jquery'],
'exports': 'jQuery.ui.position',
'preloaded': True
})
scripts.append({
'name': 'jquery.contextmenu',
'path': url_for(
'static',
filename='vendor/jQuery-contextMenu/jquery.contextMenu' if
current_app.debug else
'vendor/jQuery-contextMenu/jquery.contextMenu.min'
),
'deps': ['jquery', 'jqueryui.position'],
'exports': 'jQuery.contextMenu',
'preloaded': True
})
scripts.append({
'name': 'wcdocker',
'path': url_for(
'static',
filename='vendor/wcDocker/wcDocker' if current_app.debug
else 'vendor/wcDocker/wcDocker.min'
),
'deps': ['jquery.contextmenu'],
'exports': '',
'preloaded': True
})
scripts.append({
'name': 'pgadmin.browser.datamodel',
'path': url_for(BROWSER_STATIC, filename='js/datamodel'),
'preloaded': True
})
for name, script in [
[PGADMIN_BROWSER, 'js/browser'],
['pgadmin.browser.endpoints', 'js/endpoints'],
['pgadmin.browser.error', 'js/error'],
['pgadmin.browser.constants', 'js/constants']
]:
scripts.append({
'name': name,
'path': url_for(BROWSER_INDEX) + script,
'preloaded': True
})
for name, script in [
['pgadmin.browser.node', 'js/node'],
['pgadmin.browser.messages', 'js/messages'],
['pgadmin.browser.collection', 'js/collection']
]:
scripts.append({
'name': name,
'path': url_for(BROWSER_INDEX) + script,
'preloaded': True,
'deps': ['pgadmin.browser.datamodel']
})
for name, end in [
['pgadmin.browser.menu', 'js/menu'],
['pgadmin.browser.panel', 'js/panel'],
['pgadmin.browser.frame', 'js/frame']
]:
scripts.append({
'name': name, 'path': url_for(BROWSER_STATIC, filename=end),
'preloaded': True})
scripts.append({
'name': 'pgadmin.browser.node.ui',
'path': url_for(BROWSER_STATIC, filename='js/node.ui'),
'when': 'server_group'
})
for module in self.submodules:
scripts.extend(module.get_own_javascripts())
return scripts
def get_own_menuitems(self):
menus = {
'file_items': [
@@ -311,6 +214,15 @@ class BrowserModule(PgAdminModule):
'browser.lock_layout',
'browser.signal_runtime']
def register(self, app, options):
"""
Override the default register function to automagically register
sub-modules at once.
"""
from .server_groups import blueprint as module
self.submodules.append(module)
super(BrowserModule, self).register(app, options)
blueprint = BrowserModule(MODULE_NAME, __name__)
@@ -377,51 +289,6 @@ class BrowserPluginModule(PgAdminModule):
"""
return False
def get_own_javascripts(self):
"""
Returns the list of javascripts information used by the module.
Each javascripts information must contain name, path of the script.
The name must be unique for each module, hence - in order to refer them
properly, we do use 'pgadmin.node.<type>' as norm.
That can also refer to when to load the script.
i.e.
We may not need to load the javascript of table node, when we're
not yet connected to a server, and no database is loaded. Hence - it
make sense to load them when a database is loaded.
We may also add 'deps', which also refers to the list of javascripts,
it may depends on.
"""
scripts = []
if self.module_use_template_javascript:
scripts.extend([{
'name': PGADMIN_NODE % self.node_type,
'path': url_for(BROWSER_INDEX
) + '%s/module' % self.node_type,
'when': self.script_load,
'is_template': True
}])
else:
scripts.extend([{
'name': PGADMIN_NODE % self.node_type,
'path': url_for(
'%s.static' % self.name,
filename=('js/%s' % self.node_type)
),
'when': self.script_load,
'is_template': False
}])
for module in self.submodules:
scripts.extend(module.get_own_javascripts())
return scripts
def generate_browser_node(
self, node_id, parent_id, label, icon, inode, node_type, **kwargs
):
@@ -508,14 +375,6 @@ class BrowserPluginModule(PgAdminModule):
"""
return self.browser_url_prefix + self.node_type
@property
def javascripts(self):
"""
Override the javascript of PgAdminModule, so that - we don't return
javascripts from the get_own_javascripts itself.
"""
return []
@property
def label(self):
"""
@@ -884,30 +743,11 @@ def get_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
'reset': existing,
'errmsg': errmsg,
'is_error': True if errmsg else False
})
@@ -955,11 +795,15 @@ def set_master_password():
data = None
if hasattr(request.data, 'decode'):
data = request.data.decode('utf-8')
if request.form:
data = request.form
elif request.data:
data = request.data
if hasattr(request.data, 'decode'):
data = request.data.decode('utf-8')
if data != '':
data = json.loads(data)
if data != '':
data = json.loads(data)
# Master password is not applicable for server mode
# Enable master password if oauth is used
@@ -969,7 +813,7 @@ def set_master_password():
and config.MASTER_PASSWORD_REQUIRED:
# if master pass is set previously
if current_user.masterpass_check is not None and \
data.get('button_click') and \
data.get('submit_password', False) and \
not validate_master_password(data.get('password')):
return form_master_password_response(
existing=True,
@@ -1005,7 +849,7 @@ def set_master_password():
)
elif not get_crypt_key()[1]:
error_message = None
if data.get('button_click') and data.get('password') == '':
if data.get('submit_password') and data.get('password') == '':
# If user attempted to enter a blank password, then throw error
error_message = gettext("Master password cannot be empty")
return form_master_password_response(
-27
View File
@@ -59,33 +59,6 @@ class CollectionNodeModule(PgAdminModule, PGChildModule):
"""
return True
def get_own_javascripts(self):
scripts = []
if self.module_use_template_javascript:
scripts.extend([{
'name': PGADMIN_NODE % self.node_type,
'path': url_for('browser.index'
) + '%s/module' % self.node_type,
'when': self.script_load,
'is_template': True
}])
else:
scripts.extend([{
'name': PGADMIN_NODE % self.node_type,
'path': url_for(
'%s.static' % self.name,
filename=('js/%s' % self.node_type)
),
'when': self.script_load,
'is_template': False
}])
for module in self.submodules:
scripts.extend(module.get_own_javascripts())
return scripts
def generate_browser_node(
self, node_id, parent_id, label, icon, **kwargs
):
@@ -123,6 +123,15 @@ class ServerGroupModule(BrowserPluginModule):
"""
pass
def register(self, app, options):
"""
Override the default register function to automagically register
sub-modules at once.
"""
from .servers import blueprint as module
self.submodules.append(module)
super(BrowserPluginModule, self).register(app, options)
class ServerGroupMenuItem(MenuItem):
def __init__(self, **kwargs):
@@ -283,30 +283,6 @@ class ServerModule(sg.ServerGroupPluginModule):
return snippets
def get_own_javascripts(self):
scripts = []
scripts.extend([{
'name': 'pgadmin.browser.server.privilege',
'path': url_for('%s.static' % self.name, filename='js/privilege'),
'when': self.node_type,
'is_template': False,
'deps': ['pgadmin.browser.node.ui']
}, {
'name': 'pgadmin.browser.server.variable',
'path': url_for('%s.static' % self.name, filename='js/variable'),
'when': self.node_type,
'is_template': False
}, {
'name': 'pgadmin.server.supported_servers',
'path': url_for('browser.index') + 'server/supported_servers',
'is_template': True,
'when': self.node_type
}])
scripts.extend(sg.ServerGroupPluginModule.get_own_javascripts(self))
return scripts
def register(self, app, options):
"""
Override the default register function to automagically register
@@ -318,6 +294,23 @@ class ServerModule(sg.ServerGroupPluginModule):
app.jinja_env.filters['qtTypeIdent'] = driver.qtTypeIdent
app.jinja_env.filters['hasAny'] = has_any
from .ppas import PPAS
from .databases import blueprint as module
self.submodules.append(module)
from .pgagent import blueprint as module
self.submodules.append(module)
from .resource_groups import blueprint as module
self.submodules.append(module)
from .roles import blueprint as module
self.submodules.append(module)
from .tablespaces import blueprint as module
self.submodules.append(module)
super(ServerModule, self).register(app, options)
# We do not have any preferences for server node.
@@ -1622,9 +1615,10 @@ class ServerNode(PGChildNodeView):
sid: Server id
"""
try:
if request.form and request.form['data']:
data = json.loads(request.form['data'], encoding='utf-8')
else:
data = None
if request.form:
data = request.form
elif request.data:
data = json.loads(request.data, encoding='utf-8')
crypt_key = get_crypt_key()[1]
@@ -95,6 +95,40 @@ class DatabaseModule(CollectionNodeModule):
"""
return False
def register(self, app, options):
"""
Override the default register function to automagically register
sub-modules at once.
"""
from .casts import blueprint as module
self.submodules.append(module)
from .event_triggers import blueprint as module
self.submodules.append(module)
from .extensions import blueprint as module
self.submodules.append(module)
from .foreign_data_wrappers import blueprint as module
self.submodules.append(module)
from .languages import blueprint as module
self.submodules.append(module)
from .publications import blueprint as module
self.submodules.append(module)
from .schemas import schema_blueprint as module
self.submodules.append(module)
from .schemas import catalog_blueprint as module
self.submodules.append(module)
from .subscriptions import blueprint as module
self.submodules.append(module)
super(DatabaseModule, self).register(app, options)
blueprint = DatabaseModule(__name__)
@@ -95,6 +95,15 @@ class ForeignDataWrapperModule(CollectionNodeModule):
"""
return False
def register(self, app, options):
"""
Override the default register function to automagically register
sub-modules at once.
"""
from .foreign_servers import blueprint as module
self.submodules.append(module)
super(ForeignDataWrapperModule, self).register(app, options)
blueprint = ForeignDataWrapperModule(__name__)
@@ -96,6 +96,15 @@ class ForeignServerModule(CollectionNodeModule):
"""
return False
def register(self, app, options):
"""
Override the default register function to automagically register
sub-modules at once.
"""
from .user_mappings import blueprint as module
self.submodules.append(module)
super(ForeignServerModule, self).register(app, options)
blueprint = ForeignServerModule(__name__)
@@ -103,6 +103,73 @@ class SchemaModule(CollectionNodeModule):
"""
return False
def register(self, app, options):
"""
Override the default register function to automagically register
sub-modules at once.
"""
from .aggregates import blueprint as module
self.submodules.append(module)
from .catalog_objects import blueprint as module
self.submodules.append(module)
from .collations import blueprint as module
self.submodules.append(module)
from .domains import blueprint as module
self.submodules.append(module)
from .foreign_tables import blueprint as module
self.submodules.append(module)
from .fts_configurations import blueprint as module
self.submodules.append(module)
from .fts_dictionaries import blueprint as module
self.submodules.append(module)
from .fts_parsers import blueprint as module
self.submodules.append(module)
from .fts_templates import blueprint as module
self.submodules.append(module)
from .functions import blueprint as module
self.submodules.append(module)
from .functions import trigger_function_blueprint as module
self.submodules.append(module)
from .functions import procedure_blueprint as module
self.submodules.append(module)
from .operators import blueprint as module
self.submodules.append(module)
from .packages import blueprint as module
self.submodules.append(module)
from .sequences import blueprint as module
self.submodules.append(module)
from .synonyms import blueprint as module
self.submodules.append(module)
from .tables import blueprint as module
self.submodules.append(module)
from .types import blueprint as module
self.submodules.append(module)
from .views import view_blueprint as module
self.submodules.append(module)
from .views import mview_blueprint as module
self.submodules.append(module)
super(SchemaModule, self).register(app, options)
class CatalogModule(SchemaModule):
"""
@@ -114,6 +181,13 @@ class CatalogModule(SchemaModule):
_NODE_TYPE = 'catalog'
_COLLECTION_LABEL = gettext("Catalogs")
def register(self, app, options):
"""
Override the default register function to automagically register
sub-modules at once.
"""
super(SchemaModule, self).register(app, options)
schema_blueprint = SchemaModule(__name__)
catalog_blueprint = CatalogModule(__name__)
@@ -80,6 +80,16 @@ class CatalogObjectModule(SchemaChildModule):
"""
return database.DatabaseModule.node_type
def register(self, app, options):
"""
Override the default register function to automagically register
sub-modules at once.
"""
super(CatalogObjectModule, self).register(app, options)
from .columns import blueprint as module
app.register_blueprint(module)
blueprint = CatalogObjectModule(__name__)
@@ -71,6 +71,15 @@ class DomainModule(SchemaChildModule):
"""
return databases.DatabaseModule.node_type
def register(self, app, options):
"""
Override the default register function to automagically register
sub-modules at once.
"""
from .domain_constraints import blueprint as module
self.submodules.append(module)
super(DomainModule, self).register(app, options)
blueprint = DomainModule(__name__)
@@ -76,6 +76,21 @@ class PackageModule(SchemaChildModule):
"""
return database.DatabaseModule.node_type
def register(self, app, options):
"""
Override the default register function to automagically register
sub-modules at once.
"""
from .edbfuncs import blueprint as module
self.submodules.append(module)
from .edbfuncs import procedure_blueprint as module
self.submodules.append(module)
from .edbvars import blueprint as module
self.submodules.append(module)
super(PackageModule, self).register(app, options)
blueprint = PackageModule(__name__)
@@ -118,17 +118,36 @@ class TableModule(SchemaChildModule):
return snippets
def get_own_javascripts(self):
scripts = SchemaChildModule.get_own_javascripts(self)
def register(self, app, options):
"""
Override the default register function to automagically register
sub-modules at once.
"""
from .columns import blueprint as module
self.submodules.append(module)
scripts.append({
'name': 'pgadmin.browser.table.partition.utils',
'path': url_for('browser.index') +
'table/static/js/partition.utils',
'when': 'database', 'is_template': False
})
from .compound_triggers import blueprint as module
self.submodules.append(module)
return scripts
from .constraints import blueprint as module
self.submodules.append(module)
from .indexes import blueprint as module
self.submodules.append(module)
from .partitions import blueprint as module
self.submodules.append(module)
from .row_security_policies import blueprint as module
self.submodules.append(module)
from .rules import blueprint as module
self.submodules.append(module)
from .triggers import blueprint as module
self.submodules.append(module)
super(TableModule, self).register(app, options)
blueprint = TableModule(__name__)
@@ -83,6 +83,28 @@ class ConstraintsModule(CollectionNodeModule):
"""
return False
def register(self, app, options):
"""
Override the default register function to automagically register
sub-modules at once.
"""
from .check_constraint import blueprint as module
self.submodules.append(module)
from .exclusion_constraint import blueprint as module
self.submodules.append(module)
from .foreign_key import blueprint as module
self.submodules.append(module)
from .index_constraint import primary_key_blueprint as module
self.submodules.append(module)
from .index_constraint import unique_constraint_blueprint as module
self.submodules.append(module)
super(ConstraintsModule, self).register(app, options)
blueprint = ConstraintsModule(__name__)
@@ -122,13 +122,9 @@ class PartitionsModule(CollectionNodeModule):
sub-modules of table node under partition table node.
"""
self.submodules = list(app.find_submodules(self.import_name))
self.submodules = []
super(CollectionNodeModule, self).register(app, options)
for module in self.submodules:
module.parentmodules.append(self)
app.register_blueprint(module)
# Now add sub modules of table node to partition table node.
# Exclude 'partition' module for now to avoid cyclic import issue.
modules_to_skip = ['partition', 'column']
@@ -1,5 +1,6 @@
SELECT DISTINCT ON(cls.relname) cls.oid, cls.relname as name,
(SELECT (CASE WHEN count(i.inhrelid) > 0 THEN true ELSE false END) FROM pg_inherits i WHERE i.inhrelid = cls.oid) as is_inherited
(SELECT (CASE WHEN count(i.inhrelid) > 0 THEN true ELSE false END) FROM pg_inherits i WHERE i.inhrelid = cls.oid) as is_inherited,
CASE WHEN contype IN ('p', 'u', 'x') THEN desp.description ELSE des.description END AS description
FROM pg_catalog.pg_index idx
JOIN pg_catalog.pg_class cls ON cls.oid=indexrelid
JOIN pg_catalog.pg_class tab ON tab.oid=indrelid
@@ -8,6 +9,8 @@ FROM pg_catalog.pg_index idx
JOIN pg_catalog.pg_am am ON am.oid=cls.relam
LEFT JOIN pg_catalog.pg_depend dep ON (dep.classid = cls.tableoid AND dep.objid = cls.oid AND dep.refobjsubid = '0' AND dep.refclassid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='pg_constraint') AND dep.deptype='i')
LEFT OUTER JOIN pg_catalog.pg_constraint con ON (con.tableoid = dep.refclassid AND con.oid = dep.refobjid)
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=cls.oid AND des.classoid='pg_class'::regclass)
LEFT OUTER JOIN pg_catalog.pg_description desp ON (desp.objoid=con.oid AND desp.objsubid = 0 AND desp.classoid='pg_constraint'::regclass)
WHERE indrelid = {{tid}}::OID
AND conname is NULL
{% if idx %}
@@ -130,6 +130,25 @@ class ViewModule(SchemaChildModule):
return snippets
def register(self, app, options):
from pgadmin.browser.server_groups.servers.databases.schemas.\
tables.columns import blueprint as module
self.submodules.append(module)
from pgadmin.browser.server_groups.servers.databases.schemas.\
tables.indexes import blueprint as module
self.submodules.append(module)
from pgadmin.browser.server_groups.servers.databases.schemas.\
tables.triggers import blueprint as module
self.submodules.append(module)
from pgadmin.browser.server_groups.servers.databases.schemas.\
tables.rules import blueprint as module
self.submodules.append(module)
from pgadmin.browser.server_groups.servers.databases.schemas.\
tables.compound_triggers import blueprint as module
self.submodules.append(module)
super(ViewModule, self).register(app, options)
class Message(IProcessDesc):
def __init__(self, _sid, _data, _query):
@@ -111,6 +111,19 @@ SELECT EXISTS(
"""
return False
def register(self, app, options):
"""
Override the default register function to automagically register
sub-modules at once.
"""
from .schedules import blueprint as module
self.submodules.append(module)
from .steps import blueprint as module
self.submodules.append(module)
super(JobModule, self).register(app, options)
blueprint = JobModule(__name__)
@@ -10,18 +10,17 @@
import { getNodeListById } from '../../../../static/js/node_ajax';
import ServerSchema from './server.ui';
import Notify from '../../../../../static/js/helpers/Notifier';
import { showServerPassword } from '../../../../static/js/password_dialogs';
import { showServerPassword, showChangeServerPassword, showNamedRestorePoint } from '../../../../static/js/password_dialogs';
define('pgadmin.node.server', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
'sources/pgadmin', 'pgadmin.browser',
'pgadmin.user_management.current_user',
'pgadmin.alertifyjs', 'pgadmin.backform',
'pgadmin.authenticate.kerberos',
'pgadmin.browser.server.privilege',
], function(
gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser,
current_user, Alertify, Backform, Kerberos,
gettext, url_for, $, _, pgAdmin, pgBrowser,
current_user, Kerberos,
) {
if (!pgBrowser.Nodes['server']) {
@@ -377,34 +376,7 @@ define('pgadmin.node.server', [
if (!d)
return false;
Alertify.prompt(
gettext('Enter the name of the restore point to add'), '',
// We will execute this function when user clicks on the OK button
function(evt, value) {
// If user has provided a value, send it to the server
if(!_.isUndefined(value) && !_.isNull(value) && value !== ''
&& String(value).replace(/^\s+|\s+$/g, '') !== '') {
$.ajax({
url: obj.generate_url(i, 'restore_point', d, true),
method:'POST',
data:{ 'value': JSON.stringify(value) },
})
.done(function(res) {
Notify.success(res.data.result, 10000);
})
.fail(function(xhr, status, error) {
Notify.pgRespErrorNotify(xhr, error);
t.unload(i);
});
} else {
evt.cancel = true;
Notify.error(gettext('Please enter a valid name.'), 10000);
}
},
// We will execute this function when user clicks on the Cancel
// button. Do nothing just close it.
function(evt) { evt.cancel = false; }
).set({'title': gettext('Restore point name')});
showNamedRestorePoint(gettext('Restore point name'), d, obj, i);
},
/* Change password */
@@ -414,162 +386,10 @@ define('pgadmin.node.server', [
t = pgBrowser.tree,
i = input.item || t.selected(),
d = i ? t.itemData(i) : undefined,
url = obj.generate_url(i, 'change_password', d, true),
is_pgpass_file_used = false,
check_pgpass_url = obj.generate_url(i, 'check_pgpass', d, true);
if (d) {
if(!Alertify.changeServerPassword) {
var newPasswordModel = Backbone.Model.extend({
defaults: {
user_name: undefined,
password: undefined,
newPassword: undefined,
confirmPassword: undefined,
},
validate: function() {
return null;
},
}),
passwordChangeFields = [{
name: 'user_name', label: gettext('User'),
type: 'text', readonly: true, control: 'input',
},{
name: 'password', label: gettext('Current Password'),
type: 'password', disabled: function() { return is_pgpass_file_used; },
control: 'input', required: true,
},{
name: 'newPassword', label: gettext('New Password'),
type: 'password', disabled: false, control: 'input',
required: true,
},{
name: 'confirmPassword', label: gettext('Confirm Password'),
type: 'password', disabled: false, control: 'input',
required: true,
}];
Alertify.dialog('changeServerPassword' ,function factory() {
return {
main: function(params) {
var title = gettext('Change Password');
this.set('title', title);
this.user_name = params.user.name;
},
setup:function() {
return {
buttons: [{
text: gettext('Cancel'), key: 27,
className: 'btn btn-secondary fa fa-times pg-alertify-button', attrs: {name: 'cancel'},
},{
text: gettext('OK'), key: 13, className: 'btn btn-primary fa fa-check pg-alertify-button',
attrs: {name:'submit'},
}],
// Set options for dialog
options: {
padding : !1,
overflow: !1,
modal:false,
resizable: true,
maximizable: true,
pinnable: false,
closableByDimmer: false,
},
};
},
hooks: {
// triggered when the dialog is closed
onclose: function() {
if (this.view) {
this.view.remove({data: true, internal: true, silent: true});
}
},
},
prepare: function() {
var self = this;
// Disable Ok button until user provides input
this.__internal.buttons[1].element.disabled = true;
var $container = $('<div class=\'change_password\'></div>'),
newpasswordmodel = new newPasswordModel(
{'user_name': self.user_name}
),
view = this.view = new Backform.Form({
el: $container,
model: newpasswordmodel,
fields: passwordChangeFields,
});
view.render();
this.elements.content.appendChild($container.get(0));
// Listen to model & if filename is provided then enable Backup button
this.view.model.on('change', function() {
var that = this,
password = this.get('password'),
newPassword = this.get('newPassword'),
confirmPassword = this.get('confirmPassword');
// Only check password field if pgpass file is not available
if ((!is_pgpass_file_used &&
(_.isUndefined(password) || _.isNull(password) || password == '')) ||
_.isUndefined(newPassword) || _.isNull(newPassword) || newPassword == '' ||
_.isUndefined(confirmPassword) || _.isNull(confirmPassword) || confirmPassword == '') {
self.__internal.buttons[1].element.disabled = true;
} else if (newPassword != confirmPassword) {
self.__internal.buttons[1].element.disabled = true;
this.errorTimeout && clearTimeout(this.errorTimeout);
this.errorTimeout = setTimeout(function() {
that.errorModel.set('confirmPassword', gettext('Passwords do not match.'));
} ,400);
}else {
that.errorModel.clear();
self.__internal.buttons[1].element.disabled = false;
}
});
},
// Callback functions when click on the buttons of the Alertify dialogs
callback: function(e) {
if (e.button.element.name == 'submit') {
var self = this,
alertArgs = this.view.model.toJSON();
e.cancel = true;
$.ajax({
url: url,
method:'POST',
data:{'data': JSON.stringify(alertArgs) },
})
.done(function(res) {
if (res.success) {
// Notify user to update pgpass file
if(is_pgpass_file_used) {
Notify.alert(
gettext('Change Password'),
gettext('Please make sure to disconnect the server'
+ ' and update the new password in the pgpass file'
+ ' before performing any other operation')
);
}
Notify.success(res.info);
self.close();
} else {
Notify.error(res.errormsg);
}
})
.fail(function(xhr, status, error) {
Notify.pgRespErrorNotify(xhr, error);
});
}
},
};
});
}
// Call to check if server is using pgpass file or not
$.ajax({
url: check_pgpass_url,
@@ -579,7 +399,7 @@ define('pgadmin.node.server', [
if (res.success && res.data.is_pgpass) {
is_pgpass_file_used = true;
}
Alertify.changeServerPassword(d).resizeTo('40%','52%');
showChangeServerPassword(gettext('Change Password'), d, obj, i, is_pgpass_file_used);
})
.fail(function(xhr, status, error) {
Notify.pgRespErrorNotify(xhr, error);
@@ -0,0 +1,99 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import { makeStyles } from '@material-ui/core';
import React from 'react';
import PropTypes from 'prop-types';
import gettext from 'sources/gettext';
import BaseUISchema from '../../../static/js/SchemaView/base_schema.ui';
import SchemaView from '../../../static/js/SchemaView';
class ChangePasswordSchema extends BaseUISchema {
constructor(user, isPgpassFileUsed) {
super({
user: user,
password: '',
newPassword: '',
confirmPassword: ''
});
this.isPgpassFileUsed = isPgpassFileUsed;
}
get baseFields() {
let self = this;
return [
{
id: 'user', label: gettext('User'), type: 'text', disabled: true
}, {
id: 'password', label: gettext('Current Password'), type: 'password',
disabled: self.isPgpassFileUsed, noEmpty: self.isPgpassFileUsed ? false : true,
controlProps: {
maxLength: null
}
}, {
id: 'newPassword', label: gettext('New Password'), type: 'password',
noEmpty: true,
controlProps: {
maxLength: null
}
}, {
id: 'confirmPassword', label: gettext('Confirm Password'), type: 'password',
noEmpty: true,
controlProps: {
maxLength: null
}
}
];
}
validate(state, setError) {
let errmsg = null;
if (state.newPassword !== state.confirmPassword) {
errmsg = gettext('Passwords do not match.');
setError('confirmPassword', errmsg);
return true;
} else {
setError('confirmPassword', null);
}
return false;
}
}
const useStyles = makeStyles((theme)=>({
root: {
...theme.mixins.tabPanel,
},
}));
export default function ChangePasswordContent({onSave, onClose, userName, isPgpassFileUsed}) {
const classes = useStyles();
return<SchemaView
formType={'dialog'}
getInitData={() => { /*This is intentional (SonarQube)*/ }}
schema={new ChangePasswordSchema(userName, isPgpassFileUsed)}
viewHelperProps={{
mode: 'create',
}}
onSave={onSave}
onClose={onClose}
hasSQL={false}
disableSqlHelp={true}
disableDialogHelp={true}
isTabView={false}
formClassName={classes.root}
/>;
}
ChangePasswordContent.propTypes = {
onSave: PropTypes.func,
onClose: PropTypes.func,
userName: PropTypes.string,
isPgpassFileUsed: PropTypes.bool
};
@@ -1,3 +1,12 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React, { useState, useRef, useEffect } from 'react';
import gettext from 'sources/gettext';
import { Box } from '@material-ui/core';
@@ -0,0 +1,124 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React, { useState, useRef, useEffect } from 'react';
import PropTypes from 'prop-types';
import gettext from 'sources/gettext';
import url_for from 'sources/url_for';
import { Box } from '@material-ui/core';
import CloseIcon from '@material-ui/icons/CloseRounded';
import DeleteForeverIcon from '@material-ui/icons/DeleteForever';
import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
import HelpIcon from '@material-ui/icons/Help';
import { DefaultButton, PrimaryButton, PgIconButton } from '../../../static/js/components/Buttons';
import { useModalStyles } from '../../../static/js/helpers/ModalProvider';
import { FormFooterMessage, InputText, MESSAGE_TYPE } from '../../../static/js/components/FormComponents';
export default function MasterPasswordContent({ closeModal, onResetPassowrd, onOK, onCancel, setHeight, isPWDPresent, data}) {
const classes = useModalStyles();
const containerRef = useRef();
const firstEleRef = useRef();
const okBtnRef = useRef();
const [formData, setFormData] = useState({
password: ''
});
const onTextChange = (e, id) => {
let val = e;
if (e && e.target) {
val = e.target.value;
}
setFormData((prev) => ({ ...prev, [id]: val }));
};
const onKeyDown = (e) => {
// If enter key is pressed then click on OK button
if (e.key === 'Enter') {
okBtnRef.current?.click();
}
};
useEffect(() => {
setTimeout(() => {
firstEleRef.current && firstEleRef.current.focus();
}, 275);
}, []);
useEffect(() => {
setHeight?.(containerRef.current?.offsetHeight);
}, [containerRef.current]);
return (
<Box display="flex" flexDirection="column" className={classes.container} ref={containerRef}>
<Box flexGrow="1" p={2}>
<Box>
<span style={{ fontWeight: 'bold' }}>
{isPWDPresent ? gettext('Please enter your master password.') : gettext('Please set a master password for pgAdmin.')}
</span>
<br />
<span style={{ fontWeight: 'bold' }}>
{isPWDPresent ? gettext('This is required to unlock saved passwords and reconnect to the database server(s).') : gettext('This will be used to secure and later unlock saved passwords and other credentials.')}
</span>
</Box>
<Box marginTop='12px'>
<InputText inputRef={firstEleRef} type="password" value={formData['password']} maxLength={null}
onChange={(e) => onTextChange(e, 'password')} onKeyDown={(e) => onKeyDown(e)}/>
</Box>
<FormFooterMessage type={MESSAGE_TYPE.ERROR} message={data.errmsg} closable={false} style={{
position: 'unset', padding: '12px 0px 0px'
}} />
</Box>
<Box className={classes.footer}>
<Box style={{ marginRight: 'auto' }}>
<PgIconButton data-test="help-masterpassword" title={gettext('Help')} style={{ padding: '0.3rem', paddingLeft: '0.7rem' }} startIcon={<HelpIcon />} onClick={() => {
let _url = url_for('help.static', {
'filename': 'master_password.html',
});
window.open(_url, 'pgadmin_help');
}} >
</PgIconButton>
{isPWDPresent &&
<DefaultButton data-test="reset-masterpassword" style={{ marginLeft: '0.5rem' }} startIcon={<DeleteForeverIcon />}
onClick={() => {onResetPassowrd?.();}} >
{gettext('Reset Master Password')}
</DefaultButton>
}
</Box>
<DefaultButton data-test="close" startIcon={<CloseIcon />} onClick={() => {
onCancel?.();
closeModal();
}} >{gettext('Cancel')}</DefaultButton>
<PrimaryButton ref={okBtnRef} data-test="save" className={classes.margin} startIcon={<CheckRoundedIcon />}
disabled={formData.password.length == 0}
onClick={() => {
let postFormData = new FormData();
postFormData.append('password', formData.password);
postFormData.append('submit_password', true);
onOK?.(postFormData);
closeModal();
}}
>
{gettext('OK')}
</PrimaryButton>
</Box>
</Box>);
}
MasterPasswordContent.propTypes = {
closeModal: PropTypes.func,
onResetPassowrd: PropTypes.func,
onOK: PropTypes.func,
onCancel: PropTypes.func,
setHeight: PropTypes.func,
isPWDPresent: PropTypes.bool,
data: PropTypes.object,
};
@@ -0,0 +1,90 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React, { useState, useRef, useEffect } from 'react';
import gettext from 'sources/gettext';
import { Box } from '@material-ui/core';
import { DefaultButton, PrimaryButton } from '../../../static/js/components/Buttons';
import CloseIcon from '@material-ui/icons/CloseRounded';
import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
import PropTypes from 'prop-types';
import { useModalStyles } from '../../../static/js/helpers/ModalProvider';
import { InputText } from '../../../static/js/components/FormComponents';
import { isEmptyString } from '../../../static/js/validators';
export default function NamedRestoreContent({closeModal, onOK, setHeight}) {
const classes = useModalStyles();
const containerRef = useRef();
const firstEleRef = useRef();
const okBtnRef = useRef();
const [formData, setFormData] = useState({
namedRestorePoint: ''
});
const onTextChange = (e, id) => {
let val = e;
if(e && e.target) {
val = e.target.value;
}
setFormData((prev)=>({...prev, [id]: val}));
};
const onKeyDown = (e) => {
// If enter key is pressed then click on OK button
if (e.key === 'Enter') {
okBtnRef.current?.click();
}
};
useEffect(()=>{
setTimeout(()=>{
firstEleRef.current && firstEleRef.current.focus();
}, 275);
}, []);
useEffect(()=>{
setHeight?.(containerRef.current?.offsetHeight);
}, [containerRef.current]);
const isOKDisabled = isEmptyString(formData.namedRestorePoint);
return (
<Box display="flex" flexDirection="column" className={classes.container} ref={containerRef}>
<Box flexGrow="1" p={2}>
<Box>
<span style={{fontWeight: 'bold'}}>
{gettext('Enter the name of the restore point to add')}
</span>
</Box>
<Box marginTop='12px'>
<InputText inputRef={firstEleRef} type="text" value={formData['namedRestorePoint']}
onChange={(e)=>onTextChange(e, 'namedRestorePoint')} onKeyDown={(e)=>onKeyDown(e)}/>
</Box>
</Box>
<Box className={classes.footer}>
<DefaultButton data-test="close" startIcon={<CloseIcon />} onClick={()=>{
closeModal();
}} >{gettext('Cancel')}</DefaultButton>
<PrimaryButton ref={okBtnRef} data-test="save" disabled={isOKDisabled} className={classes.margin} startIcon={<CheckRoundedIcon />} onClick={()=>{
let postFormData = new FormData();
postFormData.append('value', formData.namedRestorePoint);
onOK?.(postFormData);
closeModal();
}} >{gettext('OK')}</PrimaryButton>
</Box>
</Box>
);
}
NamedRestoreContent.propTypes = {
closeModal: PropTypes.func,
data: PropTypes.object,
onOK: PropTypes.func,
setHeight: PropTypes.func
};
+9 -122
View File
@@ -9,6 +9,7 @@
import { generateNodeUrl } from './node_ajax';
import Notify, {initializeModalProvider, initializeNotifier} from '../../../static/js/helpers/Notifier';
import { checkMasterPassword } from './password_dialogs';
define('pgadmin.browser', [
'sources/gettext', 'sources/url_for', 'require', 'jquery', 'underscore',
@@ -569,101 +570,6 @@ define('pgadmin.browser', [
Notify.alert(error);
});
},
init_master_password: function() {
let self = this;
// Master password dialog
if (!Alertify.dlgMasterPass) {
Alertify.dialog('dlgMasterPass', function factory() {
return {
main: function(title, message, reset) {
this.set('title', title);
this.message = message;
this.reset = reset;
},
build: function() {
Alertify.pgDialogBuild.apply(this);
},
setup:function() {
return {
buttons:[{
text: '',
className: 'btn btn-primary-icon pull-left fa fa-question pg-alertify-icon-button',
attrs: {
name: 'dialog_help',
type: 'button',
label: gettext('Master password'),
url: url_for('help.static', {
'filename': 'master_password.html',
}),
},
},{
text: gettext('Reset Master Password'), className: 'btn btn-secondary fa fa-trash-alt pg-alertify-button pull-left',
},{
text: gettext('Cancel'), className: 'btn btn-secondary fa fa-times pg-alertify-button',
key: 27,
},{
text: gettext('OK'), key: 13, className: 'btn btn-primary fa fa-check pg-alertify-button',
}],
focus: {element: '#password', select: true},
options: {
modal: true, resizable: false, maximizable: false, pinnable: false,
},
};
},
prepare:function() {
let _self = this;
_self.setContent(_self.message);
/* Reset button hide */
if(!_self.reset) {
$(_self.__internal.buttons[1].element).addClass('d-none');
} else {
$(_self.__internal.buttons[1].element).removeClass('d-none');
}
},
callback: function(event) {
let parentDialog = this;
if (event.index == 3) {
/* OK Button */
self.set_master_password(
$('#frmMasterPassword #password').val(),
true,parentDialog.set_callback,
);
} else if(event.index == 2) {
/* Cancel button */
self.masterpass_callback_queue = [];
self.cancel_callback();
} else if(event.index == 1) {
/* Reset Button */
event.cancel = true;
Notify.confirm(gettext('Reset Master Password'),
gettext('This will remove all the saved passwords. This will also remove established connections to '
+ 'the server and you may need to reconnect again. Do you wish to continue?'),
function() {
/* If user clicks Yes */
self.reset_master_password();
parentDialog.close();
return true;
},
function() {/* If user clicks No */ return true;}
);
} else if(event.index == 0) {
/* help Button */
event.cancel = true;
self.showHelp(
event.button.element.name,
event.button.element.getAttribute('url'),
null, null
);
return;
}
},
};
});
}
},
check_master_password: function(on_resp_callback) {
$.ajax({
url: url_for('browser.check_master_password'),
@@ -697,40 +603,21 @@ define('pgadmin.browser', [
});
},
set_master_password: function(password='', button_click=false,
set_master_password: function(password='',
set_callback=()=>{/*This is intentional (SonarQube)*/},
cancel_callback=()=>{/*This is intentional (SonarQube)*/}) {
let data=null, self = this;
data = JSON.stringify({
// data = JSON.stringify({
// 'password': password,
// });
data = {
'password': password,
'button_click': button_click,
});
};
self.masterpass_callback_queue.push(set_callback);
self.cancel_callback = cancel_callback;
$.ajax({
url: url_for('browser.set_master_password'),
type: 'POST',
data: data,
dataType: 'json',
contentType: 'application/json',
}).done((res)=> {
if(!res.data.present) {
self.init_master_password();
Alertify.dlgMasterPass(res.data.title, res.data.content, res.data.reset);
} else {
setTimeout(()=>{
while(self.masterpass_callback_queue.length > 0) {
let callback = self.masterpass_callback_queue.shift();
callback();
}
}, 500);
}
}).fail(function(xhr, status, error) {
Notify.pgRespErrorNotify(xhr, error);
});
// Check master passowrd.
checkMasterPassword(data, self.masterpass_callback_queue, cancel_callback);
},
bind_beforeunload: function() {
+23 -2
View File
@@ -514,12 +514,15 @@ define('pgadmin.browser.node', [
new_height = height;
}
let x = (body.offsetWidth - new_width) / 2;
let y = (body.offsetHeight - new_height) / 4;
var new_panel = dockerObject.addPanel(
'utility_props', window.wcDocker.DOCK.FLOAT, undefined, {
w: new_width,
h: new_height,
x: (screen.width < 700 ? '2%' : '25%'),
y: (screen.height < 500 ? '2%' : '25%'),
x: (x),
y: (y),
}
);
/*set movable false to prevent dialog from docking,
@@ -548,7 +551,25 @@ define('pgadmin.browser.node', [
}
},
registerDockerPanel: function(docker, name, params) {
var w = docker || pgBrowser.docker,
p = w.findPanels(name);
if (p && p.length == 1)
return;
p = new pgBrowser.Panel({
name: name,
showTitle: true,
isCloseable: true,
isPrivate: true,
isLayoutMember: false,
canMaximise: true,
content: '<div class="obj_properties container-fluid h-100"></div>',
...params,
});
p.load(w);
},
registerUtilityPanel: function(docker) {
var w = docker || pgBrowser.docker,
p = w.findPanels('utility_props');
+21 -18
View File
@@ -18,6 +18,7 @@ import { generateNodeUrl } from './node_ajax';
import Notify from '../../../static/js/helpers/Notifier';
import gettext from 'sources/gettext';
import 'wcdocker';
import Theme from '../../../static/js/Theme';
/* The entry point for rendering React based view in properties, called in node.js */
export function getNodeView(nodeType, treeNodeInfo, actionType, itemNodeData, formType, container, containerPanel, onEdit, onSave) {
@@ -204,24 +205,26 @@ export function getNodeView(nodeType, treeNodeInfo, actionType, itemNodeData, fo
/* Fire at will, mount the DOM */
ReactDOM.render(
<SchemaView
formType={formType}
getInitData={initData}
schema={schema}
viewHelperProps={viewHelperProps}
onSave={onSaveClick}
onClose={()=>containerPanel.close()}
onHelp={onHelp}
onEdit={onEdit}
onDataChange={(dataChanged)=>{
isDirty = dataChanged;
}}
confirmOnCloseReset={confirmOnCloseReset}
hasSQL={nodeObj.hasSQL && (actionType === 'create' || actionType === 'edit')}
getSQLValue={getSQLValue}
disableSqlHelp={nodeObj.sqlAlterHelp == '' && nodeObj.sqlCreateHelp == '' && !nodeObj.epasHelp}
disableDialogHelp={nodeObj.dialogHelp == undefined || nodeObj.dialogHelp == ''}
/>, container);
<Theme>
<SchemaView
formType={formType}
getInitData={initData}
schema={schema}
viewHelperProps={viewHelperProps}
onSave={onSaveClick}
onClose={()=>containerPanel.close()}
onHelp={onHelp}
onEdit={onEdit}
onDataChange={(dataChanged)=>{
isDirty = dataChanged;
}}
confirmOnCloseReset={confirmOnCloseReset}
hasSQL={nodeObj.hasSQL && (actionType === 'create' || actionType === 'edit')}
getSQLValue={getSQLValue}
disableSqlHelp={nodeObj.sqlAlterHelp == '' && nodeObj.sqlCreateHelp == '' && !nodeObj.epasHelp}
disableDialogHelp={nodeObj.dialogHelp == undefined || nodeObj.dialogHelp == ''}
/>
</Theme>, container);
}
/* When switching from normal node to collection node, clean up the React mounted DOM */
+9
View File
@@ -273,6 +273,15 @@ define(
);
}
}
if (eventName == 'panelClosed' && selectedPanel._type == 'dashboard') {
getPanelView(
pgBrowser.tree,
$container[0],
pgBrowser,
this._type,
false
);
}
}
});
+3 -1
View File
@@ -24,7 +24,8 @@ export function getPanelView(
tree,
container,
pgBrowser,
panelType
panelType,
panelVisible = true
) {
let item = !_.isNull(tree)? tree.selected(): null,
nodeData, node, treeNodeInfo, preferences, graphPref, dashPref;
@@ -52,6 +53,7 @@ export function getPanelView(
sid={!_.isUndefined(treeNodeInfo) && !_.isUndefined(treeNodeInfo['server']) ? treeNodeInfo['server']._id : ''}
serverConnected={!_.isUndefined(treeNodeInfo) && !_.isUndefined(treeNodeInfo['server']) ? treeNodeInfo.server.connected: false}
dbConnected={!_.isUndefined(treeNodeInfo) && !_.isUndefined(treeNodeInfo['database']) ? treeNodeInfo.database.connected: false}
panelVisible={panelVisible}
/>
</Theme>,
container
@@ -13,7 +13,13 @@ import pgAdmin from 'sources/pgadmin';
import ConnectServerContent from './ConnectServerContent';
import Theme from 'sources/Theme';
import url_for from 'sources/url_for';
import gettext from 'sources/gettext';
import getApiInstance from '../../../static/js/api_instance';
import MasterPasswordContent from './MasterPassowrdContent';
import ChangePasswordContent from './ChangePassowrdContent';
import NamedRestoreContent from './NamedRestoreContent';
import Notify from '../../../static/js/helpers/Notifier';
function setNewSize(panel, width, height) {
// Add height of the header
@@ -130,3 +136,169 @@ export function showSchemaDiffServerPassword() {
/>
</Theme>, j[0]);
}
function masterPassCallbacks(masterpass_callback_queue) {
while(masterpass_callback_queue.length > 0) {
let callback = masterpass_callback_queue.shift();
callback();
}
}
export function checkMasterPassword(data, masterpass_callback_queue, cancel_callback) {
const api = getApiInstance();
api.post(url_for('browser.set_master_password'), data).then((res)=> {
if(!res.data.data.present) {
showMasterPassword(res.data.data.reset, res.data.data.errmsg, masterpass_callback_queue, cancel_callback);
} else {
masterPassCallbacks(masterpass_callback_queue);
}
}).catch(function(xhr, status, error) {
Notify.pgRespErrorNotify(xhr, error);
});
}
// This functions is used to show the master password dialog.
export function showMasterPassword(isPWDPresent, errmsg=null, masterpass_callback_queue, cancel_callback) {
const api = getApiInstance();
var pgBrowser = pgAdmin.Browser;
// Register dialog panel
pgBrowser.Node.registerUtilityPanel();
var panel = pgBrowser.Node.addUtilityPanel(pgBrowser.stdW.md),
j = panel.$container.find('.obj_properties').first();
let title = isPWDPresent ? gettext('Unlock Saved Passwords') : gettext('Set Master Password');
panel.title(title);
ReactDOM.render(
<Theme>
<MasterPasswordContent
isPWDPresent= {isPWDPresent}
data={{'errmsg': errmsg}}
setHeight={(containerHeight) => {
setNewSize(panel, pgBrowser.stdW.md, containerHeight);
}}
closeModal={() => {
panel.close();
}}
onResetPassowrd={()=>{
Notify.confirm(gettext('Reset Master Password'),
gettext('This will remove all the saved passwords. This will also remove established connections to '
+ 'the server and you may need to reconnect again. Do you wish to continue?'),
function() {
var _url = url_for('browser.reset_master_password');
api.delete(_url)
.then(() => {
panel.close();
showMasterPassword(false, null, masterpass_callback_queue, cancel_callback);
})
.catch((err) => {
Notify.error(err.message);
});
return true;
},
function() {/* If user clicks No */ return true;}
);
}}
onCancel={()=>{
cancel_callback?.();
}}
onOK={(formData) => {
panel.close();
checkMasterPassword(formData, masterpass_callback_queue, cancel_callback);
}}
/>
</Theme>, j[0]);
}
export function showChangeServerPassword() {
var pgBrowser = pgAdmin.Browser,
title = arguments[0],
nodeData = arguments[1],
nodeObj = arguments[2],
itemNodeData = arguments[3],
isPgPassFileUsed = arguments[4];
// Register dialog panel
pgBrowser.Node.registerUtilityPanel();
var panel = pgBrowser.Node.addUtilityPanel(pgBrowser.stdW.md),
j = panel.$container.find('.obj_properties').first();
panel.title(title);
ReactDOM.render(
<Theme>
<ChangePasswordContent
onClose={()=>{
panel.close();
}}
onSave={(isNew, data)=>{
return new Promise((resolve, reject)=>{
const api = getApiInstance();
var _url = nodeObj.generate_url(itemNodeData, 'change_password', nodeData, true);
api.post(_url, data)
.then(({data: respData})=>{
Notify.success(respData.info);
// Notify user to update pgpass file
if(isPgPassFileUsed) {
Notify.alert(
gettext('Change Password'),
gettext('Please make sure to disconnect the server'
+ ' and update the new password in the pgpass file'
+ ' before performing any other operation')
);
}
resolve(respData.data);
panel.close();
})
.catch((error)=>{
reject(error);
});
});
}}
userName={nodeData.user.name}
isPgpassFileUsed={isPgPassFileUsed}
/>
</Theme>, j[0]);
}
export function showNamedRestorePoint() {
var pgBrowser = pgAdmin.Browser,
title = arguments[0],
nodeData = arguments[1],
nodeObj = arguments[2],
itemNodeData = arguments[3];
// Register dialog panel
pgBrowser.Node.registerUtilityPanel();
var panel = pgBrowser.Node.addUtilityPanel(pgBrowser.stdW.md),
j = panel.$container.find('.obj_properties').first();
panel.title(title);
ReactDOM.render(
<Theme>
<NamedRestoreContent
setHeight={(containerHeight)=>{
setNewSize(panel, pgBrowser.stdW.md, containerHeight);
}}
closeModal={()=>{
panel.close();
}}
onOK={(formData)=>{
const api = getApiInstance();
var _url = nodeObj.generate_url(itemNodeData, 'restore_point', nodeData, true);
api.post(_url, formData)
.then(res=>{
panel.close();
Notify.success(res.data.data.result);
})
.catch(function(xhr, status, error) {
Notify.pgRespErrorNotify(xhr, error);
});
}}
/>
</Theme>, j[0]);
}
+43 -16
View File
@@ -14,6 +14,8 @@ import getApiInstance from 'sources/api_instance';
import {getHelpUrl, getEPASHelpUrl} from 'pgadmin.help';
import SchemaView from 'sources/SchemaView';
import 'wcdocker';
import Theme from '../../../static/js/Theme';
import url_for from 'sources/url_for';
/* The entry point for rendering React based view in properties, called in node.js */
export function getUtilityView(schema, treeNodeInfo, actionType, formType, container, containerPanel,
@@ -76,26 +78,51 @@ export function getUtilityView(schema, treeNodeInfo, actionType, formType, conta
inCatalog: inCatalog,
};
let initData = ()=>new Promise((resolve, reject)=>{
if(actionType === 'create') {
resolve({});
}else{
api.get(url_for('import_export.get_settings'))
.then((res)=>{
resolve(res.data.data);
})
.catch((err)=>{
if(err.response){
console.error('error resp', err.response);
} else if(err.request){
console.error('error req', err.request);
} else if(err.message){
console.error('error msg', err.message);
}reject(err);
});
}
});
let _schema = schema;
/* Fire at will, mount the DOM */
ReactDOM.render(
<SchemaView
formType={formType}
schema={_schema}
viewHelperProps={viewHelperProps}
customSaveBtnName={saveBtnName}
customSaveBtnIconType={saveBtnIcon}
onSave={onSaveClick}
onClose={()=>containerPanel.close()}
onHelp={onHelp}
onDataChange={()=>{/*This is intentional (SonarQube)*/}}
confirmOnCloseReset={confirmOnReset}
hasSQL={false}
isTabView={isTabView}
disableSqlHelp={sqlHelpUrl == undefined || sqlHelpUrl == ''}
disableDialogHelp={helpUrl == undefined || helpUrl == ''}
/>, container);
<Theme>
<SchemaView
formType={formType}
getInitData={initData}
schema={_schema}
viewHelperProps={viewHelperProps}
customSaveBtnName={saveBtnName}
customSaveBtnIconType={saveBtnIcon}
onSave={onSaveClick}
onClose={()=>containerPanel.close()}
onHelp={onHelp}
onDataChange={()=>{/*This is intentional (SonarQube)*/}}
confirmOnCloseReset={confirmOnReset}
hasSQL={false}
isTabView={isTabView}
disableSqlHelp={sqlHelpUrl == undefined || sqlHelpUrl == ''}
disableDialogHelp={helpUrl == undefined || helpUrl == ''}
/>
</Theme>, container);
}
/* When switching from normal node to collection node, clean up the React mounted DOM */
@@ -16,3 +16,39 @@
background-size: 20px !important;
background-position: center left;
}
.icon-at {
background-image: url('../../../../static/img/at.svg') !important;
background-repeat: no-repeat;
background-size: 15px !important;
align-content: center;
vertical-align: middle;
height: 15px;
}
.icon-key {
background-image: url('../../../../static/img/key.svg') !important;
background-repeat: no-repeat;
background-size: 15px !important;
align-content: center;
vertical-align: middle;
height: 15px;
}
.icon-join {
background-image: url('../../../../static/img/join.svg') !important;
background-repeat: no-repeat;
background-size: 20px !important;
align-content: center;
vertical-align: middle;
height: 15px;
}
.icon-spinner {
background-image: url('../../../../static/img/spinner.svg') !important;
background-repeat: no-repeat;
background-size: 15px !important;
align-content: center;
vertical-align: middle;
height: 15px;
}
@@ -1,23 +0,0 @@
<form name="frmMasterPassword" id="frmMasterPassword" style="height: 100%; width: 100%" onsubmit="return false;">
<div>
<div><strong>{{ content_text|safe }}</strong></div>
<div class="input-group row py-2">
<label for="password" class="col-sm-2 col-form-label">{{ _('Password') }}</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="password" name="password">
</div>
</div>
{% if errmsg %}
<div class='pg-prop-status-bar p-0'>
<div class="error-in-footer">
<div class="d-flex px-2 py-1">
<div class="pr-2">
<i class="fa fa-exclamation-triangle text-danger" aria-hidden="true"></i>
</div>
<div class="alert-text">{{ errmsg }}</div>
</div>
</div>
</div>
{% endif %}
</div>
</form>
@@ -25,30 +25,20 @@ class MasterPasswordTestCase(BaseTestGenerator):
# This testcase validates invalid confirmation password
('TestCase for Create master password dialog', dict(
password="",
content=(
"Set Master Password",
[
"Please set a master password for pgAdmin.",
"This will be used to secure and later unlock saved "
"passwords and other credentials."
]
)
errmsg=None,
is_error=False
)),
('TestCase for Setting Master Password', dict(
password="masterpasstest",
check_if_set=True,
errmsg=None,
is_error=False
)),
('TestCase for Resetting Master Password', dict(
reset=True,
password="",
content=(
"Set Master Password",
[
"Please set a master password for pgAdmin.",
"This will be used to secure and later unlock saved "
"passwords and other credentials."
]
)
errmsg=None,
is_error=False
)),
]
@@ -90,13 +80,6 @@ class MasterPasswordTestCase(BaseTestGenerator):
)
self.assertEqual(response.status_code, 200)
if hasattr(self, 'content'):
self.assertEqual(response.json['data']['title'],
self.content[0])
for text in self.content[1]:
self.assertIn(text, response.json['data']['content'])
if hasattr(self, 'check_if_set'):
response = self.tester.get(
'/browser/master_password'
-7
View File
@@ -36,13 +36,6 @@ class DashboardModule(PgAdminModule):
def get_own_menuitems(self):
return {}
def get_own_javascripts(self):
return [{
'name': 'pgadmin.dashboard',
'path': url_for('dashboard.index') + 'dashboard',
'when': None
}]
def get_own_stylesheets(self):
"""
Returns:
@@ -797,7 +797,7 @@ export default function Dashboard({
preferences={preferences}
sid={sid}
did={did}
pageVisible={true}
pageVisible={props.panelVisible}
></Graphs>
)}
{!_.isUndefined(preferences) && preferences.show_activity && (
@@ -887,6 +887,7 @@ Dashboard.propTypes = {
row: PropTypes.object,
serverConnected: PropTypes.bool,
dbConnected: PropTypes.bool,
panelVisible: PropTypes.bool,
};
export function ChartContainer(props) {
@@ -179,6 +179,9 @@ export default function Graphs({preferences, sid, did, pageVisible, enablePoll=t
});
let path = getStatsUrl(sid, did, getFor);
if (!pageVisible){
return;
}
axios.get(path)
.then((resp)=>{
let data = resp.data;
@@ -81,67 +81,83 @@ class PGDataypeFeatureTest(BaseFeatureTest):
connection.close()
def _update_preferences(self):
file_menu = self.page.find_by_css_selector(
NavMenuLocators.file_menu_css)
file_menu.click()
retry = 2
while retry > 0:
try:
file_menu = self.page.find_by_css_selector(
NavMenuLocators.file_menu_css)
file_menu.click()
self.page.retry_click(
(By.CSS_SELECTOR, NavMenuLocators.preference_menu_item_css),
(By.XPATH, NavMenuLocators.specified_preference_tree_node
.format('Browser'))
)
self.page.retry_click(
(By.CSS_SELECTOR,
NavMenuLocators.preference_menu_item_css),
(By.XPATH,
NavMenuLocators.specified_preference_tree_node
.format('Browser'))
)
wait = WebDriverWait(self.page.driver, 10)
wait = WebDriverWait(self.page.driver, 10)
browser_node = self.page.find_by_xpath(
NavMenuLocators.specified_preference_tree_node.format('Browser'))
if self.page.find_by_xpath(
NavMenuLocators.specified_pref_node_exp_status.
format('Browser')).get_attribute('aria-expanded') == 'false':
ActionChains(self.driver).double_click(browser_node).perform()
browser_node = self.page.find_by_xpath(
NavMenuLocators.specified_preference_tree_node.
format('Browser'))
if self.page.find_by_xpath(
NavMenuLocators.specified_pref_node_exp_status.
format('Browser')).\
get_attribute('aria-expanded') == 'false':
ActionChains(self.driver).\
double_click(browser_node).perform()
self.page.retry_click(
(By.XPATH, NavMenuLocators.specified_sub_node_of_pref_tree_node.
format('Browser', 'Display')),
(By.XPATH, NavMenuLocators.show_system_objects_pref_label_xpath))
self.page.retry_click(
(By.XPATH, NavMenuLocators.
specified_sub_node_of_pref_tree_node.
format('Browser', 'Display')),
(By.XPATH,
NavMenuLocators.show_system_objects_pref_label_xpath))
# Wait till the preference dialogue box is displayed by checking the
# visibility of Show System Object label
wait.until(EC.presence_of_element_located(
(By.XPATH, NavMenuLocators.show_system_objects_pref_label_xpath))
)
# Wait till the preference dialogue
# box is displayed by checking the
# visibility of Show System Object label
wait.until(EC.presence_of_element_located(
(By.XPATH,
NavMenuLocators.show_system_objects_pref_label_xpath))
)
maximize_button = self.page.find_by_xpath(
NavMenuLocators.maximize_pref_dialogue_css)
maximize_button.click()
maximize_button = self.page.find_by_xpath(
NavMenuLocators.maximize_pref_dialogue_css)
maximize_button.click()
specified_preference_tree_node_name = 'Query Tool'
sql_editor = self.page.find_by_xpath(
NavMenuLocators.specified_preference_tree_node.
format(specified_preference_tree_node_name))
if self.page.find_by_xpath(
NavMenuLocators.specified_pref_node_exp_status.
format(specified_preference_tree_node_name)).\
get_attribute('aria-expanded') == 'false':
ActionChains(self.driver).double_click(sql_editor).perform()
specified_preference_tree_node_name = 'Query Tool'
sql_editor = self.page.find_by_xpath(
NavMenuLocators.specified_preference_tree_node.
format(specified_preference_tree_node_name))
if self.page.find_by_xpath(
NavMenuLocators.specified_pref_node_exp_status.
format(specified_preference_tree_node_name)).\
get_attribute('aria-expanded') == 'false':
ActionChains(self.driver).\
double_click(sql_editor).perform()
option_node = \
self.page.find_by_xpath("//*[@id='treeContainer']"
"//div//span[text()='Editor']")
option_node.click()
option_node = \
self.page.find_by_xpath("//*[@id='treeContainer']"
"//div//span[text()='Editor']")
option_node.click()
switch_box_element = self.page.find_by_xpath(
NavMenuLocators.insert_bracket_pair_switch_btn)
switch_box_element = self.page.find_by_xpath(
NavMenuLocators.insert_bracket_pair_switch_btn)
switch_box_element.click()
switch_box_element.click()
maximize_button = self.page.find_by_xpath(
NavMenuLocators.maximize_pref_dialogue_css)
maximize_button.click()
time.sleep(0.5)
maximize_button = self.page.find_by_xpath(
NavMenuLocators.maximize_pref_dialogue_css)
maximize_button.click()
time.sleep(0.5)
# save and close the preference dialog.
self.page.click_modal('Save', react_dialog=True)
# save and close the preference dialog.
self.page.click_modal('Save', react_dialog=True)
break
except Exception:
retry -= 1
def _create_enum_type(self):
query = """CREATE TYPE public.rainbow AS ENUM ('red', 'orange',
@@ -9,6 +9,7 @@
import sys
import random
import traceback
from selenium.webdriver import ActionChains
from selenium.webdriver.common.keys import Keys
@@ -65,36 +66,42 @@ class QueryToolJourneyTest(BaseFeatureTest):
self.wait = WebDriverWait(self.page.driver, 10)
def runTest(self):
self._navigate_to_query_tool()
self.page.execute_query(
"SELECT * FROM %s ORDER BY value " % self.test_table_name)
try:
self._navigate_to_query_tool()
self.page.execute_query(
"SELECT * FROM %s ORDER BY value " % self.test_table_name)
print("Copy rows...", file=sys.stderr, end="")
self._test_copies_rows()
print(" OK.", file=sys.stderr)
print("Copy rows...", file=sys.stderr, end="")
self._test_copies_rows()
print(" OK.", file=sys.stderr)
print("Copy columns...", file=sys.stderr, end="")
self._test_copies_columns()
print(" OK.", file=sys.stderr)
print("Copy columns...", file=sys.stderr, end="")
self._test_copies_columns()
print(" OK.", file=sys.stderr)
print("History tab...", file=sys.stderr, end="")
self._test_history_tab()
print(" OK.", file=sys.stderr)
print("History tab...", file=sys.stderr, end="")
self._test_history_tab()
print(" OK.", file=sys.stderr)
self._insert_data_into_test_editable_table()
self._insert_data_into_test_editable_table()
print("History query source icons and generated queries toggle...",
file=sys.stderr, end="")
self._test_query_sources_and_generated_queries()
print(" OK.", file=sys.stderr)
print("History query source icons and generated queries toggle...",
file=sys.stderr, end="")
self._test_query_sources_and_generated_queries()
print(" OK.", file=sys.stderr)
print("Updatable result sets...", file=sys.stderr, end="")
self._test_updatable_resultset()
print(" OK.", file=sys.stderr)
print("Updatable result sets...", file=sys.stderr, end="")
self._test_updatable_resultset()
print(" OK.", file=sys.stderr)
print("Is editable column header icons...", file=sys.stderr, end="")
self._test_is_editable_columns_icons()
print(" OK.", file=sys.stderr)
print("Is editable column header icons...",
file=sys.stderr, end="")
self._test_is_editable_columns_icons()
print(" OK.", file=sys.stderr)
except Exception as e:
traceback.print_exc()
self.assertTrue(False, 'Exception occurred in run test Tests the '
'path through the query tool data' + str(e))
def _test_copies_rows(self):
self.page.driver.switch_to.default_content()
@@ -346,27 +353,6 @@ class QueryToolJourneyTest(BaseFeatureTest):
self._commit_transaction()
self.page.wait_for_spinner_to_disappear()
# Turn on autocommit
# self.page.check_execute_option("auto_commit")
# query_options = self.page.find_by_css_selector(
# QueryToolLocators.btn_query_dropdown)
# query_options.click()
# retry = 3
# while retry > 0:
# query_options = self.page.find_by_css_selector(
# QueryToolLocators.btn_query_dropdown)
# query_options.click()
# expanded = query_options.get_attribute("aria-expanded")
# if expanded == "false":
# print("query option not yet expanded clicking commit again",
# file=sys.stderr)
# self._commit_transaction()
# time.sleep(0.5)
# query_options.click()
# break
# else:
# retry -= 1
self.page.check_execute_option("auto_commit")
def _check_history_queries_and_icons(self, history_queries, history_icons):
@@ -396,16 +382,24 @@ class QueryToolJourneyTest(BaseFeatureTest):
"""
Updates a numeric cell in the first row of the resultset
"""
cell_el = self.page.find_by_css_selector(
QueryToolLocators.output_row_col.format(2, 3))
ActionChains(self.driver).double_click(cell_el).perform()
ActionChains(self.driver).send_keys(value). \
send_keys(Keys.ENTER).perform()
save_btn = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR, QueryToolLocators.btn_save_data)))
save_btn.click()
retry = 3
while retry > 0:
cell_el = self.page.find_by_css_selector(
QueryToolLocators.output_row_col.format(2, 3))
cell_el.click()
time.sleep(0.2)
ActionChains(self.driver).double_click(cell_el).perform()
ActionChains(self.driver).send_keys(value). \
send_keys(Keys.ENTER).perform()
try:
save_btn = WebDriverWait(self.driver, 2).until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR, QueryToolLocators.btn_save_data)))
save_btn.click()
break
except Exception:
print('Exception occurred')
retry -= 1
def _insert_data_into_test_editable_table(self):
self.page.click_tab(self.query_editor_tab_id, rc_dock=True)
@@ -458,31 +452,46 @@ class QueryToolJourneyTest(BaseFeatureTest):
# first column is rownum
enumerated_should_be_editable = enumerate(cols_should_be_editable, 2)
import time
time.sleep(0.5)
for column_index, should_be_editable in enumerated_should_be_editable:
is_editable = self._check_cell_editable(column_index)
self.assertEqual(is_editable, should_be_editable)
def _check_cell_editable(self, cell_index):
"""Checks if a cell in the first row of the resultset is editable"""
cell_el = self.page.find_by_css_selector(
QueryToolLocators.output_row_col.format(2, cell_index))
# Get existing value
cell_value = int(cell_el.text)
new_value = cell_value + 1
# Try to update value
ActionChains(self.driver).double_click(cell_el).perform()
ActionChains(self.driver).send_keys(new_value). \
send_keys(Keys.ENTER).perform()
time.sleep(0.5)
# Check if the value was updated
# Finding element again to avoid stale element reference exception
cell_el = self.page.\
find_by_css_selector(QueryToolLocators.
output_row_col.format(2, cell_index))
return int(cell_el.text) == new_value
retry = 2
while retry > 0:
try:
cell_el = self.page.find_by_xpath(
QueryToolLocators.output_cell_xpath.format(2, cell_index))
# Get existing value
cell_value = int(cell_el.text)
new_value = cell_value + 1
# Try to update value
ActionChains(self.driver).double_click(cell_el).perform()
cell_el = self.page.find_by_xpath(
QueryToolLocators.output_cell_xpath.format(2, cell_index))
if cell_el.get_attribute('aria-selected') and \
cell_el.text == '':
ActionChains(self.driver).send_keys(new_value).perform()
ActionChains(self.driver).send_keys(Keys.ENTER).perform()
# Check if the value was updated
# Finding element again to avoid stale element
# reference exception
cell_el = self.page. \
find_by_xpath(QueryToolLocators.
output_cell_xpath.format(2, cell_index))
return int(cell_el.text) == new_value
else:
print('Double click not succeeded in try- ' + str(
retry), file=sys.stderr)
retry -= 1
except Exception as e:
traceback.print_exc()
print('Exception while reading cell value in try ' +
str(retry), file=sys.stderr)
retry -= 1
if retry == 0:
raise Exception(e)
def _check_can_add_row(self):
return self.page.check_if_element_exist_by_xpath(
+8 -14
View File
@@ -118,9 +118,8 @@ class QueryToolFeatureTest(BaseFeatureTest):
# this will set focus to correct iframe.
self.page.fill_codemirror_area_with('')
self.page.retry_click(
(By.CSS_SELECTOR, QueryToolLocators.btn_explain_options_dropdown),
(By.CSS_SELECTOR, QueryToolLocators.btn_explain_verbose))
self.assertTrue(self.page.open_explain_options(),
'Unable to open Explain Options dropdown')
# disable Explain options and auto rollback only if they are enabled.
for op in (QueryToolLocators.btn_explain_verbose,
@@ -261,10 +260,8 @@ SELECT generate_series(1, 1000) as id order by id desc"""
self.page.fill_codemirror_area_with(query)
time.sleep(0.5)
explain_op_btn_click = self.page.retry_click(
(By.CSS_SELECTOR, QueryToolLocators.btn_explain_options_dropdown),
(By.CSS_SELECTOR, QueryToolLocators.btn_explain_verbose))
self.assertTrue(explain_op_btn_click, 'Explain Op button click failed')
self.assertTrue(self.page.open_explain_options(),
'Unable to open Explain Options dropdown')
# disable Explain options and auto rollback only if they are enabled.
for op in (QueryToolLocators.btn_explain_verbose,
@@ -298,10 +295,8 @@ SELECT generate_series(1, 1000) as id order by id desc"""
self.page.fill_codemirror_area_with(query)
explain_op_btn_click = self.page.retry_click(
(By.CSS_SELECTOR, QueryToolLocators.btn_explain_options_dropdown),
(By.CSS_SELECTOR, QueryToolLocators.btn_explain_verbose))
self.assertTrue(explain_op_btn_click, 'Explain Op button click failed')
self.assertTrue(self.page.open_explain_options(),
'Unable to open Explain Options')
# disable Explain options and auto rollback only if they are enabled.
for op in (QueryToolLocators.btn_explain_buffers,
@@ -684,9 +679,8 @@ SELECT 1, pg_sleep(300)"""
self.page.fill_codemirror_area_with(
"SELECT count(*) FROM pg_catalog.pg_class;")
self.page.retry_click(
(By.CSS_SELECTOR, QueryToolLocators.btn_explain_options_dropdown),
(By.CSS_SELECTOR, QueryToolLocators.btn_explain_verbose))
self.assertTrue(self.page.open_explain_options(),
'Unable to open Explain Options')
# disable Explain options and only enable COST option
for op in (QueryToolLocators.btn_explain_verbose,
+3 -3
View File
@@ -1,7 +1,7 @@
{
"comment": {
"sample_test_case": {
"colno": ["value to set", "value to verfiy", "data type"]
"colno": ["value to set", "value to verify", "data type"]
}
},
"table_insert_update_cases": {
@@ -16,9 +16,9 @@
"8": ["", "[null]", "text"],
"9": ["", "[51, 52]", "json"],
"10": ["[61,62]", "[61,62]", "json"],
"11": ["", "true", "bool"],
"11": ["true", "true", "bool"],
"12": ["", "[null]", "bool"],
"13": ["", "false", "bool"],
"13": ["false", "false", "bool"],
"14": ["", "[null]", "text[]"],
"15": ["{}", "{}", "text[]"],
"16": ["{data,NULL,'',\"\"}", "{data,NULL,'',\"\"}", "text[]"],
@@ -9,7 +9,9 @@
import json
import os
import sys
import time
import traceback
from selenium.webdriver import ActionChains
from regression.python_test_utils import test_utils
@@ -52,7 +54,7 @@ class CheckForViewDataTest(BaseFeatureTest):
dict())
]
TIMEOUT_STRING = "Timed out waiting for div element to appear"
TIMEOUT_STRING = "5"
# query for creating 'defaults_text' table
defaults_query = """
@@ -119,22 +121,29 @@ CREATE TABLE public.nonintpkey
connection.close()
def runTest(self):
self.page.wait_for_spinner_to_disappear()
self.page.add_server(self.server)
self.page.expand_tables_node("Server", self.server['name'],
self.server['db_password'], self.test_db,
'public')
try:
self.page.wait_for_spinner_to_disappear()
self.page.add_server(self.server)
self.page.expand_tables_node("Server", self.server['name'],
self.server['db_password'],
self.test_db,
'public')
self._load_config_data('table_insert_update_cases')
data_local = config_data
# iterate on both tables
for cnt in (1, 2):
self._perform_test_for_table('defaults_{0}'.format(str(cnt)),
data_local)
# test nonint pkey table
self._load_config_data('table_insert_update_nonint')
data_local = config_data
self._perform_test_for_table('nonintpkey', data_local)
self._load_config_data('table_insert_update_cases')
data_local = config_data
# iterate on both tables
for cnt in (1, 2):
self._perform_test_for_table('defaults_{0}'.format(str(cnt)),
data_local)
# test nonint pkey table
self._load_config_data('table_insert_update_nonint')
data_local = config_data
self._perform_test_for_table('nonintpkey', data_local)
except Exception:
traceback.print_exc()
self.assertTrue(False, 'Exception occurred in run test '
'Validate Insert, Update operations in '
'View/Edit data with given test data')
def after(self):
self.page.remove_server(self.server)
@@ -153,9 +162,6 @@ CREATE TABLE public.nonintpkey
config_data = config_data_json[config_key]
def _perform_test_for_table(self, table_name, config_data_local):
# self.page.click_a_tree_node(
# table_name,
# TreeAreaLocators.sub_nodes_of_tables_node)
table_node = self.page.check_if_element_exists_with_scroll(
TreeAreaLocators.table_node(table_name))
table_node.click()
@@ -212,67 +218,125 @@ CREATE TABLE public.nonintpkey
Returns: None
"""
self.wait.until(EC.visibility_of_element_located(
(By.XPATH, xpath)), CheckForViewDataTest.TIMEOUT_STRING
)
cell_el = self.page.find_by_xpath(xpath)
self.page.driver.execute_script("arguments[0].scrollIntoView(false)",
cell_el)
ActionChains(self.driver).move_to_element(cell_el).double_click(
cell_el
).perform()
cell_type = data[2]
value = data[0]
retry = 4
while retry > 0:
self.wait.until(EC.visibility_of_element_located(
(By.XPATH, xpath)), CheckForViewDataTest.TIMEOUT_STRING
)
cell_el = self.page.find_by_xpath(xpath)
self.page.driver.execute_script(
"arguments[0].scrollIntoView(false)", cell_el)
time.sleep(0.2)
ActionChains(self.driver).move_to_element(cell_el).perform()
ActionChains(self.driver).double_click(cell_el).perform()
if cell_type in ['int', 'int[]']:
if value == 'clear':
cell_el.find_element(By.CSS_SELECTOR, 'input').clear()
if cell_type in ['int', 'int[]'] and \
self._update_numeric_cell(xpath, value):
break
elif cell_type in ['text', 'text[]', 'boolean[]'] and \
self._update_text_cell(cell_el, value):
break
elif cell_type in ['json', 'jsonb'] and \
self._update_json_cell(cell_el, value):
retry = 0
elif cell_type in ['bool'] and \
self._update_boolean_cell(cell_el, value):
retry = 0
else:
ActionChains(self.driver).send_keys(value). \
send_keys(Keys.ENTER).perform()
elif cell_type in ['text', 'text[]', 'boolean[]']:
text_area_ele = WebDriverWait(self.driver, 5).until(
print('Unable to update cell in try ' + str(retry),
file=sys.stderr)
retry -= 1
def _update_numeric_cell(self, cell_el_xpath, value):
try:
cell_el = self.page.find_by_xpath(cell_el_xpath)
if cell_el.get_attribute('aria-selected') and \
cell_el.text == '':
if value == 'clear':
cell_el.find_element(By.CSS_SELECTOR, 'input').clear()
else:
ActionChains(self.driver).send_keys(value).perform()
ActionChains(self.driver).send_keys(Keys.TAB).perform()
return True
else:
print('Cell is NOT selected yet.', file=sys.stderr)
return False
except Exception:
traceback.print_exc()
print('Exception occurred while updating int cell',
file=sys.stderr)
return False
def _update_text_cell(self, cell_el, value):
try:
text_area_ele = WebDriverWait(self.driver, 3).until(
EC.visibility_of_element_located(
(By.CSS_SELECTOR,
QueryToolLocators.row_editor_text_area_css)))
text_area_ele.clear()
time.sleep(0.3)
text_area_ele.click()
text_area_ele.send_keys(value)
# Click on editor's Save button
self.page.find_by_css_selector(
QueryToolLocators.text_editor_ok_btn_css).click()
elif cell_type in ['json', 'jsonb']:
jsoneditor_area_ele = self.page.find_by_css_selector(
QueryToolLocators.json_editor_text_area_css)
platform = 'mac'
if "platform" in self.driver.capabilities:
platform = (self.driver.capabilities["platform"]).lower()
elif "platformName" in self.driver.capabilities:
platform = (self.driver.capabilities["platformName"]).lower()
if 'mac' in platform:
key_to_press = Keys.COMMAND
else:
key_to_press = Keys.CONTROL
actions = ActionChains(self.driver)
# actions.move_to_element(jsoneditor_area_ele).click().perform()
actions.key_down(key_to_press).send_keys('a').key_up(key_to_press)\
.send_keys(Keys.DELETE).perform()
actions.send_keys(value) .perform()
ActionChains(self.driver).send_keys(value).perform()
# Click on editor's Save button
self.page.find_by_css_selector(
QueryToolLocators.text_editor_ok_btn_css).click()
return True
except Exception:
traceback.print_exc()
print('Exception occurred while updating text cell ',
file=sys.stderr)
return False
def _update_json_cell(self, cell_el, value):
platform = 'mac'
if "platform" in self.driver.capabilities:
platform = (self.driver.capabilities["platform"]).lower()
elif "platformName" in self.driver.capabilities:
platform = (self.driver.capabilities["platformName"]).lower()
if 'mac' in platform:
key_to_press = Keys.COMMAND
else:
# Boolean editor test for to True click
key_to_press = Keys.CONTROL
try:
WebDriverWait(self.driver, 3).until(
EC.visibility_of_element_located(
(By.CSS_SELECTOR,
QueryToolLocators.json_editor_text_area_css)))
actions = ActionChains(self.driver)
actions.key_down(key_to_press).send_keys('a').\
key_up(key_to_press).send_keys(Keys.DELETE).perform()
actions.send_keys(value).perform()
# Click on editor's Save button
self.page.find_by_css_selector(
QueryToolLocators.text_editor_ok_btn_css).click()
return True
except Exception:
traceback.print_exc()
print('Exception occurred while updating json cell ',
file=sys.stderr)
return False
def _update_boolean_cell(self, cell_el, value):
# Boolean editor test for to True click
try:
checkbox_el = self.page.find_by_css_selector(
QueryToolLocators.row_editor_checkbox_css)
if data[1] == 'true':
if value == 'true':
checkbox_el.click()
# Boolean editor test for to False click
elif data[1] == 'false':
elif value == 'false':
# Sets true
checkbox_el.click()
# Sets false
ActionChains(self.driver).click(checkbox_el).perform()
return True
except Exception:
traceback.print_exc()
print('Exception occurred while updating boolean cell',
file=sys.stderr)
return False
def _view_data_grid(self, table_name):
self.page.driver.find_element(By.LINK_TEXT, "Object").click()
@@ -327,9 +391,9 @@ CREATE TABLE public.nonintpkey
items.sort(reverse=False)
for idx in items:
# rowindex starts with 2 and 1st colindex is rownum
time.sleep(0.2)
cell_xpath = CheckForViewDataTest\
._get_cell_xpath(str(idx + 1), row + 1)
time.sleep(0.5)
self._update_cell(cell_xpath, data[str(idx)])
self.page.find_by_css_selector(
QueryToolLocators.btn_save_data).click()
@@ -359,7 +423,6 @@ CREATE TABLE public.nonintpkey
"document.querySelector('.rdg').scrollLeft=0"
)
xpath = QueryToolLocators.output_row_xpath.format(2)
scroll_on_arg_for_js = "arguments[0].scrollIntoView(false)"
self.page.wait_for_query_tool_loading_indicator_to_disappear()
@@ -383,7 +446,7 @@ CREATE TABLE public.nonintpkey
except Exception:
print("stale reference exception at id:", idx)
retry -= 1
time.sleep(0.4)
time.sleep(0.4)
self.assertEqual(element.text, config_check_data[str(idx)][1])
# scroll browser back to the left
+1178 -1130
View File
File diff suppressed because it is too large. Load diff
+28 -16
View File
@@ -29,22 +29,6 @@ MODULE_NAME = 'misc'
class MiscModule(PgAdminModule):
LABEL = gettext('Miscellaneous')
def get_own_javascripts(self):
return [
{
'name': 'pgadmin.misc.explain',
'path': url_for('misc.index') + 'explain/explain',
'preloaded': False
}, {
'name': 'snap.svg',
'path': url_for(
'misc.static', filename='explain/vendor/snap.svg/' + (
'snap.svg' if config.DEBUG else 'snap.svg-min'
)),
'preloaded': False
}
]
def get_own_stylesheets(self):
stylesheets = []
return stylesheets
@@ -110,6 +94,34 @@ class MiscModule(PgAdminModule):
return ['misc.ping', 'misc.index', 'misc.cleanup',
'misc.validate_binary_path']
def register(self, app, options):
"""
Override the default register function to automagically register
sub-modules at once.
"""
from .bgprocess import blueprint as module
self.submodules.append(module)
from .cloud import blueprint as module
self.submodules.append(module)
from .dependencies import blueprint as module
self.submodules.append(module)
from .dependents import blueprint as module
self.submodules.append(module)
from .file_manager import blueprint as module
self.submodules.append(module)
from .sql import blueprint as module
self.submodules.append(module)
from .statistics import blueprint as module
self.submodules.append(module)
super(MiscModule, self).register(app, options)
# Initialise the module
blueprint = MiscModule(MODULE_NAME, __name__)
Loaded 100 of 241 files, more files were not shown because too many files have changed in this diff. Show more