Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab04b30726 | ||
|
|
dc4f59e664 | ||
|
|
8738f6dd98 | ||
|
|
039766b492 | ||
|
|
2da970a26c | ||
|
|
86e1f33231 | ||
|
|
702fbac1c0 | ||
|
|
7483ddf2b2 | ||
|
|
78b041465e | ||
|
|
5e3aa305fd | ||
|
|
703478c13d | ||
|
|
8d53c10643 | ||
|
|
025c98ced8 | ||
|
|
8e214ca8fb | ||
|
|
23265061b2 | ||
|
|
9d985d3d8f | ||
|
|
03477d407e | ||
|
|
2dd88e5a4e | ||
|
|
2452d90300 | ||
|
|
508f97b08e | ||
|
|
3f67f512ec | ||
|
|
fdc3028124 | ||
|
|
3a3eea0320 | ||
|
|
d95a6f2af7 | ||
|
|
a3d3c74e67 | ||
|
|
c2db647379 | ||
|
|
6cc3423cbe | ||
|
|
eb658a75ab | ||
|
|
b2205fc6e1 | ||
|
|
5768ade198 | ||
|
|
b0727cc532 | ||
|
|
9e8af67202 | ||
|
|
69d0fbe419 |
No files matched your search
@@ -227,6 +227,7 @@ is-arrayish 0.2.1
|
||||
is-color-stop 1.1.0 MIT git+https://github.com/pigcan/is-color-stop.git
|
||||
is-finite 1.1.0 MIT https://github.com/sindresorhus/is-finite.git
|
||||
istanbul-instrumenter-loader 3.0.1 MIT https://github.com/webpack-contrib/istanbul-instrumenter-loader.git
|
||||
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.12.1 MIT git://github.com/jquery/jquery-ui.git
|
||||
@@ -239,6 +240,7 @@ json-bignumber 1.0.2
|
||||
json-parse-even-better-errors 2.3.1 MIT https://github.com/npm/json-parse-even-better-errors
|
||||
json-schema-traverse 0.4.1 MIT git+https://github.com/epoberezkin/json-schema-traverse.git
|
||||
json-schema-traverse 0.3.1 MIT git+https://github.com/epoberezkin/json-schema-traverse.git
|
||||
json-source-map 0.6.1 MIT git+https://github.com/epoberezkin/json-source-map.git
|
||||
json5 2.2.0 MIT git+https://github.com/json5/json5.git
|
||||
json5 1.0.1 MIT git+https://github.com/json5/json5.git
|
||||
karma-coverage 2.0.3 MIT git://github.com/karma-runner/karma-coverage.git
|
||||
@@ -257,6 +259,7 @@ ml-array-max 1.2.3
|
||||
ml-array-min 1.2.2 MIT git+https://github.com/mljs/array.git
|
||||
ml-array-rescale 1.3.5 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.1 MIT https://github.com/moment/moment.git
|
||||
moment 2.24.0 MIT https://github.com/moment/moment.git
|
||||
moment-timezone 0.5.33 MIT https://github.com/moment/moment-timezone.git
|
||||
@@ -274,6 +277,7 @@ parseuri 0.0.6
|
||||
path-type 4.0.0 MIT https://github.com/sindresorhus/path-type.git
|
||||
pathfinding 0.4.18 MIT git://github.com/qiao/PathFinding.js.git
|
||||
performance-now 2.1.0 MIT git://github.com/braveg1rl/performance-now.git
|
||||
picomodal 3.0.0 MIT https://github.com/Nycto/PicoModal.git
|
||||
popper.js 1.16.1 MIT git+https://github.com/FezVrasta/popper.js.git
|
||||
postcss 8.3.0 MIT https://github.com/postcss/postcss.git
|
||||
postcss-calc 8.0.0 MIT https://github.com/postcss/postcss-calc.git
|
||||
@@ -355,12 +359,14 @@ xterm-addon-search 0.8.0
|
||||
xterm-addon-web-links 0.4.0 MIT https://github.com/xtermjs/xterm.js
|
||||
yeast 0.1.2 MIT https://github.com/unshiftio/yeast.git
|
||||
@fortawesome/fontawesome-free 5.15.3 (CC-BY-4.0 AND OFL-1.1 AND MIT) https://github.com/FortAwesome/Font-Awesome
|
||||
@sphinxxxx/color-conversion 2.2.2 ISC git+https://github.com/Sphinxxxx/color-conversion.git
|
||||
@trysound/sax 0.1.1 ISC git://github.com/svg/sax.git
|
||||
boolbase 1.0.0 ISC https://github.com/fb55/boolbase
|
||||
css-declaration-sorter 6.0.3 ISC https://github.com/Siilwyn/css-declaration-sorter.git
|
||||
electron-to-chromium 1.3.740 ISC https://github.com/kilian/electron-to-chromium/
|
||||
icss-utils 5.1.0 ISC git+https://github.com/css-modules/icss-utils.git
|
||||
is-resolvable 1.1.0 ISC https://github.com/shinnn/is-resolvable.git
|
||||
jsonrepair 2.2.1 ISC https://github.com/josdejong/jsonrepair.git
|
||||
lru-cache 6.0.0 ISC git://github.com/isaacs/node-lru-cache.git
|
||||
minimatch 3.0.4 ISC git://github.com/isaacs/minimatch.git
|
||||
postcss-modules-extract-imports 3.0.0 ISC https://github.com/css-modules/postcss-modules-extract-imports.git
|
||||
@@ -369,10 +375,25 @@ postcss-modules-values 4.0.0
|
||||
semver 7.3.5 ISC https://github.com/npm/node-semver
|
||||
semver 5.7.1 ISC https://github.com/npm/node-semver
|
||||
semver 6.3.0 ISC https://github.com/npm/node-semver
|
||||
vanilla-picker 2.11.2 ISC https://github.com/Sphinxxxx/vanilla-picker.git
|
||||
yallist 4.0.0 ISC git+https://github.com/isaacs/yallist.git
|
||||
yaml 1.10.2 ISC https://github.com/eemeli/yaml.git
|
||||
ace-builds 1.4.12 BSD-3-Clause https://github.com/ajaxorg/ace-builds.git
|
||||
istanbul-lib-coverage 1.2.1 BSD-3-Clause git@github.com:istanbuljs/istanbuljs.git
|
||||
istanbul-lib-coverage 3.0.0 BSD-3-Clause git+ssh://git@github.com/istanbuljs/istanbuljs.git
|
||||
istanbul-lib-instrument 1.10.2 BSD-3-Clause git@github.com:istanbuljs/istanbuljs.git
|
||||
istanbul-lib-instrument 4.0.3 BSD-3-Clause git+ssh://git@github.com/istanbuljs/istanbuljs.git
|
||||
istanbul-lib-report 3.0.0 BSD-3-Clause git+ssh://git@github.com/istanbuljs/istanbuljs.git
|
||||
istanbul-lib-source-maps 4.0.0 BSD-3-Clause git+ssh://git@github.com/istanbuljs/istanbuljs.git
|
||||
istanbul-reports 3.0.2 BSD-3-Clause git+ssh://git@github.com/istanbuljs/istanbuljs.git
|
||||
source-map 0.5.7 BSD-3-Clause http://github.com/mozilla/source-map.git
|
||||
source-map 0.6.1 BSD-3-Clause http://github.com/mozilla/source-map.git
|
||||
source-map-js 0.6.2 BSD-3-Clause http://github.com/7rulnik/source-map.git
|
||||
sprintf-js 1.1.2 BSD-3-Clause https://github.com/alexei/sprintf.js.git
|
||||
bootstrap-datepicker 1.9.0 Apache-2.0 https://github.com/uxsolutions/bootstrap-datepicker.git
|
||||
eve 0.5.4 Apache-2.0 git@github.com:adobe-webplatform/eve.git
|
||||
jmespath 0.15.0 Apache-2.0 git://github.com/jmespath/jmespath.js
|
||||
jsoneditor 9.5.3 Apache-2.0 https://github.com/josdejong/jsoneditor.git
|
||||
paths-js 0.4.11 Apache-2.0 https://github.com/andreaferretti/paths-js.git
|
||||
snapsvg 0.5.1 Apache-2.0 git@github.com:adobe-webplatform/Snap.svg.git
|
||||
caniuse-lite 1.0.30001230 CC-BY-4.0 https://github.com/browserslist/caniuse-lite.git
|
||||
@@ -388,22 +409,11 @@ nth-check 2.0.0
|
||||
uri-js 4.4.1 BSD-2-Clause http://github.com/garycourt/uri-js
|
||||
FileSaver 0.10.0 UNKNOWN Unknown
|
||||
heap 0.2.5 UNKNOWN git://github.com/qiao/heap.js.git
|
||||
istanbul-lib-coverage 1.2.1 BSD-3-Clause git@github.com:istanbuljs/istanbuljs.git
|
||||
istanbul-lib-coverage 3.0.0 BSD-3-Clause git+ssh://git@github.com/istanbuljs/istanbuljs.git
|
||||
istanbul-lib-instrument 1.10.2 BSD-3-Clause git@github.com:istanbuljs/istanbuljs.git
|
||||
istanbul-lib-instrument 4.0.3 BSD-3-Clause git+ssh://git@github.com/istanbuljs/istanbuljs.git
|
||||
istanbul-lib-report 3.0.0 BSD-3-Clause git+ssh://git@github.com/istanbuljs/istanbuljs.git
|
||||
istanbul-lib-source-maps 4.0.0 BSD-3-Clause git+ssh://git@github.com/istanbuljs/istanbuljs.git
|
||||
istanbul-reports 3.0.2 BSD-3-Clause git+ssh://git@github.com/istanbuljs/istanbuljs.git
|
||||
source-map 0.5.7 BSD-3-Clause http://github.com/mozilla/source-map.git
|
||||
source-map 0.6.1 BSD-3-Clause http://github.com/mozilla/source-map.git
|
||||
source-map-js 0.6.2 BSD-3-Clause http://github.com/7rulnik/source-map.git
|
||||
sprintf-js 1.1.2 BSD-3-Clause https://github.com/alexei/sprintf.js.git
|
||||
mdn-data 2.0.14 CC0-1.0 https://github.com/mdn/data.git
|
||||
mousetrap 1.6.5 Apache-2.0 WITH LLVM-exception git://github.com/ccampbell/mousetrap.git
|
||||
precond 0.2.3 MIT* https://github.com/MathieuTurcotte/node-precond.git
|
||||
webcabin-docker 2.2.5 MIT* Unknown
|
||||
tablesorter 2.31.3 (MIT OR GPL-2.0) git://github.com/Mottie/tablesorter.git
|
||||
|
||||
335 dependencies listed.
|
||||
345 dependencies listed.
|
||||
|
||||
@@ -107,6 +107,86 @@ properties of the geometries directly in map, just click the specific geometry:
|
||||
- *Internet access:* An internet connection is required for the Geometry Viewer
|
||||
to function correctly.
|
||||
|
||||
|
||||
**JSON Data Editor**
|
||||
|
||||
A built in json editor is provided for *JSON/JSONB Data*.Double clicking on json/jsonb
|
||||
data type cell in data grid will open JSON Editor.Editor provides different mode to
|
||||
view and edit json data.
|
||||
|
||||
|
||||
.. image:: images/json_editor_modes.png
|
||||
:alt: Json Editor
|
||||
:align: center
|
||||
|
||||
*Code Mode*:
|
||||
Provides way to format & compact json data.Also provides ability to repair json data
|
||||
by fixing quotes and escape characters, removing comments and JSONP notation and
|
||||
turn JavaScript objects into JSON.
|
||||
|
||||
*Tree Mode*:
|
||||
Enabled to change, add, move, remove, and duplicate fields and values.Provides ability
|
||||
to searh & hilight data.
|
||||
|
||||
*Form Mode*:
|
||||
Allows only to edit values in json data there by providing ability to keep data structure
|
||||
unchanged while editing.
|
||||
|
||||
*Preview Mode*:
|
||||
Provides ability to check data before saving and also shows size of current json data.
|
||||
Format and compact json data as well.
|
||||
|
||||
|
||||
*Editor Toolbar*
|
||||
|
||||
Different options are provided to manipulate json data.
|
||||
|
||||
Code/Preview mode:
|
||||
|
||||
.. image:: images/json_editor_code_preview_tool_bar.png
|
||||
:alt: Json Editor Code/Preview mode Tool Bar
|
||||
:align: center
|
||||
|
||||
|
||||
Tree/Form mode:
|
||||
|
||||
.. image:: images/json_editor_tree_form_mode_tool_bar.png
|
||||
:alt: Json Editor Tree/Form mode Tool Bar
|
||||
:align: center
|
||||
|
||||
|
||||
.. table::
|
||||
:class: longtable
|
||||
:widths: 1 3 2
|
||||
|
||||
+----------------------+----------------------------------------------------------------------------------------+---------------------------+
|
||||
| Icon | Behavior | Available in mode |
|
||||
+======================+========================================================================================+===========================+
|
||||
| *Format Json* | Click to *Format Json* format json data with proper indentation. | Code, Preview |
|
||||
+----------------------+----------------------------------------------------------------------------------------+---------------------------+
|
||||
| *Compact Json* | Click to *Compact Json* get compacted json data. | Code, Preview |
|
||||
+----------------------+----------------------------------------------------------------------------------------+---------------------------+
|
||||
| *Sort* | Click to *Sort* icon to set sorting criteria for the data using file values. | Code, Preview, Tree |
|
||||
+----------------------+----------------------------------------------------------------------------------------+---------------------------+
|
||||
| *Transform* | Click to *Transform* to filter data using JSME query language. | Code, Preview, Tree |
|
||||
+----------------------+----------------------------------------------------------------------------------------+---------------------------+
|
||||
| *Undo* | Click to *Undo* to undo last action performed on data . | Code, Preview, Tree, From |
|
||||
+----------------------+----------------------------------------------------------------------------------------+---------------------------+
|
||||
| *Redo* | Click to *Redo* to repat last action performed on data . | Code, Preview, Tree, From |
|
||||
+----------------------+----------------------------------------------------------------------------------------+---------------------------+
|
||||
| *Mode* | Click to *Mode* dropdown to change dipaly mode of editor.Different modes avaialble | Code, Tree, From, Preview |
|
||||
| | are Code, Preview, Tree, From. | |
|
||||
+----------------------+----------------------------------------------------------------------------------------+---------------------------+
|
||||
| *Expand All* | Click to *Expand All* to expand json data. | Tree, From |
|
||||
+----------------------+----------------------------------------------------------------------------------------+---------------------------+
|
||||
| *Collapse All* | Click to *Redo* to collapse json data. | Tree, From |
|
||||
+----------------------+----------------------------------------------------------------------------------------+---------------------------+
|
||||
| *Search Box* | Enter partial/complete string to search in data. | Tree, From |
|
||||
+----------------------+----------------------------------------------------------------------------------------+---------------------------+
|
||||
|
||||
|
||||
|
||||
|
||||
Sort/Filter options dialog
|
||||
**************************
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ Mode is pre-configured for security.
|
||||
login
|
||||
user_management
|
||||
change_user_password
|
||||
restore_locked_user
|
||||
ldap
|
||||
kerberos
|
||||
oauth2
|
||||
|
||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 387 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 185 KiB |
|
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 183 KiB |
|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 53 KiB |
@@ -10,16 +10,16 @@ Use the *Login* dialog to log in to pgAdmin:
|
||||
:alt: pgAdmin login dialog
|
||||
:align: center
|
||||
|
||||
Use the fields in the *Login* dialog to authenticate your connection. There are
|
||||
Use the fields in the *Login* dialog to authenticate your connection. There are
|
||||
two ways to authenticate your connection:
|
||||
|
||||
- From pgAdmin version 4.21 onwards, support for LDAP authentication
|
||||
has been added. If LDAP authentication has been enabled for your pgAdmin
|
||||
application, you can use your LDAP credentials to log in to pgAdmin:
|
||||
|
||||
* Provide the LDAP username in the *Email Address/Username* field.
|
||||
* Provide the LDAP username in the *Email Address/Username* field.
|
||||
|
||||
* Provide your LDAP password in the Password field.
|
||||
* Provide your LDAP password in the Password field.
|
||||
|
||||
- Alternatively, you can use the following information to log in to pgAdmin:
|
||||
|
||||
@@ -53,6 +53,13 @@ If you have forgotten the email associated with your account, please contact
|
||||
your administrator.
|
||||
|
||||
Please note that your LDAP password cannot be recovered using this dialog. If
|
||||
you enter your LDAP username in the *Email Address/Username* field, and then
|
||||
you enter your LDAP username in the *Email Address/Username* field, and then
|
||||
enter your email to recover your password, an error message will be displayed
|
||||
asking you to contact the LDAP administrator to recover your LDAP password.
|
||||
|
||||
Avoiding a bruteforce attack
|
||||
****************************
|
||||
|
||||
You have the possibility to lock an account by setting ``MAX_LOGIN_ATTEMPTS``
|
||||
once it has reached the maximum number of login attempts.
|
||||
You can disable this feature by setting the value to zero.
|
||||
@@ -353,6 +353,10 @@ Use the fields on the *Options* panel to manage editor preferences.
|
||||
* When the *Auto-Rollback?* switch is set to *True*, failed queries are rolled
|
||||
back.
|
||||
|
||||
* When the *Copy SQL from main window to query tool?* switch is set to *True*,
|
||||
the CREATE sql of the selected object will be copied to query tool when query tool
|
||||
will open.
|
||||
|
||||
* When the *Prompt to save unsaved data changes?* switch is set to *True*, the
|
||||
editor will prompt the user to saved unsaved data when exiting the data
|
||||
editor.
|
||||
@@ -377,14 +381,13 @@ Use the fields on the *Options* panel to manage editor preferences.
|
||||
Use the fields on the *Results grid* panel to specify your formatting
|
||||
preferences for copied data.
|
||||
|
||||
* Specify the maximum width of the column when 'Resize by data?' is set to True.
|
||||
If it is set to 0 then columns will auto-size to the maximum width of the data
|
||||
in the column. If 'Resize by data?' is set to False then this setting won't
|
||||
take any effect.
|
||||
* When the *Resize by data?* switch is set to *True*, then data columns will
|
||||
* When the *Columns sized by* is set to *Column data*, then data columns will
|
||||
auto-size to the maximum width of the data in the column as loaded in the
|
||||
first batch. If False, the column will be sized to the widest of the data
|
||||
type or column name.
|
||||
first batch. If set to *Column name*, the column will be sized to the widest
|
||||
of the data type or column name.
|
||||
* Specify the maximum width of the column in pixels when 'Columns sized by' is
|
||||
set to *Column data*. If 'Columns sized by' is set to *Column name* then this
|
||||
setting won't have any effect.
|
||||
* Use the *Result copy field separator* drop-down listbox to select the field
|
||||
separator for copied data.
|
||||
* Use the *Result copy quote character* drop-down listbox to select the quote
|
||||
|
||||
@@ -11,6 +11,7 @@ notes for it.
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
release_notes_5_6
|
||||
release_notes_5_5
|
||||
release_notes_5_4
|
||||
release_notes_5_3
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
************
|
||||
Version 5.6
|
||||
************
|
||||
|
||||
Release date: 2021-08-12
|
||||
|
||||
This release contains a number of bug fixes and new features since the release of pgAdmin4 5.5.
|
||||
|
||||
New features
|
||||
************
|
||||
|
||||
| `Issue #4904 <https://redmine.postgresql.org/issues/4904>`_ - Added support to copy SQL from main window to query tool.
|
||||
| `Issue #5198 <https://redmine.postgresql.org/issues/5198>`_ - Added support for formatted JSON viewer/editor when interacting with data in a JSON column.
|
||||
|
||||
Housekeeping
|
||||
************
|
||||
|
||||
| `Issue #6622 <https://redmine.postgresql.org/issues/6622>`_ - Rename the "Resize by data?" to "Columns sized by" and disabled the 'Maximum column width' button if 'Columns sized by' is set to 'Column data'.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Issue #6337 <https://redmine.postgresql.org/issues/6337>`_ - Ensure that the login account should be locked after N number of attempts. N is configurable using the 'MAX_LOGIN_ATTEMPTS' parameter.
|
||||
| `Issue #6369 <https://redmine.postgresql.org/issues/6369>`_ - Fixed CSRF errors for stale sessions by increasing the session expiration time for desktop mode.
|
||||
| `Issue #6448 <https://redmine.postgresql.org/issues/6448>`_ - Fixed an issue in the search object when searching in 'all types' or 'subscription' if the user doesn't have access to the subscription.
|
||||
| `Issue #6574 <https://redmine.postgresql.org/issues/6574>`_ - Fixed an issue where paste is not working through Right-Click option on PSQL.
|
||||
| `Issue #6580 <https://redmine.postgresql.org/issues/6580>`_ - Fixed TypeError 'NoneType' object is not sub scriptable.
|
||||
| `Issue #6586 <https://redmine.postgresql.org/issues/6586>`_ - Fixed incorrect tablespace options in the drop-down for move objects dialog.
|
||||
| `Issue #6618 <https://redmine.postgresql.org/issues/6618>`_ - Fixed an issue where the titles in query tabs are different.
|
||||
| `Issue #6619 <https://redmine.postgresql.org/issues/6619>`_ - Fixed incorrect binary path issue when the user deletes the binary path from the preferences.
|
||||
| `Issue #6643 <https://redmine.postgresql.org/issues/6643>`_ - Ensure that all the required options should be loaded when the Range data type is selected while creating a custom data type.
|
||||
| `Issue #6650 <https://redmine.postgresql.org/issues/6650>`_ - Fixed dashboard server activity issue when active_since parameter is None.
|
||||
| `Issue #6664 <https://redmine.postgresql.org/issues/6664>`_ - Fixed an issue where even if the user is locked, he can reset the password and can login into pgAdmin.
|
||||
@@ -0,0 +1,50 @@
|
||||
.. _restore_locked_user:
|
||||
|
||||
*****************************
|
||||
`Lock/Restore Account`:index:
|
||||
*****************************
|
||||
|
||||
Account locking/unlocking via user management dialog:
|
||||
|
||||
An admin can lock and unlock user from the user management dialog. This allows the admin to lock or restore
|
||||
the user when there are several failed login attempts. This page guides you through configuring per-user
|
||||
locking/restoring. For more details visit :ref:`User management <user_management>`.
|
||||
|
||||
.. csv-table::
|
||||
:header: "**Parameter**", "**Description**"
|
||||
:class: longtable
|
||||
:widths: 35, 55
|
||||
|
||||
"MAX_LOGIN_ATTEMPTS", "Which sets the number of failed login that
|
||||
are allowed. If this value is exceeded, the account is locked and can be reset by an administrator.
|
||||
By setting the variable to the value zero this feature is deactivated."
|
||||
|
||||
|
||||
When Administrator itself gets locked, following steps may be considered to restore it:
|
||||
|
||||
- Increase MAX_LOGIN_ATTEMPTS, and try to successfully login to restore the account.
|
||||
|
||||
OR
|
||||
|
||||
By updating SQLite DB (pgAdmin4.db):
|
||||
|
||||
* Locate the pgAdmin4.db file and open it using any DB Browser (or DB Browser for SQLite)
|
||||
* After opening the DB file, head towards 'Execute SQL' section.
|
||||
* Run below query -
|
||||
|
||||
*UPDATE USER SET LOCKED = false, LOGIN_ATTEMPTS = 0 WHERE USERNAME = <YOUR_EMAIL_ID>*
|
||||
|
||||
* Make sure the query changes are committed.
|
||||
|
||||
Account locking by failed login attempts:
|
||||
|
||||
.. image:: images/login_attempt_user.png
|
||||
:alt: Failed login attempt for current user
|
||||
:align: center
|
||||
|
||||
pgAdmin application is configured to lock a user account when a number of consecutive failed login attempts are
|
||||
exceeded.
|
||||
|
||||
MAX_LOGIN_ATTEMPTS is defaulted to 3 unsuccessful login attempts, after which the account would be locked.
|
||||
|
||||
The only way to restore the user account is by contacting the Administrator and ask to unlock it.
|
||||
@@ -58,6 +58,8 @@ Provide information about the new pgAdmin role in the row:
|
||||
as authentication source since LDAP password is not stored in the pgAdmin database.
|
||||
* Re-enter the password in the *Confirm password* field. This field is disabled
|
||||
if you select *ldap* as authentication source.
|
||||
* Move the *Locked* switch to the *True* position if you want to lock the account;
|
||||
the default is *False*. This functionality is useful when a user is locked by trying unsuccessful login attempts.
|
||||
|
||||
To discard a user, and revoke access to pgAdmin, click the trash icon to the
|
||||
left of the row and confirm deletion in the *Delete user?* dialog.
|
||||
|
||||
@@ -5,6 +5,9 @@ if [ "$EUID" -ne 0 ]
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Running apt update..."
|
||||
apt update
|
||||
|
||||
echo "Installing system pre-requisites..."
|
||||
apt install -y curl apt-transport-https ca-certificates gnupg
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ APP_ICON = 'pg-icon'
|
||||
|
||||
# Application version number components
|
||||
APP_RELEASE = 5
|
||||
APP_REVISION = 5
|
||||
APP_REVISION = 6
|
||||
|
||||
# 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 = 50500
|
||||
APP_VERSION_INT = 50600
|
||||
|
||||
# DO NOT CHANGE!
|
||||
# The application version string, constructed from the components
|
||||
@@ -513,6 +513,10 @@ SESSION_SKIP_PATHS = [
|
||||
# expire after the specified number of *days*.
|
||||
SESSION_EXPIRATION_TIME = 1
|
||||
|
||||
# Make SESSION_EXPIRATION_TIME to 1 week in DESKTOP mode
|
||||
if not SERVER_MODE:
|
||||
SESSION_EXPIRATION_TIME = 7
|
||||
|
||||
# CHECK_SESSION_FILES_INTERVAL is interval in Hours. Application will check
|
||||
# the session files for cleanup after specified number of *hours*.
|
||||
CHECK_SESSION_FILES_INTERVAL = 24
|
||||
@@ -570,6 +574,18 @@ ENHANCED_COOKIE_PROTECTION = True
|
||||
|
||||
AUTHENTICATION_SOURCES = ['internal']
|
||||
|
||||
##########################################################################
|
||||
# MAX_LOGIN_ATTEMPTS which sets the number of failed login attempts that
|
||||
# are allowed. If this value is exceeded the account is locked and can be
|
||||
# reset by an administrator. By setting the variable to the value zero
|
||||
# this feature is deactivated.
|
||||
##########################################################################
|
||||
MAX_LOGIN_ATTEMPTS = 3
|
||||
|
||||
##########################################################################
|
||||
# Only consider password to check the failed login attempts, email is
|
||||
# excluded from this check
|
||||
LOGIN_ATTEMPT_FIELDS = ['password']
|
||||
##########################################################################
|
||||
# LDAP Configuration
|
||||
##########################################################################
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
"""empty message
|
||||
|
||||
Revision ID: 6650c52670c2
|
||||
Revises: c465fee44968
|
||||
Create Date: 2021-07-10 18:12:38.821602
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
from pgadmin import db
|
||||
|
||||
revision = '6650c52670c2'
|
||||
down_revision = 'c465fee44968'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
db.engine.execute(
|
||||
'ALTER TABLE user ADD COLUMN locked BOOLEAN DEFAULT FALSE'
|
||||
)
|
||||
db.engine.execute(
|
||||
'ALTER TABLE user ADD COLUMN login_attempts int DEFAULT 0'
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
# pgAdmin only upgrades, downgrade not implemented.
|
||||
pass
|
||||
@@ -7,6 +7,7 @@ Create Date: 2021-06-04 14:42:12.843116
|
||||
|
||||
"""
|
||||
from pgadmin.model import db, User
|
||||
from sqlalchemy.sql import text
|
||||
import uuid
|
||||
|
||||
|
||||
@@ -42,7 +43,13 @@ def upgrade():
|
||||
'confirmed_at, masterpass_check, auth_source '
|
||||
'from user_old')
|
||||
|
||||
db.engine.execute(User.__table__.insert(), [
|
||||
statement = text("""
|
||||
INSERT INTO user(id, username, email, password, active,
|
||||
confirmed_at, masterpass_check, auth_source, fs_uniquifier)
|
||||
VALUES(:id, :username, :email, :password, :active, :confirmed_at,
|
||||
:masterpass_check, :auth_source, :fs_uniquifier)""")
|
||||
|
||||
db.engine.execute(statement, [
|
||||
{
|
||||
**row,
|
||||
'fs_uniquifier': uuid.uuid4().hex
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
"jquery-contextmenu": "^2.9.2",
|
||||
"jquery-ui": "^1.12.1",
|
||||
"json-bignumber": "^1.0.1",
|
||||
"jsoneditor": "^9.5.1",
|
||||
"karma-coverage": "^2.0.3",
|
||||
"leaflet": "^1.5.1",
|
||||
"lodash": "4.*",
|
||||
|
||||
@@ -397,8 +397,7 @@ def create_app(app_name=None):
|
||||
|
||||
# If version not available, user must have aborted. Tables are not
|
||||
# created and so its an empty db
|
||||
version = get_version()
|
||||
if not os.path.exists(SQLITE_PATH) or version == -1:
|
||||
if not os.path.exists(SQLITE_PATH) or get_version() == -1:
|
||||
# If running in cli mode then don't try to upgrade, just raise
|
||||
# the exception
|
||||
if not cli_mode:
|
||||
@@ -681,15 +680,15 @@ def create_app(app_name=None):
|
||||
from pgadmin.utils.driver import get_driver
|
||||
from flask import current_app
|
||||
|
||||
# remove key
|
||||
current_app.keyManager.reset()
|
||||
|
||||
for mdl in current_app.logout_hooks:
|
||||
try:
|
||||
mdl.on_logout(user)
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
|
||||
# remove key
|
||||
current_app.keyManager.reset()
|
||||
|
||||
_driver = get_driver(PG_DEFAULT_DRIVER)
|
||||
_driver.gc_own()
|
||||
|
||||
|
||||
@@ -12,18 +12,18 @@
|
||||
import config
|
||||
import copy
|
||||
|
||||
from flask import current_app, flash, Response, request, url_for,\
|
||||
from flask import current_app, flash, Response, request, url_for, \
|
||||
session, redirect
|
||||
from flask_babelex import gettext
|
||||
from flask_security.views import _security
|
||||
from flask_security.utils import get_post_logout_redirect, \
|
||||
get_post_login_redirect
|
||||
get_post_login_redirect, logout_user
|
||||
|
||||
from pgadmin import db, User
|
||||
from pgadmin.utils import PgAdminModule
|
||||
from pgadmin.utils.constants import KERBEROS, INTERNAL, OAUTH2, LDAP
|
||||
from pgadmin.authenticate.registry import AuthSourceRegistry
|
||||
|
||||
|
||||
MODULE_NAME = 'authenticate'
|
||||
auth_obj = None
|
||||
|
||||
@@ -46,16 +46,49 @@ def login():
|
||||
|
||||
auth_obj = AuthSourceManager(form, copy.deepcopy(
|
||||
config.AUTHENTICATION_SOURCES))
|
||||
if OAUTH2 in config.AUTHENTICATION_SOURCES\
|
||||
if OAUTH2 in config.AUTHENTICATION_SOURCES \
|
||||
and 'oauth2_button' in request.form:
|
||||
session['auth_obj'] = auth_obj
|
||||
|
||||
session['auth_source_manager'] = None
|
||||
|
||||
username = form.data['email']
|
||||
user = User.query.filter_by(username=username,
|
||||
auth_source=INTERNAL).first()
|
||||
|
||||
if user:
|
||||
if user.login_attempts >= config.MAX_LOGIN_ATTEMPTS > 0:
|
||||
user.locked = True
|
||||
else:
|
||||
user.locked = False
|
||||
db.session.commit()
|
||||
|
||||
if user.login_attempts >= config.MAX_LOGIN_ATTEMPTS > 0:
|
||||
flash(gettext('Your account is locked. Please contact the '
|
||||
'Administrator.'),
|
||||
'warning')
|
||||
logout_user()
|
||||
return redirect(get_post_logout_redirect())
|
||||
|
||||
# Validate the user
|
||||
if not auth_obj.validate():
|
||||
for field in form.errors:
|
||||
flash_login_attempt_error = None
|
||||
if user and field in config.LOGIN_ATTEMPT_FIELDS:
|
||||
if config.MAX_LOGIN_ATTEMPTS > 0:
|
||||
user.login_attempts += 1
|
||||
left_attempts = \
|
||||
config.MAX_LOGIN_ATTEMPTS - user.login_attempts
|
||||
flash_login_attempt_error = \
|
||||
gettext('You are left with {0} more attempts.'.
|
||||
format(left_attempts))
|
||||
db.session.commit()
|
||||
for error in form.errors[field]:
|
||||
flash(error, 'warning')
|
||||
|
||||
if flash_login_attempt_error:
|
||||
flash(flash_login_attempt_error, 'warning')
|
||||
|
||||
return redirect(get_post_logout_redirect())
|
||||
|
||||
# Authenticate the user
|
||||
@@ -66,14 +99,20 @@ def login():
|
||||
current_auth_obj = auth_obj.as_dict()
|
||||
|
||||
if not status:
|
||||
if current_auth_obj['current_source'] ==\
|
||||
if current_auth_obj['current_source'] == \
|
||||
KERBEROS:
|
||||
return redirect('{0}?next={1}'.format(url_for(
|
||||
'authenticate.kerberos_login'), url_for('browser.index')))
|
||||
|
||||
flash(msg, 'danger')
|
||||
return redirect(get_post_logout_redirect())
|
||||
|
||||
session['auth_source_manager'] = current_auth_obj
|
||||
|
||||
if user:
|
||||
user.login_attempts = 0
|
||||
db.session.commit()
|
||||
|
||||
if 'auth_obj' in session:
|
||||
session.pop('auth_obj')
|
||||
return redirect(get_post_login_redirect())
|
||||
|
||||
@@ -31,7 +31,8 @@ from flask_security.recoverable import reset_password_token_status, \
|
||||
generate_reset_password_token, update_password
|
||||
from flask_security.signals import reset_password_instructions_sent
|
||||
from flask_security.utils import config_value, do_flash, get_url, \
|
||||
get_message, slash_url_suffix, login_user, send_mail, logout_user
|
||||
get_message, slash_url_suffix, login_user, send_mail, logout_user, \
|
||||
get_post_logout_redirect
|
||||
from flask_security.views import _security, view_commit, _ctx
|
||||
from werkzeug.datastructures import MultiDict
|
||||
|
||||
@@ -1329,6 +1330,12 @@ if hasattr(config, 'SECURITY_RECOVERABLE') and config.SECURITY_RECOVERABLE:
|
||||
auth_obj = AuthSourceManager(form, [INTERNAL])
|
||||
session['_auth_source_manager_obj'] = auth_obj.as_dict()
|
||||
|
||||
if user.login_attempts >= config.MAX_LOGIN_ATTEMPTS > 0:
|
||||
flash(gettext('You successfully reset your password but'
|
||||
' your account is locked. Please contact '
|
||||
'the Administrator.'),
|
||||
'warning')
|
||||
return redirect(get_post_logout_redirect())
|
||||
do_flash(*get_message('PASSWORD_RESET'))
|
||||
login_user(user)
|
||||
auth_obj = AuthSourceManager(form, [INTERNAL])
|
||||
|
||||
@@ -823,9 +823,9 @@ class TableView(BaseTableView, DataTypeReader, SchemaDiffTableCompare):
|
||||
if 'relacl' in data:
|
||||
data['relacl'] = parse_priv_to_db(data['relacl'], self.acl)
|
||||
|
||||
# Parse & format columns
|
||||
data = column_utils.parse_format_columns(data)
|
||||
data = TableView.check_and_convert_name_to_string(data)
|
||||
# Parse & format columns
|
||||
data = column_utils.parse_format_columns(data)
|
||||
data = TableView.check_and_convert_name_to_string(data)
|
||||
|
||||
# 'coll_inherits' is Array but it comes as string from browser
|
||||
# We will convert it again to list
|
||||
|
||||
@@ -1473,7 +1473,7 @@ class BaseTableView(PGChildNodeView, BasePartitionTable, VacuumSettings):
|
||||
else:
|
||||
error, errmsg = BaseTableView._check_for_create_sql(data)
|
||||
if error:
|
||||
return gettext('-- definition incomplete')
|
||||
return gettext('-- definition incomplete'), data['name']
|
||||
|
||||
# validate constraint data.
|
||||
self._validate_constraint_data(data)
|
||||
|
||||
@@ -579,18 +579,18 @@ define('pgadmin.node.type', [
|
||||
},{
|
||||
id: 'opcname', label: gettext('Subtype operator class'), cell: 'string',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Range Type'),
|
||||
disabled: 'inSchema', readonly: 'inEditMode', deps: ['typname'],
|
||||
control: 'select', options: function() {
|
||||
var l_typname = this.model.get('typname'),
|
||||
self = this,
|
||||
disabled: 'inSchema', readonly: 'inEditMode', deps: ['typname'],
|
||||
control: 'select2', options: function(control) {
|
||||
var l_typname = control.model.get('typname'),
|
||||
result = [];
|
||||
|
||||
if(!_.isUndefined(l_typname) && l_typname != '')
|
||||
{
|
||||
var node = this.field.get('schema_node'),
|
||||
var node = control.field.get('schema_node'),
|
||||
_url = node.generate_url.apply(
|
||||
node, [
|
||||
null, 'get_subopclass', this.field.get('node_data'), false,
|
||||
this.field.get('node_info'),
|
||||
null, 'get_subopclass', control.field.get('node_data'), false,
|
||||
control.field.get('node_info'),
|
||||
]);
|
||||
$.ajax({
|
||||
async: false,
|
||||
@@ -602,7 +602,7 @@ define('pgadmin.node.type', [
|
||||
result = res.data;
|
||||
})
|
||||
.fail(function() {
|
||||
self.model.trigger('pgadmin:view:fetch:error', self.model, self.field);
|
||||
control.model.trigger('pgadmin:view:fetch:error', control.model, control.field);
|
||||
});
|
||||
//
|
||||
}
|
||||
@@ -640,6 +640,11 @@ define('pgadmin.node.type', [
|
||||
});
|
||||
}
|
||||
// If is_collate is true then do not disable
|
||||
if(!is_collate) {
|
||||
m.set({'collname': '', silent: true});
|
||||
this.options = [];
|
||||
}
|
||||
|
||||
return is_collate ? false : true;
|
||||
},
|
||||
},{
|
||||
@@ -647,18 +652,17 @@ define('pgadmin.node.type', [
|
||||
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||
group: gettext('Range Type'),
|
||||
disabled: 'inSchema', readonly: 'inEditMode', deps: ['name', 'typname'],
|
||||
control: 'select', options: function() {
|
||||
var name = this.model.get('name'),
|
||||
self = this,
|
||||
control: 'select2', options: function(control) {
|
||||
var name = control.model.get('name'),
|
||||
result = [];
|
||||
|
||||
if(!_.isUndefined(name) && name != '')
|
||||
{
|
||||
var node = this.field.get('schema_node'),
|
||||
var node = control.field.get('schema_node'),
|
||||
_url = node.generate_url.apply(
|
||||
node, [
|
||||
null, 'get_canonical', this.field.get('node_data'), false,
|
||||
this.field.get('node_info'),
|
||||
null, 'get_canonical', control.field.get('node_data'), false,
|
||||
control.field.get('node_info'),
|
||||
]);
|
||||
$.ajax({
|
||||
async: false,
|
||||
@@ -670,8 +674,7 @@ define('pgadmin.node.type', [
|
||||
result = res.data;
|
||||
})
|
||||
.fail(function() {
|
||||
self.model.trigger('pgadmin:view:fetch:error',
|
||||
self.model, self.field);
|
||||
control.model.trigger('pgadmin:view:fetch:error', control.model, control.field);
|
||||
});
|
||||
}
|
||||
return result;
|
||||
@@ -680,21 +683,22 @@ define('pgadmin.node.type', [
|
||||
id: 'rngsubdiff', label: gettext('Subtype diff function'), cell: 'string',
|
||||
type: 'text', mode: ['properties', 'create', 'edit'],
|
||||
group: gettext('Range Type'),
|
||||
disabled: 'inSchema', readonly: 'inEditMode', deps: ['opcname'],
|
||||
control: 'select', options: function() {
|
||||
var l_typname = this.model.get('typname'),
|
||||
l_opcname = this.model.get('opcname'),
|
||||
self = this,
|
||||
disabled: 'inSchema', readonly: 'inEditMode', deps: ['typname', 'opcname'],
|
||||
control: 'select2', options: function(control) {
|
||||
var l_typname = control.model.get('typname'),
|
||||
l_opcname = control.model.get('opcname'),
|
||||
result = [];
|
||||
this.options = [];
|
||||
control.model.set({'rngsubdiff': [], silent: true});
|
||||
|
||||
if(!_.isUndefined(l_typname) && l_typname != '' &&
|
||||
!_.isUndefined(l_opcname) && l_opcname != '') {
|
||||
var node = this.field.get('schema_node'),
|
||||
var node = control.field.get('schema_node'),
|
||||
_url = node.generate_url.apply(
|
||||
node, [
|
||||
null, 'get_stypediff',
|
||||
this.field.get('node_data'), false,
|
||||
this.field.get('node_info'),
|
||||
control.field.get('node_data'), false,
|
||||
control.field.get('node_info'),
|
||||
]);
|
||||
$.ajax({
|
||||
async: false,
|
||||
@@ -706,8 +710,7 @@ define('pgadmin.node.type', [
|
||||
result = res.data;
|
||||
})
|
||||
.fail(function() {
|
||||
self.model.trigger('pgadmin:view:fetch:error',
|
||||
self.model, self.field);
|
||||
control.model.trigger('pgadmin:view:fetch:error', control.model, control.field);
|
||||
});
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -29,11 +29,16 @@ SELECT rngsubtype, st.typname,
|
||||
rngcollation,
|
||||
CASE WHEN n.nspname IS NOT NULL THEN pg_catalog.concat(pg_catalog.quote_ident(n.nspname), '.', pg_catalog.quote_ident(col.collname)) ELSE col.collname END AS collname,
|
||||
rngsubopc, opc.opcname,
|
||||
rngcanonical, rngsubdiff
|
||||
rngcanonical, rngsubdiff as rngsubdiff_proc,
|
||||
CASE WHEN length(ns.nspname::text) > 0 AND length(pgpr.proname::text) > 0 THEN
|
||||
pg_catalog.concat(quote_ident(ns.nspname), '.', pg_catalog.quote_ident(pgpr.proname))
|
||||
ELSE '' END AS rngsubdiff
|
||||
FROM pg_catalog.pg_range
|
||||
LEFT JOIN pg_catalog.pg_type st ON st.oid=rngsubtype
|
||||
LEFT JOIN pg_catalog.pg_collation col ON col.oid=rngcollation
|
||||
LEFT JOIN pg_catalog.pg_namespace n ON col.collnamespace=n.oid
|
||||
LEFT JOIN pg_catalog.pg_opclass opc ON opc.oid=rngsubopc
|
||||
LEFT JOIN pg_catalog.pg_proc pgpr ON pgpr.oid = rngsubdiff
|
||||
LEFT JOIN pg_catalog.pg_namespace ns ON ns.oid=pgpr.pronamespace
|
||||
WHERE rngtypid={{tid}}::oid;
|
||||
{% endif %}
|
||||
@@ -114,12 +114,16 @@ define('pgadmin.node.tablespace', [
|
||||
obj_type: 'all',
|
||||
user: undefined,
|
||||
},
|
||||
initialize: function(attrs, otherArgs) {
|
||||
this.selected_ts = otherArgs.selected_ts;
|
||||
Backbone.Model.prototype.initialize.apply(this, [attrs, otherArgs]);
|
||||
},
|
||||
schema: [{
|
||||
id: 'tblspc', label: gettext('New tablespace'),
|
||||
type: 'text', disabled: false, control: 'node-list-by-name',
|
||||
node: 'tablespace', select2: {allowClear: false},
|
||||
filter: function(o) {
|
||||
return o && (o.label != d.label);
|
||||
return o && (o.label != this.model.selected_ts);
|
||||
},
|
||||
},{
|
||||
id: 'obj_type', label: gettext('Object type'),
|
||||
@@ -148,7 +152,7 @@ define('pgadmin.node.tablespace', [
|
||||
var self = this,
|
||||
sql_tab_args = self.model.toJSON();
|
||||
// Add existing tablespace
|
||||
sql_tab_args.old_tblspc = d.label;
|
||||
sql_tab_args.old_tblspc = this.model.selected_ts;
|
||||
|
||||
// Fetches modified SQL
|
||||
$.ajax({
|
||||
@@ -236,7 +240,7 @@ define('pgadmin.node.tablespace', [
|
||||
// Create treeInfo
|
||||
var treeInfo = node.getTreeNodeHierarchy.apply(node, [_i]);
|
||||
// Instance of backbone model
|
||||
var newModel = new objModel({}, {node_info: treeInfo}),
|
||||
var newModel = new objModel({}, {node_info: treeInfo, selected_ts: _d.label}),
|
||||
fields = Backform.generateViewSchema(
|
||||
treeInfo, newModel, 'create', node,
|
||||
treeInfo.server, true
|
||||
|
||||
@@ -36,7 +36,7 @@ class SharedServersGetTestCase(BaseTestGenerator):
|
||||
|
||||
if config.SERVER_MODE is False:
|
||||
self.skipTest(
|
||||
"Can not run shared servers test cases in the SERVER mode."
|
||||
"Can not run shared servers test cases in the Desktop mode."
|
||||
)
|
||||
self.server['shared'] = True
|
||||
url = "{0}{1}/".format(self.url, utils.SERVER_GROUP)
|
||||
|
||||
@@ -192,7 +192,8 @@ class ServerType(object):
|
||||
for bin_path in bin_path_json:
|
||||
if int(bin_path['version']) <= sverison < \
|
||||
int(bin_path['next_major_version']) and \
|
||||
bin_path['binaryPath'] is not None:
|
||||
bin_path['binaryPath'] is not None and \
|
||||
bin_path['binaryPath'].strip() != '':
|
||||
return bin_path['binaryPath']
|
||||
|
||||
if bin_path['isDefault']:
|
||||
|
||||
@@ -902,6 +902,7 @@ define('pgadmin.browser.node', [
|
||||
|
||||
// Callback to render query editor
|
||||
show_query_tool: function(args, item) {
|
||||
var preference = pgBrowser.get_preference('sqleditor', 'copy_sql_to_query_tool');
|
||||
var t = pgBrowser.tree,
|
||||
i = item || t.selected(),
|
||||
d = i && i.length == 1 ? t.itemData(i) : undefined;
|
||||
@@ -910,7 +911,17 @@ define('pgadmin.browser.node', [
|
||||
return;
|
||||
|
||||
// Here call data grid method to render query tool
|
||||
pgAdmin.DataGrid.show_query_tool('', i);
|
||||
//Open query tool with create script if copy_sql_to_query_tool is true else open blank query tool
|
||||
if(preference.value && !d._type.includes('coll-')){
|
||||
var stype = d._type.toLowerCase();
|
||||
var data = {
|
||||
'script': stype,
|
||||
data_disabled: gettext('The selected tree node does not support this option.'),
|
||||
};
|
||||
pgBrowser.Node.callbacks.show_script(data);
|
||||
}else{
|
||||
pgAdmin.DataGrid.show_query_tool('', i);
|
||||
}
|
||||
},
|
||||
|
||||
// Callback to render psql tool.
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
from pgadmin.utils.route import BaseTestGenerator
|
||||
import config
|
||||
from unittest.mock import patch
|
||||
|
||||
if not config.SERVER_MODE:
|
||||
MODE = 'Desktop Mode'
|
||||
SESSION_EXP_TIME_CONSTANT = 7
|
||||
else:
|
||||
MODE = 'Server Mode'
|
||||
SESSION_EXP_TIME_CONSTANT = 1
|
||||
|
||||
|
||||
class SetSessionExpirationTimeTestCase(BaseTestGenerator):
|
||||
"""
|
||||
This class verifies whether session expire time has been appropriately
|
||||
set to desktop & server mode respectively.
|
||||
"""
|
||||
|
||||
scenarios = [
|
||||
(
|
||||
'TestCase for verifying session expire time is set to {0} '
|
||||
'days for {1}'.format(SESSION_EXP_TIME_CONSTANT, MODE),
|
||||
dict(
|
||||
session_expiration_time=SESSION_EXP_TIME_CONSTANT
|
||||
))
|
||||
]
|
||||
|
||||
@patch('config.SESSION_EXPIRATION_TIME',
|
||||
side_effect=SESSION_EXP_TIME_CONSTANT)
|
||||
def runTest(self, mock_session_expiration_time):
|
||||
|
||||
self.assertEqual(
|
||||
self.session_expiration_time,
|
||||
config.SESSION_EXPIRATION_TIME.side_effect)
|
||||
@@ -365,15 +365,15 @@ def get_long_running_query_status(activities):
|
||||
for row in activities:
|
||||
row['row_type'] = None
|
||||
|
||||
# We care for only those queries which are in active state.
|
||||
if row['state'] != 'active':
|
||||
continue
|
||||
|
||||
active_since = float(row['active_since'])
|
||||
if active_since > warning_value:
|
||||
row['row_type'] = 'warning'
|
||||
if active_since > alert_value:
|
||||
row['row_type'] = 'alert'
|
||||
# We care for only those queries which are in active state and
|
||||
# have active_since parameter and not None
|
||||
if row['state'] == 'active' and 'active_since' in row and \
|
||||
row['active_since'] is not None:
|
||||
active_since = float(row['active_since'])
|
||||
if active_since > warning_value:
|
||||
row['row_type'] = 'warning'
|
||||
if active_since > alert_value:
|
||||
row['row_type'] = 'alert'
|
||||
|
||||
|
||||
@blueprint.route('/dashboard_stats',
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
import random
|
||||
import time
|
||||
|
||||
from regression.feature_utils.base_feature_test import BaseFeatureTest
|
||||
from regression.python_test_utils import test_utils
|
||||
from regression.feature_utils.tree_area_locators import TreeAreaLocators
|
||||
from regression.feature_utils.locators import NavMenuLocators, \
|
||||
QueryToolLocators
|
||||
from selenium.webdriver import ActionChains
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
|
||||
|
||||
class CopySQLFeatureTest(BaseFeatureTest):
|
||||
""" This class test acceptance test scenarios """
|
||||
|
||||
scenarios = [
|
||||
("Test copy sql to query tool", dict())
|
||||
]
|
||||
|
||||
test_table_name = ""
|
||||
|
||||
def before(self):
|
||||
|
||||
self.page.add_server(self.server)
|
||||
|
||||
def runTest(self):
|
||||
self._update_preferences_setting()
|
||||
self._create_table()
|
||||
sql_query = self._get_sql_query()
|
||||
query_tool_result = self._get_query_tool_result()
|
||||
|
||||
self.assertEqual(query_tool_result, sql_query)
|
||||
|
||||
def after(self):
|
||||
self.page.remove_server(self.server)
|
||||
test_utils.delete_table(
|
||||
self.server, self.test_db, self.test_table_name)
|
||||
|
||||
def _get_sql_query(self):
|
||||
self.page.click_tab("SQL")
|
||||
# Wait till data is displayed in SQL Tab
|
||||
self.assertTrue(self.page.check_if_element_exist_by_xpath(
|
||||
"//*[contains(@class,'CodeMirror-lines') and "
|
||||
"contains(.,'CREATE TABLE IF NOT EXISTS public.%s')]"
|
||||
% self.test_table_name, 10), "No data displayed in SQL tab")
|
||||
|
||||
# Fetch the inner html & check for escaped characters
|
||||
source_code = self.driver.find_elements_by_xpath(
|
||||
QueryToolLocators.code_mirror_data_xpath)
|
||||
|
||||
sql_query = ''
|
||||
for data in source_code:
|
||||
sql_query += data.text
|
||||
sql_query += '\n'
|
||||
|
||||
return sql_query
|
||||
|
||||
def _get_query_tool_result(self):
|
||||
self.page.open_query_tool()
|
||||
self.page.wait_for_spinner_to_disappear()
|
||||
|
||||
time.sleep(5)
|
||||
|
||||
self.driver.switch_to.default_content()
|
||||
self.driver.switch_to_frame(
|
||||
self.driver.find_element_by_tag_name("iframe"))
|
||||
|
||||
code_mirror = self.driver.find_elements_by_xpath(
|
||||
QueryToolLocators.code_mirror_data_xpath)
|
||||
query_tool_result = ''
|
||||
for data in code_mirror:
|
||||
query_tool_result += data.text
|
||||
query_tool_result += '\n'
|
||||
|
||||
return query_tool_result
|
||||
|
||||
def _create_table(self):
|
||||
self.test_table_name = "test_table" + str(random.randint(1000, 3000))
|
||||
test_utils.create_table(self.server, self.test_db,
|
||||
self.test_table_name)
|
||||
|
||||
self.page.expand_database_node(
|
||||
self.server['name'],
|
||||
self.server['db_password'], self.test_db)
|
||||
self.page.toggle_open_tables_node(
|
||||
self.server['name'], self.server['db_password'],
|
||||
self.test_db, 'public')
|
||||
self.page.click_a_tree_node(
|
||||
self.test_table_name,
|
||||
TreeAreaLocators.sub_nodes_of_tables_node)
|
||||
|
||||
def _update_preferences_setting(self):
|
||||
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'))
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
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))
|
||||
)
|
||||
|
||||
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(
|
||||
NavMenuLocators.specified_sub_node_of_pref_tree_node.format(
|
||||
specified_preference_tree_node_name, 'Options'))
|
||||
option_node.click()
|
||||
|
||||
self.page.set_switch_box_status(
|
||||
NavMenuLocators.copy_sql_to_query_tool_switch_btn, 'Yes')
|
||||
|
||||
# save and close the preference dialog.
|
||||
self.page.click_modal('Save')
|
||||
@@ -241,13 +241,34 @@ CREATE TABLE public.nonintpkey
|
||||
else:
|
||||
ActionChains(self.driver).send_keys(value). \
|
||||
send_keys(Keys.ENTER).perform()
|
||||
elif cell_type in ['text', 'json', 'text[]', 'boolean[]']:
|
||||
elif cell_type in ['text', 'text[]', 'boolean[]']:
|
||||
text_area_ele = self.page.find_by_css_selector(
|
||||
QueryToolLocators.row_editor_text_area_css)
|
||||
text_area_ele.clear()
|
||||
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)
|
||||
if not value == "":
|
||||
actions = ActionChains(self.driver)
|
||||
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
|
||||
ActionChains(self.driver).key_down(key_to_press)\
|
||||
.send_keys('a').key_up(key_to_press)\
|
||||
.send_keys(Keys.DELETE).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()
|
||||
|
||||
@@ -30,7 +30,7 @@ import uuid
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
SCHEMA_VERSION = 30
|
||||
SCHEMA_VERSION = 31
|
||||
|
||||
##########################################################################
|
||||
#
|
||||
@@ -80,6 +80,8 @@ class User(db.Model, UserMixin):
|
||||
# fs_uniquifier is required by flask-security-too >= 4.
|
||||
fs_uniquifier = db.Column(db.String(255), unique=True, nullable=False,
|
||||
default=(lambda _: uuid.uuid4().hex))
|
||||
login_attempts = db.Column(db.Integer, default=0)
|
||||
locked = db.Column(db.Boolean(), default=False)
|
||||
|
||||
|
||||
class Setting(db.Model):
|
||||
|
||||
@@ -83,7 +83,8 @@ define('pgadmin.preferences', [
|
||||
});
|
||||
|
||||
preferences.on('change', function(m) {
|
||||
var id = m.get('id');
|
||||
var id = m.get('id'),
|
||||
dependents = m.get('dependents');
|
||||
if (!(id in changed)) {
|
||||
// Keep track of the original value
|
||||
changed[id] = m._previousAttributes.value;
|
||||
@@ -91,8 +92,46 @@ define('pgadmin.preferences', [
|
||||
// Remove unchanged models.
|
||||
delete changed[id];
|
||||
}
|
||||
|
||||
// Check dependents exist or not. If exists then call dependentsFound function.
|
||||
if (!_.isNull(dependents) && Array.isArray(dependents) && dependents.length > 0) {
|
||||
dependentsFound(m.get('name'), m.get('value'), dependents);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Function: dependentsFound
|
||||
*
|
||||
* This method will be used to iterate through all the controls and
|
||||
* dependents. If found then perform the appropriate action.
|
||||
*/
|
||||
var dependentsFound = function(pref_name, pref_val, dependents) {
|
||||
// Iterate through all the controls and check the dependents
|
||||
_.each(controls, function(c) {
|
||||
let ctrl_name = c.model.get('name');
|
||||
_.each(dependents, function(deps) {
|
||||
if (ctrl_name === deps) {
|
||||
// Create methods to take appropriate actions and call here.
|
||||
enableDisableMaxWidth(pref_name, pref_val, c);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Function: enableDisableMaxWidth
|
||||
*
|
||||
* This method will be used to enable and disable Maximum Width control
|
||||
*/
|
||||
var enableDisableMaxWidth = function(pref_name, pref_val, control) {
|
||||
if (pref_name === 'column_data_auto_resize' && pref_val === 'by_name') {
|
||||
control.$el.find('input').prop('disabled', true);
|
||||
control.$el.find('input').val(0);
|
||||
} else if (pref_name === 'column_data_auto_resize' && pref_val === 'by_data') {
|
||||
control.$el.find('input').prop('disabled', false);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Function: renderPreferencePanel
|
||||
*
|
||||
@@ -144,6 +183,16 @@ define('pgadmin.preferences', [
|
||||
controls.push(cntr);
|
||||
});
|
||||
|
||||
/* Iterate through all preferences and check if dependents found.
|
||||
* If found then call the dependentsFound method
|
||||
*/
|
||||
_.each(prefs, function(p) {
|
||||
let m = preferences.get(p.id);
|
||||
let dependents = m.get('dependents');
|
||||
if (!_.isNull(dependents) && Array.isArray(dependents) && dependents.length > 0) {
|
||||
dependentsFound(m.get('name'), m.get('value'), dependents);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -383,6 +432,7 @@ define('pgadmin.preferences', [
|
||||
'category_id': d.id,
|
||||
'mid': d.mid,
|
||||
'name': p.name,
|
||||
'dependents': p.dependents,
|
||||
});
|
||||
/*
|
||||
* We don't know until now, how to render the control for
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
from pgadmin.model import Version
|
||||
from pgadmin.model import db
|
||||
|
||||
|
||||
@@ -22,9 +21,14 @@ def check_db_tables():
|
||||
db_table_names = get_db_table_names()
|
||||
# check table is actually present in the db.
|
||||
for table_name in db_table_names:
|
||||
if not db.engine.dialect.has_table(db.engine, table_name):
|
||||
invalid_tb_names.append(table_name)
|
||||
is_error = True
|
||||
try:
|
||||
if not db.inspect(db.engine).has_table(table_name=table_name):
|
||||
invalid_tb_names.append(table_name)
|
||||
is_error = True
|
||||
except AttributeError:
|
||||
if not db.engine.dialect.has_table(db.engine, table_name):
|
||||
invalid_tb_names.append(table_name)
|
||||
is_error = True
|
||||
|
||||
if is_error:
|
||||
return True, invalid_tb_names
|
||||
|
||||
@@ -23,3 +23,5 @@
|
||||
@import '../vendor/backgrid/backgrid-select-all.css';
|
||||
|
||||
@import 'node_modules/xterm/css/xterm.css';
|
||||
|
||||
@import 'node_modules/jsoneditor/dist/jsoneditor.min.css';
|
||||
@@ -44,6 +44,11 @@ import gettext from 'sources/gettext';
|
||||
return $('<textarea class=\'pg-textarea text-12\' hidefocus rows=5\'>');
|
||||
}
|
||||
|
||||
// return json editor element
|
||||
function getJsonEditor() {
|
||||
return $('<div id=\'pg-json-editor\' hidefocus\'>');
|
||||
}
|
||||
|
||||
// Generate and return editor buttons
|
||||
function getButtons(editable) {
|
||||
var $buttons = $('<div class=\'pg_buttons\' />'),
|
||||
@@ -293,15 +298,16 @@ import gettext from 'sources/gettext';
|
||||
|
||||
// JSON data type editor
|
||||
function JsonTextEditor(args) {
|
||||
var $input, $wrapper, $buttons;
|
||||
var defaultValue;
|
||||
var $input, $wrapper, $buttons, $editor;
|
||||
var defaultValue, tmpdata;
|
||||
var scope = this;
|
||||
var editorInitialized = false;
|
||||
|
||||
this.init = function() {
|
||||
var $container = $('body');
|
||||
|
||||
$wrapper = getWrapper().appendTo($container);
|
||||
$input = getTextArea().appendTo($wrapper);
|
||||
$input = getJsonEditor().appendTo($wrapper);
|
||||
$buttons = getButtons(true).appendTo($wrapper);
|
||||
|
||||
$buttons.find('button:first').on('click', this.cancel);
|
||||
@@ -309,7 +315,6 @@ import gettext from 'sources/gettext';
|
||||
$input.on('keydown', this.handleKeyDown);
|
||||
|
||||
scope.position(args.position);
|
||||
$input.trigger('focus').trigger('select');
|
||||
};
|
||||
|
||||
this.handleKeyDown = function(e) {
|
||||
@@ -318,12 +323,6 @@ import gettext from 'sources/gettext';
|
||||
} else if (e.which == $.ui.keyCode.ESCAPE) {
|
||||
e.preventDefault();
|
||||
scope.cancel();
|
||||
} else if (e.which == $.ui.keyCode.TAB && e.shiftKey) {
|
||||
e.preventDefault();
|
||||
args.grid.navigatePrev();
|
||||
} else if (e.which == $.ui.keyCode.TAB) {
|
||||
e.preventDefault();
|
||||
args.grid.navigateNext();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -332,7 +331,6 @@ import gettext from 'sources/gettext';
|
||||
};
|
||||
|
||||
this.cancel = function() {
|
||||
$input.val(defaultValue);
|
||||
args.cancelChanges();
|
||||
};
|
||||
|
||||
@@ -352,15 +350,24 @@ import gettext from 'sources/gettext';
|
||||
};
|
||||
|
||||
this.destroy = function() {
|
||||
$editor.destroy();
|
||||
$wrapper.remove();
|
||||
};
|
||||
|
||||
this.focus = function() {
|
||||
$input.trigger('focus');
|
||||
$editor.focus();
|
||||
};
|
||||
|
||||
this.loadValue = function(item) {
|
||||
var data = defaultValue = item[args.column.field];
|
||||
/* Can be useful until JSON editor loads */
|
||||
tmpdata = data;
|
||||
|
||||
if (_.isNull(data)){
|
||||
defaultValue = undefined;
|
||||
data = undefined;
|
||||
}
|
||||
|
||||
/* If jsonb or array */
|
||||
if(args.column.column_type_internal === 'jsonb' && !Array.isArray(data) && data != null) {
|
||||
data = JSONBigNumber.stringify(JSONBigNumber.parse(data), null, 4);
|
||||
@@ -375,20 +382,60 @@ import gettext from 'sources/gettext';
|
||||
});
|
||||
data = '[' + temp.join() + ']';
|
||||
}
|
||||
/* if json take as is */
|
||||
$input.val(data);
|
||||
$input.trigger('select');
|
||||
/* if data is string then convert to json*/
|
||||
if ( data != '' && typeof data === 'string'){
|
||||
data = JSON.parse(data);
|
||||
}
|
||||
|
||||
/* Create editor if required & set data*/
|
||||
if ($editor){
|
||||
$editor.set(data);
|
||||
$editor.focus();
|
||||
}else{
|
||||
editorInitialized = true;
|
||||
require.ensure(['jsoneditor'], function(require) {
|
||||
var JSONEditor = require('jsoneditor');
|
||||
var jsonContainer = document.getElementById('pg-json-editor');
|
||||
var options = { modes: ['code', 'form', 'tree','preview']};
|
||||
$editor = new JSONEditor(jsonContainer, options);
|
||||
$editor.set(data);
|
||||
$editor.focus();
|
||||
}, function(error){
|
||||
throw(error);
|
||||
}, 'jsoneditorchunk');
|
||||
}
|
||||
};
|
||||
|
||||
this.serializeValue = function() {
|
||||
if ($input.val() === '') {
|
||||
return null;
|
||||
/* Create editor if data is null/new data entry */
|
||||
if( !editorInitialized) {
|
||||
require.ensure(['jsoneditor'], function(require) {
|
||||
var JSONEditor = require('jsoneditor');
|
||||
var jsonContainer = document.getElementById('pg-json-editor');
|
||||
var options = {modes: ['code', 'form', 'tree','preview']};
|
||||
if(jsonContainer) {
|
||||
$editor = new JSONEditor(jsonContainer, options);
|
||||
$editor.focus();
|
||||
return null;
|
||||
}
|
||||
}, function(error){
|
||||
throw(error);
|
||||
}, 'jsoneditorchunk');}
|
||||
|
||||
if($editor){
|
||||
let data = $editor.getText();
|
||||
if (data === '') {
|
||||
return null;
|
||||
}
|
||||
return data;
|
||||
}else{
|
||||
// The loader is not loaded yet ?
|
||||
return tmpdata;
|
||||
}
|
||||
return $input.val();
|
||||
};
|
||||
|
||||
this.applyValue = function(item, state){
|
||||
if(args.column.column_type_internal === 'jsonb') {
|
||||
if(args.column.column_type_internal === 'jsonb' || args.column.column_type_internal === 'json') {
|
||||
setValue(args, item, state, 'jsonb');
|
||||
} else {
|
||||
setValue(args, item, state, 'text');
|
||||
@@ -396,19 +443,24 @@ import gettext from 'sources/gettext';
|
||||
};
|
||||
|
||||
this.isValueChanged = function() {
|
||||
if ($input.val() == '' && _.isUndefined(defaultValue)) {
|
||||
let data = $editor.getText();
|
||||
if (data == '' && (_.isUndefined(defaultValue) || _.isNull(defaultValue) )) {
|
||||
return false;
|
||||
} else {
|
||||
return (!($input.val() == '' && _.isNull(defaultValue))) && ($input.val() != defaultValue);
|
||||
if(! _.isUndefined(defaultValue) && defaultValue != ''){
|
||||
defaultValue = JSON.stringify(JSON.parse(defaultValue), null,2);
|
||||
}
|
||||
return (!( data == '' && _.isNull(defaultValue)) && (data != defaultValue));
|
||||
}
|
||||
};
|
||||
|
||||
this.validate = function() {
|
||||
if(args.column.column_type_internal === 'jsonb' ||
|
||||
args.column.column_type_internal === 'json') {
|
||||
let data = $editor.getText();
|
||||
try {
|
||||
if($input.val() != ''){
|
||||
JSON.parse($input.val());
|
||||
if(data != ''){
|
||||
JSON.parse(data);
|
||||
}
|
||||
} catch(e) {
|
||||
$input.addClass('pg-text-invalid');
|
||||
@@ -418,7 +470,6 @@ import gettext from 'sources/gettext';
|
||||
};
|
||||
}
|
||||
}
|
||||
$input.removeClass('pg-text-invalid');
|
||||
return {
|
||||
valid: true,
|
||||
msg: null,
|
||||
@@ -529,22 +580,22 @@ import gettext from 'sources/gettext';
|
||||
|
||||
// JSON data type editor
|
||||
function ReadOnlyJsonTextEditor(args) {
|
||||
var $input, $wrapper, $buttons;
|
||||
var $input, $wrapper, $buttons, $editor;
|
||||
var defaultValue;
|
||||
var scope = this;
|
||||
var tmpdata;
|
||||
|
||||
this.init = function() {
|
||||
var $container = $('body');
|
||||
|
||||
$wrapper = getWrapper().appendTo($container);
|
||||
$input = getTextArea().appendTo($wrapper);
|
||||
$input = getJsonEditor().appendTo($wrapper);
|
||||
$buttons = getButtons(false).appendTo($wrapper);
|
||||
|
||||
$buttons.find('button:first').on('click', this.cancel);
|
||||
$input.on('keydown', this.handleKeyDown);
|
||||
|
||||
scope.position(args.position);
|
||||
$input.trigger('focus').trigger('select');
|
||||
};
|
||||
|
||||
this.handleKeyDown = function(e) {
|
||||
@@ -553,14 +604,6 @@ import gettext from 'sources/gettext';
|
||||
} else if (e.which == $.ui.keyCode.ESCAPE) {
|
||||
e.preventDefault();
|
||||
scope.cancel();
|
||||
} else if (e.which == $.ui.keyCode.TAB && e.shiftKey) {
|
||||
scope.cancel();
|
||||
e.preventDefault();
|
||||
args.grid.navigatePrev();
|
||||
} else if (e.which == $.ui.keyCode.TAB) {
|
||||
scope.cancel();
|
||||
e.preventDefault();
|
||||
args.grid.navigateNext();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -585,15 +628,17 @@ import gettext from 'sources/gettext';
|
||||
};
|
||||
|
||||
this.destroy = function() {
|
||||
$editor.destroy();
|
||||
$wrapper.remove();
|
||||
};
|
||||
|
||||
this.focus = function() {
|
||||
$input.trigger('focus');
|
||||
$editor.focus();
|
||||
};
|
||||
|
||||
this.loadValue = function(item) {
|
||||
var data = defaultValue = item[args.column.field];
|
||||
tmpdata = data;
|
||||
if(args.column.column_type_internal === 'jsonb' && !Array.isArray(data)) {
|
||||
data = JSONBigNumber.stringify(JSONBigNumber.parse(data), null, 4);
|
||||
} else if (Array.isArray(data)) {
|
||||
@@ -607,12 +652,39 @@ import gettext from 'sources/gettext';
|
||||
});
|
||||
data = '[' + temp.join() + ']';
|
||||
}
|
||||
$input.val(data);
|
||||
$input.trigger('select');
|
||||
/* if data is string then convert to json*/
|
||||
if (typeof data === 'string')
|
||||
data = JSON.parse(data);
|
||||
|
||||
require.ensure(['jsoneditor'], function(require) {
|
||||
var JSONEditor = require('jsoneditor');
|
||||
var jsonContainer = document.getElementById('pg-json-editor');
|
||||
let options = {
|
||||
modes: ['code', 'form', 'tree', 'preview'],
|
||||
onEditable: function() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if(jsonContainer) {
|
||||
$editor = new JSONEditor(jsonContainer, options);
|
||||
$editor.set(data);
|
||||
}
|
||||
}, function(error){
|
||||
throw(error);
|
||||
}, 'jsoneditorchunk');
|
||||
};
|
||||
|
||||
this.serializeValue = function() {
|
||||
return $input.val();
|
||||
if($editor) {
|
||||
let data = $editor.getText();
|
||||
if (data === '') {
|
||||
return null;
|
||||
}
|
||||
return data;
|
||||
} else {
|
||||
// The loader is not loaded yet ?
|
||||
return tmpdata;
|
||||
}
|
||||
};
|
||||
|
||||
this.applyValue = function(item, state) {
|
||||
@@ -620,12 +692,13 @@ import gettext from 'sources/gettext';
|
||||
};
|
||||
|
||||
this.isValueChanged = function() {
|
||||
return (!($input.val() == '' && defaultValue == null)) && ($input.val() != defaultValue);
|
||||
let data = $editor.getText();
|
||||
return (!(data == '' && defaultValue == null)) && (data != defaultValue);
|
||||
};
|
||||
|
||||
this.validate = function() {
|
||||
if (args.column.validator) {
|
||||
var validationResults = args.column.validator($input.val());
|
||||
var validationResults = args.column.validator($editor.getText());
|
||||
if (!validationResults.valid) {
|
||||
return validationResults;
|
||||
}
|
||||
|
||||
@@ -39,13 +39,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
function resizeAllColumns(maxWidth) {
|
||||
function resizeAllColumns(maxWidth, max_width_changed=false) {
|
||||
var elHeaders = $container.find('.slick-header-column');
|
||||
var allColumns = grid.getColumns();
|
||||
elHeaders.each(function(index, el) {
|
||||
var columnDef = $(el).data('column');
|
||||
// Check if width is set then no need to resize that column.
|
||||
if (typeof(columnDef.width) !== 'undefined' && !isNaN(columnDef.width)) {
|
||||
if (typeof(columnDef.width) !== 'undefined' && !isNaN(columnDef.width) && !max_width_changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,437 @@
|
||||
.jsoneditor-menu a.jsoneditor-poweredBy {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.jsoneditor {
|
||||
border-color: $border-color;
|
||||
}
|
||||
|
||||
/* Menu Bar*/
|
||||
.jsoneditor-menu {
|
||||
background: $color-gray-light;
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
|
||||
/* Menu Bar buttons*/
|
||||
.jsoneditor-menu>button {
|
||||
font-family: $font-family-icon !important;
|
||||
font-size: $navbar-font-size !important;
|
||||
line-height: $line-height-base !important;
|
||||
background-image:none !important;
|
||||
font-weight: 600 !important;
|
||||
border-radius: $border-radius;
|
||||
border-color:$color-gray;
|
||||
opacity: 1;
|
||||
background-color:$btn-primary-icon-bg;
|
||||
color: $color-fg;
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
/* Navigation Bar */
|
||||
.jsoneditor-navigation-bar{
|
||||
font-family: $font-family-primary !important;
|
||||
background-color: $color-bg;
|
||||
color: $color-fg;
|
||||
border-bottom: 1px solid $color-gray;
|
||||
}
|
||||
|
||||
/* Over rides icons */
|
||||
.jsoneditor-menu>button.jsoneditor-expand-all::before{
|
||||
content: "\f424";
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.jsoneditor-menu>button.jsoneditor-collapse-all::before{
|
||||
content: "\f422";
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.jsoneditor-menu>button.jsoneditor-redo::before {
|
||||
content: "\f01e";
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.jsoneditor-menu>button.jsoneditor-undo::before {
|
||||
content: "\f0e2";
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.jsoneditor-menu>button.jsoneditor-format::before {
|
||||
content: "\f03c";
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.jsoneditor-menu>button.jsoneditor-compact::before {
|
||||
content: "\f066";
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.jsoneditor-menu>button.jsoneditor-sort::before {
|
||||
content: "\f160";
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.jsoneditor-menu>button.jsoneditor-repair::before {
|
||||
content: "\f0ad";
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.jsoneditor-menu>button.jsoneditor-transform::before {
|
||||
content: "\f0b0";
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
/* Undo redo buttons */
|
||||
.jsoneditor-menu>.jsoneditor-modes>button:disabled,
|
||||
.jsoneditor-menu>button:disabled {
|
||||
color:$color-fg !important;
|
||||
opacity: $btn-disabled-opacity;
|
||||
border: thin solid;
|
||||
border-color:$color-gray;
|
||||
border-radius: $border-radius;
|
||||
background-color:$btn-primary-icon-bg !important;
|
||||
}
|
||||
|
||||
/* Mode drop-down */
|
||||
.jsoneditor-menu>.jsoneditor-modes>button,{
|
||||
font-family: $font-family-primary !important;
|
||||
font-size: $font-size-base !important;
|
||||
line-height: $line-height-base !important;
|
||||
background-image:none !important;
|
||||
font-weight: 600 !important;
|
||||
border-radius: $border-radius;
|
||||
border-color:$color-gray;
|
||||
opacity: 1;
|
||||
background-color:$btn-primary-icon-bg;
|
||||
color: $color-fg;
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
/* Mode drop-down options */
|
||||
.jsoneditor-contextmenu .jsoneditor-menu button,
|
||||
.jsoneditor-contextmenu .jsoneditor-separator {
|
||||
font-family: $font-family-primary !important;
|
||||
font-size: $font-size-base !important;
|
||||
background-color: $color-bg !important;
|
||||
color: $color-fg !important;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Drop-down hovered*/
|
||||
.jsoneditor-contextmenu .jsoneditor-menu li button:hover {
|
||||
background-color: $btn-primary-hover-bg !important;
|
||||
border-color: $btn-primary-border-hover-bg !important;
|
||||
color: $btn-primary-color !important;
|
||||
}
|
||||
|
||||
/* Drop-down selected*/
|
||||
.jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected,
|
||||
.jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected:focus,
|
||||
.jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected:hover {
|
||||
background-color: $btn-primary-hover-bg !important;
|
||||
border-color: $btn-primary-border-hover-bg !important;
|
||||
color: $btn-primary-color !important;
|
||||
}
|
||||
|
||||
/* Search Box*/
|
||||
.jsoneditor-frame,
|
||||
.jsoneditor-search input{
|
||||
color: $input-color;
|
||||
background-color: $input-bg;
|
||||
}
|
||||
|
||||
/* Set expand icon to none*/
|
||||
div.jsoneditor-tree button.jsoneditor-button:focus {
|
||||
background-color: $color-bg !important ;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Search Box results */
|
||||
.jsoneditor-results {
|
||||
color: $color-fg;
|
||||
}
|
||||
|
||||
/* Ace editor Setting */
|
||||
/* Ace editor numbers */
|
||||
.ace-jsoneditor .ace_gutter {
|
||||
background-color:$color-gray-light;
|
||||
color:$color-fg;
|
||||
}
|
||||
/* Ace editor code background */
|
||||
.ace-jsoneditor .ace_scroller {
|
||||
background-color: $color-bg;
|
||||
}
|
||||
|
||||
|
||||
/* Ace editor hide indent guide */
|
||||
.ace-jsoneditor .ace_indent-guide {
|
||||
background: none;
|
||||
}
|
||||
/* Ace editor Key names */
|
||||
.ace-jsoneditor .ace_variable {
|
||||
color: $color-fg !important;
|
||||
}
|
||||
|
||||
/* Ace editorfonts */
|
||||
.ace_editor,
|
||||
.ace-jsoneditor *{
|
||||
font-family: $font-family-editor !important;
|
||||
font-size: $font-size-base !important;
|
||||
line-height: $line-height-base !important;
|
||||
color: $color-fg;
|
||||
}
|
||||
.ace-jsoneditor.ace_editor {
|
||||
font-family: $font-family-editor !important;
|
||||
font-size: $font-size-base !important;
|
||||
line-height: $line-height-base !important;
|
||||
color: $color-fg;
|
||||
background-color:$color-bg !important;
|
||||
}
|
||||
|
||||
|
||||
/* /* font setting all other mode */
|
||||
/* form, tree, code, preview, schema-error */
|
||||
div.jsoneditor-default,
|
||||
div.jsoneditor-field,
|
||||
div.jsoneditor-value,
|
||||
div.jsoneditor textarea,
|
||||
div.jsoneditor td,
|
||||
div.jsoneditor-readonly,
|
||||
.jsoneditor-popover,
|
||||
div.jsoneditor-tree{
|
||||
font-family: $font-family-primary !important;
|
||||
font-size: $font-size-base !important;
|
||||
line-height: $line-height-base !important;
|
||||
color: $color-fg;
|
||||
background-color:$color-bg;
|
||||
}
|
||||
|
||||
div.jsoneditor td.jsoneditor-tree {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
pre.jsoneditor-preview{
|
||||
background-color: $color-gray-lighter !important;
|
||||
}
|
||||
|
||||
|
||||
/* Hilight selected values in tree/form modes */
|
||||
div.jsoneditor-field.jsoneditor-highlight,
|
||||
div.jsoneditor-field[contenteditable=true]:focus,
|
||||
div.jsoneditor-field[contenteditable=true]:hover,
|
||||
div.jsoneditor-value.jsoneditor-highlight,
|
||||
div.jsoneditor-value[contenteditable=true]:focus,
|
||||
div.jsoneditor-value[contenteditable=true]:hover
|
||||
{
|
||||
background-color: $sql-editor-selection-bg;
|
||||
border: 1px solid $sql-editor-selection-bg;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
/* Hi-light line in code */
|
||||
.ace-jsoneditor .ace_marker-layer .ace_active-line {
|
||||
background-color: $sql-editor-selection-bg;
|
||||
border: 0;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
.ace-jsoneditor .ace_gutter-active-line {
|
||||
background-color:$color-gray-light;
|
||||
}
|
||||
|
||||
.ace-jsoneditor .ace_marker-layer .ace_selected-word {
|
||||
border: 1px solid $color-primary-light;
|
||||
}
|
||||
|
||||
|
||||
.ace-jsoneditor .ace_marker-layer .ace_selection {
|
||||
background: $color-primary-light;
|
||||
}
|
||||
|
||||
/* Nested DD*/
|
||||
.jsoneditor-contextmenu .jsoneditor-icon {
|
||||
background-image:none;
|
||||
}
|
||||
|
||||
.jsoneditor-contextmenu .jsoneditor-text {
|
||||
padding:4px 4px 4px 5px;
|
||||
}
|
||||
|
||||
|
||||
.jsoneditor-contextmenu .jsoneditor-menu{
|
||||
border: 1px solid $border-color;
|
||||
background: $color-bg !important;
|
||||
color: $color-fg !important;
|
||||
}
|
||||
|
||||
|
||||
.jsoneditor-contextmenu .jsoneditor-menu button.jsoneditor-expand, {
|
||||
height:0px;
|
||||
background-color: $color-gray-light !important;
|
||||
}
|
||||
|
||||
.jsoneditor-contextmenu .jsoneditor-menu li ul {
|
||||
padding: 0;
|
||||
border: 1px solid $border-color;
|
||||
}
|
||||
|
||||
.jsoneditor-contextmenu .jsoneditor-separator {
|
||||
height: 0;
|
||||
border-top: 1px solid $border-color;
|
||||
padding-top: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* Status Bar */
|
||||
.jsoneditor-statusbar {
|
||||
background-color: $color-gray-light !important;
|
||||
color: $color-primary-light-fg !important;
|
||||
border-top: 1px solid $border-color;
|
||||
}
|
||||
|
||||
/* Validation Bar */
|
||||
.jsoneditor .jsoneditor-validation-errors {
|
||||
background-color:$sql-history-error-bg !important;
|
||||
color:$color-fg;
|
||||
border: 1px solid $color-danger;
|
||||
}
|
||||
|
||||
.jsoneditor .jsoneditor-text-errors {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border-top: 1px solid $color-gray;;
|
||||
}
|
||||
|
||||
|
||||
.jsoneditor .jsoneditor-text-errors tr,
|
||||
.jsoneditor .jsoneditor-text-errors td,
|
||||
.jsoneditor .jsoneditor-text-errors td pre,
|
||||
.jsoneditor .jsoneditor-text-errors tr.parse-error {
|
||||
background-color: $color-danger-lighter !important;
|
||||
color:$color-fg;
|
||||
}
|
||||
|
||||
|
||||
/* Transform & sort */
|
||||
/* Header */
|
||||
.jsoneditor-modal .pico-modal-header {
|
||||
font-family: $font-family-primary !important;
|
||||
font-size: $font-size-base !important;
|
||||
font-weight: bold;
|
||||
background-color: $alert-header-bg !important;
|
||||
color: $alert-header-fg;
|
||||
}
|
||||
|
||||
/* Body */
|
||||
.pico-content{
|
||||
background-color: $color-gray-light !important;
|
||||
}
|
||||
|
||||
/* Disable links */
|
||||
.jsoneditor-modal a {
|
||||
color: $color-fg !important;
|
||||
}
|
||||
|
||||
|
||||
/* Fields */
|
||||
.jsoneditor-modal,
|
||||
.jsoneditor-modal #query,
|
||||
.jsoneditor-modal input,
|
||||
.jsoneditor-modal input[type=text],
|
||||
.jsoneditor-modal input[type=text]:focus,
|
||||
.jsoneditor-modal option,
|
||||
.jsoneditor-modal select,
|
||||
.jsoneditor-modal table td,
|
||||
.jsoneditor-modal table th,
|
||||
.jsoneditor-modal textarea, {
|
||||
color: $color-fg !important;
|
||||
background-color: $color-bg !important;
|
||||
font-size: $font-size-base !important;
|
||||
font-family: $font-family-primary !important;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
a:focus, input[type=checkbox]:focus, select:focus{
|
||||
outline:none !important;
|
||||
}
|
||||
|
||||
.jsoneditor-modal option{
|
||||
background-color: $color-primary-light !important;
|
||||
border: 1px solid $color-primary-light !important;
|
||||
border-radius: $border-radius !important;
|
||||
color:$color-fg !important;
|
||||
}
|
||||
|
||||
/* Ascending descending buttons */
|
||||
.jsoneditor-modal .jsoneditor-button-group.jsoneditor-button-group-value-asc input.jsoneditor-button-asc,
|
||||
.jsoneditor-modal .jsoneditor-button-group.jsoneditor-button-group-value-desc input.jsoneditor-button-desc {
|
||||
//color: $color-bg !important;
|
||||
background-color: $color-primary !important;
|
||||
//border-color: $color-primary !important;
|
||||
border-color: $color-primary;
|
||||
color: $btn-primary-color !important;
|
||||
}
|
||||
|
||||
|
||||
.jsoneditor-modal input[type=button]{
|
||||
color: $color-fg;
|
||||
background-color: $color-bg;
|
||||
}
|
||||
|
||||
/* OK Button */
|
||||
.jsoneditor-modal input[type=submit] {
|
||||
color: $btn-primary-color !important;
|
||||
border-color: $color-primary !important;
|
||||
background-color: $color-primary !important;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.jsoneditor-modal .jsoneditor-jmespath-label {
|
||||
color: $color-fg;
|
||||
font-weight: bold;
|
||||
}
|
||||
.selectr-option,
|
||||
.selectr-options-container {
|
||||
color: $color-fg;
|
||||
background-color: $color-bg;
|
||||
}
|
||||
|
||||
.selectr-option.active,
|
||||
.selectr-option.selected{
|
||||
color: $btn-primary-color !important;
|
||||
background-color: $color-primary !important;
|
||||
border-color: $color-primary;
|
||||
}
|
||||
|
||||
.jsoneditor-modal .selectr-selected{
|
||||
color: $color-fg;
|
||||
background-color: $color-bg;
|
||||
}
|
||||
|
||||
.jsoneditor-modal .selectr-selected .selectr-tag {
|
||||
border: 1px solid $color-primary;
|
||||
color: $btn-primary-color !important;
|
||||
background-color: $color-primary !important;
|
||||
border-color: $color-primary;
|
||||
}
|
||||
|
||||
.jsoneditor-modal .selectr-selected .selectr-tag-remove::before{
|
||||
color: $color-primary !important;
|
||||
}
|
||||
|
||||
/* Update String. Key colors code mode */
|
||||
.ace_variable {color: $color-editor-variable !important;}
|
||||
|
||||
.ace_string {color: $color-editor-string !important;}
|
||||
|
||||
.ace_constant.ace_numeric {color:$color-editor-number !important;}
|
||||
|
||||
/* Update String. Key colors tree/form mode*/
|
||||
div.jsoneditor-value.jsoneditor-string {
|
||||
color: $color-editor-string !important;
|
||||
}
|
||||
|
||||
div.jsoneditor-value.jsoneditor-number {
|
||||
color:$color-editor-number !important;
|
||||
}
|
||||
|
||||
@@ -32,3 +32,4 @@ $theme-colors: (
|
||||
@import 'bootstrap4-toggle.overrides';
|
||||
@import 'pickr.overrides';
|
||||
@import 'tippy.overrides';
|
||||
@import 'jsoneditor.overrides';
|
||||
@@ -114,6 +114,7 @@ define('pgadmin.datagrid', [
|
||||
label: gettext('Query Tool'),
|
||||
icon: 'pg-font-icon icon-query_tool',
|
||||
data:{
|
||||
applies: 'tools',
|
||||
data_disabled: gettext('Please select a database from the browser tree to access Query Tool.'),
|
||||
},
|
||||
}];
|
||||
@@ -210,7 +211,25 @@ define('pgadmin.datagrid', [
|
||||
// This is a callback function to show query tool when user click on menu item.
|
||||
show_query_tool: function(url, aciTreeIdentifier) {
|
||||
const transId = commonUtils.getRandomInt(1, 9999999);
|
||||
showQueryTool.showQueryTool(this, pgBrowser, alertify, url, aciTreeIdentifier, transId);
|
||||
var t = pgBrowser.tree,
|
||||
i = aciTreeIdentifier || t.selected(),
|
||||
d = i && i.length == 1 ? t.itemData(i) : undefined;
|
||||
|
||||
//Open query tool with create script if copy_sql_to_query_tool is true else open blank query tool
|
||||
var preference = pgBrowser.get_preference('sqleditor', 'copy_sql_to_query_tool');
|
||||
if(preference.value && !d._type.includes('coll-') && (url === '' || url['applies'] === 'tools')){
|
||||
var stype = d._type.toLowerCase();
|
||||
var data = {
|
||||
'script': stype,
|
||||
data_disabled: gettext('The selected tree node does not support this option.'),
|
||||
};
|
||||
pgBrowser.Node.callbacks.show_script(data);
|
||||
}else{
|
||||
if(d._type.includes('coll-')){
|
||||
url = '';
|
||||
}
|
||||
showQueryTool.showQueryTool(this, pgBrowser, alertify, url, aciTreeIdentifier, transId);
|
||||
}
|
||||
},
|
||||
|
||||
launch_grid: function(trans_id, panel_url, is_query_tool, panel_title, sURL=null, sql_filter=null) {
|
||||
|
||||
@@ -23,7 +23,7 @@ function isServerInformationAvailable(parentData) {
|
||||
return parentData.server === undefined;
|
||||
}
|
||||
|
||||
export function getPanelTitle(pgBrowser, selected_item=null, custom_title=null, parentData=null, conn_title=false) {
|
||||
export function getPanelTitle(pgBrowser, selected_item=null, custom_title=null, parentData=null, conn_title=false, db_label=null) {
|
||||
var preferences = pgBrowser.get_preferences_for_module('browser');
|
||||
if(selected_item == null && parentData == null) {
|
||||
selected_item = pgBrowser.treeMenu.selected();
|
||||
@@ -36,7 +36,10 @@ export function getPanelTitle(pgBrowser, selected_item=null, custom_title=null,
|
||||
}
|
||||
}
|
||||
|
||||
const db_label = getDatabaseLabel(parentData);
|
||||
if(!db_label) {
|
||||
db_label = getDatabaseLabel(parentData);
|
||||
}
|
||||
|
||||
var qt_title_placeholder = '';
|
||||
if (!conn_title) {
|
||||
if (custom_title) {
|
||||
|
||||
@@ -333,20 +333,7 @@ export function initialize(gettext, url_for, $, _, pgAdmin, csrfToken, Browser)
|
||||
// Listen key press event from terminal and emit socket event.
|
||||
term.attachCustomKeyEventHandler(e => {
|
||||
e.stopPropagation();
|
||||
if(e.type=='keydown' && (e.metaKey || e.ctrlKey) &&(e.key == 'v' || e.key == 'V')) {
|
||||
navigator.permissions.query({ name: 'clipboard-read' }).then(function(result) {
|
||||
if(result.state === 'granted' || result.state === 'prompt') {
|
||||
navigator.clipboard.readText().then( clipText => {
|
||||
var selected_text = clipText;
|
||||
if (selected_text.length > 0) {
|
||||
socket.emit('socket_input', {'input': selected_text, 'key_name': e.code});
|
||||
}
|
||||
});
|
||||
} else{
|
||||
Alertify.alert(gettext('Clipboard read permission required'), gettext('To paste data on the PSQL terminal, Clipboard read permission required.'));
|
||||
}
|
||||
});
|
||||
}else if(e.type=='keydown' && (e.metaKey || e.ctrlKey) && (e.key == 'c' || e.key == 'C')) {
|
||||
if(e.type=='keydown' && (e.metaKey || e.ctrlKey) && (e.key == 'c' || e.key == 'C')) {
|
||||
document.execCommand('copy');
|
||||
}
|
||||
|
||||
@@ -357,6 +344,21 @@ export function initialize(gettext, url_for, $, _, pgAdmin, csrfToken, Browser)
|
||||
return true;
|
||||
});
|
||||
|
||||
term.textarea.addEventListener('paste', function() {
|
||||
navigator.permissions.query({ name: 'clipboard-read' }).then(function(result) {
|
||||
if(result.state === 'granted' || result.state === 'prompt') {
|
||||
navigator.clipboard.readText().then( clipText => {
|
||||
var selected_text = clipText;
|
||||
if (selected_text.length > 0) {
|
||||
socket.emit('socket_input', {'input': selected_text, 'key_name': 'paste'});
|
||||
}
|
||||
});
|
||||
} else{
|
||||
Alertify.alert(gettext('Clipboard read permission required'), gettext('To paste data on the PSQL terminal, Clipboard read permission required.'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
term.onKey(function (ev) {
|
||||
socket.emit('socket_input', {'input': ev.key, 'key_name': ev.domEvent.code});
|
||||
});
|
||||
|
||||
@@ -98,8 +98,12 @@ class SearchObjectsHelper:
|
||||
"""
|
||||
|
||||
if obj_type == 'all':
|
||||
status, error = conn.execute_dict('select * from pg_subscription')
|
||||
if 'permission denied' in error:
|
||||
status, result = conn.execute_dict(
|
||||
"SELECT COUNT(1) FROM information_schema.table_privileges "
|
||||
"WHERE table_name = 'pg_subscription' "
|
||||
"AND privilege_type = 'SELECT'")
|
||||
if 'count' in result['rows'][0] and \
|
||||
result['rows'][0]['count'] == '0':
|
||||
skip_obj_type.append('subscription')
|
||||
|
||||
return skip_obj_type
|
||||
|
||||
@@ -892,6 +892,13 @@ define('tools.querytool', [
|
||||
column_size[table_name] = {};
|
||||
}
|
||||
|
||||
// Keep track of column_data_max_width
|
||||
self.max_width_changed = false;
|
||||
if (_.isUndefined(self.old_column_data_max_width) || self.old_column_data_max_width != self.preferences.column_data_max_width) {
|
||||
self.old_column_data_max_width = self.preferences.column_data_max_width;
|
||||
self.max_width_changed = true;
|
||||
}
|
||||
|
||||
|
||||
_.each(columns, function(c) {
|
||||
c.display_name = _.escape(c.display_name);
|
||||
@@ -931,12 +938,12 @@ define('tools.querytool', [
|
||||
}
|
||||
|
||||
if (_.isUndefined(column_size[table_name][options.nonative_field])) {
|
||||
/* If column_data_auto_resize is true then for the first time set
|
||||
* the addWidth parameter to iconWidth and if it is false then
|
||||
/* If column_data_auto_resize is 'by_data' then for the first time set
|
||||
* the addWidth parameter to iconWidth and if it is 'by_name' then
|
||||
* calculate width based on longer string among data type or
|
||||
* column name.
|
||||
*/
|
||||
if (self.preferences.column_data_auto_resize) {
|
||||
if (self.preferences.column_data_auto_resize === 'by_data') {
|
||||
options['addWidth'] = iconWidth;
|
||||
options['width'] = NaN;
|
||||
} else {
|
||||
@@ -1187,9 +1194,9 @@ define('tools.querytool', [
|
||||
dataView.onRowsChanged.subscribe(function(e, args) {
|
||||
grid.invalidateRows(args.rows);
|
||||
grid.render();
|
||||
// Resize all columns if column_data_auto_resize is true.
|
||||
if (self.preferences.column_data_auto_resize) {
|
||||
grid.resizeAllColumns && grid.resizeAllColumns(self.preferences.column_data_max_width);
|
||||
// Resize all columns if column_data_auto_resize is 'by_data'.
|
||||
if (self.preferences.column_data_auto_resize === 'by_data') {
|
||||
grid.resizeAllColumns && grid.resizeAllColumns(self.preferences.column_data_max_width, self.max_width_changed);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1430,7 +1437,7 @@ define('tools.querytool', [
|
||||
}
|
||||
dataView.setItems(collection, self.client_primary_key);
|
||||
/* Resize the columns once if data empty */
|
||||
if (collection.length === 0 && self.preferences.column_data_auto_resize) {
|
||||
if (collection.length === 0 && self.preferences.column_data_auto_resize === 'by_data') {
|
||||
self.grid.resizeAllColumns && self.grid.resizeAllColumns();
|
||||
}
|
||||
},
|
||||
@@ -2704,7 +2711,10 @@ define('tools.querytool', [
|
||||
|
||||
var server_data = pgWindow.default.pgAdmin.Browser.treeMenu.findNode(tree_data.slice(0,2));
|
||||
var database_data = pgWindow.default.pgAdmin.Browser.treeMenu.findNode(tree_data.slice(0,4));
|
||||
let conn_title = panelTitleFunc.getPanelTitle(pgWindow.default.pgAdmin.Browser, null, null, null, true);
|
||||
var dbData = tree_data.slice(0,3);
|
||||
dbData.push('database/' + url_params.did);
|
||||
var dbName = pgWindow.default.pgAdmin.Browser.treeMenu.findNode(dbData).data.label;
|
||||
let conn_title = panelTitleFunc.getPanelTitle(pgWindow.default.pgAdmin.Browser, null, null, null, true, dbName);
|
||||
self.gridView.set_editor_title(_.unescape(conn_title));
|
||||
let connection_data = {
|
||||
'server_group': self.gridView.handler.url_params.sgid,
|
||||
|
||||
@@ -300,8 +300,22 @@ li.CodeMirror-hint-active {
|
||||
& .pg-text-invalid {
|
||||
background: $color-danger-lighter;
|
||||
}
|
||||
|
||||
& #pg-json-editor {
|
||||
min-width:525px;
|
||||
min-height:280px;
|
||||
height:295px;
|
||||
width:550px;
|
||||
border: $panel-border;
|
||||
outline:0;
|
||||
resize: both;
|
||||
overflow:auto
|
||||
}
|
||||
}
|
||||
|
||||
.pg_buttons {
|
||||
padding-top: 3px;
|
||||
}
|
||||
.sql-editor-message {
|
||||
white-space:pre-wrap;
|
||||
font-family: $font-family-editor;
|
||||
|
||||
@@ -128,6 +128,17 @@ def register_query_tool_preferences(self):
|
||||
)
|
||||
)
|
||||
|
||||
self.show_prompt_commit_transaction = self.preference.register(
|
||||
'Options', 'copy_sql_to_query_tool',
|
||||
gettext("Copy SQL from main window to query tool?"), 'boolean',
|
||||
False,
|
||||
category_label=PREF_LABEL_OPTIONS,
|
||||
help_str=gettext(
|
||||
'Specifies whether or not to copy SQL to query tool from '
|
||||
'main window.'
|
||||
)
|
||||
)
|
||||
|
||||
self.sql_font_size = self.preference.register(
|
||||
'Editor', 'plain_editor_mode',
|
||||
gettext("Plain text mode?"), 'boolean', False,
|
||||
@@ -270,14 +281,17 @@ def register_query_tool_preferences(self):
|
||||
|
||||
self.column_data_auto_resize = self.preference.register(
|
||||
'Results_grid', 'column_data_auto_resize',
|
||||
gettext("Resize by data?"), 'boolean', True,
|
||||
gettext("Columns sized by"), 'radioModern', 'by_data',
|
||||
options=[{'label': gettext('Column data'), 'value': 'by_data'},
|
||||
{'label': gettext('Column name'), 'value': 'by_name'}],
|
||||
category_label=PREF_LABEL_RESULTS_GRID,
|
||||
help_str=gettext(
|
||||
'If set to True then data columns will auto-size to the maximum '
|
||||
'If set to \'Column data\' columns will auto-size to the maximum '
|
||||
'width of the data in the column as loaded in the first batch. If '
|
||||
'False, the column will be sized to the widest of the data type '
|
||||
'or column name.'
|
||||
)
|
||||
'set to \'Column name\', the column will be sized to the widest '
|
||||
'of the data type or column name.'
|
||||
),
|
||||
dependents=['column_data_max_width']
|
||||
)
|
||||
|
||||
self.column_data_max_width = self.preference.register(
|
||||
@@ -285,12 +299,9 @@ def register_query_tool_preferences(self):
|
||||
gettext("Maximum column width"), 'integer', 0,
|
||||
category_label=PREF_LABEL_RESULTS_GRID,
|
||||
help_str=gettext(
|
||||
'Specify the maximum width of the column when \'Resize by data?\' '
|
||||
'is set to True. If it is set to 0 then columns will auto-size to '
|
||||
'the maximum width of the data in the column. If '
|
||||
'\'Resize by data?\' is set to False then this setting won\'t '
|
||||
'take any effect.'
|
||||
)
|
||||
'Specify the maximum width of the column in pixels when '
|
||||
'\'Columns sized by \' is set to \'Column data\'.'
|
||||
),
|
||||
)
|
||||
|
||||
self.sql_font_size = self.preference.register(
|
||||
|
||||
@@ -129,6 +129,13 @@ def validate_user(data):
|
||||
if 'auth_source' in data and data['auth_source'] != "":
|
||||
new_data['auth_source'] = data['auth_source']
|
||||
|
||||
if 'locked' in data and type(data['locked']) == bool:
|
||||
new_data['locked'] = data['locked']
|
||||
if data['locked']:
|
||||
new_data['login_attempts'] = config.MAX_LOGIN_ATTEMPTS
|
||||
else:
|
||||
new_data['login_attempts'] = 0
|
||||
|
||||
return new_data
|
||||
|
||||
|
||||
@@ -207,7 +214,8 @@ def user(uid):
|
||||
'email': u.email,
|
||||
'active': u.active,
|
||||
'role': u.roles[0].id,
|
||||
'auth_source': u.auth_source
|
||||
'auth_source': u.auth_source,
|
||||
'locked': u.locked
|
||||
}
|
||||
else:
|
||||
users = User.query.all()
|
||||
@@ -219,7 +227,8 @@ def user(uid):
|
||||
'email': u.email,
|
||||
'active': u.active,
|
||||
'role': u.roles[0].id,
|
||||
'auth_source': u.auth_source
|
||||
'auth_source': u.auth_source,
|
||||
'locked': u.locked
|
||||
})
|
||||
|
||||
res = users_data
|
||||
@@ -316,7 +325,8 @@ def create_user(data):
|
||||
'username': usr.username,
|
||||
'email': usr.email,
|
||||
'active': usr.active,
|
||||
'role': usr.roles[0].id
|
||||
'role': usr.roles[0].id,
|
||||
'locked': usr.locked
|
||||
}
|
||||
|
||||
|
||||
@@ -599,7 +609,8 @@ def update(uid):
|
||||
'email': usr.email,
|
||||
'active': usr.active,
|
||||
'role': usr.roles[0].id,
|
||||
'auth_source': usr.auth_source
|
||||
'auth_source': usr.auth_source,
|
||||
'locked': usr.locked
|
||||
}
|
||||
|
||||
return ajax_response(
|
||||
|
||||
@@ -436,6 +436,16 @@ define([
|
||||
editable: function(m) {
|
||||
return (m.get('auth_source') == DEFAULT_AUTH_SOURCE);
|
||||
},
|
||||
},{
|
||||
id: 'locked',
|
||||
label: gettext('Locked'),
|
||||
type: 'switch',
|
||||
cell: 'switch',
|
||||
disabled: false,
|
||||
sortable: false,
|
||||
editable: function (m){
|
||||
return (m.get('id') != userInfo['id']);
|
||||
},
|
||||
}],
|
||||
validate: function() {
|
||||
var errmsg = null,
|
||||
|
||||
@@ -73,6 +73,7 @@ class _Preference(object):
|
||||
self.fields = kwargs.get('fields', None)
|
||||
self.allow_blanks = kwargs.get('allow_blanks', None)
|
||||
self.disabled = kwargs.get('disabled', False)
|
||||
self.dependents = kwargs.get('dependents', None)
|
||||
|
||||
# Look into the configuration table to find out the id of the specific
|
||||
# preference.
|
||||
@@ -254,6 +255,7 @@ class _Preference(object):
|
||||
'value': self.get(),
|
||||
'fields': self.fields,
|
||||
'disabled': self.disabled,
|
||||
'dependents': self.dependents
|
||||
}
|
||||
return res
|
||||
|
||||
@@ -427,6 +429,7 @@ class Preferences(object):
|
||||
fields = kwargs.get('fields', None)
|
||||
allow_blanks = kwargs.get('allow_blanks', None)
|
||||
disabled = kwargs.get('disabled', False)
|
||||
dependents = kwargs.get('dependents', None)
|
||||
|
||||
cat = self.__category(category, category_label)
|
||||
if name in cat['preferences']:
|
||||
@@ -444,7 +447,7 @@ class Preferences(object):
|
||||
cat['id'], name, label, _type, default, help_str=help_str,
|
||||
min_val=min_val, max_val=max_val, options=options,
|
||||
select2=select2, fields=fields, allow_blanks=allow_blanks,
|
||||
disabled=disabled
|
||||
disabled=disabled, dependents=dependents
|
||||
)
|
||||
|
||||
return res
|
||||
|
||||
@@ -68,6 +68,11 @@ class NavMenuLocators:
|
||||
"//div[span[normalize-space(text())='Insert bracket pairs?']]" \
|
||||
"//div[contains(@class,'toggle btn')]"
|
||||
|
||||
copy_sql_to_query_tool_switch_btn = \
|
||||
"//div[span[normalize-space(text())=" \
|
||||
"'Copy SQL from main window to query tool?']]" \
|
||||
"//div[contains(@class,'toggle btn')]"
|
||||
|
||||
backup_filename_txt_box_name = "file"
|
||||
|
||||
restore_file_name_txt_box_name = "file"
|
||||
@@ -183,6 +188,8 @@ class QueryToolLocators:
|
||||
|
||||
row_editor_text_area_css = ".pg-text-editor > textarea"
|
||||
|
||||
json_editor_text_area_css = "div.ace_layer.ace_text-layer"
|
||||
|
||||
text_editor_ok_btn_css = ".btn.btn-primary.long_text_editor"
|
||||
|
||||
btn_load_file_css = "#btn-load-file"
|
||||
|
||||
@@ -143,6 +143,7 @@ var webpackShimConfig = {
|
||||
'dropzone': path.join(__dirname, './node_modules/dropzone/dist/dropzone'),
|
||||
'bignumber': path.join(__dirname, './node_modules/bignumber.js/bignumber'),
|
||||
'json-bignumber': path.join(__dirname, './node_modules/json-bignumber/dist/JSONBigNumber.min'),
|
||||
'jsoneditor': path.join(__dirname, './node_modules/jsoneditor/dist/jsoneditor.min'),
|
||||
'snap.svg': path.join(__dirname, './node_modules/snapsvg-cjs/dist/snap.svg-cjs'),
|
||||
'color-picker': path.join(__dirname, './node_modules/@simonwep/pickr/dist/pickr.es5.min'),
|
||||
'mousetrap': path.join(__dirname, './node_modules/mousetrap'),
|
||||
|
||||
@@ -1214,6 +1214,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
|
||||
integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==
|
||||
|
||||
"@sphinxxxx/color-conversion@^2.2.2":
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@sphinxxxx/color-conversion/-/color-conversion-2.2.2.tgz#03ecc29279e3c0c832f6185a5bfa3497858ac8ca"
|
||||
integrity sha512-XExJS3cLqgrmNBIP3bBw6+1oQ1ksGjFh0+oClDKFYpCCqx/hlqwWO5KO/S63fzUo67SxI9dMrF0y5T/Ey7h8Zw==
|
||||
|
||||
"@tippyjs/react@^4.2.0":
|
||||
version "4.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@tippyjs/react/-/react-4.2.5.tgz#9b5837db93a1cac953962404df906aef1a18e80d"
|
||||
@@ -1500,6 +1505,11 @@ accepts@~1.3.4:
|
||||
mime-types "~2.1.24"
|
||||
negotiator "0.6.2"
|
||||
|
||||
ace-builds@^1.4.12:
|
||||
version "1.4.12"
|
||||
resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.4.12.tgz#888efa386e36f4345f40b5233fcc4fe4c588fae7"
|
||||
integrity sha512-G+chJctFPiiLGvs3+/Mly3apXTcfgE45dT5yp12BcWZ1kUs+gm0qd3/fv4gsz6fVag4mM0moHVpjHDIgph6Psg==
|
||||
|
||||
"acitree@git+https://github.com/imsurinder90/jquery-aciTree.git#rc.7":
|
||||
version "4.5.0-rc.7"
|
||||
resolved "git+https://github.com/imsurinder90/jquery-aciTree.git#24dcd7536a008abe25da6adb7bfde8eeb53892f1"
|
||||
@@ -1568,7 +1578,7 @@ ajv@^5.0.0:
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.3.0"
|
||||
|
||||
ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5:
|
||||
ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.12.6:
|
||||
version "6.12.6"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
||||
@@ -5443,6 +5453,11 @@ jasmine-enzyme@^7.1.2:
|
||||
dependencies:
|
||||
enzyme-matchers "^7.1.2"
|
||||
|
||||
javascript-natural-sort@^0.7.1:
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59"
|
||||
integrity sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=
|
||||
|
||||
jest-worker@^26.3.0, jest-worker@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed"
|
||||
@@ -5452,6 +5467,11 @@ jest-worker@^26.3.0, jest-worker@^26.6.2:
|
||||
merge-stream "^2.0.0"
|
||||
supports-color "^7.0.0"
|
||||
|
||||
jmespath@^0.15.0:
|
||||
version "0.15.0"
|
||||
resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217"
|
||||
integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=
|
||||
|
||||
jquery-contextmenu@^2.6.4, jquery-contextmenu@^2.9.2:
|
||||
version "2.9.2"
|
||||
resolved "https://registry.yarnpkg.com/jquery-contextmenu/-/jquery-contextmenu-2.9.2.tgz#f9dc362e45871dda2e50fa45de2243e917446ced"
|
||||
@@ -5559,6 +5579,11 @@ json-schema@0.2.3:
|
||||
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
|
||||
integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
|
||||
|
||||
json-source-map@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/json-source-map/-/json-source-map-0.6.1.tgz#e0b1f6f4ce13a9ad57e2ae165a24d06e62c79a0f"
|
||||
integrity sha512-1QoztHPsMQqhDq0hlXY5ZqcEdUzxQEIxgFkKl4WUp2pgShObl+9ovi4kRh2TfvAfxAoHOJ9vIMEqk3k4iex7tg==
|
||||
|
||||
json-stable-stringify-without-jsonify@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
|
||||
@@ -5583,6 +5608,21 @@ json5@^2.1.2:
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
jsoneditor@^9.5.1:
|
||||
version "9.5.3"
|
||||
resolved "https://registry.yarnpkg.com/jsoneditor/-/jsoneditor-9.5.3.tgz#a44cce2556a21e13db360865ed4d4224908df3da"
|
||||
integrity sha512-KW893jRnh/s41n7Is4aNFg5Gh2h4HhE9W8L2rP1Okue3aW5Aov9SRqXs/5rmAABmlBGx1yzyPMlj4Oqw5nXQcQ==
|
||||
dependencies:
|
||||
ace-builds "^1.4.12"
|
||||
ajv "^6.12.6"
|
||||
javascript-natural-sort "^0.7.1"
|
||||
jmespath "^0.15.0"
|
||||
json-source-map "^0.6.1"
|
||||
jsonrepair "^2.2.1"
|
||||
mobius1-selectr "^2.4.13"
|
||||
picomodal "^3.0.0"
|
||||
vanilla-picker "^2.11.2"
|
||||
|
||||
jsonfile@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
||||
@@ -5595,6 +5635,11 @@ jsonparse@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
|
||||
integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=
|
||||
|
||||
jsonrepair@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/jsonrepair/-/jsonrepair-2.2.1.tgz#7c6257c36550a310150c41ab7d5d4cab71828456"
|
||||
integrity sha512-o9Je8TceILo872uQC9fIBJm957j1Io7z8Ca1iWIqY6S5S65HGE9XN7XEEw7+tUviB9Vq4sygV89MVTxl+rhZyg==
|
||||
|
||||
jsprim@^1.2.2:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
|
||||
@@ -6212,6 +6257,11 @@ ml-matrix@^6.5.0:
|
||||
dependencies:
|
||||
ml-array-rescale "^1.3.5"
|
||||
|
||||
mobius1-selectr@^2.4.13:
|
||||
version "2.4.13"
|
||||
resolved "https://registry.yarnpkg.com/mobius1-selectr/-/mobius1-selectr-2.4.13.tgz#0019dfd9f984840d6e40f70683ab3ec78ce3b5df"
|
||||
integrity sha512-Mk9qDrvU44UUL0EBhbAA1phfQZ7aMZPjwtL7wkpiBzGh8dETGqfsh50mWoX9EkjDlkONlErWXArHCKfoxVg0Bw==
|
||||
|
||||
module-deps@^6.2.3:
|
||||
version "6.2.3"
|
||||
resolved "https://registry.yarnpkg.com/module-deps/-/module-deps-6.2.3.tgz#15490bc02af4b56cf62299c7c17cba32d71a96ee"
|
||||
@@ -6849,6 +6899,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3:
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
|
||||
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
|
||||
|
||||
picomodal@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/picomodal/-/picomodal-3.0.0.tgz#facd30f4fbf34a809c1e04ea525f004f399c0b82"
|
||||
integrity sha1-+s0w9PvzSoCcHgTqUl8ATzmcC4I=
|
||||
|
||||
pify@^2.0.0, pify@^2.2.0, pify@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||
@@ -8972,6 +9027,13 @@ validate-npm-package-license@^3.0.1:
|
||||
spdx-correct "^3.0.0"
|
||||
spdx-expression-parse "^3.0.0"
|
||||
|
||||
vanilla-picker@^2.11.2:
|
||||
version "2.11.2"
|
||||
resolved "https://registry.yarnpkg.com/vanilla-picker/-/vanilla-picker-2.11.2.tgz#eaa24efa68c27e7ee9e0776df55d6913b855f133"
|
||||
integrity sha512-2cP7LlUnxHxwOf06ReUVtd2RFJMnJGaxN2s0p8wzBH3In5b00Le7fFZ9VrIoBE0svZkSq/BC/Pwq/k/9n+AA2g==
|
||||
dependencies:
|
||||
"@sphinxxxx/color-conversion" "^2.2.2"
|
||||
|
||||
varstream@^0.3.2:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/varstream/-/varstream-0.3.2.tgz#18ac6494765f3ff1a35ad9a4be053bec188a5de1"
|
||||
|
||||