Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
901e3ede39 | ||
|
|
e89f205aa6 | ||
|
|
dccd4f0bba | ||
|
|
99c6b171e4 | ||
|
|
0f5d4cd7b7 | ||
|
|
e256f734df | ||
|
|
9ed2506062 | ||
|
|
adf4634459 | ||
|
|
2dbd09530e | ||
|
|
d8ecbcf293 | ||
|
|
3ad0862c59 | ||
|
|
1b7dce124b | ||
|
|
24c0816042 | ||
|
|
9d6098ddf2 | ||
|
|
4d6b019949 | ||
|
|
cee65fbf9a | ||
|
|
f9707a8bf5 | ||
|
|
1af431dcc2 | ||
|
|
4033bf3748 | ||
|
|
0f5a97fa6e | ||
|
|
8eeb12b600 | ||
|
|
69069e9af3 | ||
|
|
83660ec9ed | ||
|
|
6b9ae466c2 | ||
|
|
5ce3c4a5c8 | ||
|
|
f8df935404 | ||
|
|
ffc1c6c3b7 | ||
|
|
dd4849070a | ||
|
|
44b2f49839 | ||
|
|
0cd67b15cb | ||
|
|
448ede53c6 | ||
|
|
cbd29cf45b | ||
|
|
3414de9ab2 | ||
|
|
30dc805cf6 | ||
|
|
ce8e6b47c1 | ||
|
|
aca97d9f57 | ||
|
|
b539637426 | ||
|
|
b1221d5517 | ||
|
|
5a8c916bc0 | ||
|
|
677221cd9c | ||
|
|
d2efee5943 | ||
|
|
0010935707 | ||
|
|
6fb4df67a9 | ||
|
|
0fa4609220 | ||
|
|
1cdbb9a643 | ||
|
|
e90b3dad01 | ||
|
|
e61a1045f5 | ||
|
|
b89e306df0 | ||
|
|
f002b11106 | ||
|
|
bc4e8a3c82 | ||
|
|
76a4dee451 |
No files matched your search
@@ -59,12 +59,15 @@ Authlib 0.15.5
|
||||
requests 2.25.1 Apache 2.0 https://requests.readthedocs.io
|
||||
pyotp 2.6.0 MIT License https://github.com/pyotp/pyotp
|
||||
qrcode 7.3.1 BSD https://github.com/lincolnloop/python-qrcode
|
||||
Pillow 8.3.2 HPND https://python-pillow.org
|
||||
Pillow 9.0.1 HPND https://python-pillow.org
|
||||
boto3 1.20.54 Apache License 2.0 https://github.com/boto/boto3
|
||||
botocore 1.23.54 Apache License 2.0 https://github.com/boto/botocore
|
||||
urllib3 1.26.8 MIT https://urllib3.readthedocs.io/
|
||||
|
||||
NOTE: This report was generated using Python 3.10. Full information may not be
|
||||
shown for Python modules that are not required with this version.
|
||||
|
||||
37 dependencies listed.
|
||||
40 dependencies listed.
|
||||
|
||||
Javascript Dependencies
|
||||
=======================
|
||||
@@ -103,6 +106,7 @@ Name Version
|
||||
@babel/plugin-transform-react-pure-annotations 7.12.1 MIT https://github.com/babel/babel.git
|
||||
@babel/preset-react 7.13.13 MIT https://github.com/babel/babel.git
|
||||
@babel/runtime 7.14.0 MIT https://github.com/babel/babel.git
|
||||
@babel/runtime 7.17.0 MIT https://github.com/babel/babel.git
|
||||
@babel/template 7.14.5 MIT https://github.com/babel/babel.git
|
||||
@babel/traverse 7.14.7 MIT https://github.com/babel/babel.git
|
||||
@babel/types 7.15.4 MIT https://github.com/babel/babel.git
|
||||
@@ -138,6 +142,7 @@ Name Version
|
||||
@projectstorm/react-diagrams-routing 6.6.1 MIT https://github.com/projectstorm/react-diagrams.git
|
||||
@simonwep/pickr 1.8.1 MIT https://github.com/Simonwep/pickr.git
|
||||
@sindresorhus/is 0.7.0 MIT https://github.com/sindresorhus/is.git
|
||||
@szhsin/react-menu 2.3.3 MIT https://github.com/szhsin/react-menu.git
|
||||
@tippyjs/react 4.2.3 MIT git+https://github.com/atomiks/tippyjs-react.git
|
||||
@types/classnames 2.3.1 MIT Unknown
|
||||
@types/component-emitter 1.2.10 MIT https://github.com/DefinitelyTyped/DefinitelyTyped.git
|
||||
@@ -152,31 +157,21 @@ Name Version
|
||||
@types/scheduler 0.16.1 MIT https://github.com/DefinitelyTyped/DefinitelyTyped.git
|
||||
@types/styled-jsx 2.2.8 MIT https://github.com/DefinitelyTyped/DefinitelyTyped.git
|
||||
acitree 4.5.0-rc.7 MIT https://github.com/imsurinder90/jquery-aciTree.git
|
||||
ajv 8.9.0 MIT https://github.com/ajv-validator/ajv.git
|
||||
ajv 6.12.6 MIT https://github.com/ajv-validator/ajv.git
|
||||
ajv 5.5.2 MIT https://github.com/epoberezkin/ajv.git
|
||||
ajv-errors 1.0.1 MIT git+https://github.com/epoberezkin/ajv-errors.git
|
||||
ajv-keywords 3.5.2 MIT git+https://github.com/epoberezkin/ajv-keywords.git
|
||||
alertifyjs 1.7.1 MIT git@github.com:MohammadYounes/AlertifyJS.git
|
||||
alphanum-sort 1.0.2 MIT https://github.com/TrySound/alphanum-sort.git
|
||||
ansi-regex 2.1.1 MIT https://github.com/chalk/ansi-regex.git
|
||||
ansi-styles 3.2.1 MIT https://github.com/chalk/ansi-styles.git
|
||||
ansi-styles 2.2.1 MIT https://github.com/chalk/ansi-styles.git
|
||||
aspen-core 1.0.4 MIT git+https://github.com/neeksandhu/aspen-core.git
|
||||
aspen-decorations 1.1.1 MIT git+https://github.com/neeksandhu/aspen-decorations.git
|
||||
aspen-tree-model 1.0.5 MIT git+https://github.com/neeksandhu/aspen-tree-model.git
|
||||
async 2.6.3 MIT https://github.com/caolan/async.git
|
||||
axios 0.21.4 MIT https://github.com/axios/axios.git
|
||||
babel-code-frame 6.26.0 MIT https://github.com/babel/babel/tree/master/packages/babel-code-frame
|
||||
babel-generator 6.26.1 MIT https://github.com/babel/babel/tree/master/packages/babel-generator
|
||||
babel-messages 6.23.0 MIT https://github.com/babel/babel/tree/master/packages/babel-messages
|
||||
babel-plugin-styled-components 1.12.0 MIT https://github.com/styled-components/babel-plugin-styled-components.git
|
||||
babel-plugin-syntax-jsx 6.18.0 MIT https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-jsx
|
||||
babel-runtime 6.26.0 MIT https://github.com/babel/babel/tree/master/packages/babel-runtime
|
||||
babel-template 6.26.0 MIT https://github.com/babel/babel/tree/master/packages/babel-template
|
||||
babel-traverse 6.26.0 MIT https://github.com/babel/babel/tree/master/packages/babel-traverse
|
||||
babel-types 6.26.0 MIT https://github.com/babel/babel/tree/master/packages/babel-types
|
||||
babelify 10.0.0 MIT https://github.com/babel/babelify.git
|
||||
babylon 6.18.0 MIT https://github.com/babel/babylon
|
||||
backbone 1.4.0 MIT https://github.com/jashkenas/backbone.git
|
||||
backbone 1.2.3 MIT https://github.com/jashkenas/backbone.git
|
||||
backbone 1.3.3 MIT https://github.com/jashkenas/backbone.git
|
||||
@@ -193,6 +188,8 @@ bignumber.js 9.0.1
|
||||
bignumber.js 7.2.1 MIT https://github.com/MikeMcl/bignumber.js.git
|
||||
bootstrap 4.6.0 MIT git+https://github.com/twbs/bootstrap.git
|
||||
bootstrap4-toggle 3.6.1 MIT https://github.com/gitbrent/bootstrap4-toggle.git
|
||||
bowser 2.11.0 MIT git+https://github.com/lancedikson/bowser.git
|
||||
brace 0.11.1 MIT git://github.com/thlorenz/brace.git
|
||||
brace-expansion 1.1.11 MIT git://github.com/juliangruber/brace-expansion.git
|
||||
browserfs 1.4.3 MIT https://github.com/jvilk/BrowserFS.git
|
||||
browserslist 4.16.6 MIT https://github.com/browserslist/browserslist.git
|
||||
@@ -201,14 +198,12 @@ camelcase 6.2.0
|
||||
camelize 1.0.0 MIT git://github.com/substack/camelize.git
|
||||
caniuse-api 3.0.0 MIT https://github.com/nyalab/caniuse-api.git
|
||||
chalk 2.4.2 MIT https://github.com/chalk/chalk.git
|
||||
chalk 1.1.3 MIT https://github.com/chalk/chalk.git
|
||||
chart.js 2.9.4 MIT https://github.com/chartjs/Chart.js.git
|
||||
chartjs-color 2.4.1 MIT https://github.com/chartjs/chartjs-color.git
|
||||
chartjs-color-string 0.6.0 MIT https://github.com/chartjs/chartjs-color-string.git
|
||||
classnames 2.3.1 MIT https://github.com/JedWatson/classnames.git
|
||||
closest 0.0.1 MIT https://github.com/ForbesLindesay/closest.git
|
||||
clsx 1.1.1 MIT https://github.com/lukeed/clsx.git
|
||||
co 4.6.0 MIT https://github.com/tj/co.git
|
||||
codemirror 5.59.4 MIT https://github.com/codemirror/CodeMirror.git
|
||||
color-convert 1.9.3 MIT https://github.com/Qix-/color-convert.git
|
||||
color-name 1.1.3 MIT git@github.com:dfcreative/color-name.git
|
||||
@@ -220,8 +215,8 @@ component-emitter 1.3.0
|
||||
concat-map 0.0.1 MIT git://github.com/substack/node-concat-map.git
|
||||
context-menu 2.0.0 MIT https://github.com/chipto/context-menu.git
|
||||
convert-source-map 1.8.0 MIT git://github.com/thlorenz/convert-source-map.git
|
||||
copy-to-clipboard 3.3.1 MIT git+https://github.com/sudodoki/copy-to-clipboard
|
||||
core-js 3.12.1 MIT https://github.com/zloirock/core-js.git
|
||||
core-js 2.6.12 MIT https://github.com/zloirock/core-js.git
|
||||
cosmiconfig 7.0.0 MIT git+https://github.com/davidtheclark/cosmiconfig.git
|
||||
css-color-names 1.0.1 MIT git://github.com/bahamas10/css-color-names.git
|
||||
css-color-names 0.0.4 MIT git://github.com/bahamas10/css-color-names.git
|
||||
@@ -240,10 +235,9 @@ csstype 3.0.8
|
||||
dagre 0.8.5 MIT https://github.com/dagrejs/dagre.git
|
||||
date-fns 2.24.0 MIT https://github.com/date-fns/date-fns
|
||||
debug 4.3.1 MIT git://github.com/visionmedia/debug.git
|
||||
debug 2.6.9 MIT git://github.com/visionmedia/debug.git
|
||||
deep-diff 1.0.2 MIT git://github.com/flitbit/diff.git
|
||||
detect-indent 4.0.0 MIT https://github.com/sindresorhus/detect-indent.git
|
||||
diff-arrays-of-objects 1.1.8 MIT git+https://github.com/malcolmvr/diff-arrays-of-objects.git
|
||||
dom-align 1.12.2 MIT git@github.com:yiminghe/dom-align.git
|
||||
dom-helpers 5.2.0 MIT git+https://github.com/react-bootstrap/dom-helpers.git
|
||||
dom-serializer 1.3.2 MIT git://github.com/cheeriojs/dom-renderer.git
|
||||
dropzone 5.9.3 MIT https://github.com/dropzone/dropzone.git
|
||||
@@ -254,15 +248,12 @@ error-ex 1.3.2
|
||||
escalade 3.1.1 MIT https://github.com/lukeed/escalade.git
|
||||
escape-string-regexp 1.0.5 MIT https://github.com/sindresorhus/escape-string-regexp.git
|
||||
fast-deep-equal 3.1.3 MIT git+https://github.com/epoberezkin/fast-deep-equal.git
|
||||
fast-deep-equal 1.1.0 MIT git+https://github.com/epoberezkin/fast-deep-equal.git
|
||||
fast-json-stable-stringify 2.1.0 MIT git://github.com/epoberezkin/fast-json-stable-stringify.git
|
||||
filename-reserved-regex 2.0.0 MIT https://github.com/sindresorhus/filename-reserved-regex.git
|
||||
follow-redirects 1.14.7 MIT git@github.com:follow-redirects/follow-redirects.git
|
||||
follow-redirects 1.14.8 MIT git@github.com:follow-redirects/follow-redirects.git
|
||||
gensync 1.0.0-beta.2 MIT https://github.com/loganfsmyth/gensync.git
|
||||
globals 11.12.0 MIT https://github.com/sindresorhus/globals.git
|
||||
globals 9.18.0 MIT https://github.com/sindresorhus/globals.git
|
||||
graphlib 2.1.8 MIT https://github.com/dagrejs/graphlib.git
|
||||
has-ansi 2.0.0 MIT https://github.com/sindresorhus/has-ansi.git
|
||||
has-cors 1.1.0 MIT git://github.com/component/has-cors.git
|
||||
has-flag 3.0.0 MIT https://github.com/sindresorhus/has-flag.git
|
||||
has-flag 4.0.0 MIT https://github.com/sindresorhus/has-flag.git
|
||||
@@ -275,33 +266,29 @@ immutability-helper 3.1.1
|
||||
import-fresh 3.3.0 MIT https://github.com/sindresorhus/import-fresh.git
|
||||
imports-loader 2.0.0 MIT https://github.com/webpack-contrib/imports-loader.git
|
||||
insert-if 1.2.0 MIT git+https://github.com/chipto/insert-if.git
|
||||
invariant 2.2.4 MIT https://github.com/zertosh/invariant
|
||||
ip-address 7.1.0 MIT git://github.com/beaugunderson/ip-address.git
|
||||
is-absolute-url 3.0.3 MIT https://github.com/sindresorhus/is-absolute-url.git
|
||||
is-any-array 1.0.0 MIT https://github.com/cheminfo-js/is-any-array.git
|
||||
is-arrayish 0.2.1 MIT https://github.com/qix-/node-is-arrayish.git
|
||||
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
|
||||
is-in-browser 1.1.3 MIT git+https://github.com/tuxsudo/is-in-browser.git
|
||||
is-relative 1.0.0 MIT https://github.com/jonschlinkert/is-relative.git
|
||||
is-unc-path 1.0.0 MIT https://github.com/jonschlinkert/is-unc-path.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.13.0 MIT git://github.com/jquery/jquery-ui.git
|
||||
js-tokens 4.0.0 MIT https://github.com/lydell/js-tokens.git
|
||||
js-tokens 3.0.2 MIT https://github.com/lydell/js-tokens.git
|
||||
jsbn 1.1.0 MIT https://github.com/andyperlitch/jsbn.git
|
||||
jsesc 2.5.2 MIT https://github.com/mathiasbynens/jsesc.git
|
||||
jsesc 1.3.0 MIT https://github.com/mathiasbynens/jsesc.git
|
||||
json-bignumber 1.0.2 MIT git@github.com:wbuss/JSONBigNumber.git
|
||||
json-parse-even-better-errors 2.3.1 MIT https://github.com/npm/json-parse-even-better-errors
|
||||
json-schema-traverse 1.0.0 MIT git+https://github.com/epoberezkin/json-schema-traverse.git
|
||||
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
|
||||
jsoneditor-react 3.1.2 MIT git+https://github.com/vankop/jsoneditor-react.git
|
||||
jss 10.5.1 MIT https://github.com/cssinjs/jss
|
||||
jss-plugin-camel-case 10.5.1 MIT https://github.com/cssinjs/jss
|
||||
jss-plugin-default-unit 10.5.1 MIT https://github.com/cssinjs/jss
|
||||
@@ -333,7 +320,6 @@ moment 2.29.1
|
||||
moment 2.24.0 MIT https://github.com/moment/moment.git
|
||||
moment-timezone 0.5.33 MIT https://github.com/moment/moment-timezone.git
|
||||
ms 2.1.2 MIT https://github.com/zeit/ms.git
|
||||
ms 2.0.0 MIT https://github.com/zeit/ms.git
|
||||
nanocolors 0.1.12 MIT https://github.com/ai/nanocolors.git
|
||||
nanoid 3.2.0 MIT https://github.com/ai/nanoid.git
|
||||
nanopop 2.1.0 MIT https://github.com/Simonwep/nanopop.git
|
||||
@@ -390,6 +376,15 @@ postcss-value-parser 4.1.0
|
||||
prop-types 15.7.2 MIT https://github.com/facebook/prop-types.git
|
||||
punycode 2.1.1 MIT https://github.com/bestiejs/punycode.js.git
|
||||
raf 3.4.1 MIT git://github.com/chrisdickinson/raf.git
|
||||
rc-align 4.0.11 MIT git@github.com:react-component/align.git
|
||||
rc-dropdown 3.3.0 MIT git@github.com:react-component/dropdown.git
|
||||
rc-menu 9.3.2 MIT git@github.com:react-component/menu.git
|
||||
rc-motion 2.4.4 MIT git@github.com:react-component/motion.git
|
||||
rc-overflow 1.2.2 MIT git@github.com:react-component/overflow.git
|
||||
rc-resize-observer 1.2.0 MIT git@github.com:react-component/resize-observer.git
|
||||
rc-tabs 11.10.5 MIT git@github.com:react-component/tabs.git
|
||||
rc-trigger 5.2.10 MIT https://github.com/react-component/trigger.git
|
||||
rc-util 5.17.0 MIT git@github.com:react-component/util.git
|
||||
react 16.14.0 MIT git+https://github.com/facebook/react.git
|
||||
react 17.0.2 MIT https://github.com/facebook/react.git
|
||||
react-aspen 1.1.1 MIT https://github.com/neeksandhu/react-aspen.git
|
||||
@@ -402,11 +397,12 @@ react-is 16.13.1
|
||||
react-is 17.0.2 MIT https://github.com/facebook/react.git
|
||||
react-select 4.3.1 MIT https://github.com/JedWatson/react-select/tree/master/packages/react-select
|
||||
react-table 7.7.0 MIT https://github.com/tannerlinsley/react-table
|
||||
react-transition-state 1.1.3 MIT https://github.com/szhsin/react-transition-state.git
|
||||
react-virtualized-auto-sizer 1.0.6 MIT https://github.com/bvaughn/react-virtualized-auto-sizer.git
|
||||
react-window 1.8.6 MIT https://github.com/bvaughn/react-window.git
|
||||
regenerator-runtime 0.13.7 MIT https://github.com/facebook/regenerator/tree/master/packages/regenerator-runtime
|
||||
regenerator-runtime 0.11.1 MIT https://github.com/facebook/regenerator/tree/master/packages/regenerator-runtime
|
||||
repeating 2.0.1 MIT https://github.com/sindresorhus/repeating.git
|
||||
require-from-string 2.0.2 MIT https://github.com/floatdrop/require-from-string.git
|
||||
resize-observer-polyfill 1.5.1 MIT https://github.com/que-etc/resize-observer-polyfill.git
|
||||
resolve-from 4.0.0 MIT https://github.com/sindresorhus/resolve-from.git
|
||||
rgb-regex 1.0.1 MIT https://github.com/regexps/rgb-regex.git
|
||||
rgba-regex 1.0.0 MIT https://github.com/johnotander/rgba-regex.git
|
||||
@@ -415,7 +411,6 @@ safe-buffer 5.1.2
|
||||
scheduler 0.19.1 MIT https://github.com/facebook/react.git
|
||||
scheduler 0.20.2 MIT https://github.com/facebook/react.git
|
||||
schema-utils 3.0.0 MIT https://github.com/webpack/schema-utils.git
|
||||
schema-utils 0.3.0 MIT git+https://github.com/webpack-contrib/schema-utils.git
|
||||
schema-utils 1.0.0 MIT https://github.com/webpack-contrib/schema-utils
|
||||
select2 4.0.13 MIT git://github.com/select2/select2.git
|
||||
shallowequal 1.1.0 MIT https://github.com/dashed/shallowequal.git
|
||||
@@ -427,13 +422,11 @@ socket.io-parser 4.0.4
|
||||
source-list-map 1.1.2 MIT https://github.com/webpack/source-list-map.git
|
||||
split.js 1.6.4 MIT https://github.com/nathancahill/split
|
||||
stable 0.1.8 MIT https://github.com/Two-Screen/stable.git
|
||||
strip-ansi 3.0.1 MIT https://github.com/chalk/strip-ansi.git
|
||||
strip-comments 2.0.1 MIT https://github.com/jonschlinkert/strip-comments.git
|
||||
styled-components 5.2.1 MIT git+https://github.com/styled-components/styled-components.git
|
||||
stylehacks 5.0.1 MIT https://github.com/cssnano/cssnano.git
|
||||
stylis 4.0.10 MIT https://github.com/thysultan/stylis.js
|
||||
supports-color 5.5.0 MIT https://github.com/chalk/supports-color.git
|
||||
supports-color 2.0.0 MIT https://github.com/chalk/supports-color.git
|
||||
supports-color 7.2.0 MIT https://github.com/chalk/supports-color.git
|
||||
svgo 2.7.0 MIT git://github.com/svg/svgo.git
|
||||
symbol-observable 1.2.0 MIT https://github.com/blesh/symbol-observable.git
|
||||
@@ -444,8 +437,7 @@ tiny-emitter 2.1.0
|
||||
tiny-warning 1.0.3 MIT https://github.com/alexreardon/tiny-warning.git
|
||||
tippy.js 6.3.1 MIT git+https://github.com/atomiks/tippyjs.git
|
||||
to-fast-properties 2.0.0 MIT https://github.com/sindresorhus/to-fast-properties.git
|
||||
to-fast-properties 1.0.3 MIT https://github.com/sindresorhus/to-fast-properties.git
|
||||
trim-right 1.0.1 MIT https://github.com/sindresorhus/trim-right.git
|
||||
toggle-selection 1.0.6 MIT git+https://github.com/sudodoki/toggle-selection
|
||||
unc-path-regex 0.1.2 MIT https://github.com/regexhq/unc-path-regex.git
|
||||
underscore 1.13.1 MIT git://github.com/jashkenas/underscore.git
|
||||
uniqs 2.0.0 MIT git://github.com/fgnass/uniqs.git
|
||||
@@ -476,8 +468,8 @@ minimatch 3.0.4
|
||||
postcss-modules-extract-imports 3.0.0 ISC https://github.com/css-modules/postcss-modules-extract-imports.git
|
||||
postcss-modules-scope 3.0.0 ISC https://github.com/css-modules/postcss-modules-scope.git
|
||||
postcss-modules-values 4.0.0 ISC git+https://github.com/css-modules/postcss-modules-values.git
|
||||
react-timer-hook 3.0.5 ISC git+https://github.com/amrlabib/react-timer-hook.git
|
||||
semver 7.3.4 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
|
||||
@@ -485,9 +477,7 @@ yaml 1.10.2
|
||||
ace-builds 1.4.12 BSD-3-Clause https://github.com/ajaxorg/ace-builds.git
|
||||
hoist-non-react-statics 3.3.2 BSD-3-Clause git://github.com/mridgway/hoist-non-react-statics.git
|
||||
hyphenate-style-name 1.0.4 BSD-3-Clause git+ssh://git@github.com/rexxars/hyphenate-style-name.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
|
||||
@@ -502,15 +492,16 @@ indefinite-observable 2.0.1
|
||||
jmespath 0.15.0 Apache-2.0 git://github.com/jmespath/jmespath.js
|
||||
jsoneditor 9.5.6 Apache-2.0 https://github.com/josdejong/jsoneditor.git
|
||||
paths-js 0.4.11 Apache-2.0 https://github.com/andreaferretti/paths-js.git
|
||||
rc-dock 3.2.10 Apache-2.0 https://github.com/ticlo/rc-dock.git
|
||||
rc-new-window 0.1.13 Apache-2.0 https://github.com/ticlo/rc-new-window.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
|
||||
caniuse-lite 1.0.30001309 CC-BY-4.0 https://github.com/browserslist/caniuse-lite.git
|
||||
css-select 4.1.3 BSD-2-Clause git://github.com/fb55/css-select.git
|
||||
css-what 5.1.0 BSD-2-Clause https://github.com/fb55/css-what
|
||||
domelementtype 2.2.0 BSD-2-Clause git://github.com/fb55/domelementtype.git
|
||||
domhandler 4.2.0 BSD-2-Clause git://github.com/fb55/domhandler.git
|
||||
domutils 2.6.0 BSD-2-Clause git://github.com/fb55/domutils.git
|
||||
entities 2.2.0 BSD-2-Clause git://github.com/fb55/entities.git
|
||||
esutils 2.0.3 BSD-2-Clause http://github.com/estools/esutils.git
|
||||
leaflet 1.7.1 BSD-2-Clause git://github.com/Leaflet/Leaflet.git
|
||||
nth-check 2.0.1 BSD-2-Clause https://github.com/fb55/nth-check
|
||||
uri-js 4.4.1 BSD-2-Clause http://github.com/garycourt/uri-js
|
||||
@@ -523,5 +514,5 @@ precond 0.2.3
|
||||
webcabin-docker 2.2.5 MIT* Unknown
|
||||
tablesorter 2.31.3 (MIT OR GPL-2.0) git://github.com/Mottie/tablesorter.git
|
||||
|
||||
451 dependencies listed.
|
||||
439 dependencies listed.
|
||||
|
||||
@@ -211,7 +211,8 @@ REM Main build sequence Ends
|
||||
RD /Q /S "%WD%\web\pgadmin\static\js\generated\.cache" 1> nul 2>&1
|
||||
|
||||
ECHO Copying web directory...
|
||||
XCOPY /S /I /E /H /Y "%WD%\web" "%BUILDROOT%\web" > nul || EXIT /B 1
|
||||
ROBOCOPY /S "%WD%\web" "%BUILDROOT%\web" > nul
|
||||
CALL :CHECK_ROBOCOPY_ERROR
|
||||
|
||||
ECHO Installing javascript dependencies...
|
||||
CD "%BUILDROOT%\web"
|
||||
@@ -272,10 +273,15 @@ REM Main build sequence Ends
|
||||
REM YARN END
|
||||
|
||||
REM WGET
|
||||
FOR /f "tokens=2 delims='" %%i IN ('yarn info nw ^| findstr "latest: "') DO SET "NW_VERSION=%%i"
|
||||
:GET_NW
|
||||
wget https://dl.nwjs.io/v%NW_VERSION%/nwjs-v%NW_VERSION%-win-x64.zip -O "%TMPDIR%\nwjs-v%NW_VERSION%-win-x64.zip"
|
||||
IF %ERRORLEVEL% NEQ 0 GOTO GET_NW
|
||||
REM Comment out the below for loop as the latest version (0.62.0) having
|
||||
REM some problem, so for the time being hardcoded the version to 0.61.0
|
||||
REM FOR /f "tokens=2 delims='" %%i IN ('yarn info nw ^| findstr "latest: "') DO SET "NW_VERSION=%%i"
|
||||
REM :GET_NW
|
||||
REM wget https://dl.nwjs.io/v%NW_VERSION%/nwjs-v%NW_VERSION%-win-x64.zip -O "%TMPDIR%\nwjs-v%NW_VERSION%-win-x64.zip"
|
||||
REM IF %ERRORLEVEL% NEQ 0 GOTO GET_NW
|
||||
|
||||
SET "NW_VERSION=0.61.0"
|
||||
wget https://dl.nwjs.io/v%NW_VERSION%/nwjs-v%NW_VERSION%-win-x64.zip -O "%TMPDIR%\nwjs-v%NW_VERSION%-win-x64.zip"
|
||||
|
||||
tar -C "%TMPDIR%" -xvf "%TMPDIR%\nwjs-v%NW_VERSION%-win-x64.zip" || EXIT /B 1
|
||||
REM WGET END
|
||||
@@ -365,3 +371,6 @@ REM Main build sequence Ends
|
||||
|
||||
EXIT /B 1
|
||||
|
||||
:CHECK_ROBOCOPY_ERROR
|
||||
IF %ERRORLEVEL% GEQ 8 EXIT /B %ERRORLEVEL%
|
||||
EXIT /B 0
|
||||
@@ -0,0 +1,87 @@
|
||||
.. _cloud_deployment:
|
||||
|
||||
******************************
|
||||
`Cloud Deployment`:index:
|
||||
******************************
|
||||
|
||||
The PostgreSQL server can be deployed on the Amazon AWS cloud using this module.
|
||||
Currently only RDS is available, but in the future more cloud options will be available.
|
||||
|
||||
To launch the *Cloud Deployment...* tool, right click on the *Server Group* or *Server* of the tree control, and select
|
||||
*Deploy a Cloud Instance* from the *Register* menu.
|
||||
|
||||
.. image:: images/cloud_deployment_provider.png
|
||||
:alt: Cloud Deployment Provider
|
||||
:align: center
|
||||
|
||||
Once you launch the tool, the Amazon RDS is already selected as this is the only option currently available.
|
||||
Click on the *Next* button to proceed further.
|
||||
|
||||
.. image:: images/cloud_deployment_credentials.png
|
||||
:alt: Cloud Deployment Provider
|
||||
:align: center
|
||||
|
||||
In the Credentials dialog, provide the region in which you want to deploy the instance along with
|
||||
*AWS access key* and *AWS secret access key*.
|
||||
Provide *AWS session token* only if your AWS session is temporary.
|
||||
|
||||
To proceed further, click on the next button. Before going further, pgAdmin will validate your credentials.
|
||||
|
||||
.. image:: images/cloud_deployment_instance.png
|
||||
:alt: Cloud Deployment Provider
|
||||
:align: center
|
||||
|
||||
Use the fields from the Instance Specification tab to specify the Instance details.
|
||||
|
||||
* Use the *Instance name* field to add an instance name for the PostgreSQL server; the name
|
||||
specified will be displayed in the *Browser* tree control too.
|
||||
|
||||
* Use the *Public IP* field to specify the IP Address range for permitting the inbound traffic.
|
||||
|
||||
* Use the *Database version* field to specify the PostgreSQL version to deploy.
|
||||
|
||||
* Use the *Instance class* field to allocate the computational, network, and memory capacity required by planned workload of this DB instance.
|
||||
|
||||
* Use the *Instance type* field to select the instance type.
|
||||
|
||||
* Use the *Storage type* field to select the instance storage type. Three options are available. General Purpose (SSD) storage, Provisioned IOPS (SSD) and Magnetic storage.
|
||||
|
||||
* Use the *Allocated storage* field to specify the storage capacity in GiB.
|
||||
|
||||
* Use the *Provisioned IOPS* in case of Provisioned IOPS (SSD) storage type.
|
||||
|
||||
.. image:: images/cloud_deployment_database.png
|
||||
:alt: Cloud Deployment Provider
|
||||
:align: center
|
||||
|
||||
Use the fields from the Database Details tab to specify the Instance details.
|
||||
|
||||
* Use the drop-down list box in the *Server group* field to select the parent
|
||||
node for the server; the server will be displayed in the *Browser* tree
|
||||
control within the specified group.
|
||||
|
||||
* Use the *Database name* field to add the database name for the PostgreSQL server.
|
||||
|
||||
* Use the *Username* field to specify the name of a role that will be used when
|
||||
authenticating with the server.
|
||||
|
||||
* Use the *Password* field to provide a password that will be supplied when
|
||||
authenticating with the server.
|
||||
|
||||
* Use the *Confirm password* field to repeat the password.
|
||||
|
||||
* Enter the listener port number of the server host in the *Port* field.
|
||||
|
||||
.. image:: images/cloud_deployment_review.png
|
||||
:alt: Cloud Deployment Provider
|
||||
:align: center
|
||||
|
||||
At the end, review the Instance details that you provided. Click on Finish button to deploy the instance on Amazon RDS.
|
||||
|
||||
.. image:: images/cloud_deployment_tree.png
|
||||
:alt: Cloud Deployment Provider
|
||||
:align: center
|
||||
|
||||
Once you click on the finish, one background process will start which will deploy the instance in the cloud
|
||||
and monitor the progress of the deployment. The Server will be added to the tree with the cloud uploading icon.
|
||||
Once the deployment is done, the necessary server details will be updated.
|
||||
@@ -18,7 +18,7 @@ control, and select *Server Group* from the *Create* menu.
|
||||
Use the fields on the *Server* dialog to define the connection properties for
|
||||
each new server that you wish to manage with pgAdmin. To open the *Server*
|
||||
dialog, right-click on the *Servers* node of the tree control, and select
|
||||
*Server* from the *Create* menu.
|
||||
*Server* from the *Register* menu.
|
||||
|
||||
.. toctree::
|
||||
|
||||
|
||||
@@ -17,3 +17,4 @@ will provide detailed information about creating a custom deployment.
|
||||
desktop_deployment
|
||||
server_deployment
|
||||
container_deployment
|
||||
cloud_deployment
|
||||
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 83 KiB |
@@ -11,6 +11,8 @@ notes for it.
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
release_notes_6_7
|
||||
release_notes_6_6
|
||||
release_notes_6_5
|
||||
release_notes_6_4
|
||||
release_notes_6_3
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Version 6.4
|
||||
************
|
||||
|
||||
Release date: 2021-01-13
|
||||
Release date: 2022-01-13
|
||||
|
||||
This release contains a number of bug fixes and new features since the release of pgAdmin4 6.3.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Version 6.5
|
||||
************
|
||||
|
||||
Release date: 2021-02-11
|
||||
Release date: 2022-02-11
|
||||
|
||||
This release contains a number of bug fixes and new features since the release of pgAdmin4 6.4.
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
************
|
||||
Version 6.6
|
||||
************
|
||||
|
||||
Release date: 2022-03-10
|
||||
|
||||
This release contains a number of bug fixes and new features since the release of pgAdmin4 6.5.
|
||||
|
||||
New features
|
||||
************
|
||||
|
||||
| `Issue #7177 <https://redmine.postgresql.org/issues/7177>`_ - Added capability to deploy PostgreSQL servers on Amazon RDS.
|
||||
|
||||
Housekeeping
|
||||
************
|
||||
|
||||
| `Issue #7180 <https://redmine.postgresql.org/issues/7180>`_ - Rename the menu 'Disconnect Database' to 'Disconnect from database'.
|
||||
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Issue #6956 <https://redmine.postgresql.org/issues/6956>`_ - Fixed a schema diff issue in which user mappings were not compared correctly.
|
||||
| `Issue #6991 <https://redmine.postgresql.org/issues/6991>`_ - Fixed an issue where pgadmin cannot connect to LDAP when STARTTLS is required before bind.
|
||||
| `Issue #6999 <https://redmine.postgresql.org/issues/6999>`_ - Fixed an issue where a warning is flashed every time for an email address when authentication sources are internal and ldap.
|
||||
| `Issue #7105 <https://redmine.postgresql.org/issues/7105>`_ - Fixed an issue where the parent partition table was not displayed during autocomplete.
|
||||
| `Issue #7124 <https://redmine.postgresql.org/issues/7124>`_ - Fixed the schema diff issue where tables have different column positions and a column has a default value.
|
||||
| `Issue #7152 <https://redmine.postgresql.org/issues/7152>`_ - Added comments column for the functions collection node.
|
||||
| `Issue #7172 <https://redmine.postgresql.org/issues/7172>`_ - Allow users to scroll and enter input when there is a validation error.
|
||||
| `Issue #7173 <https://redmine.postgresql.org/issues/7173>`_ - Fixed an issue where the User Management dialog is not opening.
|
||||
| `Issue #7181 <https://redmine.postgresql.org/issues/7181>`_ - Ensure that the user should be able to add new server with unix socket connection.
|
||||
| `Issue #7186 <https://redmine.postgresql.org/issues/7186>`_ - Fixes an issue where the connect server/database menu was not updated correctly.
|
||||
| `Issue #7202 <https://redmine.postgresql.org/issues/7202>`_ - Ensure that Flask-Security-Too is using the latest version.
|
||||
@@ -0,0 +1,26 @@
|
||||
************
|
||||
Version 6.7
|
||||
************
|
||||
|
||||
Release date: 2022-03-14
|
||||
|
||||
This release contains a number of bug fixes and new features since the release of pgAdmin4 6.6.
|
||||
|
||||
.. note:: **Security Release**
|
||||
|
||||
Please note that this release includes a security update to fix an issue
|
||||
where a user could upload files to directories outside of their storage directory, when using pgAdmin
|
||||
running in server mode.
|
||||
|
||||
Users running pgAdmin in server mode, including the standard container based distribution, should upgrade
|
||||
to this release as soon as possible.
|
||||
|
||||
This issue does not affect users running in desktop mode.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Issue #7220 <https://redmine.postgresql.org/issues/7220>`_ - Fixed a schema diff issue where difference SQL isn't generated when foreign key values for a table differ.
|
||||
| `Issue #7228 <https://redmine.postgresql.org/issues/7228>`_ - Fixed a schema diff issue where string separator '_$PGADMIN$_' is visible for identical user mappings.
|
||||
| `Issue #7230 <https://redmine.postgresql.org/issues/7230>`_ - Fixed an issue where pgAdmin 4 took ~75 seconds to display the 'Starting pgAdmin' text on the splash screen.
|
||||
| `Issue #7233 <https://redmine.postgresql.org/issues/7233>`_ - Ensure that upload paths are children of the storage directory.
|
||||
@@ -10,6 +10,9 @@ OS_VERSION=$(cat /etc/os-release | grep "^VERSION_ID=" | awk -F "=" '{ print $2
|
||||
OS_NAME=$(cat /etc/os-release | grep "^ID=" | awk -F "=" '{ print $2 }' | sed 's/"//g')
|
||||
OS_ARCH=$(dpkg-architecture -qDEB_HOST_ARCH)
|
||||
|
||||
# Stop creating pyc files.
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# Common Linux build functions
|
||||
source pkg/linux/build-functions.sh
|
||||
|
||||
@@ -35,6 +38,7 @@ Package: ${APP_NAME}-server
|
||||
Version: ${APP_LONG_VERSION}
|
||||
Architecture: ${OS_ARCH}
|
||||
Section: database
|
||||
Priority: optional
|
||||
Depends: python3, libpq5 (>= 11.0), libgssapi-krb5-2
|
||||
Recommends: postgresql-client | postgresql-client-14 | postgresql-client-13 | postgresql-client-12 | postgresql-client-11 | postgresql-client-10
|
||||
Maintainer: pgAdmin Development Team <pgadmin-hackers@postgresql.org>
|
||||
@@ -57,6 +61,7 @@ Package: ${APP_NAME}-desktop
|
||||
Version: ${APP_LONG_VERSION}
|
||||
Architecture: ${OS_ARCH}
|
||||
Section: database
|
||||
Priority: optional
|
||||
Depends: ${APP_NAME}-server (= ${APP_LONG_VERSION}), libatomic1, xdg-utils
|
||||
Maintainer: pgAdmin Development Team <pgadmin-hackers@postgresql.org>
|
||||
Description: The desktop user interface for pgAdmin. pgAdmin is the most popular and feature rich Open Source administration and development platform for PostgreSQL, the most advanced Open Source database in the world.
|
||||
@@ -82,6 +87,7 @@ Package: ${APP_NAME}-web
|
||||
Version: ${APP_LONG_VERSION}
|
||||
Architecture: all
|
||||
Section: database
|
||||
Priority: optional
|
||||
Depends: ${APP_NAME}-server (= ${APP_LONG_VERSION}), apache2, libapache2-mod-wsgi-py3
|
||||
Maintainer: pgAdmin Development Team <pgadmin-hackers@postgresql.org>
|
||||
Description: The web interface for pgAdmin, hosted under Apache HTTPD. pgAdmin is the most popular and feature rich Open Source administration and development platform for PostgreSQL, the most advanced Open Source database in the world.
|
||||
@@ -106,6 +112,7 @@ Package: ${APP_NAME}
|
||||
Version: ${APP_LONG_VERSION}
|
||||
Architecture: all
|
||||
Section: database
|
||||
Priority: optional
|
||||
Depends: ${APP_NAME}-server (= ${APP_LONG_VERSION}), ${APP_NAME}-desktop (= ${APP_LONG_VERSION}), ${APP_NAME}-web (= ${APP_LONG_VERSION})
|
||||
Maintainer: pgAdmin Development Team <pgadmin-hackers@postgresql.org>
|
||||
Description: Installs all required components to run pgAdmin in desktop and web modes. pgAdmin is the most popular and feature rich Open Source administration and development platform for PostgreSQL, the most advanced Open Source database in the world.
|
||||
|
||||
@@ -65,6 +65,9 @@ _create_python_virtualenv() {
|
||||
sed -i 's/setenv VIRTUAL_ENV .*/setenv VIRTUAL_ENV "\/usr\/pgadmin4\/venv"/g' venv/bin/activate.csh
|
||||
sed -i 's/set -gx VIRTUAL_ENV .*/set -gx VIRTUAL_ENV "\/usr\/pgadmin4\/venv"/g' venv/bin/activate.fish
|
||||
|
||||
# Remove __pycache__
|
||||
find . -name "__pycache__" -type d -print0 | xargs -0 rm -rf
|
||||
|
||||
# Fixup hash bangs
|
||||
sed -i 's/#!.*\/python3/#\!\/usr\/pgadmin4\/venv\/bin\/python3/g' venv/bin/*
|
||||
|
||||
@@ -125,7 +128,10 @@ _build_runtime() {
|
||||
# YARN END
|
||||
|
||||
# WGET:
|
||||
NW_VERSION=$(yarn info nw | grep latest | awk -F "'" '{ print $2}')
|
||||
# Comment out the below line as the latest version (0.62.0) having some
|
||||
# problem, so for the time being hardcoded the version to 0.61.0
|
||||
# NW_VERSION=$(yarn info nw | grep latest | awk -F "'" '{ print $2}')
|
||||
NW_VERSION="0.61.0"
|
||||
|
||||
pushd "${BUILDROOT}" > /dev/null
|
||||
while true;do
|
||||
|
||||
@@ -33,7 +33,10 @@ _build_runtime() {
|
||||
# YARN END
|
||||
|
||||
# WGET:
|
||||
NW_VERSION=$(yarn info nw | grep latest | awk -F "'" '{ print $2}')
|
||||
# Comment out the below line as the latest version (0.62.0) having some
|
||||
# problem, so for the time being hardcoded the version to 0.61.0
|
||||
# NW_VERSION=$(yarn info nw | grep latest | awk -F "'" '{ print $2}')
|
||||
NW_VERSION="0.61.0"
|
||||
|
||||
pushd "${BUILD_ROOT}" > /dev/null
|
||||
while true;do
|
||||
|
||||
@@ -5,8 +5,10 @@ Redhat distributions.
|
||||
|
||||
## Supported platforms
|
||||
|
||||
* Fedora 30, 31 & 32
|
||||
* RHEL/CentOS 7 & 8
|
||||
* Fedora 34 & 35
|
||||
* RHEL 7 & 8
|
||||
* CentOS 7
|
||||
* Rocky Linux 8 (x86_64)
|
||||
|
||||
## Build configuration
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ OS_ARCH=$(arch)
|
||||
# Make sure we get the right libpq
|
||||
export PATH=/usr/pgsql-14/bin:$PATH
|
||||
|
||||
# Stop creating pyc files.
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# Common Linux build functions
|
||||
source pkg/linux/build-functions.sh
|
||||
|
||||
@@ -43,6 +46,10 @@ cat << EOF > "${BUILDROOT}/server.spec"
|
||||
%global __provides_exclude_from ^/.*$
|
||||
%global _build_id_links none
|
||||
|
||||
# Disable RPATH checking, as it will fail with some of the paths in the Python
|
||||
# virtualenv, in particular Pillow.libs.
|
||||
%global __brp_check_rpaths %{nil}
|
||||
|
||||
# Bytecompiling Python 3 doesn't work on RHEL/CentOS 7, so make it a no-op
|
||||
%if 0%{?rhel} && 0%{?rhel} == 7
|
||||
%define __python /bin/true
|
||||
|
||||
@@ -18,7 +18,7 @@ Flask-WTF==1.*
|
||||
Flask-Compress==1.*
|
||||
Flask-Paranoid==0.*
|
||||
Flask-Babel==2.*
|
||||
Flask-Security-Too==4.*
|
||||
Flask-Security-Too==4.1.*
|
||||
Flask-SocketIO>=5.0.1
|
||||
WTForms==3.*
|
||||
passlib==1.*
|
||||
@@ -44,4 +44,8 @@ Authlib==0.15.*
|
||||
requests==2.25.*
|
||||
pyotp==2.*
|
||||
qrcode==7.*
|
||||
Pillow==8.3.*
|
||||
Pillow==8.3.*; python_version <= '3.6'
|
||||
Pillow==9.*; python_version >= '3.7'
|
||||
boto3==1.20.*
|
||||
botocore==1.23.*
|
||||
urllib3==1.26.*
|
||||
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 130"><defs><style>.cls-1{stroke:#000;stroke-width:10.19px;}.cls-2{fill:#336791;}.cls-3,.cls-4,.cls-9{fill:none;}.cls-3,.cls-4,.cls-5,.cls-6{stroke:#fff;}.cls-3,.cls-4{stroke-linecap:round;stroke-width:3.4px;}.cls-3{stroke-linejoin:round;}.cls-4{stroke-linejoin:bevel;}.cls-5,.cls-6{fill:#fff;}.cls-5{stroke-width:1.13px;}.cls-6{stroke-width:0.57px;}.cls-7{fill:#2775b6;}.cls-8{fill:#333;}.cls-9{stroke:#333;stroke-width:3px;}</style></defs><title>pgAdmin_PostgreSQL</title><g id="Layer_1" data-name="Layer 1"><g id="Layer_3"><path class="cls-1" d="M95.59,93.65c.77-6.44.54-7.38,5.33-6.34l1.21.11a27.6,27.6,0,0,0,11.34-1.91c6.09-2.83,9.71-7.55,3.7-6.31-13.71,2.83-14.65-1.81-14.65-1.81C117,55.91,123,28.64,117.82,22,103.57,3.76,78.91,12.37,78.5,12.6l-.13,0a48.65,48.65,0,0,0-9.15-.95C63,11.57,58.31,13.29,54.74,16c0,0-44-18.12-41.95,22.8.44,8.7,12.48,65.86,26.84,48.6C44.88,81.08,50,75.75,50,75.75A13.39,13.39,0,0,0,58.65,78l.25-.21a9,9,0,0,0,.1,2.46c-3.7,4.13-2.62,4.86-10,6.38s-3.09,4.29-.22,5c3.48.87,11.53,2.1,17-5.52l-.22.87c1.46,1.16,1.36,8.35,1.56,13.48s.55,9.93,1.6,12.75,2.28,10.1,12,8C88.81,119.46,95,117,95.59,93.65"/><path class="cls-2" d="M117.17,79.2c-13.71,2.83-14.65-1.81-14.65-1.81C117,55.91,123,28.64,117.82,22,103.57,3.76,78.91,12.37,78.5,12.6l-.13,0a48.65,48.65,0,0,0-9.15-.95C63,11.57,58.31,13.29,54.74,16c0,0-44-18.12-41.95,22.8.44,8.7,12.48,65.86,26.84,48.6C44.88,81.08,50,75.75,50,75.75A13.39,13.39,0,0,0,58.65,78l.25-.21A9.41,9.41,0,0,0,59,80.22c-3.7,4.13-2.61,4.86-10,6.38s-3.08,4.29-.21,5c3.48.87,11.53,2.1,17-5.52l-.22.87c1.45,1.16,2.47,7.56,2.3,13.35s-.28,9.77.86,12.88,2.28,10.1,12,8C88.81,119.46,93,115,93.6,107.42,94,102.07,95,102.87,95,98.08l.75-2.26c.87-7.26.14-9.6,5.15-8.51l1.21.11a27.6,27.6,0,0,0,11.34-1.91c6.09-2.83,9.71-7.55,3.7-6.31Z"/><path class="cls-3" d="M66.33,83.36c-.38,13.5.09,27.09,1.41,30.39s4.15,9.73,13.88,7.64c8.12-1.74,11.08-5.11,12.36-12.55.94-5.47,2.77-20.67,3-23.79"/><path class="cls-3" d="M54.67,15.7s-44-18-42,22.93c.44,8.7,12.48,65.87,26.84,48.6,5.25-6.32,10-11.27,10-11.27"/><path class="cls-3" d="M78.45,12.42c-1.52.47,24.49-9.51,39.28,9.38,5.22,6.67-.83,33.94-15.31,55.42"/><path class="cls-4" d="M102.42,77.22s.94,4.64,14.65,1.81c6-1.24,2.4,3.48-3.7,6.31-5,2.32-16.21,2.92-16.39-.29-.47-8.27,5.9-5.76,5.44-7.83-.42-1.87-3.26-3.7-5.15-8.27-1.64-4-22.57-34.58,5.8-30,1-.22-7.4-27-33.95-27.42S43.45,44.14,43.45,44.14"/><path class="cls-3" d="M58.9,80.05c-3.7,4.13-2.61,4.86-10,6.38s-3.09,4.29-.22,5c3.48.87,11.53,2.1,17-5.52,1.66-2.32,0-6-2.28-7-1.1-.46-2.57-1-4.46,1.09Z"/><path class="cls-3" d="M58.66,80c-.38-2.44.79-5.33,2.05-8.71C62.6,66.19,67,61.11,63.47,45c-2.6-12-20-2.5-20-.87a81.48,81.48,0,0,1-.29,16c-1.41,10.06,6.4,18.57,15.39,17.7"/><path class="cls-5" d="M54.51,43.9c-.08.55,1,2,2.45,2.23a2.62,2.62,0,0,0,2.72-1.51c.08-.56-1-1.17-2.44-1.37s-2.65.09-2.73.65Z"/><path class="cls-6" d="M98,42.76c.07.56-1,2-2.45,2.24a2.64,2.64,0,0,1-2.73-1.52c-.07-.55,1-1.16,2.45-1.36s2.65.09,2.73.64Z"/><path class="cls-3" d="M103.07,38.92c.24,4.36-.94,7.33-1.08,12-.22,6.74,3.21,14.46-2,22.19"/></g><path class="cls-7" d="M154.72,28.15h5.16v4.16A12.84,12.84,0,0,1,163.35,29a11.17,11.17,0,0,1,6.28-1.76,11.84,11.84,0,0,1,9.08,4.09c2.48,2.72,3.73,6.62,3.73,11.67q0,10.26-5.38,14.65a12.2,12.2,0,0,1-7.95,2.79,10.78,10.78,0,0,1-6-1.56,13.55,13.55,0,0,1-3.14-3v16h-5.28Zm19.84,24.6Q177,49.65,177,43.5a17,17,0,0,0-1.09-6.44,7.51,7.51,0,0,0-7.53-5.19q-5.49,0-7.52,5.48a21.49,21.49,0,0,0-1.09,7.44A15.64,15.64,0,0,0,160.88,51a8,8,0,0,0,13.68,1.78Z"/><path class="cls-7" d="M206,29.26a14.6,14.6,0,0,1,3,3V28.3h4.86V56.83c0,4-.58,7.13-1.75,9.44q-3.27,6.38-12.35,6.38a15.07,15.07,0,0,1-8.5-2.27,8.86,8.86,0,0,1-3.85-7.1h5.36a6,6,0,0,0,1.52,3.25q1.77,1.75,5.59,1.76,6,0,7.9-4.28,1.1-2.52,1-9a10.39,10.39,0,0,1-3.8,3.57,13.56,13.56,0,0,1-14.75-2.45q-3.81-3.62-3.81-12,0-7.89,3.84-12.31a11.85,11.85,0,0,1,9.27-4.42A11.37,11.37,0,0,1,206,29.26Zm.64,5.66a7.61,7.61,0,0,0-6.09-2.81A7.52,7.52,0,0,0,193,37.32a20.56,20.56,0,0,0-1.08,7.3c0,3.53.72,6.22,2.14,8.07a6.93,6.93,0,0,0,5.76,2.77,8.09,8.09,0,0,0,8-5.13A16.72,16.72,0,0,0,209,43.56Q209,37.73,206.62,34.92Z"/><path class="cls-7" d="M235.16,16.34h6.58l15.62,43H251l-4.5-12.89H229.6l-4.67,12.89h-6Zm9.67,25.4-6.63-19-6.88,19Z"/><path class="cls-7" d="M279.16,29a14.3,14.3,0,0,1,3.18,3.08V16.2h5.07V59.38h-4.75V55a11.33,11.33,0,0,1-4.35,4.19,12.51,12.51,0,0,1-5.75,1.28,11.61,11.61,0,0,1-9-4.4q-3.82-4.41-3.83-11.74a20.35,20.35,0,0,1,3.49-11.88,11.41,11.41,0,0,1,10-5A11.15,11.15,0,0,1,279.16,29ZM267.39,52.5q2.13,3.39,6.82,3.39a7.17,7.17,0,0,0,6-3.14c1.56-2.1,2.35-5.12,2.35-9s-.81-6.9-2.42-8.81a7.56,7.56,0,0,0-6-2.85,7.88,7.88,0,0,0-6.43,3c-1.64,2-2.46,5-2.46,9A15.62,15.62,0,0,0,267.39,52.5Z"/><path class="cls-7" d="M295.29,28h5.21v4.46a17.4,17.4,0,0,1,3.4-3.37,10.24,10.24,0,0,1,5.92-1.79,9.34,9.34,0,0,1,6,1.85,9.61,9.61,0,0,1,2.34,3.1,11.37,11.37,0,0,1,4.13-3.73,11.52,11.52,0,0,1,5.33-1.22q6.33,0,8.62,4.57a15,15,0,0,1,1.23,6.62V59.38H332V3Line truncated
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 768 668"><defs><style>.cls-1{fill:#e9f8ff;opacity:0.9;}.cls-2{fill:url(#linear-gradient);}.cls-3{fill:url(#linear-gradient-2);}.cls-4{fill:url(#linear-gradient-3);}.cls-5{fill:url(#linear-gradient-4);}.cls-6{fill:url(#linear-gradient-5);}.cls-7{fill:url(#linear-gradient-6);}.cls-8{fill:url(#linear-gradient-7);}.cls-9{fill:url(#linear-gradient-8);}.cls-10{fill:url(#linear-gradient-9);}.cls-11{fill:url(#linear-gradient-10);}.cls-12{fill:url(#linear-gradient-11);}.cls-13{fill:url(#linear-gradient-12);}.cls-14{fill:url(#linear-gradient-13);}.cls-15{stroke:#000;stroke-width:5.11px;}.cls-16{fill:#336791;}.cls-17,.cls-18{fill:none;stroke-linecap:round;stroke-width:1.7px;}.cls-17,.cls-18,.cls-19,.cls-20{stroke:#fff;}.cls-17{stroke-linejoin:round;}.cls-18{stroke-linejoin:bevel;}.cls-19,.cls-20,.cls-21,.cls-23{fill:#fff;}.cls-19{stroke-width:0.57px;}.cls-20{stroke-width:0.29px;}.cls-21{font-size:35px;}.cls-21,.cls-23{font-family:Helvetica-Light, Helvetica;font-weight:300;}.cls-22{font-family:Helvetica-Bold, Helvetica;font-weight:700;}.cls-23{font-size:16px;}</style><linearGradient id="linear-gradient" x1="206.44" y1="286.69" x2="130.27" y2="158.12" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#4a519d"/><stop offset="1" stop-color="#643e99"/></linearGradient><linearGradient id="linear-gradient-2" x1="507.22" y1="289.91" x2="430.57" y2="160.52" xlink:href="#linear-gradient"/><linearGradient id="linear-gradient-3" x1="505.78" y1="463.51" x2="655.22" y2="463.51" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#5a96bd"/><stop offset="1" stop-color="#5997bd"/></linearGradient><linearGradient id="linear-gradient-4" x1="282.24" y1="332.89" x2="360.13" y2="204.7" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#47539d"/><stop offset="1" stop-color="#534b9b"/></linearGradient><linearGradient id="linear-gradient-5" x1="285.87" y1="154.1" x2="210.32" y2="283.03" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#7c5da9"/><stop offset="1" stop-color="#686cac"/></linearGradient><linearGradient id="linear-gradient-6" x1="733.76" y1="142.12" x2="658.21" y2="271.05" gradientTransform="translate(1310.01 397) rotate(180)" xlink:href="#linear-gradient-5"/><linearGradient id="linear-gradient-7" x1="655.22" y1="345.3" x2="710.53" y2="345.3" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#4e5ea3"/><stop offset="1" stop-color="#4e5ea3"/></linearGradient><linearGradient id="linear-gradient-8" x1="91.81" y1="377.04" x2="147.13" y2="377.04" xlink:href="#linear-gradient-7"/><linearGradient id="linear-gradient-9" x1="206.9" y1="377.04" x2="356.34" y2="377.04" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#3e6ba6"/><stop offset="1" stop-color="#3d6ba6"/></linearGradient><linearGradient id="linear-gradient-10" x1="356.34" y1="377.04" x2="505.78" y2="377.04" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#4773ab"/><stop offset="1" stop-color="#4a71ab"/></linearGradient><linearGradient id="linear-gradient-11" x1="505.78" y1="377.04" x2="655.22" y2="377.04" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#396ea7"/><stop offset="1" stop-color="#3e6aa6"/></linearGradient><linearGradient id="linear-gradient-12" x1="356.34" y1="290.76" x2="505.78" y2="290.76" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#535ba3"/><stop offset="1" stop-color="#535ca3"/></linearGradient><linearGradient id="linear-gradient-13" x1="356.34" y1="463.32" x2="505.78" y2="463.32" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1a73a6"/><stop offset="1" stop-color="#1e71a5"/></linearGradient></defs><g id="Layer_2" data-name="Layer 2"><rect class="cls-1" width="768" height="668" rx="10"/></g><g id="Layer_1" data-name="Layer 1"><polygon class="cls-2" points="206.9 204.49 206.9 290.76 132.19 247.63 57.47 204.49 132.19 161.35 206.9 118.21 206.9 204.49"/><polygon class="cls-3" points="505.78 204.49 505.78 290.76 431.06 247.63 356.34 204.49 431.06 161.35 505.78 118.21 505.78 204.49"/><polygon class="cls-4" points="655.22 463.51 655.22 549.79 580.5 506.65 505.78 463.51 580.5 420.37 655.22 377.24 655.22 463.51"/><polygon class="cls-5" points="356.34 290.76 356.34 377.04 281.62 333.9 206.9 290.76 281.62 247.63 356.34 204.49 356.34 290.76"/><polygon class="cls-6" points="206.9 204.49 206.9 118.21 281.62 161.35 356.34 204.49 281.62 247.63 206.9 290.76 206.9 204.49"/><polygon class="cls-7" points="655.22 204.49 655.22 290.76 580.5 247.63 505.78 204.49 580.5 161.35 655.22 118.21 655.22 204.49"/><polygon class="cls-8" points="655.22 345.3 655.22 313.36 682.88 329.33 710.53 345.3 682.88 361.27 655.22 377.24 655.22 345.3"/><polygon class="cls-9" points="91.81 377.04 91.81 345.11 119.47 361.08 147.13 377.04 119.47 393.01 91.81 408.98 91.81 377.04"/><polygon class="cls-10" points="206.9 377.04 206.9 290.76 281.62 333.9 356.34 377.04 281.62 420.18 206.9 463.32 206.9 377.04"/><pLine truncated
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 8.0 KiB |
@@ -8,8 +8,8 @@
|
||||
"chromium-args": "--disable-popup-blocking --disable-gpu",
|
||||
"user-agent": "Nwjs:%nwver-%osinfo-%chromium_ver",
|
||||
"window": {
|
||||
"width": 440,
|
||||
"height": 170,
|
||||
"width": 750,
|
||||
"height": 600,
|
||||
"toolbar": false,
|
||||
"fullscreen": false,
|
||||
"frame": false,
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^6.3.0",
|
||||
"nw": "^0.59.0"
|
||||
"nw": "0.61.0"
|
||||
},
|
||||
"scripts": {
|
||||
"linter": "yarn eslint --no-eslintrc -c .eslintrc.js --ext .js ."
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
font-size: 14px;
|
||||
margin: 0 10px;
|
||||
background-color: #fff;
|
||||
background-color: #e9f8ff;
|
||||
}
|
||||
|
||||
.bg {
|
||||
|
||||
@@ -1008,10 +1008,10 @@ number-is-nan@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
|
||||
integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
|
||||
|
||||
nw@^0.59.0:
|
||||
version "0.59.0"
|
||||
resolved "https://registry.yarnpkg.com/nw/-/nw-0.59.0.tgz#c7eff1622c917834b2b31581ba5c386816d98db1"
|
||||
integrity sha512-cLGZgP+u+nDfgT956Z7timXuyQN8OOi5Ele6OF3yVEkk6CNiMC6m2rblb5aB8y4QNwLf6O+tkt64XxSUPxvutQ==
|
||||
nw@0.61.0:
|
||||
version "0.61.0"
|
||||
resolved "https://registry.yarnpkg.com/nw/-/nw-0.61.0.tgz#c35e80be4ad7a5570659796d66bdc16d11e0e890"
|
||||
integrity sha512-SazcPW9+kplf7/K2KuTaamOFGtKnnH4Gs/cHHRMf9luTkCGe2cFsnb6/fEP5xvu9tpoW9Z/oUKzj9lZ6kylFWQ==
|
||||
dependencies:
|
||||
chalk "~1.1.3"
|
||||
decompress "^4.2.0"
|
||||
|
||||
@@ -43,7 +43,7 @@ APP_ICON = 'pg-icon'
|
||||
|
||||
# Application version number components
|
||||
APP_RELEASE = 6
|
||||
APP_REVISION = 5
|
||||
APP_REVISION = 7
|
||||
|
||||
# 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 = 60500
|
||||
APP_VERSION_INT = 60700
|
||||
|
||||
# DO NOT CHANGE!
|
||||
# The application version string, constructed from the components
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
"""Added server id for cloud deployment
|
||||
|
||||
Revision ID: 1586db67b98e
|
||||
Revises: 15c88f765bc8
|
||||
Create Date: 2022-01-04 13:08:05.484598
|
||||
|
||||
"""
|
||||
from pgadmin.model import db
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '1586db67b98e'
|
||||
down_revision = '15c88f765bc8'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
db.engine.execute(
|
||||
'ALTER TABLE process ADD COLUMN server_id INTEGER DEFAULT 0'
|
||||
)
|
||||
db.engine.execute(
|
||||
'ALTER TABLE server ADD COLUMN cloud_status INTEGER DEFAULT 0'
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
# pgAdmin only upgrades, downgrade not implemented.
|
||||
pass
|
||||
@@ -11,6 +11,7 @@
|
||||
"@babel/eslint-parser": "^7.12.13",
|
||||
"@babel/eslint-plugin": "^7.12.13",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.10.1",
|
||||
"@babel/plugin-syntax-jsx": "^7.16.0",
|
||||
"@babel/preset-env": "^7.10.2",
|
||||
"@babel/preset-typescript": "^7.8.3",
|
||||
"@emotion/core": "^10.0.14",
|
||||
@@ -18,6 +19,7 @@
|
||||
"@emotion/react": "^11.1.5",
|
||||
"@emotion/styled": "^10.0.14",
|
||||
"@emotion/utils": "^1.0.0",
|
||||
"@svgr/webpack": "^5.5.0",
|
||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.4.1",
|
||||
"autoprefixer": "^10.2.4",
|
||||
"axios-mock-adapter": "^1.17.0",
|
||||
@@ -40,9 +42,10 @@
|
||||
"imagemin-pngquant": "^9.0.1",
|
||||
"imagemin-svgo": "^8.0.0",
|
||||
"is-docker": "^2.1.1",
|
||||
"istanbul-instrumenter-loader": "^3.0.1",
|
||||
"jasmine-core": "^3.6.0",
|
||||
"jasmine-enzyme": "^7.1.2",
|
||||
"karma": "^6.3.2",
|
||||
"karma": "^6.3.15",
|
||||
"karma-babel-preprocessor": "^8.0.0",
|
||||
"karma-browserify": "^8.0.0",
|
||||
"karma-chrome-launcher": "^3.1.0",
|
||||
@@ -63,7 +66,7 @@
|
||||
"sass-resources-loader": "^2.2.1",
|
||||
"style-loader": "^2.0.0",
|
||||
"stylis": "^4.0.7",
|
||||
"svgo": "^1.1.1",
|
||||
"svgo": "^2.7.0",
|
||||
"svgo-loader": "^2.2.0",
|
||||
"terser-webpack-plugin": "^5.1.1",
|
||||
"typescript": "^3.2.2",
|
||||
@@ -86,11 +89,13 @@
|
||||
"@material-ui/pickers": "^3.2.10",
|
||||
"@projectstorm/react-diagrams": "^6.6.1",
|
||||
"@simonwep/pickr": "^1.5.1",
|
||||
"@szhsin/react-menu": "^2.2.0",
|
||||
"@tippyjs/react": "^4.2.0",
|
||||
"@types/classnames": "^2.2.6",
|
||||
"@types/react": "^16.7.18",
|
||||
"@types/react-dom": "^16.0.11",
|
||||
"acitree": "git+https://github.com/imsurinder90/jquery-aciTree.git#rc.7",
|
||||
"ajv": "^8.8.2",
|
||||
"alertifyjs": "git+https://github.com/EnterpriseDB/AlertifyJS/#72c1d794f5b6d4ec13a68d123c08f19021afe263",
|
||||
"aspen-decorations": "^1.0.2",
|
||||
"axios": "^0.21.1",
|
||||
@@ -103,12 +108,14 @@
|
||||
"bootstrap": "^4.3.1",
|
||||
"bootstrap-datepicker": "^1.8.0",
|
||||
"bootstrap4-toggle": "^3.6.1",
|
||||
"brace": "^0.11.1",
|
||||
"browserfs": "^1.4.3",
|
||||
"chart.js": "^2.9.3",
|
||||
"classnames": "^2.2.6",
|
||||
"closest": "^0.0.1",
|
||||
"codemirror": "^5.59.2",
|
||||
"context-menu": "^2.0.0",
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
"css-loader": "^5.0.1",
|
||||
"cssnano": "^5.0.2",
|
||||
"dagre": "^0.8.4",
|
||||
@@ -120,12 +127,12 @@
|
||||
"imports-loader": "^2.0.0",
|
||||
"insert-if": "^1.1.0",
|
||||
"ip-address": "^7.1.0",
|
||||
"istanbul-instrumenter-loader": "^3.0.1",
|
||||
"jquery": "^3.6.0",
|
||||
"jquery-contextmenu": "^2.9.2",
|
||||
"jquery-ui": "^1.13.0",
|
||||
"json-bignumber": "^1.0.1",
|
||||
"jsoneditor": "^9.5.4",
|
||||
"jsoneditor-react": "^3.1.1",
|
||||
"karma-coverage": "^2.0.3",
|
||||
"leaflet": "^1.5.1",
|
||||
"lodash": "4.*",
|
||||
@@ -141,6 +148,7 @@
|
||||
"pgadmin4-tree": "git+https://github.com/EnterpriseDB/pgadmin4-treeview/#bf7ac7be65898883e3e05c9733426152a1da6422",
|
||||
"postcss": "^8.2.15",
|
||||
"raf": "^3.4.1",
|
||||
"rc-dock": "^3.2.9",
|
||||
"react": "^17.0.1",
|
||||
"react-aspen": "^1.1.0",
|
||||
"react-checkbox-tree": "^1.7.2",
|
||||
@@ -148,6 +156,7 @@
|
||||
"react-draggable": "^4.4.4",
|
||||
"react-select": "^4.2.1",
|
||||
"react-table": "^7.6.3",
|
||||
"react-timer-hook": "^3.0.5",
|
||||
"react-virtualized-auto-sizer": "^1.0.6",
|
||||
"react-window": "^1.8.5",
|
||||
"select2": "^4.0.13",
|
||||
@@ -177,7 +186,7 @@
|
||||
"bundle:watch": "yarn run linter && yarn run webpacker:watch",
|
||||
"bundle:dev": "yarn run linter && yarn run webpacker",
|
||||
"bundle:analyze": "cross-env NODE_ENV=production ANALYZE=true yarn run bundle:dev",
|
||||
"bundle": "cross-env NODE_ENV=production yarn run bundle:dev",
|
||||
"bundle": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 yarn run bundle:dev",
|
||||
"test:karma-once": "yarn run linter && yarn run karma start --single-run",
|
||||
"test:karma": "yarn run linter && yarn run karma start",
|
||||
"test:karma-coverage": "yarn run test:karma-once --reporters coverage,progress",
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
import argparse
|
||||
import os
|
||||
|
||||
|
||||
def load_providers():
|
||||
""" Loads all the providers """
|
||||
providers = {}
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
modules = os.listdir(path + '/providers')
|
||||
|
||||
for filename in modules:
|
||||
filename = path + '/providers/' + filename
|
||||
|
||||
if os.path.isfile(filename):
|
||||
basename = os.path.basename(filename)
|
||||
base, extension = os.path.splitext(basename)
|
||||
|
||||
if extension == ".py" and not basename.startswith("_"):
|
||||
module = __import__("providers." + basename[:-3],
|
||||
fromlist=["providers"])
|
||||
provider = module.load()
|
||||
providers[basename[:-3]] = provider
|
||||
|
||||
return providers
|
||||
|
||||
|
||||
def get_args(providers):
|
||||
""" Creates the parsers and returns the args """
|
||||
# Create the top-level parser
|
||||
parser = argparse.ArgumentParser(prog='pgacloud.py')
|
||||
parser.add_argument('--debug', action=argparse.BooleanOptionalAction,
|
||||
default=True, help='send debug messages to stderr')
|
||||
|
||||
# Create the provider sub-parser
|
||||
parsers = parser.add_subparsers(help='provider help', dest='provider')
|
||||
|
||||
# Load the provider parsers
|
||||
for provider in providers:
|
||||
providers[provider].init_args(parsers)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
return parser, args
|
||||
|
||||
|
||||
def execute_command(providers, parser, args):
|
||||
""" Executes the command in the provider """
|
||||
|
||||
# Switch - for _ in command names. We use - in the CLI syntax for ease of
|
||||
# use, but we need an _ in Python function names
|
||||
if 'command' in args and args.command is not None:
|
||||
args.command = args.command.replace('-', '_')
|
||||
|
||||
# Figure out what provider the command was for (if any) and call the
|
||||
# relevant function. If we don't get a match, print the help
|
||||
if args.provider in providers and \
|
||||
'command' in args and \
|
||||
args.command is not None:
|
||||
command = providers[args.provider].commands()[args.command]
|
||||
command(args)
|
||||
else:
|
||||
# If no provider has been given, display the top level help,
|
||||
# otherwise, call the help() method in the provider
|
||||
if args.provider is None:
|
||||
parser.print_help()
|
||||
else:
|
||||
command = providers[args.provider].commands()['help']
|
||||
command()
|
||||
|
||||
|
||||
def main():
|
||||
""" Entry point """
|
||||
# Load the providers
|
||||
providers = load_providers()
|
||||
|
||||
# Get the args
|
||||
parser, args = get_args(providers)
|
||||
|
||||
# Execute the command
|
||||
execute_command(providers, parser, args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,31 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
class AbsProvider:
|
||||
""" Abstract provider """
|
||||
parser = None
|
||||
|
||||
def init_args(self, parsers):
|
||||
"""Not Required."""
|
||||
pass
|
||||
|
||||
def commands(self):
|
||||
""" Get the list of commands for the current provider. """
|
||||
attrs = filter(lambda attr: attr.startswith('cmd_'), dir(self))
|
||||
commands = {}
|
||||
|
||||
for attr in attrs:
|
||||
method = getattr(self, attr)
|
||||
commands[attr[4:]] = method
|
||||
|
||||
return commands
|
||||
|
||||
def cmd_help(self):
|
||||
""" Prints the provider level help """
|
||||
self.parser.print_help()
|
||||
@@ -0,0 +1,319 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
""" Amazon RDS PostgreSQL provider """
|
||||
|
||||
import os
|
||||
import time
|
||||
import boto3
|
||||
|
||||
from providers._abstract import AbsProvider
|
||||
from utils.io import debug, error, output
|
||||
from utils.misc import get_my_ip, get_random_id
|
||||
|
||||
DEL_SEC_GROUP_MSG = 'Deleting security group: {}...'
|
||||
|
||||
|
||||
class RdsProvider(AbsProvider):
|
||||
def __init__(self):
|
||||
self._clients = {}
|
||||
self._access_key = None
|
||||
self._secret_key = None
|
||||
self._session_token = None
|
||||
self._database_pass = None
|
||||
self._default_region = None
|
||||
|
||||
# Get the credentials
|
||||
if 'AWS_ACCESS_KEY_ID' in os.environ:
|
||||
self._access_key = os.environ['AWS_ACCESS_KEY_ID']
|
||||
|
||||
if 'AWS_SECRET_ACCESS_KEY' in os.environ:
|
||||
self._secret_key = os.environ['AWS_SECRET_ACCESS_KEY']
|
||||
|
||||
if 'AWS_SESSION_TOKEN' in os.environ:
|
||||
self._session_token = os.environ['AWS_SESSION_TOKEN']
|
||||
|
||||
if 'AWS_DATABASE_PASSWORD' in os.environ:
|
||||
self._database_pass = os.environ['AWS_DATABASE_PASSWORD']
|
||||
|
||||
def init_args(self, parsers):
|
||||
""" Create the command line parser for this provider """
|
||||
self.parser = parsers.add_parser('rds',
|
||||
help='Amazon AWS RDS PostgreSQL',
|
||||
epilog='Credentials are read from '
|
||||
'~/.aws/config by default and '
|
||||
'can be overridden in the '
|
||||
'AWS_ACCESS_KEY_ID and '
|
||||
'AWS_SECRET_ACCESS_KEY '
|
||||
'environment variables. '
|
||||
'The default region is read '
|
||||
'from ~/.aws/config and will '
|
||||
'fall back to us-east-1 if '
|
||||
'not present.')
|
||||
self.parser.add_argument('--region', default=self._default_region,
|
||||
help='name of the AWS region (default: {})'
|
||||
.format(self._default_region))
|
||||
|
||||
# Create the command sub-parser
|
||||
parsers = self.parser.add_subparsers(help='RDS commands',
|
||||
dest='command')
|
||||
|
||||
# Create the create instance command parser
|
||||
parser_create_instance = parsers.add_parser('create-instance',
|
||||
help='create a new '
|
||||
'instance')
|
||||
parser_create_instance.add_argument('--name', required=True,
|
||||
help='name of the instance')
|
||||
parser_create_instance.add_argument('--db-name', default='postgres',
|
||||
help='name of the default '
|
||||
'database '
|
||||
'(default: postgres)')
|
||||
parser_create_instance.add_argument('--db-password', required=False,
|
||||
help='password for the database')
|
||||
parser_create_instance.add_argument('--db-username',
|
||||
default='postgres',
|
||||
help='user name for the database '
|
||||
'(default: postgres)')
|
||||
parser_create_instance.add_argument('--db-port', type=int,
|
||||
default=5432,
|
||||
help='port of the database '
|
||||
'(default: 5432)')
|
||||
parser_create_instance.add_argument('--db-version',
|
||||
default='13.3',
|
||||
help='version of PostgreSQL '
|
||||
'to deploy (default: 13.3)')
|
||||
parser_create_instance.add_argument('--instance-type', required=True,
|
||||
help='machine type for the '
|
||||
'instance nodes, e.g. '
|
||||
'db.m3.large')
|
||||
parser_create_instance.add_argument('--storage-iops', type=int,
|
||||
default=0,
|
||||
help='storage IOPs to allocate '
|
||||
'(default: 0)')
|
||||
parser_create_instance.add_argument('--storage-size', type=int,
|
||||
required=True,
|
||||
help='storage size in GB')
|
||||
parser_create_instance.add_argument('--storage-type', default='gp2',
|
||||
help='storage type for the data '
|
||||
'database (default: gp2)')
|
||||
parser_create_instance.add_argument('--public-ip', default='127.0.0.1',
|
||||
help='Public IP '
|
||||
'(default: 127.0.0.1)')
|
||||
|
||||
# Create the delete instance command parser
|
||||
parser_delete_instance = parsers.add_parser('delete-instance',
|
||||
help='delete an instance')
|
||||
parser_delete_instance.add_argument('--name', required=True,
|
||||
help='name of the instance')
|
||||
parser_delete_instance.add_argument('--security-group',
|
||||
help='name of a security group to'
|
||||
'delete as well')
|
||||
|
||||
##########################################################################
|
||||
# AWS Helper functions
|
||||
##########################################################################
|
||||
def _get_aws_client(self, type, args):
|
||||
""" Create/cache/return an AWS client object """
|
||||
if type in self._clients:
|
||||
return self._clients[type]
|
||||
|
||||
session = boto3.Session(
|
||||
aws_access_key_id=self._access_key,
|
||||
aws_secret_access_key=self._secret_key,
|
||||
aws_session_token=self._session_token
|
||||
)
|
||||
|
||||
self._clients['type'] = session.client(type, region_name=args.region)
|
||||
|
||||
return self._clients['type']
|
||||
|
||||
def _create_security_group(self, args):
|
||||
""" Create a new security group for the instance """
|
||||
ec2 = self._get_aws_client('ec2', args)
|
||||
ip = args.public_ip if args.public_ip else get_my_ip()
|
||||
ip = ip.split(',')
|
||||
|
||||
# Deploy the security group
|
||||
try:
|
||||
name = 'pgacloud_{}_{}_{}'.format(args.name,
|
||||
ip[0].replace('.', '-'),
|
||||
get_random_id())
|
||||
debug(args, 'Creating security group: {}...'.format(name))
|
||||
output({'Creating': 'Creating security group: {}...'.format(name)})
|
||||
response = ec2.create_security_group(
|
||||
Description='Inbound access for {} to RDS instance {}'.format(
|
||||
ip[0], args.name),
|
||||
GroupName=name
|
||||
)
|
||||
except Exception as e:
|
||||
error(args, str(e))
|
||||
|
||||
return response['GroupId']
|
||||
|
||||
def _add_ingress_rule(self, args, security_group):
|
||||
""" Add a local -> PostgreSQL ingress rule to a security group """
|
||||
ec2 = self._get_aws_client('ec2', args)
|
||||
ip = args.public_ip if args.public_ip else\
|
||||
'{}/32'.format(get_my_ip())
|
||||
port = args.db_port or 5432
|
||||
IpRanges = []
|
||||
|
||||
ip = ip.split(',')
|
||||
for i in ip:
|
||||
IpRanges.append({
|
||||
'CidrIp': i,
|
||||
'Description': 'pgcloud client {}'.format(i)
|
||||
})
|
||||
try:
|
||||
output({'Adding': 'Adding ingress rule for: {}...'.format(ip)})
|
||||
debug(args,
|
||||
'Adding ingress rule for: {}...'.format(ip))
|
||||
ec2.authorize_security_group_ingress(
|
||||
GroupId=security_group,
|
||||
IpPermissions=[
|
||||
{
|
||||
'FromPort': port,
|
||||
'ToPort': port,
|
||||
'IpProtocol': 'tcp',
|
||||
'IpRanges': IpRanges
|
||||
},
|
||||
]
|
||||
)
|
||||
except Exception as e:
|
||||
error(args, e)
|
||||
|
||||
def _create_rds_instance(self, args, security_group):
|
||||
""" Create an RDS instance """
|
||||
ec2 = self._get_aws_client('ec2', args)
|
||||
rds = self._get_aws_client('rds', args)
|
||||
|
||||
db_password = self._database_pass if self._database_pass is not None\
|
||||
else args.db_password
|
||||
|
||||
try:
|
||||
debug(args, 'Creating RDS instance: {}...'.format(args.name))
|
||||
rds.create_db_instance(DBInstanceIdentifier=args.name,
|
||||
AllocatedStorage=args.storage_size,
|
||||
DBName=args.db_name,
|
||||
Engine='postgres',
|
||||
Port=args.db_port,
|
||||
EngineVersion=args.db_version,
|
||||
StorageType=args.storage_type,
|
||||
StorageEncrypted=True,
|
||||
Iops=args.storage_iops,
|
||||
AutoMinorVersionUpgrade=True,
|
||||
MultiAZ=False,
|
||||
MasterUsername=args.db_username,
|
||||
MasterUserPassword=db_password,
|
||||
DBInstanceClass=args.instance_type,
|
||||
VpcSecurityGroupIds=[
|
||||
security_group,
|
||||
])
|
||||
|
||||
except rds.exceptions.DBInstanceAlreadyExistsFault as e:
|
||||
try:
|
||||
debug(args, DEL_SEC_GROUP_MSG.format(security_group))
|
||||
ec2.delete_security_group(GroupId=security_group)
|
||||
except Exception:
|
||||
pass
|
||||
error(args, 'RDS instance {} already exists.'.format(args.name))
|
||||
except Exception as e:
|
||||
try:
|
||||
debug(args, DEL_SEC_GROUP_MSG.format(security_group))
|
||||
ec2.delete_security_group(GroupId=security_group)
|
||||
except Exception:
|
||||
pass
|
||||
error(args, str(e))
|
||||
|
||||
# Wait for completion
|
||||
running = True
|
||||
while running:
|
||||
response = rds.describe_db_instances(
|
||||
DBInstanceIdentifier=args.name)
|
||||
|
||||
db_instance = response['DBInstances'][0]
|
||||
status = db_instance['DBInstanceStatus']
|
||||
|
||||
if status != 'creating' and status != 'backing-up':
|
||||
running = False
|
||||
|
||||
if running:
|
||||
time.sleep(5)
|
||||
|
||||
return response['DBInstances']
|
||||
|
||||
def _delete_rds_instance(self, args, name):
|
||||
""" Delete an RDS instance """
|
||||
rds = self._get_aws_client('rds', args)
|
||||
|
||||
debug(args, 'Deleting RDS instance: {}...'.format(name))
|
||||
try:
|
||||
rds.delete_db_instance(
|
||||
DBInstanceIdentifier=name,
|
||||
SkipFinalSnapshot=True,
|
||||
DeleteAutomatedBackups=True
|
||||
)
|
||||
except Exception as e:
|
||||
error(args, str(e))
|
||||
|
||||
# Wait for completion
|
||||
while True:
|
||||
try:
|
||||
rds.describe_db_instances(DBInstanceIdentifier=args.name)
|
||||
except rds.exceptions.DBInstanceNotFoundFault:
|
||||
return
|
||||
except Exception as e:
|
||||
error(args, str(e))
|
||||
|
||||
time.sleep(5)
|
||||
|
||||
def _delete_security_group(self, args, id):
|
||||
""" Delete a security group """
|
||||
ec2 = self._get_aws_client('ec2', args)
|
||||
|
||||
debug(args, 'Deleting security group: {}...'.format(id))
|
||||
try:
|
||||
ec2.delete_security_group(
|
||||
GroupId=id
|
||||
)
|
||||
except Exception as e:
|
||||
error(args, str(e))
|
||||
|
||||
##########################################################################
|
||||
# User commands
|
||||
##########################################################################
|
||||
def cmd_create_instance(self, args):
|
||||
""" Create an RDS instance and security group """
|
||||
security_group = self._create_security_group(args)
|
||||
self._add_ingress_rule(args, security_group)
|
||||
instance = self._create_rds_instance(args, security_group)
|
||||
|
||||
data = {'instance': {
|
||||
'Id': instance[0]['DBInstanceIdentifier'],
|
||||
'Location': instance[0]['AvailabilityZone'],
|
||||
'SecurityGroupId': security_group,
|
||||
'Hostname': instance[0]['Endpoint']['Address'],
|
||||
'Port': instance[0]['Endpoint']['Port'],
|
||||
'Database': instance[0]['DBName'],
|
||||
'Username': instance[0]['MasterUsername']
|
||||
}}
|
||||
|
||||
output(data)
|
||||
|
||||
def cmd_delete_instance(self, args):
|
||||
""" Delete an RDS instance and (optionally) a security group """
|
||||
self._delete_rds_instance(args, args.name)
|
||||
|
||||
if args.security_group is not None:
|
||||
self._delete_security_group(args, args.security_group)
|
||||
|
||||
|
||||
def load():
|
||||
""" Loads the current provider """
|
||||
return RdsProvider()
|
||||
@@ -0,0 +1,38 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
def debug(args, message):
|
||||
""" Print a debug message """
|
||||
if not args.debug:
|
||||
return
|
||||
|
||||
now = datetime.datetime.now()
|
||||
|
||||
print('[{}]: {}'.format(now.strftime("%H:%M:%S"), message),
|
||||
file=sys.stderr, flush=True)
|
||||
|
||||
|
||||
def error(args, message):
|
||||
""" Print an error message and exit """
|
||||
debug(args, message)
|
||||
|
||||
output({'error': message})
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def output(data):
|
||||
""" Dump JSON output from a dict """
|
||||
print(json.dumps(data), flush=True)
|
||||
@@ -0,0 +1,42 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
import random
|
||||
import string
|
||||
import urllib3
|
||||
import ipaddress
|
||||
|
||||
|
||||
def get_my_ip():
|
||||
""" Return the public IP of this host """
|
||||
http = urllib3.PoolManager()
|
||||
try:
|
||||
external_ip = http.request('GET', 'https://ident.me').data
|
||||
except Exception:
|
||||
try:
|
||||
external_ip = http.request('GET', 'https://ifconfig.me/ip').data
|
||||
except Exception:
|
||||
external_ip = '127.0.0.1'
|
||||
|
||||
if type(external_ip) == bytes:
|
||||
external_ip = external_ip.decode('utf-8')
|
||||
|
||||
ip = ipaddress.ip_address(external_ip)
|
||||
if isinstance(ip, ipaddress.IPv4Address):
|
||||
return '{}/{}'.format(external_ip, 32)
|
||||
elif isinstance(ip, ipaddress.IPv6Address):
|
||||
return '{}/{}'.format(external_ip, 128)
|
||||
|
||||
return '{}/{}'.format(external_ip, 32)
|
||||
|
||||
|
||||
def get_random_id():
|
||||
""" Return a random 10 byte string """
|
||||
letters = string.ascii_letters + string.digits
|
||||
return(''.join(random.choice(letters) for _ in range(10)))
|
||||
@@ -211,10 +211,14 @@ class AuthSourceManager:
|
||||
|
||||
def validate(self):
|
||||
"""Validate through all the sources."""
|
||||
err_msg = None
|
||||
for src in self.auth_sources:
|
||||
source = get_auth_sources(src)
|
||||
if source.validate(self.form):
|
||||
status, err_msg = source.validate(self.form)
|
||||
if status:
|
||||
return True
|
||||
if err_msg:
|
||||
flash(err_msg, 'warning')
|
||||
return False
|
||||
|
||||
def authenticate(self):
|
||||
|
||||
@@ -54,14 +54,14 @@ class BaseAuthentication(object):
|
||||
form.email.errors = list(form.email.errors)
|
||||
form.email.errors.append(gettext(
|
||||
self.messages('EMAIL_NOT_PROVIDED')))
|
||||
return False
|
||||
return False, None
|
||||
if password is None or password == '':
|
||||
form.password.errors = list(form.password.errors)
|
||||
form.password.errors.append(
|
||||
self.messages('PASSWORD_NOT_PROVIDED'))
|
||||
return False
|
||||
return False, None
|
||||
|
||||
return True
|
||||
return True, None
|
||||
|
||||
def login(self, form):
|
||||
username = form.data['email']
|
||||
@@ -99,10 +99,10 @@ class InternalAuthentication(BaseAuthentication):
|
||||
"""User validation"""
|
||||
# validate the email id first
|
||||
if not validate_email(form.data['email']):
|
||||
flash(self.messages('INVALID_EMAIL'), 'warning')
|
||||
return False
|
||||
return False, self.messages('INVALID_EMAIL')
|
||||
# Flask security validation
|
||||
return form.validate_on_submit()
|
||||
submit = form.validate_on_submit()
|
||||
return submit, None
|
||||
|
||||
def authenticate(self, form):
|
||||
username = form.data['email']
|
||||
|
||||
@@ -163,7 +163,7 @@ class KerberosAuthentication(BaseAuthentication):
|
||||
return gettext("kerberos")
|
||||
|
||||
def validate(self, form):
|
||||
return True
|
||||
return True, None
|
||||
|
||||
def authenticate(self, frm):
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import ssl
|
||||
import config
|
||||
from ldap3 import Connection, Server, Tls, ALL, ALL_ATTRIBUTES, ANONYMOUS,\
|
||||
SIMPLE
|
||||
SIMPLE, AUTO_BIND_TLS_BEFORE_BIND, AUTO_BIND_NO_TLS
|
||||
from ldap3.core.exceptions import LDAPSocketOpenError, LDAPBindError,\
|
||||
LDAPInvalidScopeError, LDAPAttributeError, LDAPInvalidFilterError,\
|
||||
LDAPStartTLSError, LDAPSSLConfigurationError
|
||||
@@ -108,18 +108,21 @@ class LDAPAuthentication(BaseAuthentication):
|
||||
if not status:
|
||||
return status, server
|
||||
|
||||
auto_bind = AUTO_BIND_TLS_BEFORE_BIND if self.start_tls \
|
||||
else AUTO_BIND_NO_TLS
|
||||
|
||||
# Create the connection
|
||||
try:
|
||||
if self.anonymous_bind:
|
||||
self.conn = Connection(server,
|
||||
auto_bind=True,
|
||||
auto_bind=auto_bind,
|
||||
authentication=ANONYMOUS
|
||||
)
|
||||
else:
|
||||
self.conn = Connection(server,
|
||||
user=self.bind_user,
|
||||
password=self.bind_pass,
|
||||
auto_bind=True,
|
||||
auto_bind=auto_bind,
|
||||
authentication=SIMPLE
|
||||
)
|
||||
|
||||
@@ -131,21 +134,16 @@ class LDAPAuthentication(BaseAuthentication):
|
||||
current_app.logger.exception(
|
||||
"Error binding to the LDAP server.")
|
||||
return False, gettext("Error binding to the LDAP server.")
|
||||
except LDAPStartTLSError as e:
|
||||
current_app.logger.exception(
|
||||
"Error starting TLS: {}\n".format(e))
|
||||
return False, gettext("Error starting TLS: {}\n"
|
||||
).format(e.args[0])
|
||||
except Exception as e:
|
||||
current_app.logger.exception(
|
||||
ERROR_CONNECTING_LDAP_SERVER.format(e))
|
||||
return False, ERROR_CONNECTING_LDAP_SERVER.format(e.args[0])
|
||||
|
||||
# Enable TLS if STARTTLS is configured
|
||||
if self.start_tls:
|
||||
try:
|
||||
self.conn.start_tls()
|
||||
except LDAPStartTLSError as e:
|
||||
current_app.logger.exception(
|
||||
"Error starting TLS: {}\n".format(e))
|
||||
return False, gettext("Error starting TLS: {}\n"
|
||||
).format(e.args[0])
|
||||
|
||||
return True, None
|
||||
|
||||
def __auto_create_user(self, user_email):
|
||||
|
||||
@@ -115,7 +115,7 @@ class OAuth2Authentication(BaseAuthentication):
|
||||
return self.oauth2_config[self.oauth2_current_client]['OAUTH2_NAME']
|
||||
|
||||
def validate(self, form):
|
||||
return True
|
||||
return True, None
|
||||
|
||||
def login(self, form):
|
||||
profile = self.get_user_profile()
|
||||
|
||||
@@ -74,7 +74,7 @@ class WebserverAuthentication(BaseAuthentication):
|
||||
return gettext("webserver")
|
||||
|
||||
def validate(self, form):
|
||||
return True
|
||||
return True, None
|
||||
|
||||
def get_user(self):
|
||||
username = request.environ.get(config.WEBSERVER_REMOTE_USER)
|
||||
|
||||
@@ -763,7 +763,7 @@ def index():
|
||||
mfa_enabled=is_mfa_enabled(),
|
||||
login_url=login_url,
|
||||
_=gettext,
|
||||
auth_only_internal=auth_only_internal
|
||||
auth_only_internal=auth_only_internal,
|
||||
))
|
||||
|
||||
# Set the language cookie after login, so next time the user will have that
|
||||
|
||||
@@ -112,6 +112,10 @@ def server_icon_and_background(is_connected, manager, server):
|
||||
return 'icon-shared-server-not-connected{0}'.format(
|
||||
server_background_color
|
||||
)
|
||||
elif server.cloud_status == -1:
|
||||
return 'icon-server-cloud-deploy{0}'.format(
|
||||
server_background_color
|
||||
)
|
||||
else:
|
||||
return 'icon-server-not-connected{0}'.format(
|
||||
server_background_color
|
||||
@@ -256,7 +260,8 @@ class ServerModule(sg.ServerGroupPluginModule):
|
||||
user_name=server.username,
|
||||
shared=server.shared,
|
||||
is_kerberos_conn=bool(server.kerberos_conn),
|
||||
gss_authenticated=manager.gss_authenticated
|
||||
gss_authenticated=manager.gss_authenticated,
|
||||
cloud_status=server.cloud_status
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -998,7 +1003,8 @@ class ServerNode(PGChildNodeView):
|
||||
'tunnel_authentication': tunnel_authentication,
|
||||
'kerberos_conn': bool(server.kerberos_conn),
|
||||
'gss_authenticated': manager.gss_authenticated,
|
||||
'gss_encrypted': manager.gss_encrypted
|
||||
'gss_encrypted': manager.gss_encrypted,
|
||||
'cloud_status': server.cloud_status
|
||||
}
|
||||
|
||||
return ajax_response(response)
|
||||
|
||||
@@ -25,6 +25,7 @@ from pgadmin.utils.driver import get_driver
|
||||
from config import PG_DEFAULT_DRIVER
|
||||
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
|
||||
from pgadmin.tools.schema_diff.compare import SchemaDiffObjectCompare
|
||||
from pgadmin.utils.constants import PGADMIN_STRING_SEPARATOR
|
||||
|
||||
|
||||
class UserMappingModule(CollectionNodeModule):
|
||||
@@ -916,7 +917,14 @@ class UserMappingView(PGChildNodeView, SchemaDiffObjectCompare):
|
||||
# the empty list.
|
||||
if 'umoptions' in data and data['umoptions'] is None:
|
||||
data['umoptions'] = []
|
||||
res[row['name']] = data
|
||||
|
||||
mapping_name = row['name']
|
||||
if 'srvname' in data:
|
||||
mapping_name = \
|
||||
row['name'] + PGADMIN_STRING_SEPARATOR + \
|
||||
data['srvname']
|
||||
|
||||
res[mapping_name] = data
|
||||
|
||||
return res
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ FROM pg_catalog.pg_foreign_server srv
|
||||
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=srv.oid AND des.objsubid=0 AND des.classoid='pg_foreign_server'::regclass)
|
||||
WHERE srv.oid = {{fserid}}::oid
|
||||
{% elif fsid or umid %}
|
||||
SELECT u.umid AS oid, u.usename AS name, u.srvid AS fsid, umoptions AS umoptions, fs.srvfdw AS fdwid
|
||||
SELECT u.umid AS oid, u.usename AS name, fs.srvname, u.srvid AS fsid, umoptions AS umoptions, fs.srvfdw AS fdwid
|
||||
FROM pg_catalog.pg_user_mappings u
|
||||
LEFT JOIN pg_catalog.pg_foreign_server fs ON fs.oid = u.srvid
|
||||
{% if fsid %} WHERE u.srvid = {{fsid}}::oid {% endif %} {% if umid %} WHERE u.umid= {{umid}}::oid {% endif %}
|
||||
|
||||
@@ -139,8 +139,10 @@ define('pgadmin.node.function', [
|
||||
id: 'pronamespace', label: gettext('Schema'), cell: 'string',
|
||||
control: 'node-list-by-id', type: 'text', cache_level: 'database',
|
||||
node: 'schema', disabled: 'isDisabled', mode: ['create', 'edit'],
|
||||
},
|
||||
],
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||
type: 'multiline', disabled: 'isDisabled',
|
||||
}],
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -342,17 +342,17 @@ define('pgadmin.node.schema', [
|
||||
pgBrowser.add_menus([{
|
||||
name: 'create_schema_on_coll', node: 'coll-schema', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Schema...'),
|
||||
category: 'create', priority: 1, label: gettext('Schema...'),
|
||||
icon: 'wcTabIcon icon-schema', data: {action: 'create'},
|
||||
},{
|
||||
name: 'create_schema', node: 'schema', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Schema...'),
|
||||
category: 'create', priority: 1, label: gettext('Schema...'),
|
||||
icon: 'wcTabIcon icon-schema', data: {action: 'create'},
|
||||
},{
|
||||
name: 'create_schema', node: 'database', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 4, label: gettext('Schema...'),
|
||||
category: 'create', priority: 1, label: gettext('Schema...'),
|
||||
icon: 'wcTabIcon icon-schema', data: {action: 'create'},
|
||||
enable: 'can_create_schema',
|
||||
},
|
||||
|
||||
@@ -24,7 +24,8 @@ class SchemaDiffTableCompare(SchemaDiffObjectCompare):
|
||||
'rows_cnt', 'hastoasttable', 'relhassubclass',
|
||||
'relacl_str', 'setting']
|
||||
|
||||
column_keys_to_ignore = ['atttypid', 'edit_types', 'elemoid', 'seqrelid']
|
||||
column_keys_to_ignore = ['atttypid', 'edit_types', 'elemoid', 'seqrelid',
|
||||
'indkey']
|
||||
|
||||
constraint_keys_to_ignore = ['relname', 'nspname', 'parent_tbl',
|
||||
'attrelid', 'adrelid', 'fknsp', 'confrelid',
|
||||
@@ -167,6 +168,7 @@ class SchemaDiffTableCompare(SchemaDiffObjectCompare):
|
||||
|
||||
if item['name'] == source['name']:
|
||||
tmp = copy.deepcopy(item)
|
||||
source['attnum'] = tmp['attnum']
|
||||
|
||||
if tmp and source != tmp:
|
||||
updated.append(source)
|
||||
@@ -197,7 +199,10 @@ class SchemaDiffTableCompare(SchemaDiffObjectCompare):
|
||||
'exclude_constraint': ['amname',
|
||||
'indconstraint',
|
||||
'columns'],
|
||||
'foreign_key': []
|
||||
'foreign_key': ['condeferrable', 'condeferred',
|
||||
'confupdtype', 'confdeltype',
|
||||
'confmatchtype', 'convalidated',
|
||||
'conislocal']
|
||||
}
|
||||
|
||||
for constraint in ['primary_key', 'unique_constraint',
|
||||
|
||||
@@ -84,14 +84,14 @@ define('pgadmin.node.database', [
|
||||
},{
|
||||
name: 'connect_database', node: 'database', module: this,
|
||||
applies: ['object', 'context'], callback: 'connect_database',
|
||||
category: 'connect', priority: 4, label: gettext('Connect Database...'),
|
||||
category: 'connect', priority: 4, label: gettext('Connect Database'),
|
||||
icon: 'fa fa-link', enable : 'is_not_connected', data: {
|
||||
data_disabled: gettext('Selected database is already connected.'),
|
||||
},
|
||||
},{
|
||||
name: 'disconnect_database', node: 'database', module: this,
|
||||
applies: ['object', 'context'], callback: 'disconnect_database',
|
||||
category: 'drop', priority: 5, label: gettext('Disconnect Database...'),
|
||||
category: 'drop', priority: 5, label: gettext('Disconnect from database'),
|
||||
icon: 'fa fa-unlink', enable : 'is_connected',data: {
|
||||
data_disabled: gettext('Selected database is already disconnected.'),
|
||||
},
|
||||
@@ -219,8 +219,8 @@ define('pgadmin.node.database', [
|
||||
|
||||
if (d) {
|
||||
Notify.confirm(
|
||||
gettext('Disconnect the database'),
|
||||
gettext('Are you sure you want to disconnect the database - %s?', d.label),
|
||||
gettext('Disconnect from database'),
|
||||
gettext('Are you sure you want to disconnect from database - %s?', d.label),
|
||||
function() {
|
||||
var data = d;
|
||||
$.ajax({
|
||||
@@ -477,6 +477,9 @@ define('pgadmin.node.database', [
|
||||
pgBrowser.Events.trigger(
|
||||
'pgadmin:database:connected', _item, _data
|
||||
);
|
||||
/* Call enable/disable menu function after database is connected.
|
||||
To make sure all the menus for database is in the right state */
|
||||
pgBrowser.enable_disable_menus.apply(pgBrowser, [_item]);
|
||||
|
||||
if (!_connected) {
|
||||
setTimeout(function() {
|
||||
|
||||
@@ -1 +1,31 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#eb7324;}.cls-2{fill:#e54d25;}.cls-3{fill:#f9a11f;}</style></defs><title>ppas</title><g id="_2" data-name="2"><path class="cls-1" d="M11.58,4.62H6.73v2.3h4.85v2H6.73v2.47h4.85v2l-7.17-.05V2.55l7.17.05Z"/><path class="cls-2" d="M8.88,8.94A9.14,9.14,0,0,0,5.74,6.66a9.2,9.2,0,0,0,.5,6.75l4.82,0a9.06,9.06,0,0,0-.6-2H6.73V8.94Z"/><path class="cls-3" d="M4.42,2.55V6.2a9,9,0,0,1,1.32.46A9.12,9.12,0,0,1,8.38,2.58Z"/></g></svg>
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="body_1" width="933" height="933">
|
||||
|
||||
<defs>
|
||||
<clipPath id="1">
|
||||
|
||||
<path clip-rule="evenodd" d="M0 700L700 700L700 0L0 0L0 700z" /> </clipPath>
|
||||
</defs>
|
||||
|
||||
<g transform="matrix(0.99964285 0 0 0.99964285 0 0)">
|
||||
<g transform="matrix(1.3333333 0 -0 -1.3333333 0 933.3333)">
|
||||
<g>
|
||||
<g clip-path="url(#1)" >
|
||||
<g transform="matrix(1 0 0 1 697.2871 351.3565)">
|
||||
<path d="M0 0C 0 -192.551 -156.093 -348.644 -348.644 -348.644C -541.19403 -348.644 -697.287 -192.55101 -697.287 0C -697.287 192.55 -541.194 348.644 -348.64398 348.644C -156.093 348.644 0 192.55 0 0" stroke="none" fill="#FF3E00" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 557.8364 368.4297)">
|
||||
<path d="M0 0C 13.1 12.888 21.351 30.523 21.351 49.233C 21.351 84.117004 -7.309 109.503006 -42.662003 105.935L-42.662003 105.935L-96.414 100.507996C -102.08 99.937 -106.673004 94.940994 -106.673004 89.35L-106.673004 89.35L-106.673004 -4.806999L-106.673004 -16.729L-106.673004 -110.884995C -106.673004 -116.476 -102.08 -120.545 -96.414 -119.97299L-96.414 -119.97299L-42.662003 -114.54699C -7.309002 -110.97899 21.350998 -79.80699 21.350998 -44.92299L21.350998 -44.92299L21.350998 -44.92199C 21.351 -26.213 13.1 -10.243 0 0" stroke="none" fill="#FFFFFF" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 237.3389 468.9331)">
|
||||
<path d="M0 0L-53.752 5.431C -89.104996 9.002001 -117.764 -16.405 -117.764 -51.319C -117.764 -70.045 -109.514 -87.696 -96.414 -100.595C -109.514 -110.847 -117.764 -126.831 -117.764 -145.556L-117.764 -145.556L-117.764 -145.557C -117.764 -180.47 -89.104996 -211.669 -53.752 -215.241L-53.752 -215.241L0 -220.672C 5.666 -221.244 10.259 -217.172 10.259 -211.576L10.259 -211.576L10.259 -117.339005L10.259 -105.406006L10.259 -11.168007C 10.259 -5.572 5.666 -0.572 0 0" stroke="none" fill="#FFFFFF" fill-rule="nonzero" />
|
||||
</g>
|
||||
<g transform="matrix(1 0 0 1 348.7803 424.1963)">
|
||||
<path d="M0 0L-53.737 0C -59.403 0 -63.997 -4.602 -63.997 -10.279L-63.997 -10.279L-63.997 -150.139L-63.997 -164.343C -63.997 -170.342 -59.869003 -176.369 -53.915 -176.369L-53.915 -176.369L0.01499939 -176.369C -0.5530006 -187.58101 -15.906 -201.168 -51.912 -205.176L-51.912 -205.176L-55.995 -205.54C -58.423 -205.803 -63.996998 -205.54 -63.996998 -216.07999L-63.996998 -216.07999L-63.996998 -218.87599C -63.996998 -224.55699 -59.552 -229.54999 -53.880997 -229.54999L-53.880997 -229.54999L-0.13699722 -229.54999C 35.216003 -229.54999 64.02701 -195.31198 64.02701 -159.874L64.02701 -159.874L64.02701 -64.18299C 64.027 -28.735 35.361 0 0 0" stroke="none" fill="#FFFFFF" fill-rule="nonzero" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 507 B After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M29,12.3c0,0-5.6,0-10,4.4c-0.2,0.2-0.4,0.4-0.5,0.6c-0.8-0.1-2,0-2.6,0.6c-1.4,1.4-1.9,3.8-1.9,3.8l3.1-0.4v1.1l1.9,1.9
|
||||
h1.1l-0.4,3.1c0,0,2.4-0.5,3.8-1.9c0.6-0.6,0.6-1.8,0.6-2.6c0.2-0.2,0.4-0.3,0.6-0.5C29,17.9,29,12.3,29,12.3z M22.8,17.3
|
||||
c0-0.7,0.6-1.2,1.2-1.2s1.2,0.6,1.2,1.2s-0.6,1.2-1.2,1.2S22.8,18,22.8,17.3z"/>
|
||||
<path d="M18,24.5c-0.6,0.6-1.5,0.3-1.5,0.3s-0.3-0.9,0.3-1.5c0,0,0.1-0.1,0.1-0.1l-0.3-0.3c-0.2,0.1-0.5,0.2-0.6,0.4
|
||||
c-0.9,0.9-0.7,2.7-0.7,2.7s1.9,0.1,2.7-0.7c0.2-0.2,0.3-0.4,0.4-0.6l-0.3-0.3C18.1,24.4,18.1,24.5,18,24.5z"/>
|
||||
<path d="M2.5,8.9h18.6c0.8,0,1.5-0.7,1.5-1.5V4.3c0-0.8-0.7-1.5-1.5-1.5H2.5C1.7,2.7,1,3.4,1,4.3v3.1C1,8.2,1.7,8.9,2.5,8.9z
|
||||
M18.8,4.6c0.6,0,1.2,0.5,1.2,1.2s-0.5,1.2-1.2,1.2s-1.2-0.5-1.2-1.2S18.2,4.6,18.8,4.6z M15.8,4.6c0.6,0,1.2,0.5,1.2,1.2
|
||||
s-0.5,1.2-1.2,1.2s-1.2-0.5-1.2-1.2S15.1,4.6,15.8,4.6z"/>
|
||||
<path d="M2.5,16.5h15.4c0.1-0.2,0.3-0.4,0.4-0.5c1.4-1.4,2.9-2.3,4.3-3v-1.1c0-0.8-0.7-1.5-1.5-1.5H2.5c-0.8,0-1.5,0.7-1.5,1.5V15
|
||||
C1,15.8,1.7,16.5,2.5,16.5z M18.8,12.3c0.6,0,1.2,0.5,1.2,1.2c0,0.6-0.5,1.2-1.2,1.2s-1.2-0.5-1.2-1.2
|
||||
C17.7,12.8,18.2,12.3,18.8,12.3z M15.8,12.3c0.6,0,1.2,0.5,1.2,1.2c0,0.6-0.5,1.2-1.2,1.2s-1.2-0.5-1.2-1.2
|
||||
C14.6,12.8,15.1,12.3,15.8,12.3z"/>
|
||||
<path d="M2.5,18.1c-0.8,0-1.5,0.7-1.5,1.5v3.1c0,0.8,0.7,1.5,1.5,1.5h10.1l0.7-3.2c0,0,0.3-1.6,1.2-2.9H2.5z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -80,13 +80,15 @@ define('pgadmin.node.server', [
|
||||
pgBrowser.add_menus([{
|
||||
name: 'create_server_on_sg', node: 'server_group', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 1, label: gettext('Server...'),
|
||||
data: {action: 'create'}, icon: 'wcTabIcon icon-server', enable: 'canCreate',
|
||||
category: 'register', priority: 1, label: gettext('Server...'),
|
||||
data: {action: 'create'}, icon: 'wcTabIcon icon-server',
|
||||
enable: 'canCreate',
|
||||
},{
|
||||
name: 'create_server', node: 'server', module: this,
|
||||
applies: ['object', 'context'], callback: 'show_obj_properties',
|
||||
category: 'create', priority: 3, label: gettext('Server...'),
|
||||
data: {action: 'create'}, icon: 'wcTabIcon icon-server', enable: 'canCreate',
|
||||
category: 'register', priority: 3, label: gettext('Server...'),
|
||||
data: {action: 'create'}, icon: 'wcTabIcon icon-server',
|
||||
enable: 'canCreate',
|
||||
},{
|
||||
name: 'connect_server', node: 'server', module: this,
|
||||
applies: ['object', 'context'], callback: 'connect_server',
|
||||
@@ -808,8 +810,8 @@ define('pgadmin.node.server', [
|
||||
// Fetch the updated data
|
||||
$.get(server_url)
|
||||
.done(function(res) {
|
||||
if (res.shared && _.isNull(res.username) && data.user_id != current_user.id){
|
||||
if (!res.service){
|
||||
if (res.shared && _.isNull(res.username) && data.user_id != current_user.id) {
|
||||
if (!res.service) {
|
||||
pgAdmin.Browser.Node.callbacks.show_obj_properties.call(
|
||||
pgAdmin.Browser.Nodes[tree.itemData(item)._type], {action: 'edit'}
|
||||
);
|
||||
@@ -818,13 +820,30 @@ define('pgadmin.node.server', [
|
||||
tree.setInode(item);
|
||||
tree.addIcon(item, {icon: 'icon-shared-server-not-connected'});
|
||||
Notify.info('Please enter the server details to connect to the server. This server is a shared server.');
|
||||
}else{
|
||||
} else {
|
||||
data.is_connecting = false;
|
||||
tree.unload(item);
|
||||
tree.setInode(item);
|
||||
tree.addIcon(item, {icon: 'icon-shared-server-not-connected'});
|
||||
}
|
||||
}
|
||||
else if (res.cloud_status == -1) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
timeout: 30000,
|
||||
url: url_for('cloud.update_cloud_process', {'sid': res.id}),
|
||||
cache: false,
|
||||
async: true,
|
||||
contentType: 'application/json',
|
||||
})
|
||||
.done(function() {
|
||||
pgAdmin.Browser.BackgroundProcessObsorver.update_process_list();
|
||||
})
|
||||
.fail(function() {
|
||||
console.warn(arguments);
|
||||
});
|
||||
}
|
||||
return;
|
||||
}).always(function(){
|
||||
data.is_connecting = false;
|
||||
});
|
||||
@@ -911,6 +930,11 @@ define('pgadmin.node.server', [
|
||||
|
||||
// Load dashboard
|
||||
pgBrowser.Events.trigger('pgadmin-browser:tree:selected', _item, _data, node);
|
||||
|
||||
/* Call enable/disable menu function after database is connected.
|
||||
To make sure all the menus for database is in the right state */
|
||||
pgBrowser.enable_disable_menus.apply(pgBrowser, [_item]);
|
||||
|
||||
// We're not reconnecting
|
||||
if (!_wasConnected) {
|
||||
_tree.setInode(_item);
|
||||
@@ -1033,7 +1057,7 @@ define('pgadmin.node.server', [
|
||||
};
|
||||
|
||||
/* Wait till the existing request completes */
|
||||
if(data.is_connecting) {
|
||||
if(data.is_connecting || data.cloud_status == -1) {
|
||||
return;
|
||||
}
|
||||
data.is_connecting = true;
|
||||
|
||||
@@ -85,10 +85,6 @@ export default class ServerSchema extends BaseUISchema {
|
||||
return pgAdmin.Browser.utils.pg_libpq_version < 100000;
|
||||
}
|
||||
|
||||
validateHost(str) {
|
||||
return /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$|^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$|^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/.test(str);
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
let obj = this;
|
||||
return [
|
||||
@@ -477,7 +473,8 @@ export default class ServerSchema extends BaseUISchema {
|
||||
|
||||
/* Hostname, IP address validate */
|
||||
if (state.host) {
|
||||
if (!this.validateHost(state.host)){
|
||||
// Check for leading and trailing spaces.
|
||||
if (/^\s|\s$/.test(state.host)){
|
||||
errmsg = gettext('Host name must be valid hostname or IPv4 or IPv6 address.');
|
||||
setError('host', errmsg);
|
||||
return true;
|
||||
|
||||
@@ -33,3 +33,13 @@
|
||||
vertical-align: middle;
|
||||
height: 1.3em;
|
||||
}
|
||||
|
||||
|
||||
.icon-server-cloud-deploy {
|
||||
background-image: url('{{ url_for('NODE-server.static', filename='img/serverbeingdeployed.svg') }}') !important;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 20px !important;
|
||||
align-content: center;
|
||||
vertical-align: middle;
|
||||
height: 1.3em;
|
||||
}
|
||||
@@ -326,9 +326,18 @@ define('pgadmin.browser', [
|
||||
},
|
||||
menu_categories: {
|
||||
/* name, label (pair) */
|
||||
'register': {
|
||||
label: gettext('Register'),
|
||||
priority: 1,
|
||||
/* separator above this menu */
|
||||
above: false,
|
||||
below: true,
|
||||
/* icon: 'fa fa-magic', */
|
||||
single: true,
|
||||
},
|
||||
'create': {
|
||||
label: gettext('Create'),
|
||||
priority: 1,
|
||||
priority: 2,
|
||||
/* separator above this menu */
|
||||
above: false,
|
||||
below: true,
|
||||
|
||||
@@ -46,7 +46,7 @@ define([
|
||||
pgAdmin.Browser.add_menus([{
|
||||
name: 'refresh', node: this.type, module: this,
|
||||
applies: ['object', 'context'], callback: 'refresh',
|
||||
priority: 1, label: gettext('Refresh'),
|
||||
priority: 2, label: gettext('Refresh'),
|
||||
icon: 'fa fa-sync-alt',
|
||||
}]);
|
||||
|
||||
|
||||
@@ -118,8 +118,8 @@ define('pgadmin.browser.node', [
|
||||
module: self,
|
||||
applies: ['object', 'context'],
|
||||
callback: 'refresh',
|
||||
priority: 1,
|
||||
label: gettext('Refresh'),
|
||||
priority: 2,
|
||||
label: gettext('Refresh...'),
|
||||
icon: 'fa fa-sync-alt',
|
||||
}]);
|
||||
|
||||
@@ -792,6 +792,9 @@ define('pgadmin.browser.node', [
|
||||
}
|
||||
|
||||
l = gettext('Create - %s', this.label);
|
||||
if (this.type == 'server') {
|
||||
l = gettext('Register - %s', this.label);
|
||||
}
|
||||
p = addPanel();
|
||||
|
||||
setTimeout(function() {
|
||||
|
||||
@@ -47,14 +47,24 @@ export function getNodeView(nodeType, treeNodeInfo, actionType, itemNodeData, fo
|
||||
|
||||
/* Called when dialog is opened in edit mode, promise required */
|
||||
let initData = ()=>new Promise((resolve, reject)=>{
|
||||
api.get(url(false))
|
||||
.then((res)=>{
|
||||
resolve(res.data);
|
||||
})
|
||||
.catch((err)=>{
|
||||
onError(err);
|
||||
reject(err);
|
||||
});
|
||||
if(actionType === 'create') {
|
||||
resolve({});
|
||||
} else {
|
||||
api.get(url(false))
|
||||
.then((res)=>{
|
||||
resolve(res.data);
|
||||
})
|
||||
.catch((err)=>{
|
||||
if(err.response){
|
||||
console.error('error resp', err.response);
|
||||
} else if(err.request){
|
||||
console.error('error req', err.request);
|
||||
} else if(err.message){
|
||||
console.error('error msg', err.message);
|
||||
}
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/* on save button callback, promise required */
|
||||
|
||||
@@ -76,7 +76,7 @@ class CheckFileManagerFeatureTest(BaseFeatureTest):
|
||||
self.page.find_by_css_selector(QueryToolLocators.btn_save_file) \
|
||||
.click()
|
||||
# Set the XSS value in input
|
||||
WebDriverWait(self.driver, 10).until(EC.presence_of_element_located(
|
||||
WebDriverWait(self.driver, 15).until(EC.presence_of_element_located(
|
||||
(By.CSS_SELECTOR, ".change_file_types")))
|
||||
self.page.find_by_css_selector('.change_file_types')
|
||||
self.page.fill_input_by_css_selector(
|
||||
|
||||
@@ -82,7 +82,7 @@ class PGUtilitiesMaintenanceFeatureTest(BaseFeatureTest):
|
||||
self.page.click_modal('OK', True)
|
||||
self.page.wait_for_element_to_disappear(
|
||||
lambda driver: driver.find_element(
|
||||
By.XPATH, NavMenuLocators.maintenance_operation))
|
||||
By.XPATH, NavMenuLocators.maintenance_operation), 10)
|
||||
|
||||
# Wait for the backup status alertfier
|
||||
self.wait.until(EC.visibility_of_element_located(
|
||||
|
||||
@@ -78,7 +78,7 @@ class CheckDebuggerForXssFeatureTest(BaseFeatureTest):
|
||||
try:
|
||||
wait = WebDriverWait(self.page.driver, 2)
|
||||
is_error = wait.until(EC.presence_of_element_located(
|
||||
(By.XPATH, "//div[@class ='MuiDialogTitle-root']"
|
||||
(By.XPATH, "//div[contains(@class,'MuiDialogTitle-root')]"
|
||||
"//div[text()='Debugger Error']")
|
||||
))
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ the long running background-processes.
|
||||
from flask import url_for
|
||||
from flask_security import login_required
|
||||
from pgadmin.utils import PgAdminModule
|
||||
from pgadmin.utils.ajax import make_response, gone, success_return
|
||||
from pgadmin.utils.ajax import make_response, gone, success_return,\
|
||||
make_json_response
|
||||
|
||||
from .processes import BatchProcess
|
||||
|
||||
@@ -101,8 +102,26 @@ def acknowledge(pid):
|
||||
Positive status
|
||||
"""
|
||||
try:
|
||||
BatchProcess.acknowledge(pid)
|
||||
return success_return()
|
||||
process = BatchProcess(id=pid)
|
||||
status, server = process.acknowledge(pid)
|
||||
if status and len(server) > 0:
|
||||
return make_json_response(
|
||||
success=1,
|
||||
data={'node': {
|
||||
'sid': server['id'],
|
||||
'gid': server['servergroup_id'],
|
||||
'_type': 'server',
|
||||
'icon': 'icon-server-not-connected',
|
||||
'id': 'server_{}'.format(server['id']),
|
||||
'label': server['name'],
|
||||
'status': server['status'],
|
||||
'cloud_status': server['cloud_status']
|
||||
}}
|
||||
)
|
||||
elif status and len(server) == 0:
|
||||
return success_return()
|
||||
else:
|
||||
return gone(errormsg=str(server))
|
||||
except LookupError as lerr:
|
||||
return gone(errormsg=str(lerr))
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ from datetime import datetime
|
||||
from pickle import dumps, loads
|
||||
from subprocess import Popen, PIPE
|
||||
import logging
|
||||
import json
|
||||
|
||||
from pgadmin.utils import u_encode, file_quote, fs_encoding, \
|
||||
get_complete_file_path, get_storage_directory, IS_WIN
|
||||
@@ -119,8 +120,13 @@ class BatchProcess(object):
|
||||
if 'id' in kwargs:
|
||||
self._retrieve_process(kwargs['id'])
|
||||
else:
|
||||
_cmd = kwargs['cmd']
|
||||
# Get system's interpreter
|
||||
if kwargs['cmd'] == 'python':
|
||||
_cmd = self._get_python_interpreter()
|
||||
|
||||
self._create_process(
|
||||
kwargs['desc'], kwargs['cmd'], kwargs['args']
|
||||
kwargs['desc'], _cmd, kwargs['args']
|
||||
)
|
||||
|
||||
def _retrieve_process(self, _id):
|
||||
@@ -245,13 +251,8 @@ class BatchProcess(object):
|
||||
_('The process has already finished and cannot be restarted.')
|
||||
)
|
||||
|
||||
def start(self, cb=None):
|
||||
self.check_start_end_time()
|
||||
|
||||
executor = file_quote(os.path.join(
|
||||
os.path.dirname(u_encode(__file__)), 'process_executor.py'
|
||||
))
|
||||
|
||||
def _get_python_interpreter(self):
|
||||
"""Get Python Interpreter"""
|
||||
if os.name == 'nt':
|
||||
paths = os.environ['PATH'].split(os.pathsep)
|
||||
|
||||
@@ -260,14 +261,22 @@ class BatchProcess(object):
|
||||
str(paths)
|
||||
)
|
||||
|
||||
interpreter = self.get_interpreter(paths)
|
||||
interpreter = self.get_windows_interpreter(paths)
|
||||
else:
|
||||
interpreter = sys.executable
|
||||
|
||||
cmd = [
|
||||
interpreter if interpreter is not None else 'python',
|
||||
executor, self.cmd
|
||||
]
|
||||
return interpreter if interpreter else 'python'
|
||||
|
||||
def start(self, cb=None):
|
||||
self.check_start_end_time()
|
||||
|
||||
executor = file_quote(os.path.join(
|
||||
os.path.dirname(u_encode(__file__)), 'process_executor.py'
|
||||
))
|
||||
|
||||
interpreter = self._get_python_interpreter()
|
||||
|
||||
cmd = [interpreter, executor, self.cmd]
|
||||
cmd.extend(self.args)
|
||||
|
||||
current_app.logger.info(
|
||||
@@ -382,7 +391,7 @@ class BatchProcess(object):
|
||||
# Explicitly ignoring signals in the child process
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
|
||||
def get_interpreter(self, paths):
|
||||
def get_windows_interpreter(self, paths):
|
||||
"""
|
||||
Get interpreter.
|
||||
:param paths:
|
||||
@@ -487,6 +496,48 @@ class BatchProcess(object):
|
||||
|
||||
return pos, completed
|
||||
|
||||
def _get_cloud_instance_details(self, _process):
|
||||
"""
|
||||
Parse the output to get the cloud instance details
|
||||
"""
|
||||
ctime = get_current_time(format='%y%m%d%H%M%S%f')
|
||||
stdout = []
|
||||
stderr = []
|
||||
out = 0
|
||||
err = 0
|
||||
cloud_server_id = 0
|
||||
cloud_instance = ''
|
||||
|
||||
enc = sys.getdefaultencoding()
|
||||
if enc == 'ascii':
|
||||
enc = 'utf-8'
|
||||
|
||||
out, out_completed = self.read_log(
|
||||
self.stdout, stdout, out, ctime, _process.exit_code, enc
|
||||
)
|
||||
err, err_completed = self.read_log(
|
||||
self.stderr, stderr, err, ctime, _process.exit_code, enc
|
||||
)
|
||||
|
||||
from pgadmin.misc.cloud import update_server
|
||||
if out_completed and not _process.exit_code:
|
||||
for value in stdout:
|
||||
if 'instance' in value[1] and value[1] != '':
|
||||
cloud_instance = json.loads(value[1])
|
||||
cloud_server_id = _process.server_id
|
||||
|
||||
if type(cloud_instance) is dict and\
|
||||
'instance' in cloud_instance:
|
||||
cloud_instance['instance']['sid'] = cloud_server_id
|
||||
cloud_instance['instance']['status'] = True
|
||||
return update_server(cloud_instance)
|
||||
elif err_completed and _process.exit_code > 0:
|
||||
cloud_instance = {'instance': {}}
|
||||
cloud_instance['instance']['sid'] = _process.server_id
|
||||
cloud_instance['instance']['status'] = False
|
||||
return update_server(cloud_instance)
|
||||
return True, {}
|
||||
|
||||
def status(self, out=0, err=0):
|
||||
ctime = get_current_time(format='%y%m%d%H%M%S%f')
|
||||
|
||||
@@ -549,8 +600,7 @@ class BatchProcess(object):
|
||||
},
|
||||
'start_time': self.stime,
|
||||
'exit_code': self.ecode,
|
||||
'execution_time': execution_time,
|
||||
'process_state': self.process_state
|
||||
'execution_time': execution_time
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@@ -697,7 +747,8 @@ class BatchProcess(object):
|
||||
"'{1}'").format(desc.sid, p.pid)
|
||||
)
|
||||
try:
|
||||
BatchProcess.acknowledge(p.pid)
|
||||
process = BatchProcess(id=p.pid)
|
||||
process.acknowledge(p.pid)
|
||||
except LookupError as lerr:
|
||||
current_app.logger.warning(
|
||||
_("Status for the background process '{0}' could "
|
||||
@@ -712,8 +763,7 @@ class BatchProcess(object):
|
||||
def total_seconds(dt):
|
||||
return round(dt.total_seconds(), 2)
|
||||
|
||||
@staticmethod
|
||||
def acknowledge(_pid):
|
||||
def acknowledge(self, _pid):
|
||||
"""
|
||||
Acknowledge from the user, he/she has alredy watched the status.
|
||||
|
||||
@@ -721,6 +771,9 @@ class BatchProcess(object):
|
||||
And, delete the process information from the configuration, and the log
|
||||
files related to the process, if it has already been completed.
|
||||
"""
|
||||
status = True
|
||||
_server = {}
|
||||
|
||||
p = Process.query.filter_by(
|
||||
user_id=current_user.id, pid=_pid
|
||||
).first()
|
||||
@@ -729,15 +782,17 @@ class BatchProcess(object):
|
||||
raise LookupError(PROCESS_NOT_FOUND)
|
||||
|
||||
if p.end_time is not None:
|
||||
status, _server = self._get_cloud_instance_details(p)
|
||||
logdir = p.logdir
|
||||
db.session.delete(p)
|
||||
import shutil
|
||||
shutil.rmtree(logdir, True)
|
||||
else:
|
||||
p.acknowledge = get_current_time()
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return status, _server
|
||||
|
||||
def set_env_variables(self, server, **kwargs):
|
||||
"""Set environment variables"""
|
||||
if server:
|
||||
@@ -781,3 +836,16 @@ class BatchProcess(object):
|
||||
p.utility_pid)
|
||||
)
|
||||
current_app.logger.exception(e)
|
||||
|
||||
@staticmethod
|
||||
def update_server_id(_pid, _sid):
|
||||
p = Process.query.filter_by(
|
||||
user_id=current_user.id, pid=_pid
|
||||
).first()
|
||||
|
||||
if p is None:
|
||||
raise LookupError(PROCESS_NOT_FOUND)
|
||||
|
||||
# Update the cloud server id
|
||||
p.server_id = _sid
|
||||
db.session.commit()
|
||||
@@ -96,8 +96,10 @@ define('misc.bgprocess', [
|
||||
pgBrowser.Events && pgBrowser.Events.on(
|
||||
'pgadmin-bgprocess:finished:' + this.id,
|
||||
function(process) {
|
||||
if (!process.notifier)
|
||||
if (!process.notifier) {
|
||||
if (process.cloud_process == 1) process.update_cloud_server.apply(process);
|
||||
process.show.apply(process);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -175,6 +177,12 @@ define('misc.bgprocess', [
|
||||
}
|
||||
}
|
||||
|
||||
if ('cloud_process' in data && data.cloud_process == 1) {
|
||||
self.cloud_process = data.cloud_process;
|
||||
self.cloud_instance = data.cloud_instance;
|
||||
self.cloud_server_id = data.cloud_server_id;
|
||||
}
|
||||
|
||||
if ('err' in data) {
|
||||
self.err = data.err && data.err.pos;
|
||||
|
||||
@@ -309,6 +317,46 @@ define('misc.bgprocess', [
|
||||
});
|
||||
},
|
||||
|
||||
update_cloud_server: function() {
|
||||
var self = this,
|
||||
_url = url_for('cloud.update_cloud_server'),
|
||||
_data = {},
|
||||
cloud_instance = self.cloud_instance;
|
||||
if (cloud_instance != '') {
|
||||
_data = JSON.parse(cloud_instance);
|
||||
}
|
||||
|
||||
_data['instance']['sid'] = self.cloud_server_id;
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: _url,
|
||||
async: true,
|
||||
data: JSON.stringify(_data),
|
||||
contentType: 'application/json',
|
||||
})
|
||||
.done(function(res) {
|
||||
setTimeout(function() {
|
||||
let _server = res.data.node,
|
||||
_server_path = '/browser/server_group_' + _server.gid + '/' + _server.id,
|
||||
_tree = pgBrowser.tree,
|
||||
_item = _tree.findNode(_server_path);
|
||||
|
||||
if (_item) {
|
||||
_tree.addIcon(_item.domNode, {icon: _server.icon});
|
||||
let d = _tree.itemData(_item);
|
||||
d.cloud_status = 1;
|
||||
_tree.update(_item, d);
|
||||
}
|
||||
}, 10);
|
||||
})
|
||||
.fail(function(res) {
|
||||
// Try after some time only if job id present
|
||||
if (res.status != 410)
|
||||
console.warn('Failed Cloud Deployment.');
|
||||
});
|
||||
},
|
||||
|
||||
show: function() {
|
||||
var self = this;
|
||||
|
||||
@@ -523,8 +571,30 @@ define('misc.bgprocess', [
|
||||
async: true,
|
||||
contentType: 'application/json',
|
||||
})
|
||||
.done(function() {
|
||||
return;
|
||||
.done(function(res) {
|
||||
if (res.data && res.data.node) {
|
||||
setTimeout(function() {
|
||||
let _server = res.data.node,
|
||||
_server_path = '/browser/server_group_' + _server.gid + '/' + _server.id,
|
||||
_tree = pgBrowser.tree,
|
||||
_item = _tree.findNode(_server_path);
|
||||
|
||||
if (_item) {
|
||||
if(_server.status == true) {
|
||||
let _dom = _item.domNode;
|
||||
_tree.addIcon(_dom, {icon: _server.icon});
|
||||
let d = _tree.itemData(_dom);
|
||||
d.cloud_status = _server.cloud_status;
|
||||
_tree.update(_dom, d);
|
||||
}
|
||||
else {
|
||||
_tree.remove(_item.domNode);
|
||||
_tree.refresh(_item.domNode.parent);
|
||||
}
|
||||
}
|
||||
|
||||
}, 10);
|
||||
} else return;
|
||||
})
|
||||
.fail(function() {
|
||||
console.warn(arguments);
|
||||
@@ -595,7 +665,7 @@ define('misc.bgprocess', [
|
||||
var observer = this;
|
||||
|
||||
$.ajax({
|
||||
typs: 'GET',
|
||||
type: 'GET',
|
||||
timeout: 30000,
|
||||
url: url_for('bgprocess.list'),
|
||||
cache: false,
|
||||
|
||||
@@ -0,0 +1,422 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
"""Implements Cloud Deployment"""
|
||||
|
||||
import simplejson as json
|
||||
from flask import Response, url_for, session
|
||||
from flask import render_template, request, current_app
|
||||
from flask_babel import gettext
|
||||
from flask_security import login_required, current_user
|
||||
|
||||
from pgadmin.utils import PgAdminModule, html
|
||||
from pgadmin.utils.ajax import make_json_response,\
|
||||
internal_server_error, bad_request, success_return
|
||||
|
||||
from pgadmin.utils.constants import MIMETYPE_APP_JS
|
||||
from pgadmin.misc.bgprocess.processes import BatchProcess, IProcessDesc
|
||||
from pgadmin.model import db, Server, Process
|
||||
from pgadmin.misc.cloud.utils.rds import RDS, verify_aws_credentials,\
|
||||
get_aws_db_instances, get_aws_db_versions, clear_aws_session,\
|
||||
get_aws_regions
|
||||
from pgadmin.misc.cloud.utils import get_my_ip
|
||||
|
||||
from config import root
|
||||
|
||||
# set template path for sql scripts
|
||||
MODULE_NAME = 'cloud'
|
||||
server_info = {}
|
||||
|
||||
|
||||
class CloudModule(PgAdminModule):
|
||||
"""
|
||||
class CloudModule(Object):
|
||||
|
||||
It is a wizard which inherits PgAdminModule
|
||||
class and define methods to load its own
|
||||
javascript file.
|
||||
|
||||
LABEL = gettext('Browser')
|
||||
"""
|
||||
|
||||
def get_own_stylesheets(self):
|
||||
"""
|
||||
Returns:
|
||||
list: the stylesheets used by this module.
|
||||
"""
|
||||
stylesheets = []
|
||||
return stylesheets
|
||||
|
||||
def get_own_javascripts(self):
|
||||
""""
|
||||
Returns:
|
||||
list: js files used by this module
|
||||
"""
|
||||
scripts = []
|
||||
scripts.append({
|
||||
'name': 'pgadmin.misc.cloud',
|
||||
'path': url_for('cloud.index') + 'cloud',
|
||||
'when': None
|
||||
})
|
||||
scripts.append({
|
||||
'name': 'pgadmin.browser.wizard',
|
||||
'path': url_for('browser.static', filename='js/wizard'),
|
||||
'when': None
|
||||
})
|
||||
return scripts
|
||||
|
||||
def get_exposed_url_endpoints(self):
|
||||
"""
|
||||
Returns:
|
||||
list: URL endpoints for cloud module
|
||||
"""
|
||||
return ['cloud.deploy_on_cloud',
|
||||
'cloud.get_aws_db_versions',
|
||||
'cloud.verify_credentials',
|
||||
'cloud.get_aws_db_instances',
|
||||
'cloud.update_cloud_server',
|
||||
'cloud.update_cloud_process',
|
||||
'cloud.get_aws_regions',
|
||||
'cloud.get_host_ip']
|
||||
|
||||
|
||||
# Create blueprint for CloudModule class
|
||||
blueprint = CloudModule(
|
||||
MODULE_NAME, __name__, static_url_path='/misc/cloud')
|
||||
|
||||
|
||||
@blueprint.route("/")
|
||||
@login_required
|
||||
def index():
|
||||
return bad_request(
|
||||
errormsg=gettext("This URL cannot be called directly.")
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route("/cloud.js")
|
||||
@login_required
|
||||
def script():
|
||||
"""render own javascript"""
|
||||
res = Response(response=render_template(
|
||||
"cloud/js/cloud.js", _=gettext),
|
||||
status=200,
|
||||
mimetype=MIMETYPE_APP_JS)
|
||||
return res
|
||||
|
||||
|
||||
@blueprint.route('/get_host_ip/',
|
||||
methods=['GET'], endpoint='get_host_ip')
|
||||
@login_required
|
||||
def get_host_ip():
|
||||
"""test"""
|
||||
ip = get_my_ip()
|
||||
return make_json_response(data=ip)
|
||||
|
||||
|
||||
@blueprint.route('/verify_credentials/',
|
||||
methods=['POST'], endpoint='verify_credentials')
|
||||
@login_required
|
||||
def verify_credentials():
|
||||
"""Verify Credentials."""
|
||||
data = json.loads(request.data, encoding='utf-8')
|
||||
|
||||
status, msg = verify_aws_credentials(data)
|
||||
if status:
|
||||
msg = 'verified'
|
||||
|
||||
return make_json_response(success=status, info=msg)
|
||||
|
||||
|
||||
@blueprint.route('/get_aws_db_instances/',
|
||||
methods=['GET'], endpoint='get_aws_db_instances')
|
||||
@login_required
|
||||
def get_db_instances():
|
||||
"""
|
||||
Fetch AWS DB Instances based on engine version.
|
||||
"""
|
||||
# Get Engine Version
|
||||
eng_version = request.args.get('eng_version')
|
||||
status, versions = get_aws_db_instances(eng_version)
|
||||
|
||||
if not status:
|
||||
return make_json_response(
|
||||
status=410,
|
||||
success=0,
|
||||
errormsg=versions
|
||||
)
|
||||
|
||||
return make_json_response(data=versions)
|
||||
|
||||
|
||||
@blueprint.route('/get_aws_db_versions/',
|
||||
methods=['GET', 'POST'], endpoint='get_aws_db_versions')
|
||||
@login_required
|
||||
def get_db_versions():
|
||||
"""GET AWS Database Versions for AWS."""
|
||||
status, versions = get_aws_db_versions()
|
||||
if not status:
|
||||
return make_json_response(
|
||||
status=410,
|
||||
success=0,
|
||||
errormsg=str(versions)
|
||||
)
|
||||
return make_json_response(data=versions)
|
||||
|
||||
|
||||
@blueprint.route('/get_aws_regions/',
|
||||
methods=['GET', 'POST'], endpoint='get_aws_regions')
|
||||
@login_required
|
||||
def get_db_versions():
|
||||
"""GET AWS Regions for AWS."""
|
||||
status, regions = get_aws_regions()
|
||||
if not status:
|
||||
return make_json_response(
|
||||
status=410,
|
||||
success=0,
|
||||
errormsg=str(regions)
|
||||
)
|
||||
return make_json_response(data=regions)
|
||||
|
||||
|
||||
@blueprint.route(
|
||||
'/deploy', methods=['POST'], endpoint='deploy_on_cloud'
|
||||
)
|
||||
@login_required
|
||||
def deploy_on_cloud():
|
||||
"""Deploy on Cloud"""
|
||||
|
||||
data = json.loads(request.data, encoding='utf-8')
|
||||
from subprocess import Popen, PIPE
|
||||
_cmd = 'python'
|
||||
_cmd_script = '{0}/pgacloud/pgacloud.py'.format(root)
|
||||
|
||||
args = [_cmd_script,
|
||||
'--debug',
|
||||
data['cloud'],
|
||||
'--region',
|
||||
str(data['secret']['aws_region']),
|
||||
'create-instance',
|
||||
'--name',
|
||||
data['instance_details']['aws_name'],
|
||||
'--db-name',
|
||||
data['db_details']['aws_db_name'],
|
||||
'--db-username',
|
||||
data['db_details']['aws_db_username'],
|
||||
'--db-port',
|
||||
str(data['db_details']['aws_db_port']),
|
||||
'--db-version',
|
||||
str(data['instance_details']['aws_db_version']),
|
||||
'--instance-type',
|
||||
data['instance_details']['aws_instance_type'],
|
||||
'--storage-type',
|
||||
data['instance_details']['aws_storage_type'],
|
||||
'--storage-size',
|
||||
str(data['instance_details']['aws_storage_size']),
|
||||
'--public-ip',
|
||||
str(data['instance_details']['aws_public_ip']),
|
||||
]
|
||||
|
||||
if data['instance_details']['aws_storage_type'] == 'io1':
|
||||
args.append('--storage-iops')
|
||||
args.append(str(data['instance_details']['aws_storage_IOPS']))
|
||||
|
||||
_cmd_msg = '{0} {1} {2}'.format(_cmd, _cmd_script, ' '.join(args))
|
||||
try:
|
||||
sid = _create_server({
|
||||
'gid': data['db_details']['gid'],
|
||||
'name': data['instance_details']['aws_name'],
|
||||
'db': data['db_details']['aws_db_name'],
|
||||
'username': data['db_details']['aws_db_username'],
|
||||
'port': data['db_details']['aws_db_port'],
|
||||
'cloud_status': -1
|
||||
})
|
||||
|
||||
p = BatchProcess(
|
||||
desc=CloudProcessDesc(sid, _cmd_msg, data['cloud'],
|
||||
data['instance_details']['aws_name']),
|
||||
cmd=_cmd,
|
||||
args=args
|
||||
)
|
||||
|
||||
env = dict()
|
||||
env['AWS_ACCESS_KEY_ID'] = data['secret']['aws_access_key']
|
||||
env['AWS_SECRET_ACCESS_KEY'] = data['secret']['aws_secret_access_key']
|
||||
|
||||
if 'aws_session_token' in data['secret'] and\
|
||||
data['secret']['aws_session_token'] is not None:
|
||||
env['AWS_SESSION_TOKEN'] = data['secret']['aws_session_token']
|
||||
|
||||
if 'aws_db_password' in data['db_details']:
|
||||
env['AWS_DATABASE_PASSWORD'] = data[
|
||||
'db_details']['aws_db_password']
|
||||
|
||||
p.set_env_variables(None, env=env)
|
||||
p.update_server_id(p.id, sid)
|
||||
p.start()
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
return make_json_response(
|
||||
status=410,
|
||||
success=0,
|
||||
errormsg=str(e)
|
||||
)
|
||||
|
||||
# Return response
|
||||
return make_json_response(
|
||||
success=1,
|
||||
data={'job_id': 1, 'node': {
|
||||
'_id': sid,
|
||||
'_pid': data['db_details']['gid'],
|
||||
'connected': False,
|
||||
'_type': 'server',
|
||||
'icon': 'icon-server-cloud-deploy',
|
||||
'id': 'server_{}'.format(sid),
|
||||
'inode': True,
|
||||
'label': data['instance_details']['aws_name'],
|
||||
'server_type': 'pg',
|
||||
'module': 'pgadmin.node.server',
|
||||
'cloud_status': -1
|
||||
}}
|
||||
)
|
||||
|
||||
|
||||
def _create_server(data):
|
||||
"""Create Server"""
|
||||
server = Server(
|
||||
user_id=current_user.id,
|
||||
servergroup_id=data.get('gid'),
|
||||
name=data.get('name'),
|
||||
maintenance_db=data.get('db'),
|
||||
username=data.get('username'),
|
||||
ssl_mode='prefer',
|
||||
cloud_status=data.get('cloud_status'),
|
||||
connect_timeout=30,
|
||||
)
|
||||
|
||||
db.session.add(server)
|
||||
db.session.commit()
|
||||
|
||||
return server.id
|
||||
|
||||
|
||||
def update_server(data):
|
||||
"""Update Server."""
|
||||
server_data = data
|
||||
server = Server.query.filter_by(
|
||||
user_id=current_user.id,
|
||||
id=server_data['instance']['sid']
|
||||
).first()
|
||||
|
||||
if server is None:
|
||||
return False, "Could not find the server."
|
||||
|
||||
if server_data['instance'] == '' or\
|
||||
not server_data['instance']['status']:
|
||||
db.session.delete(server)
|
||||
else:
|
||||
server.host = server_data['instance']['Hostname']
|
||||
server.port = server_data['instance']['Port']
|
||||
server.cloud_status = 1
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return False, e.message
|
||||
|
||||
_server = {
|
||||
'id': server.id,
|
||||
'servergroup_id': server.servergroup_id,
|
||||
'name': server.name,
|
||||
'cloud_status': server.cloud_status
|
||||
}
|
||||
if not server_data['instance']['status']:
|
||||
_server['status'] = False
|
||||
else:
|
||||
_server['status'] = True
|
||||
clear_aws_session()
|
||||
|
||||
return True, _server
|
||||
|
||||
|
||||
@blueprint.route(
|
||||
'/update_cloud_process/<sid>', methods=['GET'],
|
||||
endpoint='update_cloud_process'
|
||||
)
|
||||
@login_required
|
||||
def update_cloud_process(sid):
|
||||
"""Update Cloud Server Process"""
|
||||
_process = Process.query.filter_by(user_id=current_user.id,
|
||||
server_id=sid).first()
|
||||
_process.acknowledge = None
|
||||
db.session.commit()
|
||||
return success_return()
|
||||
|
||||
|
||||
@blueprint.route(
|
||||
'/update_cloud_server', methods=['POST'],
|
||||
endpoint='update_cloud_server'
|
||||
)
|
||||
@login_required
|
||||
def update_cloud_server():
|
||||
"""Update Cloud Server."""
|
||||
server_data = json.loads(request.data, encoding='utf-8')
|
||||
status, server = update_server(server_data)
|
||||
|
||||
if not status:
|
||||
return make_json_response(
|
||||
status=410, success=0, errormsg=server
|
||||
)
|
||||
|
||||
return make_json_response(
|
||||
success=1,
|
||||
data={'node': {
|
||||
'sid': server.id,
|
||||
'gid': server.servergroup_id,
|
||||
'_type': 'server',
|
||||
'icon': 'icon-server-not-connected',
|
||||
'id': 'server_{}'.format(server.id),
|
||||
'label': server.name
|
||||
}}
|
||||
)
|
||||
|
||||
|
||||
class CloudProcessDesc(IProcessDesc):
|
||||
"""Cloud Server Process Description."""
|
||||
def __init__(self, _sid, _cmd, _provider, _instance_name):
|
||||
self.sid = _sid
|
||||
self.cmd = _cmd
|
||||
self.instance_name = _instance_name
|
||||
self.provider = 'Amazon RDS'
|
||||
|
||||
if _provider == 'rds':
|
||||
self.provider = 'Amazon RDS'
|
||||
elif _provider == 'azure':
|
||||
self.provider = 'Azure PostgreSQL'
|
||||
else:
|
||||
self.provider = 'EDB Big Animal'
|
||||
|
||||
@property
|
||||
def message(self):
|
||||
return "Deployment on {0} is started for instance {1}.".format(
|
||||
self.provider, self.instance_name)
|
||||
|
||||
def details(self, cmd, args):
|
||||
res = '<div>' + self.message
|
||||
res += '</div><div class="py-1">'
|
||||
res += '<div class="pg-bg-cmd enable-selection p-1">'
|
||||
res += html.safe_str(self.cmd)
|
||||
res += '</div></div>'
|
||||
|
||||
return res
|
||||
|
||||
@property
|
||||
def type_desc(self):
|
||||
return "Cloud Deployment"
|
||||
@@ -0,0 +1,469 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import url_for from 'sources/url_for';
|
||||
import React from 'react';
|
||||
import { Box, Table, TableBody, TableCell, TableHead, TableRow, Paper } from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import Wizard from '../../../../static/js/helpers/wizard/Wizard';
|
||||
import WizardStep from '../../../../static/js/helpers/wizard/WizardStep';
|
||||
import {FormFooterMessage, MESSAGE_TYPE, InputToggle } from '../../../../static/js/components/FormComponents';
|
||||
import getApiInstance from '../../../../static/js/api_instance';
|
||||
import SchemaView from '../../../../static/js/SchemaView';
|
||||
import Alertify from 'pgadmin.alertifyjs';
|
||||
import PropTypes from 'prop-types';
|
||||
import {CloudInstanceDetailsSchema, CloudDBCredSchema, DatabaseSchema} from './cloud_db_details_schema.ui';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import { getNodeAjaxOptions, getNodeListById } from 'pgbrowser/node_ajax';
|
||||
import { commonTableStyles } from '../../../../static/js/Theme';
|
||||
import clsx from 'clsx';
|
||||
|
||||
const useStyles = makeStyles(() =>
|
||||
({
|
||||
messageBox: {
|
||||
marginBottom: '1em',
|
||||
display: 'flex',
|
||||
},
|
||||
messagePadding: {
|
||||
flex: 2.5
|
||||
},
|
||||
toggleButton: {
|
||||
height: '100px',
|
||||
},
|
||||
table: {
|
||||
marginLeft: '4px',
|
||||
marginTop: '12px',
|
||||
},
|
||||
tableCellHeading: {
|
||||
fontWeight: 'bold',
|
||||
paddingLeft: '9px',
|
||||
},
|
||||
tableCell: {
|
||||
padding: '9px',
|
||||
paddingLeft: '11px',
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
export default function CloudWizard({ nodeInfo, nodeData }) {
|
||||
const classes = useStyles();
|
||||
const tableClasses = commonTableStyles();
|
||||
|
||||
var steps = ['Cloud Provider', 'Credentials', 'Instance Specification', 'Database Details', 'Review'];
|
||||
const [currentStep, setCurrentStep] = React.useState('');
|
||||
const [selectionVal, setCloudSelection] = React.useState('');
|
||||
const [errMsg, setErrMsg] = React.useState('');
|
||||
const [cloudInstanceDetailsInstance, setCloudInstanceDetailsInstance] = React.useState();
|
||||
const [cloudDBCredInstance, setCloudDBCredInstance] = React.useState();
|
||||
const [cloudDBInstance, setCloudDBInstance] = React.useState();
|
||||
const [cloudInstanceDetails, setCloudInstanceDetails] = React.useState({});
|
||||
const [cloudDBCred, setCloudDBCred] = React.useState({});
|
||||
const [cloudDBDetails, setCloudDBDetails] = React.useState({});
|
||||
const [callRDSAPI, setCallRDSAPI] = React.useState({});
|
||||
const [hostIP, setHostIP] = React.useState('127.0.0.1/32');
|
||||
const axiosApi = getApiInstance();
|
||||
|
||||
React.useEffect(() => {
|
||||
let _url = url_for('cloud.get_host_ip') ;
|
||||
axiosApi.get(_url)
|
||||
.then((res) => {
|
||||
if (res.data.data) {
|
||||
setHostIP(res.data.data);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
Alertify.error(gettext(`Error while getting the host ip: ${error.response.data.errormsg}`));
|
||||
});
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (callRDSAPI == 2) {
|
||||
const cloudDBInstanceSchema = new CloudInstanceDetailsSchema({
|
||||
version: ()=>getNodeAjaxOptions('get_aws_db_versions', pgAdmin.Browser.Nodes['server'], nodeInfo, nodeData, {
|
||||
useCache:false,
|
||||
cacheNode: 'server',
|
||||
customGenerateUrl: ()=>{
|
||||
return url_for('cloud.get_aws_db_versions');
|
||||
}
|
||||
}),
|
||||
getInstances: (engine, reload, options) =>
|
||||
{
|
||||
return new Promise((resolve, reject)=>{
|
||||
const api = getApiInstance();
|
||||
var _url = url_for('cloud.get_aws_db_instances') ;
|
||||
|
||||
if (engine) _url += '?eng_version=' + engine;
|
||||
if (reload || options === undefined || options.length == 0) {
|
||||
api.get(_url)
|
||||
.then(res=>{
|
||||
let data = res.data.data;
|
||||
resolve(data);
|
||||
})
|
||||
.catch((err)=>{
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
resolve(options);
|
||||
}
|
||||
});
|
||||
},
|
||||
instance_type: ()=>getNodeAjaxOptions('get_aws_db_instances', pgAdmin.Browser.Nodes['server'], nodeInfo, nodeData, {
|
||||
useCache:false,
|
||||
cacheNode: 'server',
|
||||
customGenerateUrl: ()=>{
|
||||
return url_for('cloud.get_aws_db_instances');
|
||||
}
|
||||
}),
|
||||
server_groups: ()=>getNodeListById(pgAdmin.Browser.Nodes['server_group'], nodeInfo, nodeData),
|
||||
}, {
|
||||
gid: nodeInfo['server_group']._id,
|
||||
hostIP: hostIP,
|
||||
});
|
||||
setCloudInstanceDetailsInstance(cloudDBInstanceSchema);
|
||||
}
|
||||
}, [callRDSAPI]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const cloudDBCredSchema = new CloudDBCredSchema({
|
||||
regions: ()=>getNodeAjaxOptions('get_aws_regions', pgAdmin.Browser.Nodes['server'], nodeInfo, nodeData, {
|
||||
useCache:false,
|
||||
cacheNode: 'server',
|
||||
customGenerateUrl: ()=>{
|
||||
return url_for('cloud.get_aws_regions');
|
||||
}
|
||||
}),
|
||||
});
|
||||
setCloudDBCredInstance(cloudDBCredSchema);
|
||||
|
||||
const cloudDBSchema = new DatabaseSchema({
|
||||
server_groups: ()=>getNodeListById(pgAdmin.Browser.Nodes['server_group'], nodeInfo, nodeData),
|
||||
},
|
||||
{
|
||||
gid: nodeInfo['server_group']._id,
|
||||
}
|
||||
);
|
||||
setCloudDBInstance(cloudDBSchema);
|
||||
|
||||
}, []);
|
||||
|
||||
const wizardStepChange = (data) => {
|
||||
setCurrentStep(data.currentStep);
|
||||
};
|
||||
|
||||
const validateCloudStep1 = (cloudDBCred) => {
|
||||
let isError = false;
|
||||
if (isEmptyString(cloudDBCred.aws_access_key) || isEmptyString(cloudDBCred.aws_secret_access_key)) {
|
||||
isError = true;
|
||||
}
|
||||
return isError;
|
||||
};
|
||||
|
||||
const validateCloudStep2 = (cloudInstanceDetails, host_ip) => {
|
||||
let isError = false;
|
||||
if (isEmptyString(cloudInstanceDetails.aws_name) ||
|
||||
isEmptyString(cloudInstanceDetails.aws_db_version) || isEmptyString(cloudInstanceDetails.aws_instance_type) ||
|
||||
isEmptyString(cloudInstanceDetails.aws_storage_type)|| isEmptyString(cloudInstanceDetails.aws_storage_size)) {
|
||||
isError = true;
|
||||
}
|
||||
|
||||
if(cloudInstanceDetails.aws_storage_type == 'io1' && isEmptyString(cloudInstanceDetails.aws_storage_IOPS)) {
|
||||
isError = true;
|
||||
}
|
||||
if (isEmptyString(cloudInstanceDetails.aws_public_ip)) cloudInstanceDetails.aws_public_ip = host_ip;
|
||||
return isError;
|
||||
};
|
||||
|
||||
const validateCloudStep3 = (cloudDBDetails) => {
|
||||
let isError = false;
|
||||
if (isEmptyString(cloudDBDetails.aws_db_name) ||
|
||||
isEmptyString(cloudDBDetails.aws_db_username) || isEmptyString(cloudDBDetails.aws_db_password)) {
|
||||
isError = true;
|
||||
}
|
||||
if (isEmptyString(cloudDBDetails.aws_db_port)) cloudDBDetails.aws_db_port = 5432;
|
||||
if (isEmptyString(cloudDBDetails.gid)) cloudDBDetails.gid = nodeInfo['server_group']._id;
|
||||
return isError;
|
||||
};
|
||||
|
||||
const getStorageType = (cloudInstanceDetails) => {
|
||||
let _storage_type = 'General Purpose SSD (gp2)',
|
||||
_io1 = undefined;
|
||||
|
||||
if(cloudInstanceDetails.aws_storage_type == 'gp2') _storage_type = 'General Purpose SSD (gp2)';
|
||||
else if(cloudInstanceDetails.aws_storage_type == 'io1') {
|
||||
_storage_type = 'Provisioned IOPS SSD (io1)';
|
||||
_io1 = cloudInstanceDetails.aws_storage_IOPS;
|
||||
}
|
||||
else if(cloudInstanceDetails.aws_storage_type == 'magnetic') _storage_type = 'Magnetic';
|
||||
|
||||
return [_io1, _storage_type];
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
var _url = url_for('cloud.deploy_on_cloud');
|
||||
const post_data = {
|
||||
gid: nodeInfo.server_group._id,
|
||||
cloud: selectionVal,
|
||||
secret: cloudDBCred,
|
||||
instance_details:cloudInstanceDetails,
|
||||
db_details: cloudDBDetails
|
||||
};
|
||||
axiosApi.post(_url, post_data)
|
||||
.then((res) => {
|
||||
pgAdmin.Browser.Events.trigger('pgadmin:browser:tree:add', res.data.data.node, {'server_group': nodeInfo['server_group']});
|
||||
pgAdmin.Browser.Events.trigger('pgadmin-bgprocess:created', Alertify.cloudWizardDialog());
|
||||
Alertify.cloudWizardDialog().close();
|
||||
})
|
||||
.catch((error) => {
|
||||
Alertify.error(gettext(`Error while saving cloud wizard data: ${error.response.data.errormsg}`));
|
||||
});
|
||||
};
|
||||
|
||||
const disableNextCheck = () => {
|
||||
setCallRDSAPI(currentStep);
|
||||
let isError = false;
|
||||
switch (currentStep) {
|
||||
case 0:
|
||||
setCloudSelection('rds');
|
||||
break;
|
||||
case 1:
|
||||
isError = validateCloudStep1(cloudDBCred);
|
||||
break;
|
||||
case 2:
|
||||
isError = validateCloudStep2(cloudInstanceDetails, hostIP);
|
||||
break;
|
||||
case 3:
|
||||
isError = validateCloudStep3(cloudDBDetails);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return isError;
|
||||
};
|
||||
|
||||
const onBeforeNext = (activeStep) => {
|
||||
return new Promise((resolve, reject)=>{
|
||||
if(activeStep == 1) {
|
||||
setErrMsg([MESSAGE_TYPE.INFO, 'Validating credentials...']);
|
||||
var _url = url_for('cloud.verify_credentials');
|
||||
const post_data = {
|
||||
cloud: selectionVal,
|
||||
secret: cloudDBCred,
|
||||
};
|
||||
axiosApi.post(_url, post_data)
|
||||
.then((res) => {
|
||||
if(!res.data.success) {
|
||||
setErrMsg([MESSAGE_TYPE.ERROR, res.data.info]);
|
||||
reject();
|
||||
} else {
|
||||
setErrMsg(['', '']);
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setErrMsg([MESSAGE_TYPE.ERROR, 'Error while checking cloud credentials']);
|
||||
reject();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onDialogHelp = () => {
|
||||
window.open(url_for('help.static', { 'filename': 'cloud_deployment.html' }), 'pgadmin_help');
|
||||
};
|
||||
|
||||
function createData(name, value) {
|
||||
return { name, value };
|
||||
}
|
||||
|
||||
let cloud = '';
|
||||
|
||||
switch (selectionVal) {
|
||||
case 'rds':
|
||||
cloud = 'Amazon RDS';
|
||||
break;
|
||||
case 'azure':
|
||||
cloud = 'Azure PostgreSQL';
|
||||
break;
|
||||
case 'biganimal':
|
||||
cloud = 'EDB Big Animal';
|
||||
break;
|
||||
}
|
||||
|
||||
const rows1 = [
|
||||
createData('Cloud', cloud),
|
||||
createData('Instance name', cloudInstanceDetails.aws_name),
|
||||
createData('Public IP', cloudInstanceDetails.aws_public_ip),
|
||||
];
|
||||
|
||||
const rows2 = [
|
||||
createData('PostgreSQL version', cloudInstanceDetails.aws_db_version),
|
||||
createData('Instance type', cloudInstanceDetails.aws_instance_type),
|
||||
];
|
||||
|
||||
let _storage_type = getStorageType(cloudInstanceDetails);
|
||||
|
||||
const rows3 = [
|
||||
createData('Storage type', _storage_type[1]),
|
||||
createData('Allocated storage', cloudInstanceDetails.aws_storage_size + ' GiB'),
|
||||
];
|
||||
if (_storage_type[0] !== undefined) {
|
||||
rows3.push(createData('Provisioned IOPS', _storage_type[0]));
|
||||
}
|
||||
|
||||
const rows4 = [
|
||||
createData('Database name', cloudDBDetails.aws_db_name),
|
||||
createData('Username', cloudDBDetails.aws_db_username),
|
||||
createData('Password', 'xxxxxxx'),
|
||||
createData('Port', cloudDBDetails.aws_db_port),
|
||||
];
|
||||
|
||||
const onErrClose = React.useCallback(()=>{
|
||||
setErrMsg([]);
|
||||
});
|
||||
|
||||
|
||||
const displayTableRows = (rows) => {
|
||||
return rows.map((row) => (
|
||||
<TableRow key={row.name} >
|
||||
<TableCell scope="row">{row.name}</TableCell>
|
||||
<TableCell align="right">{row.value}</TableCell>
|
||||
</TableRow>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Wizard
|
||||
title={gettext('Deploy Cloud Instance')}
|
||||
stepList={steps}
|
||||
disableNextStep={disableNextCheck}
|
||||
onStepChange={wizardStepChange}
|
||||
onSave={onSave}
|
||||
onHelp={onDialogHelp}
|
||||
beforeNext={onBeforeNext}>
|
||||
<WizardStep stepId={0}>
|
||||
<Box className={classes.messageBox}>
|
||||
<Box className={classes.messagePadding}>{gettext('Deploy on Amazon RDS cloud.')}</Box>
|
||||
</Box>
|
||||
<Box className={classes.messageBox}>
|
||||
<InputToggle
|
||||
value='rds'
|
||||
options={[{'label': gettext('Amazon RDS'), value: 'rds'}]}
|
||||
className={classes.toggleButton}
|
||||
onChange={(value) => {
|
||||
setCloudSelection(value);}
|
||||
}
|
||||
>
|
||||
</InputToggle>
|
||||
</Box>
|
||||
<Box className={classes.messageBox}>
|
||||
<Box className={classes.messagePadding}>{gettext('More cloud providers are coming soon...')}</Box>
|
||||
</Box>
|
||||
</WizardStep>
|
||||
<WizardStep stepId={1} >
|
||||
{cloudDBCredInstance &&
|
||||
<SchemaView
|
||||
formType={'dialog'}
|
||||
getInitData={() => { /*This is intentional (SonarQube)*/ }}
|
||||
viewHelperProps={{ mode: 'create' }}
|
||||
schema={cloudDBCredInstance}
|
||||
showFooter={false}
|
||||
isTabView={false}
|
||||
onDataChange={(isChanged, changedData) => {
|
||||
setCloudDBCred(changedData);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
<FormFooterMessage type={errMsg[0]} message={errMsg[1]} onClose={onErrClose} />
|
||||
</WizardStep>
|
||||
<WizardStep stepId={2} >
|
||||
{cloudInstanceDetailsInstance &&
|
||||
<SchemaView
|
||||
formType={'dialog'}
|
||||
getInitData={() => { /*This is intentional (SonarQube)*/ }}
|
||||
viewHelperProps={{ mode: 'create' }}
|
||||
schema={cloudInstanceDetailsInstance}
|
||||
showFooter={false}
|
||||
isTabView={false}
|
||||
onDataChange={(isChanged, changedData) => {
|
||||
setCloudInstanceDetails(changedData);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</WizardStep>
|
||||
<WizardStep stepId={3} >
|
||||
{cloudDBInstance &&
|
||||
<SchemaView
|
||||
formType={'dialog'}
|
||||
getInitData={() => { /*This is intentional (SonarQube)*/ }}
|
||||
viewHelperProps={{ mode: 'create' }}
|
||||
schema={cloudDBInstance}
|
||||
showFooter={false}
|
||||
isTabView={false}
|
||||
onDataChange={(isChanged, changedData) => {
|
||||
setCloudDBDetails(changedData);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</WizardStep>
|
||||
<WizardStep stepId={4} >
|
||||
<Box className={classes.boxText}>{gettext('Please review the details before creating the cloud instance.')}</Box>
|
||||
<Paper variant="outlined" elevation={0} className={classes.summaryContainer}>
|
||||
<Table aria-label="simple table" className={clsx(tableClasses.table)}>
|
||||
<TableBody>
|
||||
{displayTableRows(rows1)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Table aria-label="simple table" className={clsx(tableClasses.table)}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>{gettext('Version and Instance Details')}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{displayTableRows(rows2)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Table aria-label="simple table" className={clsx(tableClasses.table)}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>{gettext('Storage Details')}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{displayTableRows(rows3)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Table aria-label="simple table" className={clsx(tableClasses.table)}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>{gettext('Database Details')}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{displayTableRows(rows4)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Paper>
|
||||
</WizardStep>
|
||||
</Wizard>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
CloudWizard.propTypes = {
|
||||
nodeInfo: PropTypes.object,
|
||||
nodeData: PropTypes.object,
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Theme from 'sources/Theme';
|
||||
import CloudWizard from './CloudWizard';
|
||||
|
||||
|
||||
// Cloud Wizard
|
||||
define('pgadmin.misc.cloud', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||
'pgadmin.alertifyjs',
|
||||
'pgadmin.browser',
|
||||
'pgadmin.browser.wizard',
|
||||
], function(
|
||||
gettext, url_for, $, _, Alertify, pgBrowser
|
||||
) {
|
||||
|
||||
// if module is already initialized, refer to that.
|
||||
if (pgBrowser.Cloud) {
|
||||
return pgBrowser.Cloud;
|
||||
}
|
||||
|
||||
|
||||
// Create an Object Cloud of pgBrowser class
|
||||
pgBrowser.Cloud = {
|
||||
init: function() {
|
||||
if (this.initialized)
|
||||
return;
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
// Define the nodes on which the menus to be appear
|
||||
var menus = [{
|
||||
name: 'register_and_deploy_cloud_instance',
|
||||
module: this,
|
||||
applies: ['object', 'context'],
|
||||
callback: 'start_cloud_wizard',
|
||||
priority: 15,
|
||||
label: gettext('Deploy Cloud Instance...'),
|
||||
icon: 'wcTabIcon icon-server',
|
||||
enable: true,
|
||||
data: {action: 'create'},
|
||||
category: 'register',
|
||||
node: 'server_group',
|
||||
}, {
|
||||
name: 'register_and_deploy_cloud_instance',
|
||||
module: this,
|
||||
applies: ['object', 'context'],
|
||||
callback: 'start_cloud_wizard',
|
||||
priority: 15,
|
||||
label: gettext('Deploy Cloud Instance...'),
|
||||
icon: 'wcTabIcon icon-server',
|
||||
enable: true,
|
||||
data: {action: 'create'},
|
||||
category: 'register',
|
||||
node: 'server',
|
||||
}];
|
||||
|
||||
pgBrowser.add_menus(menus);
|
||||
return this;
|
||||
},
|
||||
|
||||
// Callback to draw Wizard Dialog
|
||||
start_cloud_wizard: function() {
|
||||
|
||||
// Declare Wizard dialog
|
||||
if (!Alertify.cloudWizardDialog) {
|
||||
Alertify.dialog('cloudWizardDialog', function factory() {
|
||||
|
||||
// Generate wizard main container
|
||||
var $container = $('<div class=\'wizard_dlg\' id=\'cloudWizardDlg\'></div>');
|
||||
return {
|
||||
main: function () {
|
||||
/*This is intentional (SonarQube)*/
|
||||
},
|
||||
setup: function () {
|
||||
return {
|
||||
// Set options for dialog
|
||||
options: {
|
||||
frameless: true,
|
||||
resizable: true,
|
||||
autoReset: false,
|
||||
maximizable: true,
|
||||
closable: true,
|
||||
closableByDimmer: false,
|
||||
modal: true,
|
||||
pinnable: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
build: function () {
|
||||
this.elements.content.appendChild($container.get(0));
|
||||
Alertify.pgDialogBuild.apply(this);
|
||||
var t = pgBrowser.tree,
|
||||
i = t.selected(),
|
||||
d = this.d = i ? t.itemData(i) : undefined,
|
||||
info = this.info = pgBrowser.tree.getTreeNodeHierarchy(i);
|
||||
|
||||
setTimeout(function () {
|
||||
if (document.getElementById('cloudWizardDlg')) {
|
||||
ReactDOM.render(
|
||||
<Theme>
|
||||
<CloudWizard nodeInfo={info} nodeData={d} />
|
||||
</Theme>,
|
||||
document.getElementById('cloudWizardDlg'));
|
||||
Alertify.cloudWizardDialog().elements.modal.style.maxHeight=0;
|
||||
Alertify.cloudWizardDialog().elements.modal.style.maxWidth='none';
|
||||
Alertify.cloudWizardDialog().elements.modal.style.overflow='visible';
|
||||
Alertify.cloudWizardDialog().elements.dimmer.style.display='none';
|
||||
}
|
||||
}, 500);
|
||||
|
||||
},
|
||||
prepare: function () {
|
||||
$container.empty().append('<div class=\'cloud_wizard_container\'></div>');
|
||||
},
|
||||
hooks: {
|
||||
// Triggered when the dialog is closed
|
||||
onclose: function () {
|
||||
// Clear the view and remove the react component.
|
||||
return setTimeout((function () {
|
||||
ReactDOM.unmountComponentAtNode(document.getElementById('cloudWizardDlg'));
|
||||
return Alertify.cloudWizardDialog().destroy();
|
||||
}), 10);
|
||||
},
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
// Call Grant Wizard Dialog and set dimensions for wizard
|
||||
Alertify.cloudWizardDialog('').set({
|
||||
onmaximize:function(){
|
||||
Alertify.cloudWizardDialog().elements.modal.style.maxHeight='initial';
|
||||
},
|
||||
onrestore:function(){
|
||||
Alertify.cloudWizardDialog().elements.modal.style.maxHeight=0;
|
||||
},
|
||||
}).resizeTo(920, 620);
|
||||
},
|
||||
|
||||
|
||||
};
|
||||
|
||||
return pgBrowser.Cloud;
|
||||
});
|
||||
@@ -0,0 +1,308 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
|
||||
|
||||
class CloudInstanceDetailsSchema extends BaseUISchema {
|
||||
constructor(fieldOptions = {}, initValues = {}) {
|
||||
super({
|
||||
oid: undefined,
|
||||
aws_name: '',
|
||||
aws_public_ip: initValues.hostIP,
|
||||
...initValues
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
...fieldOptions,
|
||||
};
|
||||
this.initValues = initValues;
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [
|
||||
{
|
||||
id: 'aws_name', label: gettext('Instance name'), type: 'text',
|
||||
mode: ['create'], noEmpty: true,
|
||||
}, {
|
||||
id: 'aws_public_ip', label: gettext('Public IP range'), type: 'text',
|
||||
mode: ['create'],
|
||||
helpMessage: gettext('IP Address range for permitting the inbound traffic. Ex: 127.0.0.1/32, add multiple ip addresses/ranges by comma separated.'),
|
||||
}, {
|
||||
type: 'nested-fieldset', label: gettext('Version & Instance'),
|
||||
mode: ['create'],
|
||||
schema: new InstanceSchema(this.fieldOptions.version,
|
||||
this.fieldOptions.instance_type,
|
||||
this.fieldOptions.getInstances),
|
||||
}, {
|
||||
type: 'nested-fieldset', label: gettext('Storage'),
|
||||
mode: ['create'],
|
||||
schema: new StorageSchema(),
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CloudDBCredSchema extends BaseUISchema {
|
||||
constructor(fieldOptions = {}, initValues = {}) {
|
||||
super({
|
||||
oid: null,
|
||||
aws_region: '',
|
||||
aws_access_key: '',
|
||||
aws_secret_access_key: '',
|
||||
aws_session_token: '',
|
||||
is_valid_cred: false,
|
||||
...initValues
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
...fieldOptions,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [
|
||||
{
|
||||
id: 'aws_region', label: gettext('Region'),
|
||||
type: 'select',
|
||||
options: this.fieldOptions.regions,
|
||||
controlProps: { allowClear: false },
|
||||
noEmpty: true,
|
||||
helpMessage: gettext('The cloud instance will be deployed in the selected region.')
|
||||
},{
|
||||
id: 'aws_access_key', label: gettext('AWS access key'), type: 'text',
|
||||
mode: ['create'], noEmpty: true,
|
||||
}, {
|
||||
id: 'aws_secret_access_key', label: gettext('AWS secret access key'), type: 'password',
|
||||
mode: ['create'], noEmpty: true,
|
||||
}, {
|
||||
id: 'aws_session_token', label: gettext('AWS session token'), type: 'multiline',
|
||||
mode: ['create'], noEmpty: false,
|
||||
helpMessage: gettext('Temporary AWS session required session token.')
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class DatabaseSchema extends BaseUISchema {
|
||||
constructor(fieldOptions = {}, initValues={}) {
|
||||
super({
|
||||
oid: undefined,
|
||||
gid: undefined,
|
||||
aws_db_name: '',
|
||||
aws_db_username: '',
|
||||
aws_db_password: '',
|
||||
aws_db_confirm_password: '',
|
||||
aws_db_port: 5432,
|
||||
...initValues,
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
...fieldOptions,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
validate(data, setErrMsg) {
|
||||
if(!isEmptyString(data.aws_db_password) && !isEmptyString(data.aws_db_confirm_password)
|
||||
&& data.aws_db_password != data.aws_db_confirm_password) {
|
||||
setErrMsg('aws_db_confirm_password', gettext('Passwords do not match.'));
|
||||
return true;
|
||||
}
|
||||
if (!isEmptyString(data.aws_db_confirm_password) && data.aws_db_confirm_password.length < 8) {
|
||||
setErrMsg('aws_db_confirm_password', gettext('Password must be 8 characters or more.'));
|
||||
return true;
|
||||
}
|
||||
if (data.aws_db_confirm_password.includes('\'') || data.aws_db_confirm_password.includes('"') ||
|
||||
data.aws_db_confirm_password.includes('@') || data.aws_db_confirm_password.includes('/')) {
|
||||
setErrMsg('aws_db_confirm_password', gettext('Invalid passowrd.'));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [{
|
||||
id: 'gid', label: gettext('Server group'), type: 'select',
|
||||
options: this.fieldOptions.server_groups,
|
||||
mode: ['create'],
|
||||
controlProps: { allowClear: false },
|
||||
noEmpty: true,
|
||||
}, {
|
||||
id: 'aws_db_name', label: gettext('Database name'), type: 'text',
|
||||
mode: ['create'], noEmpty: true,
|
||||
}, {
|
||||
id: 'aws_db_username', label: gettext('Username'), type: 'text',
|
||||
mode: ['create'], noEmpty: true,
|
||||
}, {
|
||||
id: 'aws_db_password', label: gettext('Password'), type: 'password',
|
||||
mode: ['create'], noEmpty: true,
|
||||
helpMessage: gettext('At least 8 printable ASCII characters. Can not contain any of the following: / \(slash\), \'\(single quote\), "\(double quote\) and @ \(at sign\).')
|
||||
}, {
|
||||
id: 'aws_db_confirm_password', label: gettext('Confirm password'),
|
||||
type: 'password',
|
||||
mode: ['create'], noEmpty: true,
|
||||
}, {
|
||||
id: 'aws_db_port', label: gettext('Port'), type: 'text',
|
||||
mode: ['create'], noEmpty: true,
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
export class InstanceSchema extends BaseUISchema {
|
||||
constructor(versionOpts, instanceOpts, getInstances) {
|
||||
super({
|
||||
aws_db_version: '',
|
||||
aws_db_instance_class: 'm',
|
||||
aws_instance_type: '',
|
||||
reload_instances: true,
|
||||
});
|
||||
this.versionOpts = versionOpts;
|
||||
this.instanceOpts = instanceOpts;
|
||||
this.getInstances = getInstances;
|
||||
this.instanceData = [];
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [{
|
||||
id: 'aws_db_version', label: gettext('Database version'),
|
||||
type: 'select',
|
||||
options: this.versionOpts,
|
||||
controlProps: { allowClear: false },
|
||||
deps: ['aws_name'],
|
||||
noEmpty: true,
|
||||
},{
|
||||
id: 'aws_db_instance_class', label: gettext('Instance class'),
|
||||
type: 'toggle',
|
||||
options: [
|
||||
{'label': gettext('Standard classes (includes m classes)'), value: 'm'},
|
||||
{'label': gettext('Memory optimized classes (includes r & x classes)'), value: 'x'},
|
||||
{'label': gettext('Burstable classes (includes t classes)'), value: 't'},
|
||||
], noEmpty: true, orientation: 'vertical',
|
||||
},{
|
||||
id: 'aws_instance_type', label: gettext('Instance type'),
|
||||
options: this.instanceOpts,
|
||||
deps: ['aws_db_version', 'aws_db_instance_class'],
|
||||
depChange: (state, source)=> {
|
||||
if (source[0] == 'aws_db_instance_class') {
|
||||
return {reload_instances: false};
|
||||
} else {
|
||||
state.instanceData = [];
|
||||
return {reload_instances: true};
|
||||
}
|
||||
},
|
||||
type: (state) => {
|
||||
return {
|
||||
type: 'select',
|
||||
options: ()=>this.getInstances(state.aws_db_version,
|
||||
state.reload_instances, state.instanceData),
|
||||
optionsLoaded: (options) => { state.instanceData = options; },
|
||||
optionsReloadBasis: state.aws_db_version + (state.aws_db_instance_class || 'm'),
|
||||
controlProps: {
|
||||
allowClear: false,
|
||||
filter: (options) => {
|
||||
if (options.length == 0) return;
|
||||
let pattern = 'db.m';
|
||||
let pattern_1 = 'db.m';
|
||||
|
||||
if (state.aws_db_instance_class) {
|
||||
pattern = 'db.' + state.aws_db_instance_class;
|
||||
pattern_1 = 'db.' + state.aws_db_instance_class;
|
||||
}
|
||||
if (state.aws_db_instance_class == 'x') {
|
||||
pattern_1 = 'db.' + 'r';
|
||||
}
|
||||
return options.filter((option) => {
|
||||
return (option.value.includes(pattern) || option.value.includes(pattern_1));
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
},
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class StorageSchema extends BaseUISchema {
|
||||
constructor() {
|
||||
super({
|
||||
aws_storage_type: 'io1',
|
||||
aws_storage_size: 100,
|
||||
aws_storage_IOPS: 3000,
|
||||
aws_storage_msg: 'Minimum: 20 GiB. Maximum: 16,384 GiB.'
|
||||
});
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [
|
||||
{
|
||||
id: 'aws_storage_type', label: gettext('Storage type'), type: 'select',
|
||||
mode: ['create'],
|
||||
options: [
|
||||
{'label': gettext('General Purpose SSD (gp2)'), 'value': 'gp2'},
|
||||
{'label': gettext('Provisioned IOPS SSD (io1)'), 'value': 'io1'},
|
||||
{'label': gettext('Magnetic'), 'value': 'standard'}
|
||||
], noEmpty: true,
|
||||
},{
|
||||
id: 'aws_storage_size', label: gettext('Allocated storage'), type: 'text',
|
||||
mode: ['create'], noEmpty: true, deps: ['aws_storage_type'],
|
||||
depChange: (state, source)=> {
|
||||
if (source[0] !== 'aws_storage_size')
|
||||
if(state.aws_storage_type === 'io1') {
|
||||
return {aws_storage_size: 100};
|
||||
} else if(state.aws_storage_type === 'gp2') {
|
||||
return {aws_storage_size: 20};
|
||||
} else {
|
||||
return {aws_storage_size: 5};
|
||||
}
|
||||
},
|
||||
helpMessage: gettext('Size in GiB.')
|
||||
}, {
|
||||
id: 'aws_storage_IOPS', label: gettext('Provisioned IOPS'), type: 'text',
|
||||
mode: ['create'],
|
||||
visible: (state) => {
|
||||
if(state.aws_storage_type === 'io1') return true;
|
||||
return false;
|
||||
} , deps: ['aws_storage_type'],
|
||||
depChange: (state, source) => {
|
||||
if (source[0] !== 'aws_storage_IOPS') {
|
||||
if(state.aws_storage_type === 'io1') {
|
||||
return {aws_storage_IOPS: 3000};
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
CloudInstanceDetailsSchema,
|
||||
CloudDBCredSchema,
|
||||
DatabaseSchema,
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
# ##########################################################################
|
||||
# #
|
||||
# # pgAdmin 4 - PostgreSQL Tools
|
||||
# #
|
||||
# # Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
# # This software is released under the PostgreSQL Licence
|
||||
# #
|
||||
# ##########################################################################
|
||||
|
||||
import urllib3
|
||||
import ipaddress
|
||||
|
||||
|
||||
def get_my_ip():
|
||||
""" Return the public IP of this host """
|
||||
http = urllib3.PoolManager()
|
||||
try:
|
||||
external_ip = http.request('GET', 'https://ident.me').data
|
||||
except Exception:
|
||||
try:
|
||||
external_ip = http.request('GET', 'https://ifconfig.me/ip').data
|
||||
except Exception:
|
||||
external_ip = '127.0.0.1'
|
||||
|
||||
if type(external_ip) == bytes:
|
||||
external_ip = external_ip.decode('utf-8')
|
||||
|
||||
ip = ipaddress.ip_address(external_ip)
|
||||
if isinstance(ip, ipaddress.IPv4Address):
|
||||
return '{}/{}'.format(external_ip, 32)
|
||||
elif isinstance(ip, ipaddress.IPv6Address):
|
||||
return '{}/{}'.format(external_ip, 128)
|
||||
|
||||
return '{}/{}'.format(external_ip, 32)
|
||||
@@ -0,0 +1,40 @@
|
||||
# ##########################################################################
|
||||
# #
|
||||
# # pgAdmin 4 - PostgreSQL Tools
|
||||
# #
|
||||
# # Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
# # This software is released under the PostgreSQL Licence
|
||||
# #
|
||||
# ##########################################################################
|
||||
#
|
||||
# # AWS Regions
|
||||
|
||||
|
||||
AWS_REGIONS = {
|
||||
'us-gov-east-1': 'AWS GovCloud (US-East)',
|
||||
'us-gov-west-1': 'AWS GovCloud (US-West)',
|
||||
'af-south-1': 'Africa (Cape Town)',
|
||||
'ap-east-1': 'Asia Pacific (Hong Kong)',
|
||||
'ap-south-1': 'Asia Pacific (Mumbai)',
|
||||
'ap-northeast-3': 'Asia Pacific (Osaka)',
|
||||
'ap-northeast-2': 'Asia Pacific (Seoul)',
|
||||
'ap-southeast-1': 'Asia Pacific (Singapore)',
|
||||
'ap-southeast-2': 'Asia Pacific (Sydney)',
|
||||
'ap-southeast-3': 'Asia Pacific (Jakarta)',
|
||||
'ap-northeast-1': 'Asia Pacific (Tokyo)',
|
||||
'ca-central-1': 'Canada (Central)',
|
||||
'cn-north-1': 'China (Beijing)',
|
||||
'cn-northwest-1': 'China (Ningxia)',
|
||||
'eu-central-1': 'Europe (Frankfurt)',
|
||||
'eu-west-1': 'Europe (Ireland)',
|
||||
'eu-west-2': 'Europe (London)',
|
||||
'eu-west-3': 'Europe (Paris)',
|
||||
'eu-north-1': 'Europe (Stockholm)',
|
||||
'eu-south-1': 'Europe (Milan)',
|
||||
'sa-east-1': 'South America (Sao Paulo)',
|
||||
'us-east-1': 'US East (N. Virginia)',
|
||||
'us-east-2': 'US East (Ohio)',
|
||||
'us-west-1': 'US West (N. California)',
|
||||
'us-west-2': 'US West (Oregon)',
|
||||
'me-south-1': 'Middle East (Bahrain)',
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
# AWS RDS PostgreSQL provider
|
||||
|
||||
import boto3
|
||||
import pickle
|
||||
from flask import session
|
||||
from boto3.session import Session
|
||||
from .aws_regions import AWS_REGIONS
|
||||
|
||||
|
||||
class RDS():
|
||||
def __init__(self, access_key, secret_key, session_token=None,
|
||||
default_region='ap-south-1'):
|
||||
self._clients = {}
|
||||
|
||||
self._access_key = access_key
|
||||
self._secret_key = secret_key
|
||||
self._session_token = session_token
|
||||
|
||||
self._default_region = default_region
|
||||
|
||||
##########################################################################
|
||||
# AWS Helper functions
|
||||
##########################################################################
|
||||
def _get_aws_client(self, type):
|
||||
""" Create/cache/return an AWS client object """
|
||||
if type in self._clients:
|
||||
return self._clients[type]
|
||||
|
||||
session = boto3.Session(
|
||||
aws_access_key_id=self._access_key,
|
||||
aws_secret_access_key=self._secret_key,
|
||||
aws_session_token=self._session_token
|
||||
)
|
||||
|
||||
self._clients[type] = session.client(
|
||||
type, region_name=self._default_region)
|
||||
|
||||
return self._clients[type]
|
||||
|
||||
def get_available_db_version(self, engine='postgres'):
|
||||
rds = self._get_aws_client('rds')
|
||||
return rds.describe_db_engine_versions(Engine=engine)
|
||||
|
||||
def get_available_db_instance_class(self, engine='postgres',
|
||||
engine_version='9.6'):
|
||||
rds = self._get_aws_client('rds')
|
||||
_instances = rds.describe_orderable_db_instance_options(
|
||||
Engine=engine,
|
||||
EngineVersion=engine_version)
|
||||
_instances_list = _instances['OrderableDBInstanceOptions']
|
||||
_marker = _instances['Marker'] if 'Marker' in _instances else None
|
||||
while _marker:
|
||||
_tmp_instances = rds.describe_orderable_db_instance_options(
|
||||
Engine=engine,
|
||||
EngineVersion=engine_version,
|
||||
Marker=_marker)
|
||||
_instances_list = [*_instances_list,
|
||||
*_tmp_instances['OrderableDBInstanceOptions']]
|
||||
_marker = _tmp_instances['Marker'] if 'Marker'\
|
||||
in _tmp_instances else None
|
||||
|
||||
return _instances_list
|
||||
|
||||
def get_db_instance(self, instance_name):
|
||||
rds = self._get_aws_client('rds')
|
||||
return rds.describe_db_instances(
|
||||
DBInstanceIdentifier=instance_name)
|
||||
|
||||
def validate_credentials(self):
|
||||
client = self._get_aws_client('sts')
|
||||
try:
|
||||
identity = client.get_caller_identity()
|
||||
return True, identity
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
finally:
|
||||
self._clients.pop('sts')
|
||||
|
||||
|
||||
def verify_aws_credentials(data):
|
||||
"""Verify Credentials"""
|
||||
session_token = data['secret']['aws_session_token'] if\
|
||||
'aws_session_token' in data['secret'] else None
|
||||
|
||||
if 'aws' not in session:
|
||||
session['aws'] = {}
|
||||
|
||||
if 'aws_rds_obj' not in session['aws'] or\
|
||||
session['aws']['secret'] != data['secret']:
|
||||
_rds = RDS(
|
||||
access_key=data['secret']['aws_access_key'],
|
||||
secret_key=data['secret']['aws_secret_access_key'],
|
||||
session_token=session_token,
|
||||
default_region=data['secret']['aws_region'])
|
||||
status, identity = _rds.validate_credentials()
|
||||
if status:
|
||||
session['aws']['secret'] = data['secret']
|
||||
session['aws']['aws_rds_obj'] = pickle.dumps(_rds, -1)
|
||||
return status, identity
|
||||
|
||||
return True, None
|
||||
|
||||
|
||||
def clear_aws_session():
|
||||
"""Clear AWS Session"""
|
||||
if 'aws' in session:
|
||||
session.pop('aws')
|
||||
|
||||
|
||||
def get_aws_db_instances(eng_version):
|
||||
"""Get AWS DB Instances"""
|
||||
if 'aws' not in session:
|
||||
return False, 'Session has not created yet.'
|
||||
|
||||
if not eng_version or eng_version == '' or eng_version == 'undefined':
|
||||
eng_version = '10.17'
|
||||
|
||||
rds_obj = pickle.loads(session['aws']['aws_rds_obj'])
|
||||
res = rds_obj.get_available_db_instance_class(
|
||||
engine_version=eng_version)
|
||||
versions_set = set()
|
||||
versions = []
|
||||
for value in res:
|
||||
versions_set.add(value['DBInstanceClass'])
|
||||
|
||||
for value in versions_set:
|
||||
versions.append({
|
||||
'label': value,
|
||||
'value': value
|
||||
})
|
||||
return True, versions
|
||||
|
||||
|
||||
def get_aws_db_versions():
|
||||
"""Get AWS DB Versions"""
|
||||
|
||||
if 'aws' not in session:
|
||||
return False, 'Session has not created yet.'
|
||||
|
||||
rds_obj = pickle.loads(session['aws']['aws_rds_obj'])
|
||||
db_versions = rds_obj.get_available_db_version()
|
||||
res = list(filter(lambda val: not val['EngineVersion'].startswith('9.6'),
|
||||
db_versions['DBEngineVersions']))
|
||||
versions = []
|
||||
for value in res:
|
||||
versions.append({
|
||||
'label': value['DBEngineVersionDescription'],
|
||||
'value': value['EngineVersion']
|
||||
})
|
||||
return True, versions
|
||||
|
||||
|
||||
def get_aws_regions():
|
||||
"""Get AWS DB Versions"""
|
||||
clear_aws_session()
|
||||
_session = Session()
|
||||
res = _session.get_available_regions('rds')
|
||||
regions = []
|
||||
|
||||
for value in res:
|
||||
if value in AWS_REGIONS:
|
||||
regions.append({
|
||||
'label': AWS_REGIONS[value] + ' | ' + value,
|
||||
'value': value
|
||||
})
|
||||
return True, regions
|
||||
@@ -985,7 +985,11 @@ class Filemanager(object):
|
||||
try:
|
||||
# Check if the new file is inside the users directory
|
||||
if config.SERVER_MODE:
|
||||
pathlib.Path(new_name).relative_to(the_dir)
|
||||
pathlib.Path(
|
||||
os.path.abspath(
|
||||
os.path.join(the_dir, new_name)
|
||||
)
|
||||
).relative_to(the_dir)
|
||||
except ValueError:
|
||||
return self.ERROR_NOT_ALLOWED
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ FROM pg_catalog.pg_attribute att
|
||||
ON def.adrelid = att.attrelid
|
||||
AND def.adnum = att.attnum
|
||||
WHERE nsp.nspname IN ({{schema_names}})
|
||||
AND cls.relkind = ANY(array['r'])
|
||||
AND cls.relkind = ANY(array['r', 'p'])
|
||||
AND NOT att.attisdropped
|
||||
AND att.attnum > 0
|
||||
ORDER BY 1, 2, att.attnum
|
||||
|
||||
@@ -4,7 +4,7 @@ SELECT n.nspname schema_name,
|
||||
c.relname object_name
|
||||
FROM pg_catalog.pg_class c
|
||||
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE c.relkind = ANY(array['r']) and n.nspname IN ({{schema_names}})
|
||||
WHERE c.relkind = ANY(array['r', 'p']) and n.nspname IN ({{schema_names}})
|
||||
ORDER BY 1,2
|
||||
{% endif %}
|
||||
{% if object_name == 'views' %}
|
||||
|
||||
@@ -30,7 +30,7 @@ import uuid
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
SCHEMA_VERSION = 32
|
||||
SCHEMA_VERSION = 33
|
||||
|
||||
##########################################################################
|
||||
#
|
||||
@@ -192,6 +192,7 @@ class Server(db.Model):
|
||||
tunnel_password = db.Column(db.String(64), nullable=True)
|
||||
shared = db.Column(db.Boolean(), nullable=False)
|
||||
kerberos_conn = db.Column(db.Boolean(), nullable=False, default=0)
|
||||
cloud_status = db.Column(db.Integer(), nullable=False, default=0)
|
||||
|
||||
@property
|
||||
def serialize(self):
|
||||
@@ -325,6 +326,11 @@ class Process(db.Model):
|
||||
acknowledge = db.Column(db.String(), nullable=True)
|
||||
utility_pid = db.Column(db.Integer, nullable=False)
|
||||
process_state = db.Column(db.Integer, nullable=False)
|
||||
server_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('server.id'),
|
||||
nullable=True
|
||||
)
|
||||
|
||||
|
||||
class Keys(db.Model):
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 20 20" height="18px" viewBox="0 0 20 20" width="18px" fill="#000000"><g><rect fill="none" height="20" width="20"/></g><g><path d="M13,9h-1V5c0-1.1-0.9-2-2-2h0C8.9,3,8,3.9,8,5v4H7c-1.66,0-3,1.34-3,3v4c0,0.55,0.45,1,1,1h10c0.55,0,1-0.45,1-1v-4 C16,10.34,14.66,9,13,9z M15,16h-2v-1.5c0-0.28-0.22-0.5-0.5-0.5S12,14.22,12,14.5V16h-1.5v-1.5c0-0.28-0.22-0.5-0.5-0.5 s-0.5,0.22-0.5,0.5V16H8v-1.5C8,14.22,7.78,14,7.5,14S7,14.22,7,14.5V16H5v-4c0-1.1,0.9-2,2-2h6c1.1,0,2,0.9,2,2V16z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 551 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 2h-4.18C14.4.84 13.3 0 12 0c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm7 18H5V4h2v3h10V4h2v16z"/></svg>
|
||||
|
After Width: | Height: | Size: 344 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="18px" viewBox="0 0 24 24" width="18px" fill="#000000"><g><path d="M0,0h24 M24,24H0" fill="none"/><path d="M4.25,5.61C6.57,8.59,10,13,10,13v5c0,1.1,0.9,2,2,2h0c1.1,0,2-0.9,2-2v-5c0,0,3.43-4.41,5.75-7.39 C20.26,4.95,19.79,4,18.95,4H5.04C4.21,4,3.74,4.95,4.25,5.61z"/><path d="M0,0h24v24H0V0z" fill="none"/></g></svg>
|
||||
|
After Width: | Height: | Size: 396 B |
@@ -1 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><defs><style>.cls-1{fill:#646464;}</style></defs><path class="cls-1" d="M5.42,15.76A21.75,21.75,0,0,0,12,18a47.55,47.55,0,0,0,9.06.81l1,0a16.63,16.63,0,0,1,14.8-3.11c1.48-.89,2.23-1.85,2.23-2.89v-3c0-1.08-.81-2.08-2.42-3a22,22,0,0,0-6.58-2.19,47.44,47.44,0,0,0-9-.81A47.55,47.55,0,0,0,12,4.54,22,22,0,0,0,5.42,6.73C3.81,7.66,3,8.66,3,9.74v3C3,13.83,3.81,14.84,5.42,15.76Z"/><path class="cls-1" d="M15.89,30.58a45.4,45.4,0,0,1-5.25-.77,20.41,20.41,0,0,1-7.64-3v4c0,1.08.81,2.08,2.42,3A22,22,0,0,0,12,36a44.81,44.81,0,0,0,4.56.62,16.69,16.69,0,0,1-.73-4.87C15.83,31.37,15.86,31,15.89,30.58Z"/><path class="cls-1" d="M3,17.8v4c0,1.08.81,2.09,2.42,3A21.75,21.75,0,0,0,12,27c1.4.27,2.86.47,4.38.61a16.74,16.74,0,0,1,2.81-5.86,50,50,0,0,1-8.55-1A20.58,20.58,0,0,1,3,17.8Z"/><path class="cls-1" d="M32.3,18.88A12.7,12.7,0,1,0,45,31.58,12.74,12.74,0,0,0,32.3,18.88Zm7.94,10-9.36,9.36a1,1,0,0,1-1.11.16L24.37,33a.76.76,0,0,1,0-1.11l1.11-1.27a.76.76,0,0,1,1.11,0l3.65,3.65L38,26.65a.78.78,0,0,1,1.11,0l1.11,1.12A.76.76,0,0,1,40.24,28.88Z"/></svg>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><defs></defs><path class="cls-1" d="M5.42,15.76A21.75,21.75,0,0,0,12,18a47.55,47.55,0,0,0,9.06.81l1,0a16.63,16.63,0,0,1,14.8-3.11c1.48-.89,2.23-1.85,2.23-2.89v-3c0-1.08-.81-2.08-2.42-3a22,22,0,0,0-6.58-2.19,47.44,47.44,0,0,0-9-.81A47.55,47.55,0,0,0,12,4.54,22,22,0,0,0,5.42,6.73C3.81,7.66,3,8.66,3,9.74v3C3,13.83,3.81,14.84,5.42,15.76Z"/><path class="cls-1" d="M15.89,30.58a45.4,45.4,0,0,1-5.25-.77,20.41,20.41,0,0,1-7.64-3v4c0,1.08.81,2.08,2.42,3A22,22,0,0,0,12,36a44.81,44.81,0,0,0,4.56.62,16.69,16.69,0,0,1-.73-4.87C15.83,31.37,15.86,31,15.89,30.58Z"/><path class="cls-1" d="M3,17.8v4c0,1.08.81,2.09,2.42,3A21.75,21.75,0,0,0,12,27c1.4.27,2.86.47,4.38.61a16.74,16.74,0,0,1,2.81-5.86,50,50,0,0,1-8.55-1A20.58,20.58,0,0,1,3,17.8Z"/><path class="cls-1" d="M32.3,18.88A12.7,12.7,0,1,0,45,31.58,12.74,12.74,0,0,0,32.3,18.88Zm7.94,10-9.36,9.36a1,1,0,0,1-1.11.16L24.37,33a.76.76,0,0,1,0-1.11l1.11-1.27a.76.76,0,0,1,1.11,0l3.65,3.65L38,26.65a.78.78,0,0,1,1.11,0l1.11,1.12A.76.76,0,0,1,40.24,28.88Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M20.06 18a3.99 3.99 0 0 1-.2-.89c-.67.7-1.48 1.05-2.41 1.05c-.83 0-1.52-.24-2.05-.71c-.53-.45-.8-1.06-.8-1.79c0-.88.33-1.56 1-2.05c.67-.49 1.61-.73 2.83-.73h1.4v-.64c0-.49-.15-.88-.45-1.17c-.3-.29-.75-.43-1.33-.43c-.52 0-.95.12-1.3.36c-.35.25-.52.54-.52.89h-1.46c0-.43.15-.84.45-1.24c.28-.4.71-.71 1.22-.94c.51-.21 1.06-.35 1.69-.35c.98 0 1.74.24 2.29.73s.84 1.16.86 2.02V16c0 .8.1 1.42.3 1.88V18h-1.52m-2.4-1.12c.45 0 .88-.11 1.29-.32c.4-.21.7-.49.88-.83v-1.57H18.7c-1.77 0-2.66.47-2.66 1.41c0 .43.15.73.46.96c.3.23.68.35 1.16.35m-12.2-3.17h4.07L7.5 8.29l-2.04 5.42M6.64 6h1.72l4.71 12h-1.93l-.97-2.57H4.82L3.86 18H1.93L6.64 6z" fill="currentColor"/></svg>
|
||||
|
After Width: | Height: | Size: 861 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M16 16.92c-.33.05-.66.08-1 .08c-.34 0-.67-.03-1-.08v-3.51l-2.5 2.48c-.5-.39-1-.89-1.39-1.39l2.48-2.5H9.08c-.05-.33-.08-.66-.08-1c0-.34.03-.67.08-1h3.51l-2.48-2.5c.19-.25.39-.5.65-.74c.24-.26.49-.46.74-.65L14 8.59V5.08c.33-.05.66-.08 1-.08c.34 0 .67.03 1 .08v3.51l2.5-2.48c.5.39 1 .89 1.39 1.39L17.41 10h3.51c.05.33.08.66.08 1c0 .34-.03.67-.08 1h-3.51l2.48 2.5c-.19.25-.39.5-.65.74c-.24.26-.49.46-.74.65L16 13.41v3.51M5 19a2 2 0 0 1 2-2a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2z" fill="currentColor"/></svg>
|
||||
|
After Width: | Height: | Size: 711 B |
@@ -1 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><defs><style>.cls-1{fill:#646464;}</style></defs><path class="cls-1" d="M5.42,15.76A21.75,21.75,0,0,0,12,18a47.55,47.55,0,0,0,9.06.81l1,0a16.63,16.63,0,0,1,14.8-3.11c1.48-.89,2.23-1.85,2.23-2.89v-3c0-1.08-.81-2.08-2.42-3a22,22,0,0,0-6.58-2.19,47.44,47.44,0,0,0-9-.81A47.55,47.55,0,0,0,12,4.54,22,22,0,0,0,5.42,6.73C3.81,7.66,3,8.66,3,9.74v3C3,13.83,3.81,14.84,5.42,15.76Z"/><path class="cls-1" d="M15.89,30.58a45.4,45.4,0,0,1-5.25-.77,20.34,20.34,0,0,1-7.64-3v4c0,1.08.81,2.08,2.42,3A22,22,0,0,0,12,36a44.81,44.81,0,0,0,4.56.62,16.69,16.69,0,0,1-.73-4.87C15.83,31.37,15.86,31,15.89,30.58Z"/><path class="cls-1" d="M3,17.8v4c0,1.08.81,2.09,2.42,3A21.75,21.75,0,0,0,12,27c1.4.27,2.86.47,4.38.61a16.74,16.74,0,0,1,2.81-5.86,50,50,0,0,1-8.55-1A20.51,20.51,0,0,1,3,17.8Z"/><path class="cls-1" d="M32.3,18.88A12.7,12.7,0,1,0,45,31.58,12.74,12.74,0,0,0,32.3,18.88Zm5.93,20.69a.63.63,0,0,1-1-.66c1.61-5.16-.77-6.52-6.28-6.6v3.12a.85.85,0,0,1-1.41.65l-6.26-5.41a.86.86,0,0,1,0-1.29L29.55,24a.85.85,0,0,1,1.41.65v2.84c5.71.07,10.24,1.21,10.24,6.62A7.25,7.25,0,0,1,38.23,39.57Z"/></svg>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><defs></defs><path class="cls-1" d="M5.42,15.76A21.75,21.75,0,0,0,12,18a47.55,47.55,0,0,0,9.06.81l1,0a16.63,16.63,0,0,1,14.8-3.11c1.48-.89,2.23-1.85,2.23-2.89v-3c0-1.08-.81-2.08-2.42-3a22,22,0,0,0-6.58-2.19,47.44,47.44,0,0,0-9-.81A47.55,47.55,0,0,0,12,4.54,22,22,0,0,0,5.42,6.73C3.81,7.66,3,8.66,3,9.74v3C3,13.83,3.81,14.84,5.42,15.76Z"/><path class="cls-1" d="M15.89,30.58a45.4,45.4,0,0,1-5.25-.77,20.34,20.34,0,0,1-7.64-3v4c0,1.08.81,2.08,2.42,3A22,22,0,0,0,12,36a44.81,44.81,0,0,0,4.56.62,16.69,16.69,0,0,1-.73-4.87C15.83,31.37,15.86,31,15.89,30.58Z"/><path class="cls-1" d="M3,17.8v4c0,1.08.81,2.09,2.42,3A21.75,21.75,0,0,0,12,27c1.4.27,2.86.47,4.38.61a16.74,16.74,0,0,1,2.81-5.86,50,50,0,0,1-8.55-1A20.51,20.51,0,0,1,3,17.8Z"/><path class="cls-1" d="M32.3,18.88A12.7,12.7,0,1,0,45,31.58,12.74,12.74,0,0,0,32.3,18.88Zm5.93,20.69a.63.63,0,0,1-1-.66c1.61-5.16-.77-6.52-6.28-6.6v3.12a.85.85,0,0,1-1.41.65l-6.26-5.41a.86.86,0,0,1,0-1.29L29.55,24a.85.85,0,0,1,1.41.65v2.84c5.71.07,10.24,1.21,10.24,6.62A7.25,7.25,0,0,1,38.23,39.57Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -39,6 +39,10 @@ const useStyles = makeStyles((theme)=>({
|
||||
nestedControl: {
|
||||
height: 'unset',
|
||||
},
|
||||
errorMargin: {
|
||||
/* Error footer margin */
|
||||
marginBottom: '36px',
|
||||
},
|
||||
sqlTabInput: {
|
||||
border: 0,
|
||||
}
|
||||
@@ -186,6 +190,9 @@ export default function FormView({
|
||||
if(field.depChange) {
|
||||
depListener.addDepListener(source, accessPath.concat(field.id), field.depChange);
|
||||
}
|
||||
if(field.depChange || field.deferredDepChange) {
|
||||
depListener.addDepListener(source, accessPath.concat(field.id), field.depChange, field.deferredDepChange);
|
||||
}
|
||||
});
|
||||
});
|
||||
return ()=>{
|
||||
@@ -288,7 +295,7 @@ export default function FormView({
|
||||
if(visible && !disabled && !firstEleID.current) {
|
||||
firstEleID.current = field.id;
|
||||
}
|
||||
|
||||
|
||||
tabs[group].push(
|
||||
useMemo(()=><MappedFormControl
|
||||
inputRef={(ele)=>{
|
||||
@@ -365,7 +372,6 @@ export default function FormView({
|
||||
onChange={(event, selTabValue) => {
|
||||
setTabValue(selTabValue);
|
||||
}}
|
||||
// indicatorColor="primary"
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
action={(ref)=>ref && ref.updateIndicator()}
|
||||
@@ -376,9 +382,13 @@ export default function FormView({
|
||||
</Tabs>
|
||||
</Box>
|
||||
{Object.keys(finalTabs).map((tabName, i)=>{
|
||||
let contentClassName = null;
|
||||
if(fullTabs.indexOf(tabName) == -1) {
|
||||
contentClassName = clsx(classes.nestedControl, stateUtils.formErr.message ? classes.errorMargin : null);
|
||||
}
|
||||
return (
|
||||
<TabPanel key={tabName} value={tabValue} index={i} classNameRoot={clsx(tabsClassname[tabName], isNested ? classes.nestedTabPanel : null)}
|
||||
className={fullTabs.indexOf(tabName) == -1 ? classes.nestedControl : null}>
|
||||
className={contentClassName}>
|
||||
{finalTabs[tabName]}
|
||||
</TabPanel>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ import React, { useCallback } from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { FormInputText, FormInputSelect, FormInputSwitch, FormInputCheckbox, FormInputColor,
|
||||
FormInputFileSelect, FormInputToggle, InputSwitch, FormInputSQL, FormNote, FormInputDateTimePicker, PlainString,
|
||||
FormInputFileSelect, FormInputToggle, InputSwitch, FormInputSQL, FormNote, FormInputDateTimePicker, PlainString, InputSQL,
|
||||
InputSelect, InputText, InputCheckbox, InputDateTimePicker } from '../components/FormComponents';
|
||||
import Privilege from '../components/Privilege';
|
||||
import { evalFunc } from 'sources/utils';
|
||||
@@ -111,6 +111,10 @@ function MappedCellControlBase({cell, value, id, optionsLoaded, onCellChange, vi
|
||||
onCellChange && onCellChange(val);
|
||||
}, []);
|
||||
|
||||
const onSqlChange = useCallback((val) => {
|
||||
onCellChange && onCellChange(val);
|
||||
}, []);
|
||||
|
||||
/* Some grid cells are based on options selected in other cells.
|
||||
* lets trigger a re-render for the row if optionsLoaded
|
||||
*/
|
||||
@@ -146,6 +150,8 @@ function MappedCellControlBase({cell, value, id, optionsLoaded, onCellChange, vi
|
||||
return <Privilege name={name} value={value} onChange={onTextChange} {...props}/>;
|
||||
case 'datetimepicker':
|
||||
return <InputDateTimePicker name={name} value={value} onChange={onTextChange} {...props}/>;
|
||||
case 'sql':
|
||||
return <InputSQL name={name} value={value} onChange={onSqlChange} {...props} />;
|
||||
default:
|
||||
return <PlainString value={value} {...props} />;
|
||||
}
|
||||
@@ -167,7 +173,8 @@ MappedCellControlBase.propTypes = {
|
||||
const ALLOWED_PROPS_FIELD_COMMON = [
|
||||
'mode', 'value', 'readonly', 'disabled', 'hasError', 'id',
|
||||
'label', 'options', 'optionsLoaded', 'controlProps', 'schema', 'inputRef',
|
||||
'visible', 'autoFocus', 'helpMessage', 'className', 'optionsReloadBasis'
|
||||
'visible', 'autoFocus', 'helpMessage', 'className', 'optionsReloadBasis',
|
||||
'orientation'
|
||||
];
|
||||
|
||||
const ALLOWED_PROPS_FIELD_FORM = [
|
||||
|
||||
@@ -16,7 +16,7 @@ export default class BaseUISchema {
|
||||
constructor(defaults) {
|
||||
/* Pass the initial data to constructor so that
|
||||
they will set to defaults */
|
||||
this._defaults = defaults;
|
||||
this._defaults = defaults || {};
|
||||
|
||||
this.keys = null; // If set, other fields except keys will be filtered
|
||||
this.filterGroups = []; // If set, these groups will be filtered out
|
||||
|
||||
@@ -487,42 +487,34 @@ function SchemaDialogView({
|
||||
}, [sessData.__deferred__?.length]);
|
||||
|
||||
useEffect(()=>{
|
||||
let unmounted = false;
|
||||
/* Docker on load focusses itself, so our focus should execute later */
|
||||
let focusTimeout = setTimeout(()=>{
|
||||
firstEleRef.current && firstEleRef.current.focus();
|
||||
}, 250);
|
||||
|
||||
/* Re-triggering focus on already focussed loses the focus */
|
||||
if(viewHelperProps.mode === 'edit') {
|
||||
setLoaderText('Loading...');
|
||||
/* If its an edit mode, get the initial data using getInitData
|
||||
getInitData should be a promise */
|
||||
if(!getInitData) {
|
||||
throw new Error('getInitData must be passed for edit');
|
||||
setLoaderText('Loading...');
|
||||
/* Get the initial data using getInitData */
|
||||
/* If its an edit mode, getInitData should be present and a promise */
|
||||
if(!getInitData && viewHelperProps.mode === 'edit') {
|
||||
throw new Error('getInitData must be passed for edit');
|
||||
}
|
||||
let initDataPromise = (getInitData && getInitData()) || Promise.resolve({});
|
||||
initDataPromise.then((data)=>{
|
||||
if(unmounted) {
|
||||
return;
|
||||
}
|
||||
getInitData && getInitData().then((data)=>{
|
||||
data = data || {};
|
||||
data = data || {};
|
||||
if(viewHelperProps.mode === 'edit') {
|
||||
/* Set the origData to incoming data, useful for comparing and reset */
|
||||
schema.origData = prepareData(data || {});
|
||||
schema.initialise(schema.origData);
|
||||
sessDispatch({
|
||||
type: SCHEMA_STATE_ACTIONS.INIT,
|
||||
payload: schema.origData,
|
||||
});
|
||||
setFormReady(true);
|
||||
setLoaderText('');
|
||||
}).catch((err)=>{
|
||||
setLoaderText('');
|
||||
if (err.response && err.response.data && err.response.data.errormsg) {
|
||||
Notify.alert(
|
||||
gettext(err.response.statusText),
|
||||
gettext(err.response.data.errormsg)
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
/* Use the defaults as the initital data */
|
||||
schema.origData = prepareData(schema.defaults, true);
|
||||
} else {
|
||||
/* In create mode, merge with defaults */
|
||||
schema.origData = prepareData({
|
||||
...schema.defaults,
|
||||
...data,
|
||||
}, true);
|
||||
}
|
||||
schema.initialise(schema.origData);
|
||||
sessDispatch({
|
||||
type: SCHEMA_STATE_ACTIONS.INIT,
|
||||
@@ -530,10 +522,23 @@ function SchemaDialogView({
|
||||
});
|
||||
setFormReady(true);
|
||||
setLoaderText('');
|
||||
}
|
||||
|
||||
}).catch((err)=>{
|
||||
if(unmounted) {
|
||||
return;
|
||||
}
|
||||
setLoaderText('');
|
||||
if (err.response && err.response.data && err.response.data.errormsg) {
|
||||
Notify.alert(
|
||||
gettext(err.response.statusText),
|
||||
gettext(err.response.data.errormsg)
|
||||
);
|
||||
}
|
||||
});
|
||||
/* Clear the focus timeout if unmounted */
|
||||
return ()=>clearTimeout(focusTimeout);
|
||||
return ()=>{
|
||||
unmounted = true;
|
||||
clearTimeout(focusTimeout);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(()=>{
|
||||
@@ -700,7 +705,7 @@ function SchemaDialogView({
|
||||
<Loader message={loaderText}/>
|
||||
<FormView value={sessData} viewHelperProps={viewHelperProps}
|
||||
schema={schema} accessPath={[]} dataDispatch={sessDispatchWithListener}
|
||||
hasSQLTab={props.hasSQL} getSQLValue={getSQLValue} firstEleRef={firstEleRef} isTabView={isTabView} />
|
||||
hasSQLTab={props.hasSQL} getSQLValue={getSQLValue} firstEleRef={firstEleRef} isTabView={isTabView} className={props.formClassName} />
|
||||
<FormFooterMessage type={MESSAGE_TYPE.ERROR} message={formErr.message}
|
||||
onClose={onErrClose} />
|
||||
</Box>
|
||||
@@ -754,6 +759,7 @@ SchemaDialogView.propTypes = {
|
||||
resetKey: PropTypes.any,
|
||||
customSaveBtnName: PropTypes.string,
|
||||
customSaveBtnIconType: PropTypes.string,
|
||||
formClassName: CustomPropTypes.className,
|
||||
};
|
||||
|
||||
const usePropsStyles = makeStyles((theme)=>({
|
||||
|
||||
@@ -72,12 +72,12 @@ basicSettings = createMuiTheme(basicSettings, {
|
||||
},
|
||||
MuiButton: {
|
||||
root: {
|
||||
textTransform: 'none,',
|
||||
textTransform: 'none',
|
||||
padding: basicSettings.spacing(0.5, 1.5),
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.65,
|
||||
opacity: 0.60,
|
||||
},
|
||||
'&.MuiButton-outlinedSizeSmall': {
|
||||
'&.MuiButton-sizeSmall, &.MuiButton-outlinedSizeSmall, &.MuiButton-containedSizeSmall': {
|
||||
height: '28px',
|
||||
fontSize: '0.875rem',
|
||||
'& .MuiSvgIcon-root': {
|
||||
@@ -111,7 +111,7 @@ basicSettings = createMuiTheme(basicSettings, {
|
||||
resize: 'vertical',
|
||||
},
|
||||
adornedEnd: {
|
||||
paddingRight: basicSettings.spacing(1.5),
|
||||
paddingRight: basicSettings.spacing(0.75),
|
||||
}
|
||||
},
|
||||
MuiAccordion: {
|
||||
@@ -184,6 +184,16 @@ basicSettings = createMuiTheme(basicSettings, {
|
||||
root: {
|
||||
fontSize: 14,
|
||||
}
|
||||
},
|
||||
MuiSelect: {
|
||||
selectMenu: {
|
||||
minHeight: 'unset',
|
||||
},
|
||||
select:{
|
||||
'&:focus':{
|
||||
backgroundColor: 'unset',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
transitions: {
|
||||
@@ -220,7 +230,7 @@ basicSettings = createMuiTheme(basicSettings, {
|
||||
},
|
||||
MuiListItem: {
|
||||
disableGutters: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -321,7 +331,10 @@ function getFinalTheme(baseTheme) {
|
||||
color: baseTheme.palette.text.muted,
|
||||
backgroundColor: baseTheme.otherVars.inputDisabledBg,
|
||||
},
|
||||
}
|
||||
'&:focus': {
|
||||
outline: '0 !important',
|
||||
}
|
||||
},
|
||||
},
|
||||
MuiIconButton: {
|
||||
root: {
|
||||
@@ -417,6 +430,10 @@ function getFinalTheme(baseTheme) {
|
||||
backgroundColor: 'abc',
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
label: {
|
||||
textTransform: 'initial',
|
||||
}
|
||||
},
|
||||
MuiFormHelperText: {
|
||||
|
||||
@@ -92,13 +92,16 @@ export default function(basicSettings) {
|
||||
inputBorderColor: '#dde0e6',
|
||||
inputDisabledBg: '#f3f5f9',
|
||||
headerBg: '#fff',
|
||||
activeBorder: '#326690',
|
||||
activeColor: '#326690',
|
||||
tableBg: '#fff',
|
||||
activeStepBg: '#326690',
|
||||
activeStepFg: '#FFFFFF',
|
||||
stepBg: '#ddd',
|
||||
stepFg: '#000',
|
||||
toggleBtnBg: '#000'
|
||||
toggleBtnBg: '#000',
|
||||
editorToolbarBg: '#ebeef3',
|
||||
datagridBg: '#fff',
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -14,13 +14,14 @@ import axios from 'axios';
|
||||
/* Get the axios instance to call back end APIs.
|
||||
Do not import axios directly, instead use this */
|
||||
export default function getApiInstance(headers={}) {
|
||||
return axios.create({
|
||||
const api = axios.create({
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
[pgAdmin.csrf_token_header]: pgAdmin.csrf_token,
|
||||
...headers,
|
||||
}
|
||||
});
|
||||
return api;
|
||||
}
|
||||
|
||||
export function parseApiError(error) {
|
||||
@@ -28,7 +29,7 @@ export function parseApiError(error) {
|
||||
// The request was made and the server responded with a status code
|
||||
// that falls out of the range of 2xx
|
||||
if(error.response.headers['content-type'] == 'application/json') {
|
||||
return error.response.data.errormsg;
|
||||
return `INTERNAL SERVER ERROR: ${error.response.data.errormsg}`;
|
||||
} else {
|
||||
return error.response.statusText;
|
||||
}
|
||||
@@ -37,8 +38,10 @@ export function parseApiError(error) {
|
||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||
// http.ClientRequest in node.js
|
||||
return gettext('Connection to pgAdmin server has been lost');
|
||||
} else {
|
||||
} else if(error.message) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
return error.message;
|
||||
} else {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
@@ -7,20 +7,16 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import { Button, makeStyles, Tooltip } from '@material-ui/core';
|
||||
import { Button, ButtonGroup, makeStyles, Tooltip } from '@material-ui/core';
|
||||
import React, { forwardRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import PropTypes from 'prop-types';
|
||||
import CustomPropTypes from '../custom_prop_types';
|
||||
import ShortcutTitle from './ShortcutTitle';
|
||||
|
||||
const useStyles = makeStyles((theme)=>({
|
||||
primaryButton: {
|
||||
'&.MuiButton-outlinedSizeSmall': {
|
||||
height: '28px',
|
||||
'& .MuiSvgIcon-root': {
|
||||
height: '0.8em',
|
||||
}
|
||||
},
|
||||
border: '1px solid '+theme.palette.primary.main,
|
||||
'&.Mui-disabled': {
|
||||
color: theme.palette.primary.contrastText,
|
||||
backgroundColor: theme.palette.primary.disabledMain,
|
||||
@@ -35,12 +31,6 @@ const useStyles = makeStyles((theme)=>({
|
||||
color: theme.palette.default.contrastText,
|
||||
border: '1px solid '+theme.palette.default.borderColor,
|
||||
whiteSpace: 'nowrap',
|
||||
'&.MuiButton-outlinedSizeSmall': {
|
||||
height: '28px',
|
||||
'& .MuiSvgIcon-root': {
|
||||
height: '0.8em',
|
||||
}
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
color: theme.palette.default.disabledContrastText,
|
||||
borderColor: theme.palette.default.disabledBorderColor
|
||||
@@ -53,6 +43,11 @@ const useStyles = makeStyles((theme)=>({
|
||||
},
|
||||
iconButton: {
|
||||
padding: '3px 6px',
|
||||
'&.MuiButton-sizeSmall, &.MuiButton-outlinedSizeSmall, &.MuiButton-containedSizeSmall': {
|
||||
padding: '1px 4px',
|
||||
},
|
||||
},
|
||||
iconButtonDefault: {
|
||||
borderColor: theme.custom.icon.borderColor,
|
||||
color: theme.custom.icon.contrastText,
|
||||
backgroundColor: theme.custom.icon.main,
|
||||
@@ -64,7 +59,15 @@ const useStyles = makeStyles((theme)=>({
|
||||
'&:hover': {
|
||||
backgroundColor: theme.custom.icon.hoverMain,
|
||||
color: theme.custom.icon.hoverContrastText,
|
||||
}
|
||||
},
|
||||
},
|
||||
splitButton: {
|
||||
'&.MuiButton-sizeSmall, &.MuiButton-outlinedSizeSmall, &.MuiButton-containedSizeSmall': {
|
||||
width: '20px',
|
||||
'& svg': {
|
||||
height: '0.8em',
|
||||
}
|
||||
},
|
||||
},
|
||||
xsButton: {
|
||||
padding: '2px 1px',
|
||||
@@ -89,7 +92,7 @@ export const PrimaryButton = forwardRef((props, ref)=>{
|
||||
}
|
||||
noBorder && allClassName.push(classes.noBorder);
|
||||
return (
|
||||
<Button ref={ref} variant="contained" color="primary" size={size} className={clsx(allClassName)} {...otherProps}>{children}</Button>
|
||||
<Button ref={ref} size={size} className={clsx(allClassName)} {...otherProps} variant="contained" color="primary" >{children}</Button>
|
||||
);
|
||||
});
|
||||
PrimaryButton.displayName = 'PrimaryButton';
|
||||
@@ -111,7 +114,7 @@ export const DefaultButton = forwardRef((props, ref)=>{
|
||||
}
|
||||
noBorder && allClassName.push(classes.noBorder);
|
||||
return (
|
||||
<Button ref={ref} variant="outlined" color="default" size={size} className={clsx(allClassName)} {...otherProps}>{children}</Button>
|
||||
<Button variant="outlined" color="default" ref={ref} size={size} className={clsx(allClassName)} {...otherProps} >{children}</Button>
|
||||
);
|
||||
});
|
||||
DefaultButton.displayName = 'DefaultButton';
|
||||
@@ -122,24 +125,77 @@ DefaultButton.propTypes = {
|
||||
className: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
};
|
||||
|
||||
|
||||
/* pgAdmin Icon button, takes Icon component as input */
|
||||
export const PgIconButton = forwardRef(({icon, title, className, ...props}, ref)=>{
|
||||
export const PgIconButton = forwardRef(({icon, title, shortcut, accessKey, className, splitButton, style, color, ...props}, ref)=>{
|
||||
const classes = useStyles();
|
||||
|
||||
let shortcutTitle = null;
|
||||
if(accessKey || shortcut) {
|
||||
shortcutTitle = <ShortcutTitle title={title} accessKey={accessKey} shortcut={shortcut}/>;
|
||||
}
|
||||
|
||||
/* Tooltip does not work for disabled items */
|
||||
return (
|
||||
<Tooltip title={title || ''} aria-label={title || ''}>
|
||||
<span>
|
||||
<DefaultButton ref={ref} style={{minWidth: 0}} className={clsx(classes.iconButton, className)} {...props}>
|
||||
if(props.disabled) {
|
||||
if(color == 'primary') {
|
||||
return (
|
||||
<PrimaryButton ref={ref} style={{minWidth: 0, ...style}}
|
||||
className={clsx(classes.iconButton, (splitButton ? classes.splitButton : ''), className)} {...props}>
|
||||
{icon}
|
||||
</PrimaryButton>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<DefaultButton ref={ref} style={{minWidth: 0, ...style}}
|
||||
className={clsx(classes.iconButton, classes.iconButtonDefault, (splitButton ? classes.splitButton : ''), className)} {...props}>
|
||||
{icon}
|
||||
</DefaultButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if(color == 'primary') {
|
||||
return (
|
||||
<Tooltip title={shortcutTitle || title || ''} aria-label={title || ''}>
|
||||
<PrimaryButton ref={ref} style={{minWidth: 0, ...style}}
|
||||
className={clsx(classes.iconButton, (splitButton ? classes.splitButton : ''), className)} {...props}>
|
||||
{icon}
|
||||
</PrimaryButton>
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Tooltip title={shortcutTitle || title || ''} aria-label={title || ''}>
|
||||
<DefaultButton ref={ref} style={{minWidth: 0, ...style}}
|
||||
className={clsx(classes.iconButton, classes.iconButtonDefault, (splitButton ? classes.splitButton : ''), className)} {...props}>
|
||||
{icon}
|
||||
</DefaultButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
PgIconButton.displayName = 'PgIconButton';
|
||||
PgIconButton.propTypes = {
|
||||
icon: CustomPropTypes.children,
|
||||
title: PropTypes.string.isRequired,
|
||||
shortcut: CustomPropTypes.shortcut,
|
||||
accessKey: PropTypes.string,
|
||||
className: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
style: PropTypes.object,
|
||||
color: PropTypes.oneOf(['primary', 'default', undefined]),
|
||||
disabled: PropTypes.bool,
|
||||
splitButton: PropTypes.bool,
|
||||
};
|
||||
|
||||
export const PgButtonGroup = forwardRef(({children, ...props}, ref)=>{
|
||||
/* Tooltip does not work for disabled items */
|
||||
return (
|
||||
<ButtonGroup disableElevation innerRef={ref} {...props}>
|
||||
{children}
|
||||
</ButtonGroup>
|
||||
);
|
||||
});
|
||||
PgButtonGroup.displayName = 'PgButtonGroup';
|
||||
PgButtonGroup.propTypes = {
|
||||
children: CustomPropTypes.children,
|
||||
};
|
||||
@@ -7,11 +7,258 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {default as OrigCodeMirror} from 'bundled_codemirror';
|
||||
import {useOnScreen} from 'sources/custom_hooks';
|
||||
import PropTypes from 'prop-types';
|
||||
import CustomPropTypes from '../custom_prop_types';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import gettext from 'sources/gettext';
|
||||
import { Box, InputAdornment, makeStyles } from '@material-ui/core';
|
||||
import clsx from 'clsx';
|
||||
import { InputText } from './FormComponents';
|
||||
import { PgIconButton } from './Buttons';
|
||||
import CloseIcon from '@material-ui/icons/CloseRounded';
|
||||
import ArrowDownwardRoundedIcon from '@material-ui/icons/ArrowDownwardRounded';
|
||||
import ArrowUpwardRoundedIcon from '@material-ui/icons/ArrowUpwardRounded';
|
||||
import SwapHorizRoundedIcon from '@material-ui/icons/SwapHorizRounded';
|
||||
import SwapCallsRoundedIcon from '@material-ui/icons/SwapCallsRounded';
|
||||
import _ from 'lodash';
|
||||
import { RegexIcon, FormatCaseIcon } from './ExternalIcon';
|
||||
import { isMac } from '../keyboard_shortcuts';
|
||||
|
||||
const useStyles = makeStyles((theme)=>({
|
||||
root: {
|
||||
position: 'relative',
|
||||
},
|
||||
findDialog: {
|
||||
position: 'absolute',
|
||||
zIndex: 99,
|
||||
right: '4px',
|
||||
...theme.mixins.panelBorder.all,
|
||||
borderTop: 'none',
|
||||
padding: '2px 4px',
|
||||
width: '250px',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
marginTop: {
|
||||
marginTop: '0.25rem',
|
||||
}
|
||||
}));
|
||||
|
||||
function parseString(string) {
|
||||
return string.replace(/\\([nrt\\])/g, function(match, ch) {
|
||||
if (ch == 'n') return '\n';
|
||||
if (ch == 'r') return '\r';
|
||||
if (ch == 't') return '\t';
|
||||
if (ch == '\\') return '\\';
|
||||
return match;
|
||||
});
|
||||
}
|
||||
|
||||
function parseQuery(query, useRegex=false, matchCase=false) {
|
||||
if (useRegex) {
|
||||
query = new RegExp(query, matchCase ? 'g': 'gi');
|
||||
} else {
|
||||
query = parseString(query);
|
||||
if(!matchCase) {
|
||||
query = query.toLowerCase();
|
||||
}
|
||||
}
|
||||
if (typeof query == 'string' ? query == '' : query.test(''))
|
||||
query = /x^/;
|
||||
return query;
|
||||
}
|
||||
|
||||
function searchOverlay(query, matchCase) {
|
||||
return {
|
||||
token: typeof query == 'string' ?
|
||||
(stream) =>{
|
||||
var matchIndex = (matchCase ? stream.string : stream.string.toLowerCase()).indexOf(query, stream.pos);
|
||||
if(matchIndex == -1) {
|
||||
stream.skipToEnd();
|
||||
} else if(matchIndex == stream.pos) {
|
||||
stream.pos += query.length;
|
||||
return 'searching';
|
||||
} else {
|
||||
stream.pos = matchIndex;
|
||||
}
|
||||
} : (stream) => {
|
||||
query.lastIndex = stream.pos;
|
||||
var match = query.exec(stream.string);
|
||||
if (match && match.index == stream.pos) {
|
||||
stream.pos += match[0].length || 1;
|
||||
return 'searching';
|
||||
} else if (match) {
|
||||
stream.pos = match.index;
|
||||
} else {
|
||||
stream.skipToEnd();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const CodeMirrorInstancType = PropTypes.shape({
|
||||
getCursor: PropTypes.func,
|
||||
getSearchCursor: PropTypes.func,
|
||||
removeOverlay: PropTypes.func,
|
||||
addOverlay: PropTypes.func,
|
||||
setSelection: PropTypes.func,
|
||||
scrollIntoView: PropTypes.func,
|
||||
});
|
||||
|
||||
export function FindDialog({editor, show, replace, onClose}) {
|
||||
const [findVal, setFindVal] = useState('');
|
||||
const [replaceVal, setReplaceVal] = useState('');
|
||||
const [useRegex, setUseRegex] = useState(false);
|
||||
const [matchCase, setMatchCase] = useState(false);
|
||||
const findInputRef = useRef();
|
||||
const highlightsearch = useRef();
|
||||
const searchCursor = useRef();
|
||||
const classes = useStyles();
|
||||
|
||||
const search = ()=>{
|
||||
if(editor) {
|
||||
let query = parseQuery(findVal, useRegex, matchCase);
|
||||
searchCursor.current = editor.getSearchCursor(query, editor.getCursor(true), !matchCase);
|
||||
if(findVal != '') {
|
||||
editor.removeOverlay(highlightsearch.current);
|
||||
highlightsearch.current = searchOverlay(query, matchCase);
|
||||
editor.addOverlay(highlightsearch.current);
|
||||
onFindNext();
|
||||
} else {
|
||||
editor.removeOverlay(highlightsearch.current);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
if(show) {
|
||||
findInputRef.current && findInputRef.current.select();
|
||||
search();
|
||||
}
|
||||
}, [show]);
|
||||
|
||||
useEffect(()=>{
|
||||
search();
|
||||
}, [findVal, useRegex, matchCase]);
|
||||
|
||||
const clearAndClose = ()=>{
|
||||
editor.removeOverlay(highlightsearch.current);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const toggle = (name)=>{
|
||||
if(name == 'regex') {
|
||||
setUseRegex((prev)=>!prev);
|
||||
} else if(name == 'case') {
|
||||
setMatchCase((prev)=>!prev);
|
||||
}
|
||||
};
|
||||
|
||||
const onFindEnter = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if(e.shiftKey) {
|
||||
onFindPrev();
|
||||
} else {
|
||||
onFindNext();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onReplaceEnter = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
onReplace();
|
||||
}
|
||||
};
|
||||
|
||||
const onEscape = (e)=>{
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
clearAndClose();
|
||||
}
|
||||
};
|
||||
|
||||
const onFindNext = ()=>{
|
||||
if(searchCursor.current && searchCursor.current.find()) {
|
||||
editor.setSelection(searchCursor.current.from(), searchCursor.current.to());
|
||||
editor.scrollIntoView({
|
||||
from: searchCursor.current.from(),
|
||||
to: searchCursor.current.to()
|
||||
}, 20);
|
||||
}
|
||||
};
|
||||
|
||||
const onFindPrev = ()=>{
|
||||
if(searchCursor.current && searchCursor.current.find(true)) {
|
||||
editor.setSelection(searchCursor.current.from(), searchCursor.current.to());
|
||||
editor.scrollIntoView({
|
||||
from: searchCursor.current.from(),
|
||||
to: searchCursor.current.to()
|
||||
}, 20);
|
||||
}
|
||||
};
|
||||
|
||||
const onReplace = ()=>{
|
||||
searchCursor.current.replace(replaceVal);
|
||||
onFindNext();
|
||||
};
|
||||
|
||||
const onReplaceAll = ()=>{
|
||||
while(searchCursor.current.from()) {
|
||||
onReplace();
|
||||
}
|
||||
};
|
||||
|
||||
if(!editor) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box className={classes.findDialog} visibility={show ? 'visible' : 'hidden'} tabIndex="0" onKeyDown={onEscape}>
|
||||
<InputText value={findVal}
|
||||
inputRef={(ele)=>{findInputRef.current = ele;}}
|
||||
onChange={(value)=>setFindVal(value)}
|
||||
onKeyPress={onFindEnter}
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
<PgIconButton data-test="case" title="Match case" icon={<FormatCaseIcon />} size="xs" noBorder
|
||||
onClick={()=>toggle('case')} color={matchCase ? 'primary' : 'default'} style={{marginRight: '2px'}}/>
|
||||
<PgIconButton data-test="regex" title="Use regex" icon={<RegexIcon />} size="xs" noBorder
|
||||
onClick={()=>toggle('regex')} color={useRegex ? 'primary' : 'default'}/>
|
||||
</InputAdornment>
|
||||
}
|
||||
/>
|
||||
{replace &&
|
||||
<InputText value={replaceVal}
|
||||
className={classes.marginTop}
|
||||
onChange={(value)=>setReplaceVal(value)}
|
||||
onKeyPress={onReplaceEnter}
|
||||
/>}
|
||||
|
||||
<Box display="flex" className={classes.marginTop}>
|
||||
<PgIconButton title={gettext('Previous')} icon={<ArrowUpwardRoundedIcon />} size="xs" noBorder onClick={onFindPrev} />
|
||||
<PgIconButton title={gettext('Next')} icon={<ArrowDownwardRoundedIcon />} size="xs" noBorder onClick={onFindNext}/>
|
||||
{replace && <>
|
||||
<PgIconButton title={gettext('Replace')} icon={<SwapHorizRoundedIcon style={{height: 'unset'}}/>} size="xs" noBorder onClick={onReplace} />
|
||||
<PgIconButton title={gettext('Replace All')} icon={<SwapCallsRoundedIcon />} size="xs" noBorder onClick={onReplaceAll}/>
|
||||
</>}
|
||||
<Box marginLeft="auto">
|
||||
<PgIconButton title={gettext('Close')} icon={<CloseIcon />} size="xs" noBorder onClick={clearAndClose}/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
FindDialog.propTypes = {
|
||||
editor: CodeMirrorInstancType,
|
||||
show: PropTypes.bool,
|
||||
replace: PropTypes.bool,
|
||||
onClose: PropTypes.func,
|
||||
};
|
||||
|
||||
/* React wrapper for CodeMirror */
|
||||
export default function CodeMirror({currEditor, name, value, options, events, readonly, disabled, className}) {
|
||||
@@ -19,13 +266,34 @@ export default function CodeMirror({currEditor, name, value, options, events, re
|
||||
const editor = useRef();
|
||||
const cmWrapper = useRef();
|
||||
const isVisibleTrack = useRef();
|
||||
const classes = useStyles();
|
||||
const [[showFind, isReplace], setShowFind] = useState([false, false]);
|
||||
const defaultOptions = {
|
||||
tabindex: '0',
|
||||
lineNumbers: true,
|
||||
styleSelectedText: true,
|
||||
mode: 'text/x-pgsql',
|
||||
foldOptions: {
|
||||
widget: '\u2026',
|
||||
},
|
||||
foldGutter: true,
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||
extraKeys: pgAdmin.Browser.editor_shortcut_keys,
|
||||
dragDrop: false,
|
||||
screenReaderLabel: gettext('SQL editor'),
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
const finalOptions = {...defaultOptions, ...options};
|
||||
/* Create the object only once on mount */
|
||||
editor.current = new OrigCodeMirror.fromTextArea(
|
||||
taRef.current, options);
|
||||
taRef.current, finalOptions);
|
||||
|
||||
editor.current.setValue(value);
|
||||
if(!_.isEmpty(value)) {
|
||||
editor.current.setValue(value);
|
||||
} else {
|
||||
editor.current.setValue('');
|
||||
}
|
||||
currEditor && currEditor(editor.current);
|
||||
if(editor.current) {
|
||||
try {
|
||||
@@ -33,11 +301,33 @@ export default function CodeMirror({currEditor, name, value, options, events, re
|
||||
} catch(e) {
|
||||
cmWrapper.current = null;
|
||||
}
|
||||
|
||||
let findKey = 'Ctrl-F', replaceKey = 'Shift-Ctrl-F';
|
||||
if(isMac()) {
|
||||
findKey = 'Cmd-F';
|
||||
replaceKey = 'Cmd-Alt-F';
|
||||
}
|
||||
editor.current.addKeyMap({
|
||||
[findKey]: ()=>{
|
||||
setShowFind([false, false]);
|
||||
setShowFind([true, false]);
|
||||
},
|
||||
[replaceKey]: ()=>{
|
||||
if(!finalOptions.readOnly) {
|
||||
setShowFind([false, false]);
|
||||
setShowFind([true, true]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Object.keys(events||{}).forEach((eventName)=>{
|
||||
editor.current.on(eventName, events[eventName]);
|
||||
});
|
||||
|
||||
return ()=>{
|
||||
editor.current?.toTextArea();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(()=>{
|
||||
@@ -62,7 +352,11 @@ export default function CodeMirror({currEditor, name, value, options, events, re
|
||||
useMemo(() => {
|
||||
if(editor.current) {
|
||||
if(value != editor.current.getValue()) {
|
||||
editor.current.setValue(value);
|
||||
if(!_.isEmpty(value)) {
|
||||
editor.current.setValue(value);
|
||||
} else {
|
||||
editor.current.setValue('');
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [value]);
|
||||
@@ -75,8 +369,16 @@ export default function CodeMirror({currEditor, name, value, options, events, re
|
||||
isVisibleTrack.current = false;
|
||||
}
|
||||
|
||||
const closeFind = ()=>{
|
||||
setShowFind([false, false]);
|
||||
editor.current?.focus();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={className}><textarea ref={taRef} name={name} /></div>
|
||||
<div className={clsx(className, classes.root)}>
|
||||
<FindDialog editor={editor.current} show={showFind} replace={isReplace} onClose={closeFind}/>
|
||||
<textarea ref={taRef} name={name} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import QueryToolSvg from '../../img/fonticon/query_tool.svg?svgr';
|
||||
import SaveDataSvg from '../../img/fonticon/save_data_changes.svg?svgr';
|
||||
import PasteSvg from '../../img/content_paste.svg?svgr';
|
||||
import FilterSvg from '../../img/filter_alt_black.svg?svgr';
|
||||
import ClearSvg from '../../img/cleaning_services_black.svg?svgr';
|
||||
import CommitSvg from '../../img/fonticon/commit.svg?svgr';
|
||||
import RollbackSvg from '../../img/fonticon/rollback.svg?svgr';
|
||||
import ConnectedSvg from '../../img/fonticon/connected.svg?svgr';
|
||||
import DisconnectedSvg from '../../img/fonticon/disconnected.svg?svgr';
|
||||
import RegexSvg from '../../img/fonticon/regex.svg?svgr';
|
||||
import FormatCaseSvg from '../../img/fonticon/format_case.svg?svgr';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default function ExternalIcon({Icon, ...props}) {
|
||||
return <Icon className='MuiSvgIcon-root' {...props} />;
|
||||
}
|
||||
|
||||
ExternalIcon.propTypes = {
|
||||
Icon: PropTypes.elementType.isRequired,
|
||||
};
|
||||
|
||||
export const QueryToolIcon = ()=><ExternalIcon Icon={QueryToolSvg} style={{height: '0.7em'}} />;
|
||||
export const SaveDataIcon = ()=><ExternalIcon Icon={SaveDataSvg} style={{height: '0.7em'}} />;
|
||||
export const PasteIcon = ()=><ExternalIcon Icon={PasteSvg} />;
|
||||
export const FilterIcon = ()=><ExternalIcon Icon={FilterSvg} />;
|
||||
export const CommitIcon = ()=><ExternalIcon Icon={CommitSvg} />;
|
||||
export const RollbackIcon = ()=><ExternalIcon Icon={RollbackSvg} />;
|
||||
export const ClearIcon = ()=><ExternalIcon Icon={ClearSvg} />;
|
||||
export const ConnectedIcon = ()=><ExternalIcon Icon={ConnectedSvg} style={{height: '0.7em'}} />;
|
||||
export const DisonnectedIcon = ()=><ExternalIcon Icon={DisconnectedSvg} style={{height: '0.7em'}} />;
|
||||
export const RegexIcon = ()=><ExternalIcon Icon={RegexSvg} />;
|
||||
export const FormatCaseIcon = ()=><ExternalIcon Icon={FormatCaseSvg} />;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import React, { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { Box, FormControl, OutlinedInput, FormHelperText,
|
||||
Grid, IconButton, FormControlLabel, Switch, Checkbox, useTheme, InputLabel, Paper } from '@material-ui/core';
|
||||
Grid, IconButton, FormControlLabel, Switch, Checkbox, useTheme, InputLabel, Paper, Select as MuiSelect } from '@material-ui/core';
|
||||
import { ToggleButton, ToggleButtonGroup } from '@material-ui/lab';
|
||||
import ErrorRoundedIcon from '@material-ui/icons/ErrorOutlineRounded';
|
||||
import InfoRoundedIcon from '@material-ui/icons/InfoRounded';
|
||||
@@ -148,7 +148,7 @@ FormInput.propTypes = {
|
||||
testcid: PropTypes.any,
|
||||
};
|
||||
|
||||
export function InputSQL({value, options, onChange, className, ...props}) {
|
||||
export function InputSQL({value, onChange, className, controlProps, ...props}) {
|
||||
const classes = useStyles();
|
||||
const editor = useRef();
|
||||
|
||||
@@ -156,17 +156,13 @@ export function InputSQL({value, options, onChange, className, ...props}) {
|
||||
<CodeMirror
|
||||
currEditor={(obj)=>editor.current=obj}
|
||||
value={value||''}
|
||||
options={{
|
||||
lineNumbers: true,
|
||||
mode: 'text/x-pgsql',
|
||||
...options,
|
||||
}}
|
||||
className={clsx(classes.sql, className)}
|
||||
events={{
|
||||
change: (cm)=>{
|
||||
onChange && onChange(cm.getValue());
|
||||
},
|
||||
}}
|
||||
{...controlProps}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -177,15 +173,16 @@ InputSQL.propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
readonly: PropTypes.bool,
|
||||
className: CustomPropTypes.className,
|
||||
controlProps: PropTypes.object,
|
||||
};
|
||||
|
||||
export function FormInputSQL({hasError, required, label, className, helpMessage, testcid, value, controlProps, noLabel, ...props}) {
|
||||
export function FormInputSQL({hasError, required, label, className, helpMessage, testcid, value, noLabel, ...props}) {
|
||||
if(noLabel) {
|
||||
return <InputSQL value={value} options={controlProps} {...props}/>;
|
||||
return <InputSQL value={value} {...props}/>;
|
||||
} else {
|
||||
return (
|
||||
<FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid} >
|
||||
<InputSQL value={value} options={controlProps} {...props}/>
|
||||
<InputSQL value={value} {...props}/>
|
||||
</FormInput>
|
||||
);
|
||||
}
|
||||
@@ -198,7 +195,6 @@ FormInputSQL.propTypes = {
|
||||
helpMessage: PropTypes.string,
|
||||
testcid: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
controlProps: PropTypes.object,
|
||||
noLabel: PropTypes.bool,
|
||||
change: PropTypes.func,
|
||||
};
|
||||
@@ -739,6 +735,17 @@ function getRealValue(options, value, creatable, formatter) {
|
||||
}
|
||||
return realValue;
|
||||
}
|
||||
export function InputSelectNonSearch({options, ...props}) {
|
||||
return <MuiSelect native {...props} variant="outlined">
|
||||
{(options||[]).map((o)=><option key={o.value} value={o.value}>{o.label}</option>)}
|
||||
</MuiSelect>;
|
||||
}
|
||||
InputSelectNonSearch.propTypes = {
|
||||
options: PropTypes.arrayOf(PropTypes.shape({
|
||||
label: PropTypes.shape,
|
||||
value: PropTypes.any,
|
||||
})),
|
||||
};
|
||||
|
||||
export const InputSelect = forwardRef(({
|
||||
cid, onChange, options, readonly=false, value, controlProps={}, optionsLoaded, optionsReloadBasis, disabled, ...props}, ref) => {
|
||||
@@ -755,6 +762,7 @@ export const InputSelect = forwardRef(({
|
||||
if(typeof options === 'function') {
|
||||
optPromise = options();
|
||||
}
|
||||
setFinalOptions([[], true]);
|
||||
Promise.resolve(optPromise)
|
||||
.then((res)=>{
|
||||
/* If component unmounted, dont update state */
|
||||
|
||||