Significant changes to use ReactJS extensively.

1. Replace the current layout library wcDocker with ReactJS based rc-dock. #6479
2. Have close buttons on individual panel tabs instead of common. #2821
3. Changes in the context menu on panel tabs - Add close, close all and close others menu items. #5394
4. Allow closing all the tabs, including SQL and Properties. #4733
5. Changes in docking behaviour of different tabs based on user requests and remove lock layout menu.
6. Fix an issue where the scroll position of panels was not remembered on Firefox. #2986
7. Reset layout now will not require page refresh and is done spontaneously.
8. Use the zustand store for storing preferences instead of plain JS objects. This will help reflecting preferences immediately.
9. The above fix incorrect format (no indent) of SQL stored functions/procedures. #6720
10. New version check is moved to an async request now instead of app start to improve startup performance.
11. Remove jQuery and Bootstrap completely.
12. Replace jasmine and karma test runner with jest. Migrate all the JS test cases to jest. This will save time in writing and debugging JS tests.
13. Other important code improvements and cleanup.
This commit is contained in:
Aditya Toshniwal 2023-10-23 17:43:17 +05:30 committed by GitHub
parent 6d555645e9
commit 862f101772
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
373 changed files with 11149 additions and 14836 deletions

View File

@ -36,4 +36,4 @@ jobs:
- name: Run the tests - name: Run the tests
run: | run: |
cd web cd web
yarn run test:karma-once yarn run test:js-once

View File

@ -55,7 +55,8 @@ RUN export CPPFLAGS="-DPNG_ARM_NEON_OPT=0" && \
.[^.]* \ .[^.]* \
babel.cfg \ babel.cfg \
webpack.* \ webpack.* \
karma.conf.js \ jest.config.js \
babel.* \
./pgadmin/static/js/generated/.cache ./pgadmin/static/js/generated/.cache
######################################################################### #########################################################################

View File

@ -42,7 +42,7 @@ linter:
cd web && yarn run linter cd web && yarn run linter
check: install-node bundle linter check-pep8 check: install-node bundle linter check-pep8
cd web && yarn run karma start --single-run && python regression/runtests.py cd web && yarn run test:js-once && python regression/runtests.py
check-audit: check-audit:
cd web && yarn run audit cd web && yarn run audit
@ -77,10 +77,10 @@ check-feature: install-node bundle
cd web && python regression/runtests.py --pkg feature_tests cd web && python regression/runtests.py --pkg feature_tests
check-js: install-node linter check-js: install-node linter
cd web && yarn run karma start --single-run cd web && yarn run test:js-once
check-js-coverage: check-js-coverage:
cd web && yarn run test:karma-coverage cd web && yarn run test:js-coverage
# Include all clean sub-targets in clean # Include all clean sub-targets in clean
clean: clean-appbundle clean-debian clean-dist clean-docs clean-node clean-pip clean-redhat clean-src clean: clean-appbundle clean-debian clean-dist clean-docs clean-node clean-pip clean-redhat clean-src

View File

@ -1,7 +1,7 @@
# pgAdmin 4 # pgAdmin 4
pgAdmin 4 is a rewrite of the popular pgAdmin3 management tool for the pgAdmin 4 is a rewrite of the popular pgAdmin3 management tool for the
PostgreSQL (http://www.postgresql.org) database. PostgreSQL (http://www.postgresql.org) database.
In the following documentation and examples, *$PGADMIN4_SRC/* is used to denote In the following documentation and examples, *$PGADMIN4_SRC/* is used to denote
the top-level directory of a copy of the pgAdmin source tree, either from a the top-level directory of a copy of the pgAdmin source tree, either from a
@ -9,9 +9,8 @@ tarball or a git checkout.
## Architecture ## Architecture
pgAdmin 4 is written as a web application in Python, using jQuery and Bootstrap pgAdmin 4 is written as a web application with Python(Flask) on the server side
for the client side processing and UI. On the server side, Flask is being and ReactJS, HTML5 with CSS for the client side processing and UI.
utilised.
Although developed using web technologies, pgAdmin 4 can be deployed either on Although developed using web technologies, pgAdmin 4 can be deployed either on
a web server using a browser, or standalone on a workstation. The runtime/ a web server using a browser, or standalone on a workstation. The runtime/
@ -22,7 +21,7 @@ which will execute the Python server and display the UI.
To build the runtime, the following packages must be installed: To build the runtime, the following packages must be installed:
* NodeJS 12+ * NodeJS 16+
* Yarn * Yarn
Change into the runtime directory, and run *yarn install*. This will install the Change into the runtime directory, and run *yarn install*. This will install the
@ -59,20 +58,20 @@ simple - adapt as required for your distribution:
```bash ```bash
$ python3 -m venv venv $ python3 -m venv venv
``` ```
2. Now activate the virtual environment: 2. Now activate the virtual environment:
```bash ```bash
$ source venv/bin/activate $ source venv/bin/activate
``` ```
3. Some of the components used by pgAdmin require a very recent version of *pip*, 3. Some of the components used by pgAdmin require a very recent version of *pip*,
so update that to the latest: so update that to the latest:
```bash ```bash
$ pip install --upgrade pip $ pip install --upgrade pip
``` ```
4. Ensure that a PostgreSQL installation's bin/ directory is in the path (so 4. Ensure that a PostgreSQL installation's bin/ directory is in the path (so
pg_config can be found for building psycopg3), and install the required pg_config can be found for building psycopg3), and install the required
packages: packages:
@ -80,20 +79,20 @@ simple - adapt as required for your distribution:
```bash ```bash
(venv) $ PATH=$PATH:/usr/local/pgsql/bin pip install -r $PGADMIN4_SRC/requirements.txt (venv) $ PATH=$PATH:/usr/local/pgsql/bin pip install -r $PGADMIN4_SRC/requirements.txt
``` ```
If you are planning to run the regression tests, you also need to install If you are planning to run the regression tests, you also need to install
additional requirements from web/regression/requirements.txt: additional requirements from web/regression/requirements.txt:
```bash ```bash
(venv) $ pip install -r $PGADMIN4_SRC/web/regression/requirements.txt (venv) $ pip install -r $PGADMIN4_SRC/web/regression/requirements.txt
``` ```
5. Create a local configuration file for pgAdmin. Edit 5. Create a local configuration file for pgAdmin. Edit
$PGADMIN4_SRC/web/config_local.py and add any desired configuration options $PGADMIN4_SRC/web/config_local.py and add any desired configuration options
(use the config.py file as a reference - any settings duplicated in (use the config.py file as a reference - any settings duplicated in
config_local.py will override those in config.py). A typical development config_local.py will override those in config.py). A typical development
configuration may look like: configuration may look like:
```python ```python
from config import * from config import *
@ -126,7 +125,7 @@ simple - adapt as required for your distribution:
'pgadmin4-server.db' 'pgadmin4-server.db'
) )
``` ```
This configuration allows easy switching between server and desktop modes This configuration allows easy switching between server and desktop modes
for testing. for testing.
@ -137,13 +136,13 @@ simple - adapt as required for your distribution:
```bash ```bash
(venv) $ python3 $PGADMIN4_SRC/web/setup.py (venv) $ python3 $PGADMIN4_SRC/web/setup.py
``` ```
or by starting pgAdmin 4: or by starting pgAdmin 4:
```bash ```bash
(venv) $ python3 $PGADMIN4_SRC/web/pgAdmin4.py (venv) $ python3 $PGADMIN4_SRC/web/pgAdmin4.py
``` ```
Whilst it is possible to automatically run setup in desktop mode by running Whilst it is possible to automatically run setup in desktop mode by running
the runtime, that will not work in server mode as the runtime doesn't allow the runtime, that will not work in server mode as the runtime doesn't allow
command line interaction with the setup program. command line interaction with the setup program.
@ -228,7 +227,7 @@ To build a source tarball:
(venv) $ make src (venv) $ make src
``` ```
To build a PIP Wheel, activate either a Python 3 virtual environment, configured To build a PIP Wheel, activate either a Python 3 virtual environment, configured
with all the required packages, and then run: with all the required packages, and then run:
```bash ```bash
@ -266,7 +265,7 @@ See https://www.pgadmin.org/support/ for support options.
If you would like to report a security issue with pgAdmin, please email If you would like to report a security issue with pgAdmin, please email
**security (at) pgadmin (dot) org**. **security (at) pgadmin (dot) org**.
Note that this address should only be used for reporting security issues Note that this address should only be used for reporting security issues
that you believe you've found in the design or code of pgAdmin, pgAgent, that you believe you've found in the design or code of pgAdmin, pgAgent,
and the pgAdmin website. It should not be used to ask security questions. and the pgAdmin website. It should not be used to ask security questions.

View File

@ -5,7 +5,7 @@
********************** **********************
The bulk of pgAdmin is a Python web application written using the Flask framework The bulk of pgAdmin is a Python web application written using the Flask framework
on the backend, and HTML5 with CSS3, Bootstrap and jQuery on the front end. A on the backend, and HTML5 with CSS3,ReactJS on the front end. A
desktop runtime is also included for users that prefer a desktop application to desktop runtime is also included for users that prefer a desktop application to
a web application, which is written using NWjs (Node Webkit). a web application, which is written using NWjs (Node Webkit).

View File

@ -64,7 +64,7 @@ All HTML must be HTML 5 compliant.
Javascript Javascript
********** **********
Client-side code is written in Javascript using jQuery and various plugins. Client-side code is written in Javascript using ReactJS and various plugins.
Whilst much of the code is rendered from static files, there is also code that Whilst much of the code is rendered from static files, there is also code that
is rendered from templates using Jinja2 (often to inject the users settings) or is rendered from templates using Jinja2 (often to inject the users settings) or
constructed on the fly from module hooks. constructed on the fly from module hooks.

View File

@ -25,9 +25,6 @@ Use the *File* menu to access the following options:
+-------------------------+---------------------------------------------------------------------------------------------------------+ +-------------------------+---------------------------------------------------------------------------------------------------------+
| *Reset Layout* | If you have modified the workspace, click to restore the default layout. | | *Reset Layout* | If you have modified the workspace, click to restore the default layout. |
+-------------------------+---------------------------------------------------------------------------------------------------------+ +-------------------------+---------------------------------------------------------------------------------------------------------+
| *Lock Layout* | Click to open a submenu to select the level for locking the UI layout |
| | This can also be changed from browser display :ref:`preferences <preferences>` |
+-------------------------+---------------------------------------------------------------------------------------------------------+
| *Runtime* | Click to open a submenu to Configure, View Log and Zoom settings. Only visible when pgAdmin4 runs in | | *Runtime* | Click to open a submenu to Configure, View Log and Zoom settings. Only visible when pgAdmin4 runs in |
| | desktop mode. To know more about runtime menu :ref:`click here <desktop_deployment>` | | | desktop mode. To know more about runtime menu :ref:`click here <desktop_deployment>` |
+-------------------------+---------------------------------------------------------------------------------------------------------+ +-------------------------+---------------------------------------------------------------------------------------------------------+

View File

@ -46,19 +46,6 @@ Use the fields on the *Display* panel to specify general display preferences:
* When the *Hide shared servers?* switch is set to *True*, the client will hide * When the *Hide shared servers?* switch is set to *True*, the client will hide
all the shared servers from the object explorer. all the shared servers from the object explorer.
* Use the *Lock layout* field to lock the UI layout at different levels. This
can also be changed from File menu on the :ref:`menu bar <menu_bar>`
+---------------------+-------------------------------------------------------------------+
| Option | Action |
+=====================+===================================================================+
| *None* | No locking. Every panel is resizable and dockable. |
+---------------------+-------------------------------------------------------------------+
| *Prevent docking* | This will disable the docking/undocking of the panels |
+---------------------+-------------------------------------------------------------------+
| *Full* | This will disable resizing, docking/undocking of the panels |
+---------------------+-------------------------------------------------------------------+
* When the *Show empty object collections?* switch is turned off, then all object * When the *Show empty object collections?* switch is turned off, then all object
collections which are empty will be hidden from browser tree. collections which are empty will be hidden from browser tree.
* When the *Show system objects?* switch is set to *True*, the client will * When the *Show system objects?* switch is set to *True*, the client will

View File

@ -2,7 +2,7 @@
html{ html{
background-color: #fff; background-color: #fff;
} }
body { body {
@ -10,6 +10,7 @@ body {
font-family: Roboto, Helvetica Neue, -apple-system, BlinkMacSystemFont, Segoe UI, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; font-family: Roboto, Helvetica Neue, -apple-system, BlinkMacSystemFont, Segoe UI, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
background-color: #fff; background-color: #fff;
height: 100vh;
} }
div.body { div.body {

View File

@ -224,7 +224,7 @@ _copy_code() {
cp "${SOURCEDIR}/pkg/linux/config_distro.py" "${SERVERROOT}/usr/${APP_NAME}/web/" cp "${SOURCEDIR}/pkg/linux/config_distro.py" "${SERVERROOT}/usr/${APP_NAME}/web/"
cd "${SERVERROOT}/usr/${APP_NAME}/web/" || exit cd "${SERVERROOT}/usr/${APP_NAME}/web/" || exit
rm -f pgadmin4.db config_local.* rm -f pgadmin4.db config_local.*
rm -rf karma.conf.js package.json node_modules/ regression/ tools/ pgadmin/static/js/generated/.cache rm -rf jest.config.js babel.* package.json node_modules/ regression/ tools/ pgadmin/static/js/generated/.cache
find . -name "tests" -type d -print0 | xargs -0 rm -rf find . -name "tests" -type d -print0 | xargs -0 rm -rf
find . -name "feature_tests" -type d -print0 | xargs -0 rm -rf find . -name "feature_tests" -type d -print0 | xargs -0 rm -rf
find . -name "__pycache__" -type d -print0 | xargs -0 rm -rf find . -name "__pycache__" -type d -print0 | xargs -0 rm -rf

View File

@ -298,7 +298,7 @@ _complete_bundle() {
cp -r "${SOURCE_DIR}/web" "${BUNDLE_DIR}/Contents/Resources/" cp -r "${SOURCE_DIR}/web" "${BUNDLE_DIR}/Contents/Resources/"
cd "${BUNDLE_DIR}/Contents/Resources/web" || exit cd "${BUNDLE_DIR}/Contents/Resources/web" || exit
rm -f pgadmin4.db config_local.* rm -f pgadmin4.db config_local.*
rm -rf karma.conf.js package.json .yarn* yarn* .editorconfig .eslint* node_modules/ regression/ tools/ pgadmin/static/js/generated/.cache rm -rf jest.config.js babel.* package.json .yarn* yarn* .editorconfig .eslint* node_modules/ regression/ tools/ pgadmin/static/js/generated/.cache
find . -name "tests" -type d -print0 | xargs -0 rm -rf find . -name "tests" -type d -print0 | xargs -0 rm -rf
find . -name "feature_tests" -type d -print0 | xargs -0 rm -rf find . -name "feature_tests" -type d -print0 | xargs -0 rm -rf
find . -name "__pycache__" -type d -print0 | xargs -0 rm -rf find . -name "__pycache__" -type d -print0 | xargs -0 rm -rf

View File

@ -12,7 +12,6 @@ module.exports = {
'browser': true, 'browser': true,
'es6': true, 'es6': true,
'amd': true, 'amd': true,
'jasmine': true,
}, },
'extends': [ 'extends': [
'eslint:recommended', 'eslint:recommended',

View File

@ -12,7 +12,6 @@ module.exports = {
'browser': true, 'browser': true,
'es6': true, 'es6': true,
'amd': true, 'amd': true,
'jasmine': true,
}, },
'extends': [ 'extends': [
'eslint:recommended', 'eslint:recommended',
@ -41,10 +40,11 @@ module.exports = {
'plugins': [ 'plugins': [
'react', 'react',
'@babel', '@babel',
'jest'
], ],
'overrides': [ 'overrides': [
{ {
'files': ['**/*.ts', '**/*.tsx'], 'files': ['**/*.{ts,tsx}'],
'plugins': [ 'plugins': [
'@typescript-eslint', '@typescript-eslint',
], ],
@ -55,10 +55,20 @@ module.exports = {
'@typescript-eslint/no-this-alias': ['off'], '@typescript-eslint/no-this-alias': ['off'],
} }
}, },
{
'files': ['**/*{spec,test}.{js,jsx}', './regression/javascript/**/*.{js}'],
'extends': ['eslint:recommended'],
'env': {
'jest': true
}
},
], ],
'globals': { 'globals': {
'_': true, '_': true,
'module': true, 'module': true,
'__dirname': true,
'global': true,
'jest': true
}, },
'rules': { 'rules': {
'indent': [ 'indent': [

4
web/babel.config.json Normal file
View File

@ -0,0 +1,4 @@
{
"presets": [["@babel/preset-env", {"modules": "commonjs", "useBuiltIns": "usage", "corejs": 3}], "@babel/preset-react", "@babel/preset-typescript"],
"plugins": ["@babel/plugin-proposal-class-properties", "@babel/proposal-object-rest-spread", "@babel/plugin-transform-runtime"]
}

57
web/jest.config.js Normal file
View File

@ -0,0 +1,57 @@
const webpackShimAlias = require('./webpack.shim').resolveAlias;
const webpackAliasToJestModules = ()=>{
const ret = {
'\\.svg': '<rootDir>/regression/javascript/__mocks__/svg.js'
};
Object.keys(webpackShimAlias).forEach((an)=>{
// eg - sources: ./pgadmin/static/js/ to '^sources/(.*)$': '<rootDir>/pgadmin/static/js/$1'
let ap = webpackShimAlias[an].replace(__dirname, '<rootDir>');
if(ap.endsWith('/')) {
ret[`^${an}/(.*)$`] = ap + '$1';
return;
}
ret[`^${an}$`] = ap;
});
// Overrides
ret['^translations$'] = '<rootDir>/regression/javascript/fake_translations';
ret['^pgadmin.browser.messages$'] = '<rootDir>/regression/javascript/fake_messages';
ret['^pgadmin.browser.endpoints$'] = '<rootDir>/regression/javascript/fake_endpoints';
ret['^pgadmin.browser.translations$'] = '<rootDir>/regression/javascript/fake_translations';
ret['^pgadmin.user_management.current_user$'] = '<rootDir>/regression/javascript/fake_current_user';
ret['^pgadmin.server.supported_servers$'] = '<rootDir>/regression/javascript/fake_supported_servers';
const sources = ret['^sources/(.*)$'];
delete ret['^sources/(.*)$'];
ret['^sources/pgadmin$'] = '<rootDir>/regression/javascript/fake_pgadmin';
ret['^sources/gettext$'] = '<rootDir>/regression/javascript/fake_gettext';
ret['^sources/(.*)$'] = sources;
// Only for tests
ret['^pgadmin.schema.dir/(.*)$'] = '<rootDir>/pgadmin/browser/server_groups/servers/databases/schemas/static/js/$1';
ret['^browser/(.*)$'] = '<rootDir>/pgadmin/browser/static/js/$1';
return ret;
};
module.exports = {
'roots': ['<rootDir>/pgadmin/', '<rootDir>/regression/javascript'],
'moduleFileExtensions': ['js', 'jsx', 'ts', 'tsx'],
'moduleNameMapper': webpackAliasToJestModules(),
'transform': {
'^.+\\.(js|jsx|mjs|cjs|ts|tsx)$': 'babel-jest',
},
'setupFilesAfterEnv': [
'<rootDir>/regression/javascript/setup-jest.js',
],
'testMatch': [
'<rootDir>/regression/javascript/**/*{spec,test}.{js,jsx,ts,tsx}'
],
'testEnvironment': 'jsdom',
'transformIgnorePatterns': [
'[/\\\\]node_modules[/\\\\](?!react-dnd|dnd-core|@react-dnd).+\\.(js|jsx|mjs|cjs|ts|tsx)$',
'^.+\\.module\\.(css|sass|scss)$'
]
};

View File

@ -1,106 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
// Karma configuration
const webpackConfig = require('./webpack.test.config.js');
const isDocker = require('is-docker')();
const webpack = require('webpack');
module.exports = function (config) {
config.set({
frameworks: ['jasmine', 'source-map-support'],
reporters: ['progress', 'kjhtml'],
plugins: [
'karma-webpack',
'karma-chrome-launcher',
'karma-jasmine',
'karma-jasmine-html-reporter',
'karma-source-map-support',
'karma-sourcemap-loader',
'karma-coverage',
new webpack.SourceMapDevToolPlugin({
/*
* filename: null, // if no value is provided the sourcemap is inlined
*/
filename: '[name].js.map',
test: /\.js$/i, // process .js files only
}),
],
files: [
{pattern: 'pgadmin/static/**/*.js', included: false, watched: true},
{pattern: 'pgadmin/browser/static/js/**/*.js', included: false, watched: true},
{pattern: 'regression/javascript/**/*.js', watched: true},
],
// list of files to exclude
exclude: [
'pgadmin/static/vendor/**/*[Tt]est.js',
'pgadmin/static/vendor/**/*[Ss]pec.js',
],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'pgadmin/**/js/**/*.js?': ['sourcemap'],
'regression/javascript/**/*.js': ['webpack', 'sourcemap'],
},
// optionally, configure the reporter
coverageReporter: {
reporters: [
// reporters not supporting the `file` property
{ type: 'html', subdir: 'report-html' },
{ type: 'lcovonly', subdir: 'report-lcov' },
],
dir : 'coverage/',
includeAllSources: true,
},
webpack: webpackConfig,
webpackMiddleware: {
stats: 'errors-only',
},
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_WARN,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
usePolling: true,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
customLaunchers: {
ChromeCustom: {
base: 'ChromeHeadless',
// We must disable the Chrome sandbox when running Chrome inside Docker (Chrome's sandbox needs
// more permissions than Docker allows by default)
flags: isDocker ? ['--no-sandbox'] : [],
},
},
browsers: ['ChromeCustom'],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: false,
// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity,
});
};

View File

@ -12,6 +12,7 @@
"@babel/eslint-plugin": "^7.17.7", "@babel/eslint-plugin": "^7.17.7",
"@babel/plugin-proposal-object-rest-spread": "^7.10.1", "@babel/plugin-proposal-object-rest-spread": "^7.10.1",
"@babel/plugin-syntax-jsx": "^7.16.0", "@babel/plugin-syntax-jsx": "^7.16.0",
"@babel/plugin-transform-runtime": "^7.22.15",
"@babel/preset-env": "^7.10.2", "@babel/preset-env": "^7.10.2",
"@babel/preset-typescript": "^7.22.5", "@babel/preset-typescript": "^7.22.5",
"@emotion/core": "^10.0.14", "@emotion/core": "^10.0.14",
@ -20,44 +21,35 @@
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@emotion/utils": "^1.0.0", "@emotion/utils": "^1.0.0",
"@svgr/webpack": "^6.2.1", "@svgr/webpack": "^6.2.1",
"@testing-library/jest-dom": "^6.1.2",
"@testing-library/react": "12",
"@testing-library/user-event": "^14.4.3",
"@types/jest": "^29.5.4",
"@typescript-eslint/eslint-plugin": "^5.59.9", "@typescript-eslint/eslint-plugin": "^5.59.9",
"@typescript-eslint/parser": "^5.57.0", "@typescript-eslint/parser": "^5.57.0",
"@wojtekmaj/enzyme-adapter-react-17": "^0.8.0",
"autoprefixer": "^10.2.4", "autoprefixer": "^10.2.4",
"axios-mock-adapter": "^1.17.0", "axios-mock-adapter": "^1.17.0",
"babel-loader": "^8.1.0", "babel-loader": "^8.1.0",
"browserify": "^17.0.0", "browserify": "^17.0.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"copy-webpack-plugin": "^11.0.0", "copy-webpack-plugin": "^11.0.0",
"core-js": "^3.2.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"css-loader": "^6.7.2", "css-loader": "^6.7.2",
"css-minimizer-webpack-plugin": "^5.0.0", "css-minimizer-webpack-plugin": "^5.0.0",
"enzyme": "^3.11.0",
"eslint": "^8.37.0", "eslint": "^8.37.0",
"eslint-plugin-jest": "^27.4.0",
"eslint-plugin-react": "^7.33.2", "eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.3.0", "eslint-plugin-react-hooks": "^4.3.0",
"exports-loader": "^4.0.0", "exports-loader": "^4.0.0",
"html-react-parser": "^4.2.0", "html-react-parser": "^4.2.2",
"image-minimizer-webpack-plugin": "^3.8.2", "image-minimizer-webpack-plugin": "^3.8.2",
"imagemin": "^8.0.1", "imagemin": "^8.0.1",
"imagemin-mozjpeg": "^10.0.0", "imagemin-mozjpeg": "^10.0.0",
"imagemin-optipng": "^8.0.0", "imagemin-optipng": "^8.0.0",
"imports-loader": "^4.0.1", "imports-loader": "^4.0.1",
"is-docker": "^2.1.1", "is-docker": "^2.1.1",
"istanbul-instrumenter-loader": "^3.0.1", "jest": "^29.6.4",
"jasmine-core": "3.10.1", "jest-environment-jsdom": "^29.6.4",
"jasmine-enzyme": "^7.1.2",
"karma": "^6.3.15",
"karma-babel-preprocessor": "^8.0.0",
"karma-browserify": "^8.0.0",
"karma-chrome-launcher": "^3.1.0",
"karma-jasmine": "^4.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"karma-requirejs": "~1.1.0",
"karma-source-map-support": "^1.4.0",
"karma-sourcemap-loader": "^0.4.0",
"karma-webpack": "^5.0.0",
"loader-utils": "^3.2.1", "loader-utils": "^3.2.1",
"mini-css-extract-plugin": "^2.7.6", "mini-css-extract-plugin": "^2.7.6",
"postcss-loader": "^7.1.0", "postcss-loader": "^7.1.0",
@ -87,6 +79,7 @@
"@date-io/core": "^1.3.6", "@date-io/core": "^1.3.6",
"@date-io/date-fns": "1.x", "@date-io/date-fns": "1.x",
"@emotion/sheet": "^1.0.1", "@emotion/sheet": "^1.0.1",
"@fortawesome/fontawesome-free": "latest",
"@material-ui/core": "4.12.4", "@material-ui/core": "4.12.4",
"@material-ui/icons": "^4.11.2", "@material-ui/icons": "^4.11.2",
"@material-ui/lab": "4.0.0-alpha.61", "@material-ui/lab": "4.0.0-alpha.61",
@ -103,7 +96,6 @@
"axios": "^1.4.0", "axios": "^1.4.0",
"babelify": "~10.0.0", "babelify": "~10.0.0",
"bignumber.js": "^9.0.1", "bignumber.js": "^9.0.1",
"bootstrap": "^4.3.1",
"brace": "^0.11.1", "brace": "^0.11.1",
"browserfs": "^1.4.3", "browserfs": "^1.4.3",
"chart.js": "^3.0.0", "chart.js": "^3.0.0",
@ -120,11 +112,8 @@
"immutability-helper": "^3.0.0", "immutability-helper": "^3.0.0",
"insert-if": "^1.1.0", "insert-if": "^1.1.0",
"ip-address": "^7.1.0", "ip-address": "^7.1.0",
"jquery": "^3.6.0",
"jquery-contextmenu": "^2.9.2",
"json-bignumber": "^1.0.1", "json-bignumber": "^1.0.1",
"jsoneditor": "^9.5.4", "jsoneditor": "^9.5.4",
"karma-coverage": "^2.0.3",
"leaflet": "^1.5.1", "leaflet": "^1.5.1",
"lodash": "4.*", "lodash": "4.*",
"ml-matrix": "^6.5.0", "ml-matrix": "^6.5.0",
@ -150,10 +139,11 @@
"react-dom": "^17.0.1", "react-dom": "^17.0.1",
"react-draggable": "^4.4.4", "react-draggable": "^4.4.4",
"react-dropzone": "^14.2.1", "react-dropzone": "^14.2.1",
"react-frame-component": "^5.2.6",
"react-leaflet": "^3.2.2", "react-leaflet": "^3.2.2",
"react-new-window": "^1.0.1",
"react-resize-detector": "^9.1.0", "react-resize-detector": "^9.1.0",
"react-rnd": "^10.3.5", "react-rnd": "^10.3.5",
"react-router-dom": "^6.2.2",
"react-select": "^5.7.2", "react-select": "^5.7.2",
"react-table": "^7.6.3", "react-table": "^7.6.3",
"react-timer-hook": "^3.0.5", "react-timer-hook": "^3.0.5",
@ -163,16 +153,15 @@
"socket.io-client": "^4.5.0", "socket.io-client": "^4.5.0",
"split.js": "^1.5.10", "split.js": "^1.5.10",
"styled-components": "^5.2.1", "styled-components": "^5.2.1",
"tempusdominus-core": "^5.19.3",
"uplot": "^1.6.24", "uplot": "^1.6.24",
"uplot-react": "^1.1.4", "uplot-react": "^1.1.4",
"valid-filename": "^2.0.1", "valid-filename": "^2.0.1",
"webcabin-docker": "https://github.com/pgadmin-org/wcdocker#460fc6d90ba170bb177faaa8277f5fbb8279522a",
"wkx": "^0.5.0", "wkx": "^0.5.0",
"xterm": "^4.11.0", "xterm": "^4.11.0",
"xterm-addon-fit": "^0.5.0", "xterm-addon-fit": "^0.5.0",
"xterm-addon-search": "^0.8.0", "xterm-addon-search": "^0.8.0",
"xterm-addon-web-links": "^0.4.0" "xterm-addon-web-links": "^0.4.0",
"zustand": "^4.4.1"
}, },
"scripts": { "scripts": {
"linter": "yarn eslint --no-eslintrc -c .eslintrc.js --ext .js --ext .jsx --ext .ts --ext .tsx .", "linter": "yarn eslint --no-eslintrc -c .eslintrc.js --ext .js --ext .jsx --ext .ts --ext .tsx .",
@ -181,12 +170,12 @@
"bundle:watch": "yarn run linter && yarn run webpacker:watch", "bundle:watch": "yarn run linter && yarn run webpacker:watch",
"bundle:dev": "yarn run linter && yarn run webpacker", "bundle:dev": "yarn run linter && yarn run webpacker",
"bundle:analyze": "cross-env NODE_ENV=production ANALYZE=true yarn run bundle:dev", "bundle:analyze": "cross-env NODE_ENV=production ANALYZE=true yarn run bundle:dev",
"bundle": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=3072 yarn run bundle:dev", "bundle": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=2048 yarn run bundle:dev",
"test:karma-once": "yarn run linter && yarn run karma start --single-run", "test:js-once": "yarn run linter && yarn run jest --maxWorkers=50%",
"test:karma": "yarn run linter && yarn run karma start", "test:js": "yarn run test:js-once --watch",
"test:karma-coverage": "yarn run test:karma-once --reporters coverage,progress", "test:js-coverage": "yarn run test:js-once --collect-coverage",
"test:feature": "yarn run bundle && python regression/runtests.py --pkg feature_tests", "test:feature": "yarn run bundle && python regression/runtests.py --pkg feature_tests",
"test": "yarn run test:karma-once && yarn run bundle && python regression/runtests.py", "test": "yarn run test:js-once && yarn run bundle && python regression/runtests.py",
"pep8": "pycodestyle --config=../.pycodestyle ../docs && pycodestyle --config=../.pycodestyle ../pkg && pycodestyle --config=../.pycodestyle ../tools && pycodestyle --config=../.pycodestyle ../web", "pep8": "pycodestyle --config=../.pycodestyle ../docs && pycodestyle --config=../.pycodestyle ../pkg && pycodestyle --config=../.pycodestyle ../tools && pycodestyle --config=../.pycodestyle ../web",
"auditjs-html": "yarn audit --json | yarn run yarn-audit-html --output ../auditjs.html", "auditjs-html": "yarn audit --json | yarn run yarn-audit-html --output ../auditjs.html",
"auditjs": "yarn audit --groups dependencies", "auditjs": "yarn audit --groups dependencies",

View File

@ -165,13 +165,6 @@ class PgAdmin(Flask):
return scripts return scripts
@property
def panels(self):
panels = []
for module in self.submodules:
panels.extend(module.get_panels())
return panels
@property @property
def menu_items(self): def menu_items(self):
from operator import attrgetter from operator import attrgetter

View File

@ -17,8 +17,8 @@ import { makeStyles } from '@material-ui/styles';
import { InputText } from '../../../static/js/components/FormComponents'; import { InputText } from '../../../static/js/components/FormComponents';
import getApiInstance from '../../../static/js/api_instance'; import getApiInstance from '../../../static/js/api_instance';
import { copyToClipboard } from '../../../static/js/clipboard'; import { copyToClipboard } from '../../../static/js/clipboard';
import Notify from '../../../static/js/helpers/Notifier';
import { useDelayedCaller } from '../../../static/js/custom_hooks'; import { useDelayedCaller } from '../../../static/js/custom_hooks';
import { usePgAdmin } from '../../../static/js/BrowserComponent';
const useStyles = makeStyles((theme)=>({ const useStyles = makeStyles((theme)=>({
@ -44,6 +44,7 @@ export default function AboutComponent() {
const revertCopiedText = useDelayedCaller(()=>{ const revertCopiedText = useDelayedCaller(()=>{
setCopyText(gettext('Copy')); setCopyText(gettext('Copy'));
}); });
const pgAdmin = usePgAdmin();
useEffect(() => { useEffect(() => {
const about_url = url_for('about.index'); const about_url = url_for('about.index');
@ -52,7 +53,7 @@ export default function AboutComponent() {
api.get(about_url).then((res)=>{ api.get(about_url).then((res)=>{
setAboutData(res.data.data); setAboutData(res.data.data);
}).catch((err)=>{ }).catch((err)=>{
Notify.error(err); pgAdmin.Browser.notifier.error(err);
}); });
}, []); }, []);

View File

@ -9,7 +9,6 @@
import React from 'react'; import React from 'react';
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';
import Notify from '../../../static/js/helpers/Notifier';
import pgAdmin from 'sources/pgadmin'; import pgAdmin from 'sources/pgadmin';
import AboutComponent from './AboutComponent'; import AboutComponent from './AboutComponent';
import current_user from 'pgadmin.user_management.current_user'; import current_user from 'pgadmin.user_management.current_user';
@ -41,9 +40,9 @@ class About {
} }
// Render About component // Render About component
Notify.showModal(gettext('About %s', pgAdmin.Browser.utils.app_name), () => { pgAdmin.Browser.notifier.showModal(gettext('About %s', pgAdmin.Browser.utils.app_name), () => {
return <AboutComponent />; return <AboutComponent />;
}, { isFullScreen: false, isResizeable: true, showFullScreen: true, }, { isFullScreen: false, isResizeable: true, showFullScreen: true,
isFullWidth: true, dialogWidth: dlgWidth, dialogHeight: dlgHeight, minHeight: dlgHeight isFullWidth: true, dialogWidth: dlgWidth, dialogHeight: dlgHeight, minHeight: dlgHeight
}); });
} }
@ -53,4 +52,4 @@ pgAdmin.About = About.getInstance();
module.exports = { module.exports = {
About: About, About: About,
}; };

View File

@ -9,7 +9,7 @@
import url_for from 'sources/url_for'; import url_for from 'sources/url_for';
import userInfo from 'pgadmin.user_management.current_user'; import userInfo from 'pgadmin.user_management.current_user';
import pgConst from 'pgadmin.browser.constants'; import {AUTH_METHODS} from 'pgadmin.browser.constants';
function fetch_ticket() { function fetch_ticket() {
// Fetch the Kerberos Updated ticket through SPNEGO // Fetch the Kerberos Updated ticket through SPNEGO
@ -52,7 +52,7 @@ function fetch_ticket_lifetime () {
function validate_kerberos_ticket() { function validate_kerberos_ticket() {
// Ping pgAdmin server every 10 seconds // Ping pgAdmin server every 10 seconds
// to fetch the Kerberos ticket lifetime left // to fetch the Kerberos ticket lifetime left
if (userInfo['current_auth_source'] != pgConst['KERBEROS']) return; if (userInfo['current_auth_source'] != AUTH_METHODS['KERBEROS']) return;
return setInterval(function() { return setInterval(function() {
let newPromise = fetch_ticket_lifetime(); let newPromise = fetch_ticket_lifetime();

View File

@ -88,61 +88,6 @@ PASS_ERROR = gettext('Error: {error}\n {pass_error}').format(
class BrowserModule(PgAdminModule): class BrowserModule(PgAdminModule):
LABEL = gettext('Browser') LABEL = gettext('Browser')
def get_own_stylesheets(self):
stylesheets = []
context_menu_file = 'vendor/jQuery-contextMenu/' \
'jquery.contextMenu.min.css'
wcdocker_file = 'vendor/wcDocker/wcDocker.min.css'
if current_app.debug:
context_menu_file = 'vendor/jQuery-contextMenu/' \
'jquery.contextMenu.css'
wcdocker_file = 'vendor/wcDocker/wcDocker.css'
# Add browser stylesheets
for (endpoint, filename) in [
('static', 'vendor/codemirror/codemirror.css'),
('static', 'vendor/codemirror/addon/dialog/dialog.css'),
('static', context_menu_file),
('static', wcdocker_file)
]:
stylesheets.append(url_for(endpoint, filename=filename))
return stylesheets
def get_own_menuitems(self):
menus = {
'file_items': [
MenuItem(
name='mnu_locklayout',
module=PGADMIN_BROWSER,
label=gettext('Lock Layout'),
priority=999,
menu_items=[MenuItem(
name='mnu_lock_none',
module=PGADMIN_BROWSER,
callback='mnu_lock_none',
priority=0,
label=gettext('None'),
checked=True
), MenuItem(
name='mnu_lock_docking',
module=PGADMIN_BROWSER,
callback='mnu_lock_docking',
priority=1,
label=gettext('Prevent Docking'),
checked=False
), MenuItem(
name='mnu_lock_full',
module=PGADMIN_BROWSER,
callback='mnu_lock_full',
priority=2,
label=gettext('Full Lock'),
checked=False
)]
)
]
}
return menus
def register_preferences(self): def register_preferences(self):
register_browser_preferences(self) register_browser_preferences(self)
@ -156,7 +101,6 @@ class BrowserModule(PgAdminModule):
'browser.check_master_password', 'browser.check_master_password',
'browser.set_master_password', 'browser.set_master_password',
'browser.reset_master_password', 'browser.reset_master_password',
'browser.lock_layout',
] ]
def register(self, app, options): def register(self, app, options):
@ -416,47 +360,6 @@ def _get_supported_browser():
return browser_name, browser_known, version return browser_name, browser_known, version
def check_browser_upgrade():
"""
This function is used to check the browser version.
:return:
"""
data = None
url = '%s?version=%s' % (config.UPGRADE_CHECK_URL, config.APP_VERSION)
current_app.logger.debug('Checking version data at: %s' % url)
try:
# Do not wait for more than 5 seconds.
# It stuck on rendering the browser.html, while working in the
# broken network.
if os.path.exists(config.CA_FILE):
response = urlopen(url, data, 5, cafile=config.CA_FILE)
else:
response = urlopen(url, data, 5)
current_app.logger.debug(
'Version check HTTP response code: %d' % response.getcode()
)
if response.getcode() == 200:
data = json.loads(response.read().decode('utf-8'))
current_app.logger.debug('Response data: %s' % data)
except Exception:
current_app.logger.exception('Exception when checking for update')
if data is not None and \
data[config.UPGRADE_CHECK_KEY]['version_int'] > \
config.APP_VERSION_INT:
msg = render_template(
MODULE_NAME + "/upgrade.html",
current_version=config.APP_VERSION,
upgrade_version=data[config.UPGRADE_CHECK_KEY]['version'],
product_name=config.APP_NAME,
download_url=data[config.UPGRADE_CHECK_KEY]['download_url']
)
flash(msg, MessageType.WARNING)
@blueprint.route("/") @blueprint.route("/")
@pgCSRFProtect.exempt @pgCSRFProtect.exempt
@login_required @login_required
@ -491,15 +394,6 @@ def index():
flash(msg, MessageType.WARNING) flash(msg, MessageType.WARNING)
# Get the current version info from the website, and flash a message if
# the user is out of date, and the check is enabled.
if config.UPGRADE_CHECK_ENABLED:
last_check = get_setting('LastUpdateCheck', default='0')
today = time.strftime('%Y%m%d')
if int(last_check) < int(today):
check_browser_upgrade()
store_setting('LastUpdateCheck', today)
session['allow_save_password'] = True session['allow_save_password'] = True
if config.SERVER_MODE and not config.MASTER_PASSWORD_REQUIRED and \ if config.SERVER_MODE and not config.MASTER_PASSWORD_REQUIRED and \
@ -605,7 +499,6 @@ def utils():
editor_indent_with_tabs = False if editor_use_spaces else True editor_indent_with_tabs = False if editor_use_spaces else True
prefs = Preferences.module('browser') prefs = Preferences.module('browser')
current_ui_lock = prefs.preference('lock_layout').get()
# Try to fetch current libpq version from the driver # Try to fetch current libpq version from the driver
try: try:
from config import PG_DEFAULT_DRIVER from config import PG_DEFAULT_DRIVER
@ -672,7 +565,6 @@ def utils():
auth_source=auth_source, auth_source=auth_source,
heartbeat_timeout=config.SERVER_HEARTBEAT_TIMEOUT, heartbeat_timeout=config.SERVER_HEARTBEAT_TIMEOUT,
password_length_min=config.PASSWORD_LENGTH_MIN, password_length_min=config.PASSWORD_LENGTH_MIN,
current_ui_lock=current_ui_lock,
shared_storage_list=shared_storage_list, shared_storage_list=shared_storage_list,
restricted_shared_storage_list=[] if current_user.has_role( restricted_shared_storage_list=[] if current_user.has_role(
"Administrator") else restricted_shared_storage_list, "Administrator") else restricted_shared_storage_list,
@ -689,19 +581,6 @@ def exposed_urls():
) )
@blueprint.route("/js/constants.js")
@pgCSRFProtect.exempt
def app_constants():
return make_response(
render_template('browser/js/constants.js',
INTERNAL=INTERNAL,
LDAP=LDAP,
KERBEROS=KERBEROS,
OAUTH2=OAUTH2),
200, {'Content-Type': MIMETYPE_APP_JS}
)
@blueprint.route("/js/error.js") @blueprint.route("/js/error.js")
@pgCSRFProtect.exempt @pgCSRFProtect.exempt
@login_required @login_required
@ -1033,21 +912,6 @@ def set_master_password():
) )
@blueprint.route("/lock_layout", endpoint="lock_layout", methods=["PUT"])
def lock_layout():
data = None
if hasattr(request.data, 'decode'):
data = request.data.decode('utf-8')
if data != '':
data = json.loads(data)
blueprint.lock_layout.set(data['value'])
return make_json_response()
# Only register route if SECURITY_CHANGEABLE is set to True # Only register route if SECURITY_CHANGEABLE is set to True
# We can't access app context here so cannot # We can't access app context here so cannot
# use app.config['SECURITY_CHANGEABLE'] # use app.config['SECURITY_CHANGEABLE']

View File

@ -15,12 +15,6 @@ from pgadmin.utils.constants import PREF_LABEL_DISPLAY,\
from flask import current_app from flask import current_app
import config import config
LOCK_LAYOUT_LEVEL = {
'PREVENT_DOCKING': 'docking',
'FULL': 'full',
'NONE': 'none'
}
def register_browser_preferences(self): def register_browser_preferences(self):
self.show_system_objects = self.preference.register( self.show_system_objects = self.preference.register(
@ -97,21 +91,6 @@ def register_browser_preferences(self):
) )
) )
self.lock_layout = self.preference.register(
'display', 'lock_layout',
gettext('Lock Layout'), 'radioModern', LOCK_LAYOUT_LEVEL['NONE'],
category_label=PREF_LABEL_DISPLAY, options=[
{'label': gettext('None'), 'value': LOCK_LAYOUT_LEVEL['NONE']},
{'label': gettext('Prevent Docking'),
'value': LOCK_LAYOUT_LEVEL['PREVENT_DOCKING']},
{'label': gettext('Full Lock'),
'value': LOCK_LAYOUT_LEVEL['FULL']},
],
help_str=gettext(
'Lock the UI layout at different levels'
)
)
self.table_row_count_threshold = self.preference.register( self.table_row_count_threshold = self.preference.register(
'properties', 'table_row_count_threshold', 'properties', 'table_row_count_threshold',
gettext("Count rows if estimated less than"), 'integer', 2000, gettext("Count rows if estimated less than"), 'integer', 2000,

View File

@ -370,9 +370,6 @@ def convert_length_precision_to_string(data):
:return: :return:
""" """
# We need to handle the below case because jquery has changed
# undefined/null values to empty strings
# https://github.com/jquery/jquery/commit/36d2d9ae937f626d98319ed850905e8d1cbfd078
if 'attlen' in data and data['attlen'] == '': if 'attlen' in data and data['attlen'] == '':
data['attlen'] = None data['attlen'] = None
elif 'attlen' in data and data['attlen'] is not None: elif 'attlen' in data and data['attlen'] is not None:

View File

@ -9,7 +9,6 @@
import { getNodeListByName } from '../../../../../../../../static/js/node_ajax'; import { getNodeListByName } from '../../../../../../../../static/js/node_ajax';
import CompoundTriggerSchema from './compound_trigger.ui'; import CompoundTriggerSchema from './compound_trigger.ui';
import Notify from '../../../../../../../../../static/js/helpers/Notifier';
import getApiInstance from '../../../../../../../../../static/js/api_instance'; import getApiInstance from '../../../../../../../../../static/js/api_instance';
define('pgadmin.node.compound_trigger', [ define('pgadmin.node.compound_trigger', [
@ -118,14 +117,14 @@ define('pgadmin.node.compound_trigger', [
{'is_enable_trigger' : 'O'} {'is_enable_trigger' : 'O'}
).then(({data: res})=> { ).then(({data: res})=> {
if(res.success == 1) { if(res.success == 1) {
Notify.success(res.info); pgAdmin.Browser.notifier.success(res.info);
t.removeIcon(i); t.removeIcon(i);
data.icon = 'icon-compound_trigger'; data.icon = 'icon-compound_trigger';
t.addIcon(i, {icon: data.icon}); t.addIcon(i, {icon: data.icon});
t.updateAndReselectNode(i, data); t.updateAndReselectNode(i, data);
} }
}).catch(function(error) { }).catch(function(error) {
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.refresh(i); t.refresh(i);
}); });
}, },
@ -146,14 +145,14 @@ define('pgadmin.node.compound_trigger', [
{'is_enable_trigger' : 'D'} {'is_enable_trigger' : 'D'}
).then(({data: res})=> { ).then(({data: res})=> {
if(res.success == 1) { if(res.success == 1) {
Notify.success(res.info); pgAdmin.Browser.notifier.success(res.info);
t.removeIcon(i); t.removeIcon(i);
data.icon = 'icon-compound_trigger-bad'; data.icon = 'icon-compound_trigger-bad';
t.addIcon(i, {icon: data.icon}); t.addIcon(i, {icon: data.icon});
t.updateAndReselectNode(i, data); t.updateAndReselectNode(i, data);
} }
}).catch(function(error) { }).catch(function(error) {
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.refresh(i); t.refresh(i);
}); });
}, },

View File

@ -8,7 +8,6 @@
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import CheckConstraintSchema from './check_constraint.ui'; import CheckConstraintSchema from './check_constraint.ui';
import Notify from '../../../../../../../../../../static/js/helpers/Notifier';
import _ from 'lodash'; import _ from 'lodash';
import getApiInstance from '../../../../../../../../../../static/js/api_instance'; import getApiInstance from '../../../../../../../../../../static/js/api_instance';
@ -73,7 +72,7 @@ define('pgadmin.node.check_constraint', [
getApiInstance().get(obj.generate_url(i, 'validate', d, true)) getApiInstance().get(obj.generate_url(i, 'validate', d, true))
.then(({data: res})=>{ .then(({data: res})=>{
if (res.success == 1) { if (res.success == 1) {
Notify.success(res.info); pgAdmin.Browser.notifier.success(res.info);
t.removeIcon(i); t.removeIcon(i);
data.valid = true; data.valid = true;
data.icon = 'icon-check_constraint'; data.icon = 'icon-check_constraint';
@ -83,7 +82,7 @@ define('pgadmin.node.check_constraint', [
} }
}) })
.catch((error)=>{ .catch((error)=>{
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.unload(i); t.unload(i);
}); });
} }

View File

@ -14,7 +14,7 @@ import { SCHEMA_STATE_ACTIONS } from '../../../../../../../../../../static/js/Sc
import DataGridViewWithHeaderForm from '../../../../../../../../../../static/js/helpers/DataGridViewWithHeaderForm'; import DataGridViewWithHeaderForm from '../../../../../../../../../../static/js/helpers/DataGridViewWithHeaderForm';
import { getNodeAjaxOptions, getNodeListByName } from '../../../../../../../../../static/js/node_ajax'; import { getNodeAjaxOptions, getNodeListByName } from '../../../../../../../../../static/js/node_ajax';
import TableSchema from '../../../../static/js/table.ui'; import TableSchema from '../../../../static/js/table.ui';
import Notify from '../../../../../../../../../../static/js/helpers/Notifier'; import pgAdmin from 'sources/pgadmin';
function getData(data) { function getData(data) {
let res = []; let res = [];
@ -275,7 +275,7 @@ export default class ExclusionConstraintSchema extends BaseUISchema {
options: this.fieldOptions.amname, options: this.fieldOptions.amname,
deferredDepChange: (state, source, topState, actionObj)=>{ deferredDepChange: (state, source, topState, actionObj)=>{
return new Promise((resolve)=>{ return new Promise((resolve)=>{
Notify.confirm( pgAdmin.Browser.notifier.confirm(
gettext('Change access method?'), gettext('Change access method?'),
gettext('Changing access method will clear columns collection'), gettext('Changing access method will clear columns collection'),
function () { function () {

View File

@ -8,7 +8,6 @@
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import { getNodeForeignKeySchema } from './foreign_key.ui'; import { getNodeForeignKeySchema } from './foreign_key.ui';
import Notify from '../../../../../../../../../../static/js/helpers/Notifier';
import _ from 'lodash'; import _ from 'lodash';
import getApiInstance from '../../../../../../../../../../static/js/api_instance'; import getApiInstance from '../../../../../../../../../../static/js/api_instance';
@ -69,7 +68,7 @@ define('pgadmin.node.foreign_key', [
getApiInstance().get(obj.generate_url(i, 'validate', d, true)) getApiInstance().get(obj.generate_url(i, 'validate', d, true))
.then(({data: res})=>{ .then(({data: res})=>{
if (res.success == 1) { if (res.success == 1) {
Notify.success(res.info); pgAdmin.Browser.notifier.success(res.info);
t.removeIcon(i); t.removeIcon(i);
data.valid = true; data.valid = true;
data.icon = 'icon-foreign_key'; data.icon = 'icon-foreign_key';
@ -79,7 +78,7 @@ define('pgadmin.node.foreign_key', [
} }
}) })
.catch((error)=>{ .catch((error)=>{
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.unload(i); t.unload(i);
}); });
} }

View File

@ -12,7 +12,7 @@ import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import DataGridViewWithHeaderForm from '../../../../../../../../../static/js/helpers/DataGridViewWithHeaderForm'; import DataGridViewWithHeaderForm from '../../../../../../../../../static/js/helpers/DataGridViewWithHeaderForm';
import _ from 'lodash'; import _ from 'lodash';
import { isEmptyString } from 'sources/validators'; import { isEmptyString } from 'sources/validators';
import Notify from '../../../../../../../../../static/js/helpers/Notifier'; import pgAdmin from 'sources/pgadmin';
function inSchema(node_info) { function inSchema(node_info) {
@ -476,7 +476,7 @@ export default class IndexSchema extends BaseUISchema {
}; };
if((state.amname != actionObj?.oldState.amname) && state.columns?.length > 0) { if((state.amname != actionObj?.oldState.amname) && state.columns?.length > 0) {
return new Promise((resolve)=>{ return new Promise((resolve)=>{
Notify.confirm( pgAdmin.Browser.notifier.confirm(
gettext('Warning'), gettext('Warning'),
gettext('Changing access method will clear columns collection. Do you want to continue?'), gettext('Changing access method will clear columns collection. Do you want to continue?'),
function () { function () {

View File

@ -8,18 +8,17 @@
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import { getNodePartitionTableSchema } from './partition.ui'; import { getNodePartitionTableSchema } from './partition.ui';
import Notify from '../../../../../../../../../static/js/helpers/Notifier';
import _ from 'lodash'; import _ from 'lodash';
import getApiInstance from '../../../../../../../../../static/js/api_instance'; import getApiInstance from '../../../../../../../../../static/js/api_instance';
define([ define([
'sources/gettext', 'sources/url_for', 'jquery', 'sources/gettext', 'sources/url_for',
'sources/pgadmin', 'pgadmin.browser', 'sources/pgadmin', 'pgadmin.browser',
'pgadmin.node.schema.dir/schema_child_tree_node', 'sources/utils', 'pgadmin.node.schema.dir/schema_child_tree_node', 'sources/utils',
'pgadmin.browser.collection', 'pgadmin.browser.collection',
], ],
function( function(
gettext, url_for, $, pgAdmin, pgBrowser, gettext, url_for, pgAdmin, pgBrowser,
SchemaChildTreeNode, pgadminUtils SchemaChildTreeNode, pgadminUtils
) { ) {
@ -151,7 +150,7 @@ function(
}, },
on_done: function(res, data, t, i) { on_done: function(res, data, t, i) {
if (res.success == 1) { if (res.success == 1) {
Notify.success(res.info); pgAdmin.Browser.notifier.success(res.info);
t.removeIcon(i); t.removeIcon(i);
data.icon = 'icon-partition'; data.icon = 'icon-partition';
t.addIcon(i, {icon: data.icon}); t.addIcon(i, {icon: data.icon});
@ -186,12 +185,12 @@ function(
getApiInstance().put(obj.generate_url(i, 'set_trigger' , d, true), params) getApiInstance().put(obj.generate_url(i, 'set_trigger' , d, true), params)
.then(({data: res})=>{ .then(({data: res})=>{
if (res.success == 1) { if (res.success == 1) {
Notify.success(res.info); pgAdmin.Browser.notifier.success(res.info);
t.updateAndReselectNode(i, d); t.updateAndReselectNode(i, d);
} }
}) })
.catch((error)=>{ .catch((error)=>{
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.refresh(i); t.refresh(i);
}); });
}, },
@ -215,7 +214,7 @@ function(
if (!d) if (!d)
return false; return false;
Notify.confirm( pgAdmin.Browser.notifier.confirm(
gettext('Truncate Table'), gettext('Truncate Table'),
gettext('Are you sure you want to truncate table %s?', d.label), gettext('Are you sure you want to truncate table %s?', d.label),
function () { function () {
@ -225,7 +224,7 @@ function(
obj.on_done(res, data, t, i); obj.on_done(res, data, t, i);
}) })
.catch((error)=>{ .catch((error)=>{
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.unload(i); t.unload(i);
}); });
}, },
@ -241,7 +240,7 @@ function(
if (!d) if (!d)
return false; return false;
Notify.confirm( pgAdmin.Browser.notifier.confirm(
gettext('Reset statistics'), gettext('Reset statistics'),
gettext('Are you sure you want to reset the statistics for table "%s"?', d._label), gettext('Are you sure you want to reset the statistics for table "%s"?', d._label),
function () { function () {
@ -251,7 +250,7 @@ function(
obj.on_done(res, data, t, i); obj.on_done(res, data, t, i);
}) })
.catch((error)=>{ .catch((error)=>{
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.unload(i); t.unload(i);
}); });
}, },
@ -276,14 +275,14 @@ function(
title = gettext('Detach Partition Finalize'); title = gettext('Detach Partition Finalize');
} }
Notify.confirm( pgAdmin.Browser.notifier.confirm(
title, title,
gettext('Are you sure you want to detach the partition %s?', d._label), gettext('Are you sure you want to detach the partition %s?', d._label),
function () { function () {
getApiInstance().put(obj.generate_url(i, 'detach' , d, true), params) getApiInstance().put(obj.generate_url(i, 'detach' , d, true), params)
.then(({data: res})=>{ .then(({data: res})=>{
if (res.success == 1) { if (res.success == 1) {
Notify.success(res.info); pgAdmin.Browser.notifier.success(res.info);
let n = t.next(i); let n = t.next(i);
if (!n) { if (!n) {
n = t.prev(i); n = t.prev(i);
@ -298,7 +297,7 @@ function(
} }
}) })
.catch((error)=>{ .catch((error)=>{
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
}); });
}, },
function() {/*This is intentional (SonarQube)*/} function() {/*This is intentional (SonarQube)*/}

View File

@ -7,15 +7,14 @@
// //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import RuleSchema from './rule.ui'; import RuleSchema from './rule.ui';
import Notify from '../../../../../../../../../static/js/helpers/Notifier';
import _ from 'lodash'; import _ from 'lodash';
import getApiInstance from '../../../../../../../../../static/js/api_instance'; import getApiInstance from '../../../../../../../../../static/js/api_instance';
define('pgadmin.node.rule', [ define('pgadmin.node.rule', [
'sources/gettext', 'sources/url_for', 'jquery', 'sources/gettext', 'sources/url_for',
'sources/pgadmin', 'pgadmin.browser', 'sources/pgadmin', 'pgadmin.browser',
'pgadmin.node.schema.dir/schema_child_tree_node', 'pgadmin.node.schema.dir/schema_child_tree_node',
], function(gettext, url_for, $, pgAdmin, pgBrowser, SchemaChildTreeNode) { ], function(gettext, url_for, pgAdmin, pgBrowser, SchemaChildTreeNode) {
/** /**
Create and add a rule collection into nodes Create and add a rule collection into nodes
@ -137,14 +136,14 @@ define('pgadmin.node.rule', [
let data = d; let data = d;
getApiInstance().put(obj.generate_url(i, 'obj' , d, true), {'is_enable_rule' : 'O'}) getApiInstance().put(obj.generate_url(i, 'obj' , d, true), {'is_enable_rule' : 'O'})
.then(()=>{ .then(()=>{
Notify.success('Rule updated.'); pgAdmin.Browser.notifier.success('Rule updated.');
t.removeIcon(i); t.removeIcon(i);
data.icon = 'icon-rule'; data.icon = 'icon-rule';
t.addIcon(i, {icon: data.icon}); t.addIcon(i, {icon: data.icon});
t.updateAndReselectNode(i, data); t.updateAndReselectNode(i, data);
}) })
.catch((error)=>{ .catch((error)=>{
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.refresh(i); t.refresh(i);
}); });
}, },
@ -162,14 +161,14 @@ define('pgadmin.node.rule', [
let data = d; let data = d;
getApiInstance().put(obj.generate_url(i, 'obj' , d, true), {'is_enable_rule' : 'D'}) getApiInstance().put(obj.generate_url(i, 'obj' , d, true), {'is_enable_rule' : 'D'})
.then(()=>{ .then(()=>{
Notify.success('Rule updated'); pgAdmin.Browser.notifier.success('Rule updated');
t.removeIcon(i); t.removeIcon(i);
data.icon = 'icon-rule-bad'; data.icon = 'icon-rule-bad';
t.addIcon(i, {icon: data.icon}); t.addIcon(i, {icon: data.icon});
t.updateAndReselectNode(i, data); t.updateAndReselectNode(i, data);
}) })
.catch((error)=>{ .catch((error)=>{
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.refresh(i); t.refresh(i);
}); });
}, },

View File

@ -8,15 +8,16 @@
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import axios from 'axios'; import axios from 'axios';
import pgAdmin from 'sources/pgadmin';
export function disableTriggers(tree, Notify, generateUrl, args) { export function disableTriggers(tree, generateUrl, args) {
return setTriggers(tree, Notify, generateUrl, args, {is_enable_trigger: 'D' }); return setTriggers(tree, generateUrl, args, {is_enable_trigger: 'D' });
} }
export function enableTriggers(tree, Notify, generateUrl, args) { export function enableTriggers(tree, generateUrl, args) {
return setTriggers(tree, Notify, generateUrl, args, {is_enable_trigger: 'O' }); return setTriggers(tree, generateUrl, args, {is_enable_trigger: 'O' });
} }
function setTriggers(tree, Notify, generateUrl, args, params) { function setTriggers(tree, generateUrl, args, params) {
const treeNode = retrieveTreeNode(args, tree); const treeNode = retrieveTreeNode(args, tree);
if (!treeNode || treeNode.getData() === null || treeNode.getData() === undefined) if (!treeNode || treeNode.getData() === null || treeNode.getData() === undefined)
@ -28,7 +29,7 @@ function setTriggers(tree, Notify, generateUrl, args, params) {
) )
.then((res) => { .then((res) => {
if (res.data.success === 1) { if (res.data.success === 1) {
Notify.success(res.data.info); pgAdmin.Browser.notifier.success(res.data.info);
treeNode.data.has_enable_triggers = res.data.data.has_enable_triggers; treeNode.data.has_enable_triggers = res.data.data.has_enable_triggers;
treeNode.reload(tree); treeNode.reload(tree);
@ -38,7 +39,7 @@ function setTriggers(tree, Notify, generateUrl, args, params) {
try { try {
const err = xhr.response.data; const err = xhr.response.data;
if (err.success === 0) { if (err.success === 0) {
Notify.error(err.errormsg); pgAdmin.Browser.notifier.error(err.errormsg);
} }
} catch (e) { } catch (e) {
console.warn(e.stack || e); console.warn(e.stack || e);

View File

@ -7,20 +7,19 @@
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import { getNodeTableSchema } from './table.ui'; import { getNodeTableSchema } from './table.ui';
import Notify from '../../../../../../../../static/js/helpers/Notifier';
import _ from 'lodash'; import _ from 'lodash';
import getApiInstance from '../../../../../../../../static/js/api_instance'; import getApiInstance from '../../../../../../../../static/js/api_instance';
define('pgadmin.node.table', [ define('pgadmin.node.table', [
'pgadmin.tables.js/enable_disable_triggers', 'pgadmin.tables.js/enable_disable_triggers',
'sources/gettext', 'sources/url_for', 'jquery', 'sources/gettext', 'sources/url_for',
'sources/pgadmin', 'pgadmin.browser', 'sources/pgadmin', 'pgadmin.browser',
'pgadmin.node.schema.dir/child','pgadmin.node.schema.dir/schema_child_tree_node', 'pgadmin.node.schema.dir/child','pgadmin.node.schema.dir/schema_child_tree_node',
'pgadmin.browser.collection', 'pgadmin.node.column', 'pgadmin.browser.collection', 'pgadmin.node.column',
'pgadmin.node.constraints', 'pgadmin.node.constraints',
], function( ], function(
tableFunctions, tableFunctions,
gettext, url_for, $, pgAdmin, pgBrowser, SchemaChild, SchemaChildTreeNode gettext, url_for, pgAdmin, pgBrowser, SchemaChild, SchemaChildTreeNode
) { ) {
if (!pgBrowser.Nodes['coll-table']) { if (!pgBrowser.Nodes['coll-table']) {
@ -145,7 +144,6 @@ define('pgadmin.node.table', [
enable_triggers_on_table: function(args) { enable_triggers_on_table: function(args) {
tableFunctions.enableTriggers( tableFunctions.enableTriggers(
pgBrowser.tree, pgBrowser.tree,
Notify,
this.generate_url.bind(this), this.generate_url.bind(this),
args args
); );
@ -154,7 +152,6 @@ define('pgadmin.node.table', [
disable_triggers_on_table: function(args) { disable_triggers_on_table: function(args) {
tableFunctions.disableTriggers( tableFunctions.disableTriggers(
pgBrowser.tree, pgBrowser.tree,
Notify,
this.generate_url.bind(this), this.generate_url.bind(this),
args args
); );
@ -183,7 +180,7 @@ define('pgadmin.node.table', [
if (!d) if (!d)
return false; return false;
Notify.confirm( pgAdmin.Browser.notifier.confirm(
gettext('Truncate Table'), gettext('Truncate Table'),
gettext('Are you sure you want to truncate table %s?', d.label), gettext('Are you sure you want to truncate table %s?', d.label),
function () { function () {
@ -191,18 +188,18 @@ define('pgadmin.node.table', [
getApiInstance().put(obj.generate_url(i, 'truncate' , d, true), params) getApiInstance().put(obj.generate_url(i, 'truncate' , d, true), params)
.then(({data: res})=>{ .then(({data: res})=>{
if (res.success == 1) { if (res.success == 1) {
Notify.success(res.info); pgAdmin.Browser.notifier.success(res.info);
t.removeIcon(i); t.removeIcon(i);
data.icon = data.is_partitioned ? 'icon-partition': 'icon-table'; data.icon = data.is_partitioned ? 'icon-partition': 'icon-table';
t.addIcon(i, {icon: data.icon}); t.addIcon(i, {icon: data.icon});
t.updateAndReselectNode(i, data); t.updateAndReselectNode(i, data);
} }
if (res.success == 2) { if (res.success == 2) {
Notify.error(res.info); pgAdmin.Browser.notifier.error(res.info);
} }
}) })
.catch((error)=>{ .catch((error)=>{
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.refresh(i); t.refresh(i);
}); });
}, function() {/*This is intentional (SonarQube)*/} }, function() {/*This is intentional (SonarQube)*/}
@ -218,7 +215,7 @@ define('pgadmin.node.table', [
if (!d) if (!d)
return false; return false;
Notify.confirm( pgAdmin.Browser.notifier.confirm(
gettext('Reset statistics'), gettext('Reset statistics'),
gettext('Are you sure you want to reset the statistics for table "%s"?', d._label), gettext('Are you sure you want to reset the statistics for table "%s"?', d._label),
function () { function () {
@ -226,7 +223,7 @@ define('pgadmin.node.table', [
getApiInstance().delete(obj.generate_url(i, 'reset' , d, true)) getApiInstance().delete(obj.generate_url(i, 'reset' , d, true))
.then(({data: res})=>{ .then(({data: res})=>{
if (res.success == 1) { if (res.success == 1) {
Notify.success(res.info); pgAdmin.Browser.notifier.success(res.info);
t.removeIcon(i); t.removeIcon(i);
data.icon = data.is_partitioned ? 'icon-partition': 'icon-table'; data.icon = data.is_partitioned ? 'icon-partition': 'icon-table';
t.addIcon(i, {icon: data.icon}); t.addIcon(i, {icon: data.icon});
@ -234,7 +231,7 @@ define('pgadmin.node.table', [
} }
}) })
.catch((error)=>{ .catch((error)=>{
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.refresh(i); t.refresh(i);
}); });
}, },
@ -257,12 +254,12 @@ define('pgadmin.node.table', [
// Fetch the total rows of a table // Fetch the total rows of a table
getApiInstance().get(obj.generate_url(i, 'count_rows' , newD, true)) getApiInstance().get(obj.generate_url(i, 'count_rows' , newD, true))
.then(({data: res})=>{ .then(({data: res})=>{
Notify.success(res.info, null); pgAdmin.Browser.notifier.success(res.info, null);
d.rows_cnt = res.data.total_rows; d.rows_cnt = res.data.total_rows;
t.updateAndReselectNode(i, d); t.updateAndReselectNode(i, d);
}) })
.catch((error)=>{ .catch((error)=>{
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.refresh(i); t.refresh(i);
}); });
}, },

View File

@ -22,7 +22,7 @@ import { getNodeVacuumSettingsSchema } from '../../../../../static/js/vacuum.ui'
import { getNodeForeignKeySchema } from '../../constraints/foreign_key/static/js/foreign_key.ui'; import { getNodeForeignKeySchema } from '../../constraints/foreign_key/static/js/foreign_key.ui';
import { getNodeExclusionConstraintSchema } from '../../constraints/exclusion_constraint/static/js/exclusion_constraint.ui'; import { getNodeExclusionConstraintSchema } from '../../constraints/exclusion_constraint/static/js/exclusion_constraint.ui';
import { getNodePrivilegeRoleSchema } from '../../../../../static/js/privilege.ui'; import { getNodePrivilegeRoleSchema } from '../../../../../static/js/privilege.ui';
import Notify from '../../../../../../../../static/js/helpers/Notifier'; import pgAdmin from 'sources/pgadmin';
export function getNodeTableSchema(treeNodeInfo, itemNodeData, pgBrowser) { export function getNodeTableSchema(treeNodeInfo, itemNodeData, pgBrowser) {
const spcname = ()=>getNodeListByName('tablespace', treeNodeInfo, itemNodeData, {}, (m)=>{ const spcname = ()=>getNodeListByName('tablespace', treeNodeInfo, itemNodeData, {}, (m)=>{
@ -622,7 +622,7 @@ export default class TableSchema extends BaseUISchema {
group: 'advanced', min_version: 90600, group: 'advanced', min_version: 90600,
depChange: (state)=>{ depChange: (state)=>{
if (state.rlspolicy && this.origData.rlspolicy != state.rlspolicy) { if (state.rlspolicy && this.origData.rlspolicy != state.rlspolicy) {
Notify.alert( pgAdmin.Browser.notifier.alert(
gettext('Check Policy?'), gettext('Check Policy?'),
gettext('Please check if any policy exists. If no policy exists for the table, a default-deny policy is used, meaning that no rows are visible or can be modified by other users') gettext('Please check if any policy exists. If no policy exists for the table, a default-deny policy is used, meaning that no rows are visible or can be modified by other users')
); );
@ -752,7 +752,7 @@ export default class TableSchema extends BaseUISchema {
}; };
if(!isEmptyString(state.typname) && isEmptyString(actionObj.oldState.typname)) { if(!isEmptyString(state.typname) && isEmptyString(actionObj.oldState.typname)) {
return new Promise((resolve)=>{ return new Promise((resolve)=>{
Notify.confirm( pgAdmin.Browser.notifier.confirm(
gettext('Remove column definitions?'), gettext('Remove column definitions?'),
gettext('Changing \'Of type\' will remove column definitions.'), gettext('Changing \'Of type\' will remove column definitions.'),
function () { function () {

View File

@ -8,17 +8,16 @@
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import { getNodeListByName, getNodeAjaxOptions } from '../../../../../../../../static/js/node_ajax'; import { getNodeListByName, getNodeAjaxOptions } from '../../../../../../../../static/js/node_ajax';
import TriggerSchema from './trigger.ui'; import TriggerSchema from './trigger.ui';
import Notify from '../../../../../../../../../static/js/helpers/Notifier';
import _ from 'lodash'; import _ from 'lodash';
import getApiInstance from '../../../../../../../../../static/js/api_instance'; import getApiInstance from '../../../../../../../../../static/js/api_instance';
define('pgadmin.node.trigger', [ define('pgadmin.node.trigger', [
'sources/gettext', 'sources/url_for', 'jquery', 'sources/gettext', 'sources/url_for',
'sources/pgadmin', 'pgadmin.browser', 'sources/pgadmin', 'pgadmin.browser',
'pgadmin.node.schema.dir/schema_child_tree_node', 'pgadmin.node.schema.dir/schema_child_tree_node',
'pgadmin.browser.collection', 'pgadmin.browser.collection',
], function( ], function(
gettext, url_for, $, pgAdmin, pgBrowser, SchemaChildTreeNode gettext, url_for, pgAdmin, pgBrowser, SchemaChildTreeNode
) { ) {
if (!pgBrowser.Nodes['coll-trigger']) { if (!pgBrowser.Nodes['coll-trigger']) {
@ -118,7 +117,7 @@ define('pgadmin.node.trigger', [
getApiInstance().put(obj.generate_url(i, 'enable' , d, true), {'is_enable_trigger' : 'O'}) getApiInstance().put(obj.generate_url(i, 'enable' , d, true), {'is_enable_trigger' : 'O'})
.then(({data: res})=>{ .then(({data: res})=>{
if (res.success == 1) { if (res.success == 1) {
Notify.success(res.info); pgAdmin.Browser.notifier.success(res.info);
t.removeIcon(i); t.removeIcon(i);
data.icon = 'icon-trigger'; data.icon = 'icon-trigger';
data.has_enable_triggers = res.data.has_enable_triggers; data.has_enable_triggers = res.data.has_enable_triggers;
@ -127,7 +126,7 @@ define('pgadmin.node.trigger', [
} }
}) })
.catch((error)=>{ .catch((error)=>{
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.refresh(i); t.refresh(i);
}); });
}, },
@ -146,7 +145,7 @@ define('pgadmin.node.trigger', [
getApiInstance().put(obj.generate_url(i, 'enable' , d, true), {'is_enable_trigger' : 'D'}) getApiInstance().put(obj.generate_url(i, 'enable' , d, true), {'is_enable_trigger' : 'D'})
.then(({data: res})=>{ .then(({data: res})=>{
if (res.success == 1) { if (res.success == 1) {
Notify.success(res.info); pgAdmin.Browser.notifier.success(res.info);
t.removeIcon(i); t.removeIcon(i);
data.icon = 'icon-trigger-bad'; data.icon = 'icon-trigger-bad';
data.has_enable_triggers = res.data.has_enable_triggers; data.has_enable_triggers = res.data.has_enable_triggers;
@ -155,7 +154,7 @@ define('pgadmin.node.trigger', [
} }
}) })
.catch((error)=>{ .catch((error)=>{
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.refresh(i); t.refresh(i);
}); });
}, },

View File

@ -11,17 +11,16 @@ import MViewSchema from './mview.ui';
import { getNodeListByName, getNodeAjaxOptions } from '../../../../../../../static/js/node_ajax'; import { getNodeListByName, getNodeAjaxOptions } from '../../../../../../../static/js/node_ajax';
import { getNodePrivilegeRoleSchema } from '../../../../../static/js/privilege.ui'; import { getNodePrivilegeRoleSchema } from '../../../../../static/js/privilege.ui';
import { getNodeVacuumSettingsSchema } from '../../../../../static/js/vacuum.ui'; import { getNodeVacuumSettingsSchema } from '../../../../../static/js/vacuum.ui';
import Notify from '../../../../../../../../static/js/helpers/Notifier';
import _ from 'lodash'; import _ from 'lodash';
import getApiInstance from '../../../../../../../../static/js/api_instance'; import getApiInstance from '../../../../../../../../static/js/api_instance';
define('pgadmin.node.mview', [ define('pgadmin.node.mview', [
'sources/gettext', 'sources/url_for', 'jquery', 'sources/gettext', 'sources/url_for',
'sources/pgadmin', 'pgadmin.browser', 'sources/pgadmin', 'pgadmin.browser',
'pgadmin.node.schema.dir/child', 'pgadmin.node.schema.dir/child',
'pgadmin.node.schema.dir/schema_child_tree_node', 'sources/utils', 'pgadmin.node.schema.dir/schema_child_tree_node', 'sources/utils',
], function( ], function(
gettext, url_for, $, pgAdmin, pgBrowser, gettext, url_for, pgAdmin, pgBrowser,
schemaChild, schemaChildTreeNode, commonUtils schemaChild, schemaChildTreeNode, commonUtils
) { ) {
@ -176,7 +175,7 @@ define('pgadmin.node.mview', [
if (pgBrowser.tree.hasParent(j)) { if (pgBrowser.tree.hasParent(j)) {
j = pgBrowser.tree.parent(j); j = pgBrowser.tree.parent(j);
} else { } else {
Notify.alert(gettext('Please select server or child node from tree.')); pgAdmin.Browser.notifier.alert(gettext('Please select server or child node from tree.'));
break; break;
} }
} }
@ -193,7 +192,7 @@ define('pgadmin.node.mview', [
api.get(obj.generate_url(i, 'check_utility_exists' , d, true)) api.get(obj.generate_url(i, 'check_utility_exists' , d, true))
.then(({data: res})=>{ .then(({data: res})=>{
if (!res.success) { if (!res.success) {
Notify.alert( pgAdmin.Browser.notifier.alert(
gettext('Utility not found'), gettext('Utility not found'),
res.errormsg res.errormsg
); );
@ -206,20 +205,20 @@ define('pgadmin.node.mview', [
//Do nothing as we are creating the job and exiting from the main dialog //Do nothing as we are creating the job and exiting from the main dialog
pgBrowser.BgProcessManager.startProcess(refreshed_res.data.job_id, refreshed_res.data.desc); pgBrowser.BgProcessManager.startProcess(refreshed_res.data.job_id, refreshed_res.data.desc);
} else { } else {
Notify.alert( pgAdmin.Browser.notifier.alert(
gettext('Failed to create materialized view refresh job.'), gettext('Failed to create materialized view refresh job.'),
refreshed_res.errormsg refreshed_res.errormsg
); );
} }
}) })
.catch((error)=>{ .catch((error)=>{
Notify.pgRespErrorNotify( pgAdmin.Browser.notifier.pgRespErrorNotify(
error, gettext('Failed to create materialized view refresh job.') error, gettext('Failed to create materialized view refresh job.')
); );
}); });
}) })
.catch(()=>{ .catch(()=>{
Notify.alert( pgAdmin.Browser.notifier.alert(
gettext('Utility not found'), gettext('Utility not found'),
gettext('Failed to fetch Utility information') gettext('Failed to fetch Utility information')
); );

View File

@ -11,16 +11,15 @@ import { getNodeAjaxOptions, getNodeListByName } from '../../../../../static/js/
import { getNodePrivilegeRoleSchema } from '../../../static/js/privilege.ui'; import { getNodePrivilegeRoleSchema } from '../../../static/js/privilege.ui';
import { getNodeVariableSchema } from '../../../static/js/variable.ui'; import { getNodeVariableSchema } from '../../../static/js/variable.ui';
import DatabaseSchema from './database.ui'; import DatabaseSchema from './database.ui';
import Notify from '../../../../../../static/js/helpers/Notifier';
import { showServerPassword } from '../../../../../../static/js/Dialogs/index'; import { showServerPassword } from '../../../../../../static/js/Dialogs/index';
import _ from 'lodash'; import _ from 'lodash';
import getApiInstance, { parseApiError } from '../../../../../../static/js/api_instance'; import getApiInstance, { parseApiError } from '../../../../../../static/js/api_instance';
define('pgadmin.node.database', [ define('pgadmin.node.database', [
'sources/gettext', 'sources/url_for', 'jquery', 'sources/gettext', 'sources/url_for',
'sources/pgadmin', 'pgadmin.browser.utils', 'sources/pgadmin', 'pgadmin.browser.utils',
'pgadmin.authenticate.kerberos', 'pgadmin.browser.collection', 'pgadmin.authenticate.kerberos', 'pgadmin.browser.collection',
], function(gettext, url_for, $, pgAdmin, pgBrowser, Kerberos) { ], function(gettext, url_for, pgAdmin, pgBrowser, Kerberos) {
function canDeleteWithForce(itemNodeData, item) { function canDeleteWithForce(itemNodeData, item) {
let treeData = pgBrowser.tree.getTreeNodeHierarchy(item), let treeData = pgBrowser.tree.getTreeNodeHierarchy(item),
@ -189,7 +188,7 @@ define('pgadmin.node.database', [
connect(self, d, t, i, true); connect(self, d, t, i, true);
return; return;
} }
Notify.confirm( pgAdmin.Browser.notifier.confirm(
gettext('Connection lost'), gettext('Connection lost'),
gettext('Would you like to reconnect to the database?'), gettext('Would you like to reconnect to the database?'),
function() { function() {
@ -231,7 +230,7 @@ define('pgadmin.node.database', [
d = i ? t.itemData(i) : undefined; d = i ? t.itemData(i) : undefined;
if (d) { if (d) {
Notify.confirm( pgAdmin.Browser.notifier.confirm(
gettext('Disconnect from database'), gettext('Disconnect from database'),
gettext('Are you sure you want to disconnect from database - %s?', d.label), gettext('Are you sure you want to disconnect from database - %s?', d.label),
function() { function() {
@ -244,7 +243,7 @@ define('pgadmin.node.database', [
if(res.data.info_prefix) { if(res.data.info_prefix) {
res.info = `${_.escape(res.data.info_prefix)} - ${res.info}`; res.info = `${_.escape(res.data.info_prefix)} - ${res.info}`;
} }
Notify.success(res.info); pgAdmin.Browser.notifier.success(res.info);
t.removeIcon(i); t.removeIcon(i);
data.connected = false; data.connected = false;
data.icon = data.isTemplate ? 'icon-database-template-not-connected':'icon-database-not-connected'; data.icon = data.isTemplate ? 'icon-database-template-not-connected':'icon-database-not-connected';
@ -258,14 +257,14 @@ define('pgadmin.node.database', [
} else { } else {
try { try {
Notify.error(res.errormsg); pgAdmin.Browser.notifier.error(res.errormsg);
} catch (e) { } catch (e) {
console.warn(e.stack || e); console.warn(e.stack || e);
} }
t.unload(i); t.unload(i);
} }
}).catch(function(error) { }).catch(function(error) {
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.unload(i); t.unload(i);
}); });
}, },
@ -408,7 +407,7 @@ define('pgadmin.node.database', [
tree.setInode(_item); tree.setInode(_item);
let dbIcon = data.isTemplate ? 'icon-database-template-not-connected':'icon-database-not-connected'; let dbIcon = data.isTemplate ? 'icon-database-template-not-connected':'icon-database-not-connected';
tree.addIcon(_item, {icon: dbIcon}); tree.addIcon(_item, {icon: dbIcon});
Notify.pgNotifier(fun_error, error, gettext('Connect to database.')); pgAdmin.Browser.notifier.pgNotifier(fun_error, error, gettext('Connect to database.'));
} }
); );
} else { } else {
@ -418,7 +417,7 @@ define('pgadmin.node.database', [
tree.addIcon(_item, {icon: dbIcon}); tree.addIcon(_item, {icon: dbIcon});
} }
Notify.pgNotifier('error', error, 'Error', function(msg) { pgAdmin.Browser.notifier.pgNotifier('error', error, 'Error', function(msg) {
setTimeout(function() { setTimeout(function() {
if (msg == 'CRYPTKEY_SET') { if (msg == 'CRYPTKEY_SET') {
connect_to_database(_model, _data, _tree, _item, _wasConnected); connect_to_database(_model, _data, _tree, _item, _wasConnected);
@ -454,9 +453,9 @@ define('pgadmin.node.database', [
res.info = `${_.escape(res.data.info_prefix)} - ${res.info}`; res.info = `${_.escape(res.data.info_prefix)} - ${res.info}`;
} }
if(res.data.already_connected) { if(res.data.already_connected) {
Notify.info(res.info); pgAdmin.Browser.notifier.info(res.info);
} else { } else {
Notify.success(res.info); pgAdmin.Browser.notifier.success(res.info);
} }
pgBrowser.Events.trigger( pgBrowser.Events.trigger(
'pgadmin:database:connected', _item, _data 'pgadmin:database:connected', _item, _data

View File

@ -10,7 +10,7 @@ import { getNodeListByName } from '../../../../../../static/js/node_ajax';
import SubscriptionSchema from './subscription.ui'; import SubscriptionSchema from './subscription.ui';
import getApiInstance from '../../../../../../../static/js/api_instance'; import getApiInstance from '../../../../../../../static/js/api_instance';
import _ from 'lodash'; import _ from 'lodash';
import Notify from '../../../../../../../static/js/helpers/Notifier'; import pgAdmin from 'sources/pgadmin';
define('pgadmin.node.subscription', [ define('pgadmin.node.subscription', [
'sources/gettext', 'sources/url_for', 'sources/gettext', 'sources/url_for',
@ -100,12 +100,12 @@ define('pgadmin.node.subscription', [
.then(res=>{ .then(res=>{
if ((res.data.errormsg === '') && !_.isNull(res.data.data)){ if ((res.data.errormsg === '') && !_.isNull(res.data.data)){
resolve(res.data.data); resolve(res.data.data);
Notify.info( pgAdmin.Browser.notifier.info(
gettext('Publication fetched successfully.') gettext('Publication fetched successfully.')
); );
}else if(!_.isNull(res.data.errormsg) && _.isNull(res.data.data)){ }else if(!_.isNull(res.data.errormsg) && _.isNull(res.data.data)){
reject(res.data.errormsg); reject(res.data.errormsg);
Notify.alert( pgAdmin.Browser.notifier.alert(
gettext('Check connection?'), gettext('Check connection?'),
gettext(res.data.errormsg) gettext(res.data.errormsg)
); );

View File

@ -10,13 +10,13 @@
import { getNodeAjaxOptions } from '../../../../../static/js/node_ajax'; import { getNodeAjaxOptions } from '../../../../../static/js/node_ajax';
import PgaJobSchema from './pga_job.ui'; import PgaJobSchema from './pga_job.ui';
import { getNodePgaJobStepSchema } from '../../steps/static/js/pga_jobstep.ui'; import { getNodePgaJobStepSchema } from '../../steps/static/js/pga_jobstep.ui';
import Notify from '../../../../../../static/js/helpers/Notifier';
import getApiInstance from '../../../../../../static/js/api_instance'; import getApiInstance from '../../../../../../static/js/api_instance';
import pgAdmin from 'sources/pgadmin';
define('pgadmin.node.pga_job', [ define('pgadmin.node.pga_job', [
'sources/gettext', 'sources/url_for', 'jquery', 'pgadmin.browser', 'sources/gettext', 'sources/url_for', 'pgadmin.browser',
'pgadmin.node.pga_jobstep', 'pgadmin.node.pga_schedule', 'pgadmin.node.pga_jobstep', 'pgadmin.node.pga_schedule',
], function(gettext, url_for, $, pgBrowser) { ], function(gettext, url_for, pgBrowser) {
if (!pgBrowser.Nodes['coll-pga_job']) { if (!pgBrowser.Nodes['coll-pga_job']) {
pgBrowser.Nodes['coll-pga_job'] = pgBrowser.Nodes['coll-pga_job'] =
@ -95,10 +95,10 @@ define('pgadmin.node.pga_job', [
getApiInstance().put( getApiInstance().put(
obj.generate_url(i, 'run_now', d, true), obj.generate_url(i, 'run_now', d, true),
).then(({data: res})=> { ).then(({data: res})=> {
Notify.success(res.info); pgAdmin.Browser.notifier.success(res.info);
t.unload(i); t.unload(i);
}).catch(function(error) { }).catch(function(error) {
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.unload(i); t.unload(i);
}); });
} }

View File

@ -12,8 +12,6 @@ import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import url_for from 'sources/url_for'; import url_for from 'sources/url_for';
import { getNodeListByName, generateNodeUrl } from '../../../../../static/js/node_ajax'; import { getNodeListByName, generateNodeUrl } from '../../../../../static/js/node_ajax';
import pgBrowser from 'top/browser/static/js/browser'; import pgBrowser from 'top/browser/static/js/browser';
import { getUtilityView } from '../../../../../static/js/utility_view';
import Notify from '../../../../../../static/js/helpers/Notifier';
import { isEmptyString } from 'sources/validators'; import { isEmptyString } from 'sources/validators';
import pgAdmin from 'sources/pgadmin'; import pgAdmin from 'sources/pgadmin';
@ -180,17 +178,6 @@ export default class RoleReassign extends BaseUISchema{
} }
} }
function saveCallBack (data) {
if (data.errormsg) {
Notify.alert(
gettext('Error'),
gettext(data.errormsg)
);
} else {
Notify.success(gettext(data.info));
}
}
function getUISchema(treeNodeInfo, itemNodeData ) { function getUISchema(treeNodeInfo, itemNodeData ) {
return new RoleReassign( return new RoleReassign(
{ {
@ -211,13 +198,7 @@ export function showRoleReassign() {
treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item), treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item),
itemNodeData = pgBrowser.tree.findNodeByDomElement(item).getData(); itemNodeData = pgBrowser.tree.findNodeByDomElement(item).getData();
pgBrowser.Node.registerUtilityPanel(); const urlBase = generateNodeUrl.call( pgAdmin.Browser.Nodes[data._type], treeNodeInfo, 'reassign', data, true);
let panel = pgBrowser.Node.addUtilityPanel(pgBrowser.stdW.md, 480),
j = panel.$container.find('.obj_properties').first();
panel.title(gettext(`Reassign/Drop Owned - ${data.label}`));
panel.focus();
const baseUrl = generateNodeUrl.call( pgAdmin.Browser.Nodes[data._type], treeNodeInfo, 'reassign', data, true);
let schema = getUISchema(treeNodeInfo, itemNodeData), let schema = getUISchema(treeNodeInfo, itemNodeData),
sqlHelpUrl = '', sqlHelpUrl = '',
@ -227,6 +208,9 @@ export function showRoleReassign() {
'filename': 'role_reassign_dialog.html', 'filename': 'role_reassign_dialog.html',
}); });
getUtilityView( pgAdmin.Browser.Events.trigger('pgadmin:utility:show', item,
schema, treeNodeInfo, 'create', 'dialog', j[0], panel, saveCallBack, extraData, 'Reassign/Drop', baseUrl, sqlHelpUrl, helpUrl); gettext(gettext(`Reassign/Drop Owned - ${data.label}`), treeNodeInfo.table.label),{
} schema, extraData, urlBase, sqlHelpUrl, helpUrl, saveBtnName: gettext('Reassign/Drop'),
}, pgAdmin.Browser.stdW.md
);
}

View File

@ -12,7 +12,6 @@ import _ from 'lodash';
import url_for from 'sources/url_for'; import url_for from 'sources/url_for';
import BaseUISchema from 'sources/SchemaView/base_schema.ui'; import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import getApiInstance from '../../../../../static/js/api_instance'; import getApiInstance from '../../../../../static/js/api_instance';
import Notify from '../../../../../static/js/helpers/Notifier';
import pgAdmin from 'sources/pgadmin'; import pgAdmin from 'sources/pgadmin';
export function getBinaryPathSchema() { export function getBinaryPathSchema() {
@ -60,15 +59,15 @@ export default class BinaryPathSchema extends BaseUISchema {
validate: (data) => { validate: (data) => {
const api = getApiInstance(); const api = getApiInstance();
if (_.isNull(data) || data.trim() === '') { if (_.isNull(data) || data.trim() === '') {
Notify.alert(gettext('Validate Path'), gettext('Path should not be empty.')); pgAdmin.Browser.notifier.alert(gettext('Validate Path'), gettext('Path should not be empty.'));
} else { } else {
api.post(url_for('misc.validate_binary_path'), api.post(url_for('misc.validate_binary_path'),
JSON.stringify({ 'utility_path': data })) JSON.stringify({ 'utility_path': data }))
.then(function (res) { .then(function (res) {
Notify.alert(gettext('Validate binary path'), gettext(res.data.data)); pgAdmin.Browser.notifier.alert(gettext('Validate binary path'), gettext(res.data.data));
}) })
.catch(function (error) { .catch(function (error) {
Notify.pgNotifier('error', error, gettext('Failed to validate binary path.')); pgAdmin.Browser.notifier.pgNotifier('error', error, gettext('Failed to validate binary path.'));
}); });
} }
return true; return true;

View File

@ -9,18 +9,17 @@
import { getNodeListById } from '../../../../static/js/node_ajax'; import { getNodeListById } from '../../../../static/js/node_ajax';
import ServerSchema from './server.ui'; import ServerSchema from './server.ui';
import Notify from '../../../../../static/js/helpers/Notifier';
import { showServerPassword, showChangeServerPassword, showNamedRestorePoint } from '../../../../../static/js/Dialogs/index'; import { showServerPassword, showChangeServerPassword, showNamedRestorePoint } from '../../../../../static/js/Dialogs/index';
import _ from 'lodash'; import _ from 'lodash';
import getApiInstance, { parseApiError } from '../../../../../static/js/api_instance'; import getApiInstance, { parseApiError } from '../../../../../static/js/api_instance';
define('pgadmin.node.server', [ define('pgadmin.node.server', [
'sources/gettext', 'sources/url_for', 'jquery', 'sources/gettext', 'sources/url_for',
'sources/pgadmin', 'pgadmin.browser', 'sources/pgadmin', 'pgadmin.browser',
'pgadmin.user_management.current_user', 'pgadmin.user_management.current_user',
'pgadmin.authenticate.kerberos', 'pgadmin.authenticate.kerberos',
], function( ], function(
gettext, url_for, $, pgAdmin, pgBrowser, gettext, url_for, pgAdmin, pgBrowser,
current_user, Kerberos, current_user, Kerberos,
) { ) {
@ -42,6 +41,12 @@ define('pgadmin.node.server', [
can_expand: function(d) { can_expand: function(d) {
return d && d.connected; return d && d.connected;
}, },
title: function(d, action) {
if(action == 'create') {
return gettext('Register - %s', this.label);
}
return d.label??'';
},
Init: function() { Init: function() {
/* Avoid multiple registration of same menus */ /* Avoid multiple registration of same menus */
if (this.initialized) if (this.initialized)
@ -201,7 +206,7 @@ define('pgadmin.node.server', [
obj.generate_url(i, 'connect', d, true), obj.generate_url(i, 'connect', d, true),
).then(({data: res})=> { ).then(({data: res})=> {
if (res.success == 1) { if (res.success == 1) {
Notify.success(res.info); pgAdmin.Browser.notifier.success(res.info, null);
d = t.itemData(i); d = t.itemData(i);
t.removeIcon(i); t.removeIcon(i);
d.connected = false; d.connected = false;
@ -226,7 +231,7 @@ define('pgadmin.node.server', [
} }
else { else {
try { try {
Notify.error(res.errormsg); pgAdmin.Browser.notifier.error(res.errormsg);
} catch (e) { } catch (e) {
console.warn(e.stack || e); console.warn(e.stack || e);
} }
@ -234,13 +239,13 @@ define('pgadmin.node.server', [
} }
} }
}).catch(function(error) { }).catch(function(error) {
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.unload(i); t.unload(i);
}); });
}; };
if (notify) { if (notify) {
Notify.confirm( pgAdmin.Browser.notifier.confirm(
gettext('Disconnect from server'), gettext('Disconnect from server'),
gettext('Are you sure you want to disconnect from the server %s?', d.label), gettext('Are you sure you want to disconnect from the server %s?', d.label),
function() { disconnect(); }, function() { disconnect(); },
@ -293,7 +298,7 @@ define('pgadmin.node.server', [
d = i ? t.itemData(i) : undefined; d = i ? t.itemData(i) : undefined;
if (d) { if (d) {
Notify.confirm( pgAdmin.Browser.notifier.confirm(
gettext('Reload server configuration'), gettext('Reload server configuration'),
gettext('Are you sure you want to reload the server configuration on %s?', d.label), gettext('Are you sure you want to reload the server configuration on %s?', d.label),
function() { function() {
@ -301,13 +306,13 @@ define('pgadmin.node.server', [
obj.generate_url(i, 'reload', d, true), obj.generate_url(i, 'reload', d, true),
).then(({data: res})=> { ).then(({data: res})=> {
if (res.data.status) { if (res.data.status) {
Notify.success(res.data.result); pgAdmin.Browser.notifier.success(res.data.result);
} }
else { else {
Notify.error(res.data.result); pgAdmin.Browser.notifier.error(res.data.result);
} }
}).catch(function(error) { }).catch(function(error) {
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.unload(i); t.unload(i);
}); });
}, },
@ -351,7 +356,7 @@ define('pgadmin.node.server', [
} }
showChangeServerPassword(gettext('Change Password'), d, obj, i, is_pgpass_file_used); showChangeServerPassword(gettext('Change Password'), d, obj, i, is_pgpass_file_used);
}).catch(function(error) { }).catch(function(error) {
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
}); });
} }
@ -360,7 +365,7 @@ define('pgadmin.node.server', [
on_done: function(res, t, i) { on_done: function(res, t, i) {
if (res.success == 1) { if (res.success == 1) {
Notify.success(res.info); pgAdmin.Browser.notifier.success(res.info);
t.itemData(i).wal_pause=res.data.wal_pause; t.itemData(i).wal_pause=res.data.wal_pause;
t.deselect(i); t.deselect(i);
// Fetch updated data from server // Fetch updated data from server
@ -386,7 +391,7 @@ define('pgadmin.node.server', [
).then(({data: res})=> { ).then(({data: res})=> {
obj.callbacks.on_done(res, t, i); obj.callbacks.on_done(res, t, i);
}).catch(function(error) { }).catch(function(error) {
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.unload(i); t.unload(i);
}); });
}, },
@ -407,7 +412,7 @@ define('pgadmin.node.server', [
).then(({data: res})=> { ).then(({data: res})=> {
obj.callbacks.on_done(res, t, i); obj.callbacks.on_done(res, t, i);
}).catch(function(error) { }).catch(function(error) {
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.unload(i); t.unload(i);
}); });
}, },
@ -421,7 +426,7 @@ define('pgadmin.node.server', [
d = i ? t.itemData(i) : undefined; d = i ? t.itemData(i) : undefined;
if (d) { if (d) {
Notify.confirm( pgAdmin.Browser.notifier.confirm(
gettext('Clear saved password'), gettext('Clear saved password'),
gettext('Are you sure you want to clear the saved password for server %s?', d.label), gettext('Are you sure you want to clear the saved password for server %s?', d.label),
function() { function() {
@ -429,7 +434,7 @@ define('pgadmin.node.server', [
obj.generate_url(i, 'clear_saved_password' , d, true) obj.generate_url(i, 'clear_saved_password' , d, true)
).then(({data: res})=> { ).then(({data: res})=> {
if (res.success == 1) { if (res.success == 1) {
Notify.success(res.info); pgAdmin.Browser.notifier.success(res.info);
t.itemData(i).is_password_saved=res.data.is_password_saved; t.itemData(i).is_password_saved=res.data.is_password_saved;
t.deselect(i); t.deselect(i);
setTimeout(function() { setTimeout(function() {
@ -437,10 +442,10 @@ define('pgadmin.node.server', [
}); });
} }
else { else {
Notify.error(res.info); pgAdmin.Browser.notifier.error(res.info);
} }
}).catch(function(error) { }).catch(function(error) {
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
}); });
}, },
function() { return true; } function() { return true; }
@ -459,7 +464,7 @@ define('pgadmin.node.server', [
d = i ? t.itemData(i) : undefined; d = i ? t.itemData(i) : undefined;
if (d) { if (d) {
Notify.confirm( pgAdmin.Browser.notifier.confirm(
gettext('Clear SSH Tunnel password'), gettext('Clear SSH Tunnel password'),
gettext('Are you sure you want to clear the saved password of SSH Tunnel for server %s?', d.label), gettext('Are you sure you want to clear the saved password of SSH Tunnel for server %s?', d.label),
function() { function() {
@ -467,14 +472,14 @@ define('pgadmin.node.server', [
obj.generate_url(i, 'clear_sshtunnel_password' , d, true) obj.generate_url(i, 'clear_sshtunnel_password' , d, true)
).then(({data: res})=> { ).then(({data: res})=> {
if (res.success == 1) { if (res.success == 1) {
Notify.success(res.info); pgAdmin.Browser.notifier.success(res.info);
t.itemData(i).is_tunnel_password_saved=res.data.is_tunnel_password_saved; t.itemData(i).is_tunnel_password_saved=res.data.is_tunnel_password_saved;
} }
else { else {
Notify.error(res.info); pgAdmin.Browser.notifier.error(res.info);
} }
}).catch(function(error) { }).catch(function(error) {
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
}); });
}, },
function() { return true; } function() { return true; }
@ -542,7 +547,7 @@ define('pgadmin.node.server', [
pgBrowser.Events.on( pgBrowser.Events.on(
'pgadmin:server:connect:cancelled', disconnect 'pgadmin:server:connect:cancelled', disconnect
); );
Notify.confirm( pgAdmin.Browser.notifier.confirm(
gettext('Connection lost'), gettext('Connection lost'),
gettext('Would you like to reconnect to the database?'), gettext('Would you like to reconnect to the database?'),
function() { function() {
@ -565,12 +570,12 @@ define('pgadmin.node.server', [
let checkSupportedVersion = function (version, info) { let checkSupportedVersion = function (version, info) {
if (!_.isUndefined(version) && !_.isNull(version) && version < 100000) { if (!_.isUndefined(version) && !_.isNull(version) && version < 100000) {
Notify.warning(gettext('You have connected to a server version that is older ' + pgAdmin.Browser.notifier.warning(gettext('You have connected to a server version that is older ' +
'than is supported by pgAdmin. This may cause pgAdmin to break in strange and ' + 'than is supported by pgAdmin. This may cause pgAdmin to break in strange and ' +
'unpredictable ways. Or a plague of frogs. Either way, you have been warned!') + 'unpredictable ways. Or a plague of frogs. Either way, you have been warned!') +
'<br /><br />' + gettext('Server connected'), null); '<br /><br />' + gettext('Server connected'), null);
} else if (!_.isUndefined(info) && !_.isNull(info)) { } else if (!_.isUndefined(info) && !_.isNull(info)) {
Notify.success(info); pgAdmin.Browser.notifier.success(info);
} }
}; };
@ -588,7 +593,7 @@ define('pgadmin.node.server', [
data.is_connecting = false; data.is_connecting = false;
tree.unload(item); tree.unload(item);
tree.addIcon(item, {icon: 'icon-shared-server-not-connected'}); 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.'); pgAdmin.Browser.notifier.info('Please enter the server details to connect to the server. This server is a shared server.');
} else { } else {
data.is_connecting = false; data.is_connecting = false;
tree.unload(item); tree.unload(item);
@ -629,11 +634,11 @@ define('pgadmin.node.server', [
}, },
function() { function() {
tree.addIcon(_item, {icon: 'icon-server-not-connected'}); tree.addIcon(_item, {icon: 'icon-server-not-connected'});
Notify.pgNotifier('error', error, 'Connection error', gettext('Connect to server.')); pgAdmin.Browser.notifier.pgNotifier('error', error, 'Connection error', gettext('Connect to server.'));
} }
); );
} else { } else {
Notify.pgNotifier('error', error, errormsg, function(msg) { pgAdmin.Browser.notifier.pgNotifier('error', error, errormsg, function(msg) {
setTimeout(function() { setTimeout(function() {
if (msg == 'CRYPTKEY_SET') { if (msg == 'CRYPTKEY_SET') {
connect_to_server(_node, _data, _tree, _item, _wasConnected); connect_to_server(_node, _data, _tree, _item, _wasConnected);
@ -768,7 +773,7 @@ define('pgadmin.node.server', [
serverInfo[data._id] = _.extend({}, data); serverInfo[data._id] = _.extend({}, data);
if(data.errmsg) { if(data.errmsg) {
Notify.error(data.errmsg); pgAdmin.Browser.notifier.error(data.errmsg);
} }
// Generate the event that server is connected // Generate the event that server is connected
pgBrowser.Events.trigger( pgBrowser.Events.trigger(
@ -783,7 +788,7 @@ define('pgadmin.node.server', [
}else{ }else{
tree.addIcon(item, {icon: 'icon-server-not-connected'}); tree.addIcon(item, {icon: 'icon-server-not-connected'});
} }
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
}); });
}; };
} }

View File

@ -11,7 +11,6 @@ import pgAdmin from 'sources/pgadmin';
import Menu, { MenuItem } from '../../../static/js/helpers/Menu'; import Menu, { MenuItem } from '../../../static/js/helpers/Menu';
import getApiInstance from '../../../static/js/api_instance'; import getApiInstance from '../../../static/js/api_instance';
import url_for from 'sources/url_for'; import url_for from 'sources/url_for';
import Notifier from '../../../static/js/helpers/Notifier';
import { getBrowser } from '../../../static/js/utils'; import { getBrowser } from '../../../static/js/utils';
import { isMac } from '../../../static/js/keyboard_shortcuts'; import { isMac } from '../../../static/js/keyboard_shortcuts';
@ -98,7 +97,7 @@ export default class MainMenuFactory {
).then(()=>{ ).then(()=>{
window.open(options.url); window.open(options.url);
}).catch(()=>{ }).catch(()=>{
Notifier.error(gettext('Error in opening window')); pgAdmin.Browser.notifier.error(gettext('Error in opening window'));
}); });
} }
} }

View File

@ -7,7 +7,6 @@
// //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import $ from 'jquery';
import _ from 'lodash'; import _ from 'lodash';
import pgAdmin from 'sources/pgadmin'; import pgAdmin from 'sources/pgadmin';
@ -106,7 +105,6 @@ _.extend(pgBrowser, {
if(self.is_inactivity_timeout()) { if(self.is_inactivity_timeout()) {
clearInterval(timeout_daemon_id); clearInterval(timeout_daemon_id);
self.inactivity_timeout_daemon_running = false; self.inactivity_timeout_daemon_running = false;
$(window).off('beforeunload');
self.logout_inactivity_user(); self.logout_inactivity_user();
} }
}, MIN_ACTIVITY_TIME_UNIT); }, MIN_ACTIVITY_TIME_UNIT);

View File

@ -7,45 +7,25 @@
// //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import React from 'react';
import { generateNodeUrl } from './node_ajax';
import MainMenuFactory from './MainMenuFactory'; import MainMenuFactory from './MainMenuFactory';
import _ from 'lodash'; import _ from 'lodash';
import Notify, {initializeModalProvider, initializeNotifier} from '../../../static/js/helpers/Notifier'; import { checkMasterPassword, showQuickSearch } from '../../../static/js/Dialogs/index';
import { checkMasterPassword } from '../../../static/js/Dialogs/index';
import { pgHandleItemError } from '../../../static/js/utils'; import { pgHandleItemError } from '../../../static/js/utils';
import { Search } from './quick_search/trigger_search';
import { send_heartbeat, stop_heartbeat } from './heartbeat'; import { send_heartbeat, stop_heartbeat } from './heartbeat';
import getApiInstance from '../../../static/js/api_instance'; import getApiInstance from '../../../static/js/api_instance';
import { copyToClipboard } from '../../../static/js/clipboard'; import usePreferences, { setupPreferenceBroadcast } from '../../../preferences/static/js/store';
import { TAB_CHANGE } from './constants'; import checkNodeVisibility from '../../../static/js/check_node_visibility';
define('pgadmin.browser', [ define('pgadmin.browser', [
'sources/gettext', 'sources/url_for', 'jquery', 'sources/gettext', 'sources/url_for', 'sources/pgadmin', 'bundled_codemirror',
'sources/pgadmin', 'bundled_codemirror', 'sources/csrf', 'pgadmin.authenticate.kerberos',
'sources/check_node_visibility', './toolbar', 'pgadmin.help', 'pgadmin.browser.utils', 'pgadmin.browser.messages',
'sources/csrf', 'sources/utils', 'sources/window', 'pgadmin.authenticate.kerberos',
'sources/tree/tree_init',
'pgadmin.browser.utils',
'pgadmin.browser.preferences', 'pgadmin.browser.messages',
'pgadmin.browser.panel', 'pgadmin.browser.layout',
'pgadmin.browser.frame',
'pgadmin.browser.node', 'pgadmin.browser.collection', 'pgadmin.browser.activity', 'pgadmin.browser.node', 'pgadmin.browser.collection', 'pgadmin.browser.activity',
'sources/codemirror/addon/fold/pgadmin-sqlfoldcode', 'sources/codemirror/addon/fold/pgadmin-sqlfoldcode',
'pgadmin.browser.keyboard', 'sources/tree/pgadmin_tree_save_state', 'pgadmin.browser.keyboard', 'sources/tree/pgadmin_tree_save_state',
/* wcDocker dependencies */
'bootstrap', 'jquery-contextmenu', 'wcdocker',
], function( ], function(
gettext, url_for, $, gettext, url_for, pgAdmin, codemirror, csrfToken, Kerberos,
pgAdmin, codemirror,
checkNodeVisibility, toolBar, help, csrfToken, pgadminUtils, pgWindow,
Kerberos, InitTree,
) { ) {
window.jQuery = window.$ = $;
// Some scripts do export their object in the window only.
// Generally the one, which do no have AMD support.
let wcDocker = window.wcDocker;
$ = $ || window.jQuery || window.$;
let CodeMirror = codemirror.default; let CodeMirror = codemirror.default;
let pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {}; let pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
@ -55,104 +35,10 @@ define('pgadmin.browser', [
Kerberos.validate_kerberos_ticket(); Kerberos.validate_kerberos_ticket();
let panelEvents = {};
panelEvents[wcDocker.EVENT.VISIBILITY_CHANGED] = function() {
if (this.isVisible()) {
let obj = pgAdmin.Browser,
i = obj.tree ? obj.tree.selected() : undefined,
d = i ? obj.tree.itemData(i) : undefined;
if (d && obj.Nodes[d._type].callbacks['selected'] &&
_.isFunction(obj.Nodes[d._type].callbacks['selected'])) {
return obj.Nodes[d._type].callbacks['selected'].apply(
obj.Nodes[d._type], [i, d, obj, [], '', TAB_CHANGE]);
}
}
};
let initializeBrowserTree = pgAdmin.Browser.initializeBrowserTree =
function(b) {
const draggableTypes = [
'collation domain domain_constraints fts_configuration fts_dictionary fts_parser fts_template synonym table partition type sequence package view mview foreign_table edbvar',
'schema column database cast event_trigger extension language foreign_data_wrapper foreign_server user_mapping compound_trigger index index_constraint primary_key unique_constraint check_constraint exclusion_constraint foreign_key rule',
'trigger trigger_function',
'edbfunc function edbproc procedure'
];
InitTree.initBrowserTree(b).then(() => {
const getQualifiedName = (data, item)=>{
if(draggableTypes[0].includes(data._type)) {
return pgadminUtils.fully_qualify(b, data, item);
} else if(draggableTypes[1].includes(data._type)) {
return pgadminUtils.quote_ident(data._label);
} else if(draggableTypes[3].includes(data._type)) {
let newData = {...data};
let parsedFunc = pgadminUtils.parseFuncParams(newData._label);
newData._label = parsedFunc.func_name;
return pgadminUtils.fully_qualify(b, newData, item);
} else {
return data._label;
}
};
b.tree.registerDraggableType({
[draggableTypes[0]] : (data, item, treeNodeInfo)=>{
let text = getQualifiedName(data, item);
return {
text: text,
objUrl: generateNodeUrl.call(pgBrowser.Nodes[data._type], treeNodeInfo, 'properties', data, true),
nodeType: data._type,
cur: {
from: text.length,
to: text.length,
},
};
},
[draggableTypes[1]] : (data)=>{
return getQualifiedName(data);
},
[draggableTypes[2]] : (data)=>{
return getQualifiedName(data);
},
[draggableTypes[3]] : (data, item)=>{
let parsedFunc = pgadminUtils.parseFuncParams(data._label),
dropVal = getQualifiedName(data, item),
curPos = {from: 0, to: 0};
if(parsedFunc.params.length > 0) {
dropVal = dropVal + '(';
curPos.from = dropVal.length;
dropVal = dropVal + parsedFunc.params[0][0];
curPos.to = dropVal.length;
for(let i=1; i<parsedFunc.params.length; i++) {
dropVal = dropVal + ', ' + parsedFunc.params[i][0];
}
dropVal = dropVal + ')';
} else {
dropVal = dropVal + '()';
curPos.from = curPos.to = dropVal.length + 1;
}
return {
text: dropVal,
cur: curPos,
};
},
});
b.tree.onNodeCopy((data, item)=>{
copyToClipboard(getQualifiedName(data, item));
});
}, () => {console.warn('Tree Load Error');});
};
// Extend the browser class attributes // Extend the browser class attributes
_.extend(pgAdmin.Browser, { _.extend(pgAdmin.Browser, {
// The base url for browser // The base url for browser
URL: url_for('browser.index'), URL: url_for('browser.index'),
// We do have docker of type wcDocker to take care of different
// containers. (i.e. panels, tabs, frames, etc.)
docker:null, docker:null,
// Reversed Engineer query for the selected database node object goes // Reversed Engineer query for the selected database node object goes
// here // here
@ -215,102 +101,7 @@ define('pgadmin.browser', [
}, },
}, },
// Default panels // Default panels
panels: { panels: {},
// Panel to keep the left hand browser tree
'browser': new pgAdmin.Browser.Panel({
name: 'browser',
title: gettext('Object Explorer'),
showTitle: true,
isCloseable: false,
isPrivate: true,
icon: '',
limit: 1,
content: '<div id="tree" class="browser-tree"></div>',
onCreate: function(panel, container) {
toolBar.initializeToolbar(panel, wcDocker);
container.classList.add('pg-no-overflow');
},
}),
// Properties of the object node
'properties': new pgAdmin.Browser.Panel({
name: 'properties',
title: gettext('Properties'),
icon: '',
width: 500,
isCloseable: false,
isPrivate: true,
elContainer: true,
limit: 1,
content: '<div class="obj_properties"><div role="status" class="pg-panel-message">' + select_object_msg + '</div></div>',
events: panelEvents,
onCreate: function(myPanel, container) {
container.classList.add('pg-no-overflow');
},
}),
// Statistics of the object
'statistics': new pgAdmin.Browser.Panel({
name: 'statistics',
title: gettext('Statistics'),
icon: '',
width: 500,
isCloseable: true,
isPrivate: false,
limit : 1,
canHide: true,
content: '<div></div>',
events: panelEvents,
}),
// Reversed engineered SQL for the object
'sql': new pgAdmin.Browser.Panel({
name: 'sql',
title: gettext('SQL'),
icon: '',
width: 500,
isCloseable: false,
isPrivate: true,
limit: 1,
content: '<div></div>',
}),
// Dependencies of the object
'dependencies': new pgAdmin.Browser.Panel({
name: 'dependencies',
title: gettext('Dependencies'),
icon: '',
width: 500,
isCloseable: true,
isPrivate: false,
canHide: true,
limit: 1,
content: '<div></div>',
events: panelEvents,
}),
// Dependents of the object
'dependents': new pgAdmin.Browser.Panel({
name: 'dependents',
title: gettext('Dependents'),
icon: '',
width: 500,
isCloseable: true,
isPrivate: false,
limit: 1,
canHide: true,
content: '<div></div>',
events: panelEvents,
}),
// Background processes
'processes': new pgAdmin.Browser.Panel({
name: 'processes',
title: gettext('Processes'),
icon: '',
width: 500,
isCloseable: true,
isPrivate: false,
limit: 1,
canHide: true,
content: '<div></div>',
events: panelEvents,
}),
},
// We also support showing dashboards, HTML file, external URL // We also support showing dashboards, HTML file, external URL
frames: {}, frames: {},
/* Menus */ /* Menus */
@ -336,40 +127,6 @@ define('pgadmin.browser', [
MainMenus: [], MainMenus: [],
BrowserContextMenu: [], BrowserContextMenu: [],
add_panels: function() {
/* Add hooked-in panels by extensions */
let panels = JSON.parse(pgBrowser.panels_items);
_.each(panels, function(panel) {
if (panel.isIframe) {
pgBrowser.frames[panel.name] = new pgBrowser.Frame({
name: panel.name,
title: panel.title,
icon: panel.icon,
width: panel.width,
height: panel.height,
showTitle: panel.showTitle,
isCloseable: panel.isCloseable,
isPrivate: panel.isPrivate,
url: panel.content,
});
} else {
pgBrowser.panels[panel.name] = new pgBrowser.Panel({
name: panel.name,
title: panel.title,
icon: panel.icon,
width: panel.width,
height: panel.height,
showTitle: panel.showTitle,
isCloseable: panel.isCloseable,
isPrivate: panel.isPrivate,
limit: (panel.limit) ? panel.limit : null,
content: (panel.content) ? panel.content : '',
events: (panel.events) ? panel.events : '',
canHide: (panel.canHide) ? panel.canHide : '',
});
}
});
},
menu_categories: { menu_categories: {
/* name, label (pair) */ /* name, label (pair) */
'register': { 'register': {
@ -490,8 +247,6 @@ define('pgadmin.browser', [
enable_disable_menus: function(item) { enable_disable_menus: function(item) {
let obj = this; let obj = this;
let d = item ? obj.tree.itemData(item) : undefined; let d = item ? obj.tree.itemData(item) : undefined;
toolBar.enable(gettext('View Data'), false);
toolBar.enable(gettext('Filtered Rows'), false);
// All menus (except for the object menus) are already present. // All menus (except for the object menus) are already present.
// They will just require to check, whether they are // They will just require to check, whether they are
@ -529,64 +284,8 @@ define('pgadmin.browser', [
obj.initialized = true; obj.initialized = true;
// Cache preferences // Cache preferences
obj.cache_preferences(); usePreferences.getState().cache();
obj.add_panels(); setupPreferenceBroadcast();
// Initialize the Docker
obj.docker = new wcDocker(
'#dockerContainer', {
allowContextMenu: true,
allowCollapse: false,
loadingClass: 'pg-sp-icon',
themePath: url_for('static', {
'filename': 'css',
}),
theme: 'webcabin.overrides.css',
});
if (obj.docker) {
// Initialize all the panels
_.each(obj.panels, function(panel, name) {
obj.panels[name].load(obj.docker);
});
// Initialize all the frames
_.each(obj.frames, function(frame, name) {
obj.frames[name].load(obj.docker);
});
// Stored layout in database from the previous session
let layout = pgBrowser.utils.layout;
obj.restore_layout(obj.docker, layout, obj.buildDefaultLayout.bind(obj), true);
obj.docker.on(wcDocker.EVENT.LAYOUT_CHANGED, function() {
obj.save_current_layout('Browser/Layout', obj.docker);
});
}
initializeBrowserTree(obj);
initializeModalProvider();
initializeNotifier();
/* Cache may take time to load for the first time
* Reflect the changes once cache is available
*/
let cacheIntervalId = setInterval(()=> {
let sqlEditPreferences = obj.get_preferences_for_module('sqleditor');
let browserPreferences = obj.get_preferences_for_module('browser');
if(sqlEditPreferences && browserPreferences) {
clearInterval(cacheIntervalId);
obj.reflectPreferences('sqleditor');
obj.reflectPreferences('browser');
}
}, 500);
/* Check for sql editor preference changes */
obj.onPreferencesChange('sqleditor', function() {
obj.reflectPreferences('sqleditor');
});
/* Check for browser preference changes */
obj.onPreferencesChange('browser', function() {
obj.reflectPreferences('browser');
});
setTimeout(function() { setTimeout(function() {
obj?.editor?.setValue('-- ' + select_object_msg); obj?.editor?.setValue('-- ' + select_object_msg);
@ -621,7 +320,6 @@ define('pgadmin.browser', [
obj.Events.on('pgadmin:browser:tree:update', obj.onUpdateTreeNode.bind(obj)); obj.Events.on('pgadmin:browser:tree:update', obj.onUpdateTreeNode.bind(obj));
obj.Events.on('pgadmin:browser:tree:refresh', obj.onRefreshTreeNodeReact.bind(obj)); obj.Events.on('pgadmin:browser:tree:refresh', obj.onRefreshTreeNodeReact.bind(obj));
obj.Events.on('pgadmin-browser:tree:loadfail', obj.onLoadFailNode.bind(obj)); obj.Events.on('pgadmin-browser:tree:loadfail', obj.onLoadFailNode.bind(obj));
obj.Events.on('pgadmin-browser:panel-browser:' + wcDocker.EVENT.RESIZE_ENDED, obj.onResizeEnded.bind(obj));
obj.bind_beforeunload(); obj.bind_beforeunload();
/* User UI activity */ /* User UI activity */
@ -629,8 +327,8 @@ define('pgadmin.browser', [
obj.register_to_activity_listener(document); obj.register_to_activity_listener(document);
obj.start_inactivity_timeout_daemon(); obj.start_inactivity_timeout_daemon();
}, },
onResizeEnded: function() { uiloaded: function() {
if (this.tree) this.tree.resizeTree(); this.check_version_update();
}, },
check_corrupted_db_file: function() { check_corrupted_db_file: function() {
getApiInstance().get( getApiInstance().get(
@ -638,7 +336,7 @@ define('pgadmin.browser', [
).then(({data: res})=> { ).then(({data: res})=> {
if(res.data.length > 0) { if(res.data.length > 0) {
Notify.alert( pgAdmin.Browser.notifier.alert(
'Warning', 'Warning',
'pgAdmin detected unrecoverable corruption in it\'s SQLite configuration database. ' + 'pgAdmin detected unrecoverable corruption in it\'s SQLite configuration database. ' +
'The database has been backed up and recreated with default settings. '+ 'The database has been backed up and recreated with default settings. '+
@ -649,7 +347,7 @@ define('pgadmin.browser', [
); );
} }
}).catch(function(error) { }).catch(function(error) {
Notify.alert(error); pgAdmin.Browser.notifier.alert(error);
}); });
}, },
check_master_password: function(on_resp_callback) { check_master_password: function(on_resp_callback) {
@ -664,7 +362,7 @@ define('pgadmin.browser', [
} }
} }
}).catch(function(error) { }).catch(function(error) {
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
}); });
}, },
@ -677,7 +375,7 @@ define('pgadmin.browser', [
self.set_master_password(''); self.set_master_password('');
} }
}).catch(function(error) { }).catch(function(error) {
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
}); });
}, },
@ -695,13 +393,33 @@ define('pgadmin.browser', [
checkMasterPassword(data, self.masterpass_callback_queue, cancel_callback); checkMasterPassword(data, self.masterpass_callback_queue, cancel_callback);
}, },
bind_beforeunload: function() { check_version_update: function() {
$(window).on('beforeunload', function(e) { getApiInstance().get(
/* Can open you in new tab */ url_for('misc.upgrade_check')
let openerBrowser = pgWindow.default.pgAdmin.Browser; ).then((res)=> {
const data = res.data.data;
if(data.outdated) {
pgAdmin.Browser.notifier.warning(
`
${gettext('You are currently running version %s of %s, <br/>however the current version is %s.', data.current_version, data.product_name, data.upgrade_version)}
<br/><br/>
${gettext('Please click <a href="%s" target="_new" style="color:inherit">here</a> for more information.', data.download_url)}
`,
null
);
}
let tree_save_interval = pgBrowser.get_preference('browser', 'browser_tree_state_save_interval'), }).catch(function() {
confirm_on_refresh_close = openerBrowser.get_preference('browser', 'confirm_on_refresh_close'); // Suppress any errors
});
},
bind_beforeunload: function() {
window.addEventListener('beforeunload', function(e) {
/* Can open you in new tab */
const prefStore = usePreferences.getState();
let tree_save_interval = prefStore.getPreferences('browser', 'browser_tree_state_save_interval'),
confirm_on_refresh_close = prefStore.getPreferences('browser', 'confirm_on_refresh_close');
if (!_.isUndefined(tree_save_interval) && tree_save_interval.value !== -1) if (!_.isUndefined(tree_save_interval) && tree_save_interval.value !== -1)
pgAdmin.Browser.browserTreeState.save_state(); pgAdmin.Browser.browserTreeState.save_state();
@ -709,7 +427,9 @@ define('pgadmin.browser', [
if(!_.isUndefined(confirm_on_refresh_close) && confirm_on_refresh_close.value) { if(!_.isUndefined(confirm_on_refresh_close) && confirm_on_refresh_close.value) {
/* This message will not be displayed in Chrome, Firefox, Safari as they have disabled it*/ /* This message will not be displayed in Chrome, Firefox, Safari as they have disabled it*/
let msg = gettext('Are you sure you want to close the %s browser?', pgBrowser.utils.app_name); let msg = gettext('Are you sure you want to close the %s browser?', pgBrowser.utils.app_name);
e.originalEvent.returnValue = msg; if(e.originalEvent?.returnValue) {
e.originalEvent.returnValue = msg;
}
return msg; return msg;
} }
}); });
@ -730,8 +450,7 @@ define('pgadmin.browser', [
// Add menus of module/extension at appropriate menu // Add menus of module/extension at appropriate menu
add_menus: function(menus) { add_menus: function(menus) {
let self = this, let pgMenu = this.all_menus_cache;
pgMenu = this.all_menus_cache;
_.each(menus, function(m) { _.each(menus, function(m) {
_.each(m.applies, function(a) { _.each(m.applies, function(a) {
@ -741,12 +460,12 @@ define('pgadmin.browser', [
// If current node is not visible in browser tree // If current node is not visible in browser tree
// then return from here // then return from here
if(!checkNodeVisibility(self, m.node)) { if(!checkNodeVisibility(m.node)) {
return; return;
} else if(_.has(m, 'module') && !_.isUndefined(m.module)) { } else if(_.has(m, 'module') && !_.isUndefined(m.module)) {
// If module to which this menu applies is not visible in // If module to which this menu applies is not visible in
// browser tree then also we do not display menu // browser tree then also we do not display menu
if(!checkNodeVisibility(self, m.module.type)) { if(!checkNodeVisibility(m.module.type)) {
return; return;
} }
} }
@ -770,14 +489,9 @@ define('pgadmin.browser', [
} }
// This is to handel quick search callback // This is to handel quick search callback
if(gettext(_m.label) == gettext('Quick Search')) { if(_m.name == 'mnu_quick_search_help') {
_m.callback = () => { _m.callback = () => {
// Render Search component showQuickSearch();
Notify.showModal(gettext('Quick Search'), (closeModal) => {
return <Search closeModal={closeModal}/>;
},
{ isFullScreen: false, isResizeable: false, showFullScreen: false, isFullWidth: false, showTitle: false}
);
}; };
} }
@ -1683,7 +1397,6 @@ define('pgadmin.browser', [
if (!n) { if (!n) {
ctx.t.destroy({ ctx.t.destroy({
success: function() { success: function() {
initializeBrowserTree(ctx.b);
ctx.t = ctx.b.tree; ctx.t = ctx.b.tree;
ctx.i = null; ctx.i = null;
ctx.b._refreshNode(ctx, ctx.branch); ctx.b._refreshNode(ctx, ctx.branch);
@ -1760,7 +1473,7 @@ define('pgadmin.browser', [
} }
} }
Notify.pgNotifier('error', error, gettext('Error retrieving details for the node.'), function (msg) { pgAdmin.Browser.notifier.pgNotifier('error', error, gettext('Error retrieving details for the node.'), function (msg) {
if (msg == 'CRYPTKEY_SET') { if (msg == 'CRYPTKEY_SET') {
fetchNodeInfo(__i, __d, __n); fetchNodeInfo(__i, __d, __n);
} else { } else {
@ -1974,7 +1687,7 @@ define('pgadmin.browser', [
} }
fetchNodeInfo(_callback); fetchNodeInfo(_callback);
}).catch(function(error) { }).catch(function(error) {
Notify.pgRespErrorNotify(error); pgAdmin.Browser.notifier.pgRespErrorNotify(error);
fetchNodeInfo(_callback); fetchNodeInfo(_callback);
}); });
}; };

View File

@ -6,7 +6,6 @@
// This software is released under the PostgreSQL Licence // This software is released under the PostgreSQL Licence
// //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import {getPanelView} from './panel_view';
import _ from 'lodash'; import _ from 'lodash';
define([ define([
@ -84,15 +83,6 @@ define([
canDrop: true, canDrop: true,
canDropCascade: true, canDropCascade: true,
selectParentNodeOnDelete: false, selectParentNodeOnDelete: false,
showProperties: function(item, data, panel) {
let container = panel.$container.find('.obj_properties').first();
getPanelView(
pgBrowser.tree,
container[0],
pgBrowser,
panel._type
);
},
generate_url: function(item, type) { generate_url: function(item, type) {
/* /*
* Using list, and collection functions of a node to get the nodes * Using list, and collection functions of a node to get the nodes

View File

@ -1,2 +1,34 @@
export const AUTH_METHODS = {
INTERNAL: 'internal',
LDAP: 'ldap',
KERBEROS: 'kerberos',
OAUTH2: 'oauth2',
WEBSERVER: 'webserver'
};
export const TAB_CHANGE = 'TAB_CHANGE'; export const TAB_CHANGE = 'TAB_CHANGE';
export const BROWSER_PANELS = {
MAIN: 'id-main',
OBJECT_EXPLORER: 'id-object-explorer',
DASHBOARD: 'id-dashboard',
PROPERTIES: 'id-properties',
SQL: 'id-sql',
STATISTICS: 'id-statistics',
DEPENDENCIES: 'id-dependencies',
DEPENDENTS: 'id-dependents',
PROCESSES: 'id-processes',
PROCESS_DETAILS: 'id-process-details',
EDIT_PROPERTIES: 'id-edit-properties',
UTILITY_DIALOG: 'id-utility',
QUERY_TOOL: 'id-query-tool',
PSQL_TOOL: 'id-psql-tool',
ERD_TOOL: 'id-erd-tool',
SCHEMA_DIFF_TOOL: 'id-schema-diff-tool',
DEBUGGER_TOOL: 'id-debugger-tool',
CLOUD_WIZARD: 'id-cloud-wizard',
GRANT_WIZARD: 'id-grant-wizard',
SEARCH_OBJECTS: 'id-search-objects',
USER_MANAGEMENT: 'id-user-management',
IMPORT_EXPORT_SERVERS: 'id-import-export-servers'
};

View File

@ -1,135 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import _ from 'lodash';
define([
'sources/pgadmin', 'jquery', 'wcdocker',
], function(pgAdmin, $) {
let pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {},
wcDocker = window.wcDocker,
wcIFrame = window.wcIFrame;
pgAdmin.Browser.Frame = function(options) {
let defaults = [
'name', 'title', 'width', 'height', 'showTitle', 'isCloseable',
'isPrivate', 'url', 'icon', 'onCreate', 'isLayoutMember', 'isRenamable',
];
_.extend(this, _.pick(options, defaults));
};
_.extend(pgAdmin.Browser.Frame.prototype, {
name: '',
title: '',
width: 300,
height: 600,
showTitle: true,
isClosable: true,
isRenamable: false,
isPrivate: false,
isLayoutMember: false,
url: '',
icon: '',
panel: null,
frame: null,
onCreate: null,
load: function(docker) {
let that = this;
if (!that.panel) {
docker.registerPanelType(this.name, {
title: that.title,
isPrivate: that.isPrivate,
isLayoutMember: that.isLayoutMember,
onCreate: function(myPanel) {
myPanel.initSize(that.width, that.height);
if (!(myPanel.showTitle??true))
myPanel.title(false);
myPanel.icon(that.icon);
myPanel.closeable(!!that.isCloseable);
myPanel.renamable(that.isRenamable);
let $frameArea = $('<div style="position:absolute;top:0 !important;width:100%;height:100%;display:table;z-index:0;">');
myPanel.layout().addItem($frameArea);
that.panel = myPanel;
let frame = new wcIFrame($frameArea, myPanel);
myPanel.frameData = {
pgAdminName: that.name,
frameInitialized: false,
embeddedFrame: frame,
};
if (that.url != '' && that.url != 'about:blank') {
setTimeout(function() {
frame.openURL(that.url);
myPanel.frameData.frameInitialized = true;
pgBrowser.Events.trigger(
'pgadmin-browser:frame:urlloaded:' + that.name, frame,
that.url, self
);
}, 50);
} else {
frame.openURL('about:blank');
myPanel.frameData.frameInitialized = true;
pgBrowser.Events.trigger(
'pgadmin-browser:frame:urlloaded:' + that.name, frame,
that.url, self
);
}
if (that.events && _.isObject(that.events)) {
_.each(that.events, function(v, k) {
if (v && _.isFunction(v)) {
myPanel.on(k, v);
}
});
}
_.each([
wcDocker.EVENT.UPDATED, wcDocker.EVENT.VISIBILITY_CHANGED,
wcDocker.EVENT.BEGIN_DOCK, wcDocker.EVENT.END_DOCK,
wcDocker.EVENT.GAIN_FOCUS, wcDocker.EVENT.LOST_FOCUS,
wcDocker.EVENT.CLOSED, wcDocker.EVENT.BUTTON,
wcDocker.EVENT.ATTACHED, wcDocker.EVENT.DETACHED,
wcDocker.EVENT.MOVE_STARTED, wcDocker.EVENT.MOVE_ENDED,
wcDocker.EVENT.MOVED, wcDocker.EVENT.RESIZE_STARTED,
wcDocker.EVENT.RESIZE_ENDED, wcDocker.EVENT.RESIZED,
wcDocker.EVENT.SCROLLED,
], function(ev) {
myPanel.on(ev, that.eventFunc.bind(myPanel, ev));
});
if (that.onCreate && _.isFunction(that.onCreate)) {
that.onCreate.apply(that, [myPanel, frame]);
}
},
});
}
},
eventFunc: function(eventName) {
let name = this.frameData.pgAdminName;
try {
pgBrowser.Events.trigger('pgadmin-browser:frame', eventName, this, arguments);
pgBrowser.Events.trigger('pgadmin-browser:frame:' + eventName, this, arguments);
if (name) {
pgBrowser.Events.trigger('pgadmin-browser:frame-' + name, eventName, this, arguments);
pgBrowser.Events.trigger('pgadmin-browser:frame-' + name + ':' + eventName, this, arguments);
}
} catch (e) {
console.warn(e.stack || e);
}
},
});
return pgAdmin.Browser.Frame;
});

View File

@ -10,7 +10,6 @@
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';
import url_for from 'sources/url_for'; import url_for from 'sources/url_for';
import getApiInstance from '../../../static/js/api_instance'; import getApiInstance from '../../../static/js/api_instance';
import Notifier from '../../../static/js/helpers/Notifier';
import pgAdmin from 'sources/pgadmin'; import pgAdmin from 'sources/pgadmin';
const axiosApi = getApiInstance(); const axiosApi = getApiInstance();
@ -28,9 +27,9 @@ export function send_heartbeat(_server_id, _item) {
}) })
.catch((error) => { .catch((error) => {
if (error && error.message == 'Network Error') { if (error && error.message == 'Network Error') {
Notifier.error(gettext(`pgAdmin server not responding, try to login again: ${error.message || error.response.data.errormsg}`)); pgAdmin.Browser.notifier.error(gettext(`pgAdmin server not responding, try to login again: ${error.message || error.response.data.errormsg}`));
} else { } else {
Notifier.error(gettext(`Server heartbeat logging error: ${error.message || error.response.data.errormsg}`)); pgAdmin.Browser.notifier.error(gettext(`Server heartbeat logging error: ${error.message || error.response.data.errormsg}`));
} }
stop_heartbeat(_item); stop_heartbeat(_item);
}); });

View File

@ -13,6 +13,7 @@ import Mousetrap from 'mousetrap';
import * as commonUtils from '../../../static/js/utils'; import * as commonUtils from '../../../static/js/utils';
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';
import pgWindow from 'sources/window'; import pgWindow from 'sources/window';
import usePreferences from '../../../preferences/static/js/store';
const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {}; const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
@ -20,51 +21,53 @@ pgBrowser.keyboardNavigation = pgBrowser.keyboardNavigation || {};
_.extend(pgBrowser.keyboardNavigation, { _.extend(pgBrowser.keyboardNavigation, {
init: function() { init: function() {
Mousetrap.reset(); usePreferences.subscribe((prefStore)=>{
if (pgBrowser.preferences_cache.length > 0) { Mousetrap.reset();
this.keyboardShortcut = { if (prefStore.version > 0) {
...(pgBrowser.get_preference('browser', 'main_menu_file')?.value) && {'file_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'main_menu_file')?.value)}, this.keyboardShortcut = {
...(pgBrowser.get_preference('browser', 'main_menu_object')?.value) && {'object_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'main_menu_object')?.value)}, ...(prefStore.getPreferences('browser', 'main_menu_file')?.value) && {'file_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'main_menu_file')?.value)},
...(pgBrowser.get_preference('browser', 'main_menu_tools')?.value) && {'tools_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'main_menu_tools')?.value)}, ...(prefStore.getPreferences('browser', 'main_menu_object')?.value) && {'object_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'main_menu_object')?.value)},
...(pgBrowser.get_preference('browser', 'main_menu_help')?.value) && {'help_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'main_menu_help')?.value)}, ...(prefStore.getPreferences('browser', 'main_menu_tools')?.value) && {'tools_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'main_menu_tools')?.value)},
'left_tree_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'browser_tree').value), ...(prefStore.getPreferences('browser', 'main_menu_help')?.value) && {'help_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'main_menu_help')?.value)},
'tabbed_panel_backward': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'tabbed_panel_backward').value), 'left_tree_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'browser_tree').value),
'tabbed_panel_forward': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'tabbed_panel_forward').value), 'tabbed_panel_backward': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'tabbed_panel_backward').value),
'sub_menu_query_tool': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_query_tool').value), 'tabbed_panel_forward': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'tabbed_panel_forward').value),
'sub_menu_view_data': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_view_data').value), 'sub_menu_query_tool': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_query_tool').value),
'sub_menu_search_objects': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_search_objects').value), 'sub_menu_view_data': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_view_data').value),
'sub_menu_properties': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_properties').value), 'sub_menu_search_objects': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_search_objects').value),
'sub_menu_create': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_create').value), 'sub_menu_properties': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_properties').value),
'sub_menu_delete': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_delete').value), 'sub_menu_create': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_create').value),
'sub_menu_refresh': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_refresh').value), 'sub_menu_delete': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_delete').value),
'context_menu': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'context_menu').value), 'sub_menu_refresh': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_refresh').value),
'direct_debugging': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'direct_debugging').value), 'context_menu': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'context_menu').value),
'add_grid_row': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'add_grid_row').value), 'direct_debugging': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'direct_debugging').value),
'open_quick_search': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'open_quick_search').value), 'add_grid_row': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'add_grid_row').value),
'open_quick_search': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'open_quick_search').value),
}; };
this.shortcutMethods = { this.shortcutMethods = {
...(pgBrowser.get_preference('browser', 'main_menu_file')?.value) && {'bindMainMenu': { ...(prefStore.getPreferences('browser', 'main_menu_file')?.value) && {'bindMainMenu': {
'shortcuts': [this.keyboardShortcut.file_shortcut, 'shortcuts': [this.keyboardShortcut.file_shortcut,
this.keyboardShortcut.object_shortcut, this.keyboardShortcut.tools_shortcut, this.keyboardShortcut.object_shortcut, this.keyboardShortcut.tools_shortcut,
this.keyboardShortcut.help_shortcut], this.keyboardShortcut.help_shortcut],
}}, // Main menu }}, // Main menu
'bindRightPanel': {'shortcuts': [this.keyboardShortcut.tabbed_panel_backward, this.keyboardShortcut.tabbed_panel_forward]}, // Main window panels 'bindRightPanel': {'shortcuts': [this.keyboardShortcut.tabbed_panel_backward, this.keyboardShortcut.tabbed_panel_forward]}, // Main window panels
'bindLeftTree': {'shortcuts': this.keyboardShortcut.left_tree_shortcut}, // Main menu, 'bindLeftTree': {'shortcuts': this.keyboardShortcut.left_tree_shortcut}, // Main menu,
'bindSubMenuQueryTool': {'shortcuts': this.keyboardShortcut.sub_menu_query_tool}, // Sub menu - Open Query Tool, 'bindSubMenuQueryTool': {'shortcuts': this.keyboardShortcut.sub_menu_query_tool}, // Sub menu - Open Query Tool,
'bindSubMenuViewData': {'shortcuts': this.keyboardShortcut.sub_menu_view_data}, // Sub menu - Open View Data, 'bindSubMenuViewData': {'shortcuts': this.keyboardShortcut.sub_menu_view_data}, // Sub menu - Open View Data,
'bindSubMenuSearchObjects': {'shortcuts': this.keyboardShortcut.sub_menu_search_objects}, // Sub menu - Open search objects, 'bindSubMenuSearchObjects': {'shortcuts': this.keyboardShortcut.sub_menu_search_objects}, // Sub menu - Open search objects,
'bindSubMenuProperties': {'shortcuts': this.keyboardShortcut.sub_menu_properties}, // Sub menu - Edit Properties, 'bindSubMenuProperties': {'shortcuts': this.keyboardShortcut.sub_menu_properties}, // Sub menu - Edit Properties,
'bindSubMenuCreate': {'shortcuts': this.keyboardShortcut.sub_menu_create}, // Sub menu - Create Object, 'bindSubMenuCreate': {'shortcuts': this.keyboardShortcut.sub_menu_create}, // Sub menu - Create Object,
'bindSubMenuDelete': {'shortcuts': this.keyboardShortcut.sub_menu_delete}, // Sub menu - Delete object, 'bindSubMenuDelete': {'shortcuts': this.keyboardShortcut.sub_menu_delete}, // Sub menu - Delete object,
'bindSubMenuRefresh': {'shortcuts': this.keyboardShortcut.sub_menu_refresh, 'bindElem': '#tree'}, // Sub menu - Refresh object, 'bindSubMenuRefresh': {'shortcuts': this.keyboardShortcut.sub_menu_refresh, 'bindElem': '#tree'}, // Sub menu - Refresh object,
'bindContextMenu': {'shortcuts': this.keyboardShortcut.context_menu}, // Sub menu - Open context menu, 'bindContextMenu': {'shortcuts': this.keyboardShortcut.context_menu}, // Sub menu - Open context menu,
'bindDirectDebugging': {'shortcuts': this.keyboardShortcut.direct_debugging}, // Sub menu - Direct Debugging 'bindDirectDebugging': {'shortcuts': this.keyboardShortcut.direct_debugging}, // Sub menu - Direct Debugging
'bindAddGridRow': {'shortcuts': this.keyboardShortcut.add_grid_row}, // Subnode Grid Add Row 'bindAddGridRow': {'shortcuts': this.keyboardShortcut.add_grid_row}, // Subnode Grid Add Row
'bindOpenQuickSearch': {'shortcuts': this.keyboardShortcut.open_quick_search}, // Subnode Grid Refresh Row 'bindOpenQuickSearch': {'shortcuts': this.keyboardShortcut.open_quick_search}, // Subnode Grid Refresh Row
}; };
this.bindShortcuts(); this.bindShortcuts();
} }
});
}, },
bindShortcuts: function() { bindShortcuts: function() {
const self = this; const self = this;
@ -116,7 +119,7 @@ _.extend(pgBrowser.keyboardNavigation, {
} }
if(menuLabel) { if(menuLabel) {
document.querySelector(`#main-menu-container button[data-label="${menuLabel}"]`)?.click(); document.querySelector(`div[data-test="app-menu-bar"] button[data-label="${menuLabel}"]`)?.click();
} }
}, },
bindRightPanel: function(event, combo) { bindRightPanel: function(event, combo) {

View File

@ -1,170 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import pgAdmin from 'sources/pgadmin';
import url_for from 'sources/url_for';
import gettext from 'sources/gettext';
import 'wcdocker';
import pgWindow from 'sources/window';
import Notify from '../../../static/js/helpers/Notifier';
import getApiInstance from '../../../static/js/api_instance';
const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
let wcDocker = window.wcDocker;
/* Add cache related methods and properties */
_.extend(pgBrowser, {
lock_layout_levels : {
PREVENT_DOCKING: 'docking',
FULL: 'full',
NONE: 'none',
},
// Build the default layout
buildDefaultLayout: function(docker) {
let browserPanel = docker.addPanel('browser', wcDocker.DOCK.LEFT);
let dashboardPanel = docker.addPanel(
'dashboard', wcDocker.DOCK.RIGHT, browserPanel);
docker.addPanel('properties', wcDocker.DOCK.STACKED, dashboardPanel, {
tabOrientation: wcDocker.TAB.TOP,
});
docker.addPanel('sql', wcDocker.DOCK.STACKED, dashboardPanel);
docker.addPanel(
'statistics', wcDocker.DOCK.STACKED, dashboardPanel);
docker.addPanel(
'dependencies', wcDocker.DOCK.STACKED, dashboardPanel);
docker.addPanel(
'dependents', wcDocker.DOCK.STACKED, dashboardPanel);
docker.addPanel(
'processes', wcDocker.DOCK.STACKED, dashboardPanel);
},
save_current_layout: function(layout_id, docker) {
if(docker) {
let layout = docker.save(),
settings = { setting: layout_id, value: layout };
getApiInstance().post(url_for('settings.store_bulk'), settings);
}
},
restore_layout: function(docker, layout, defaultLayoutCallback, checkLayout= false) {
// Try to restore the layout if there is one
if (layout != '') {
try {
docker.restore(layout);
if(checkLayout) {
// Check restore layout is restored pgAdmin 4 layout successfully if not then reset layout to default pgAdmin 4 layout.
if((docker.findPanels('properties').length == 0 || docker.findPanels('browser').length == 0) && defaultLayoutCallback){
// clear the wcDocker before reset layout.
docker.clear();
Notify.info(gettext('pgAdmin has reset the layout because the previously saved layout is invalid.'), null);
defaultLayoutCallback(docker);
}
}
}
catch(err) {
docker.clear();
if(defaultLayoutCallback) {
defaultLayoutCallback(docker);
}
}
} else {
if(defaultLayoutCallback) {
defaultLayoutCallback(docker);
}
}
/* preference available only with top/opener browser. */
let browser = pgWindow.pgAdmin.Browser;
/* interval required initially as preference load may take time */
let cacheIntervalId = setInterval(()=> {
let browserPref = browser.get_preferences_for_module('browser');
if(browserPref) {
clearInterval(cacheIntervalId);
browser.reflectLocklayoutChange(docker);
browser.onPreferencesChange('browser', function() {
browser.reflectLocklayoutChange(docker);
});
}
}, 5000);
},
reflectLocklayoutChange: function(docker) {
let browser = pgWindow.pgAdmin.Browser;
let browserPref = browser.get_preferences_for_module('browser');
browser.lock_layout(docker, browserPref.lock_layout);
},
lock_layout: function(docker, op) {
let menu_items = [];
if('mnu_locklayout' in this.all_menus_cache['file']) {
menu_items = this.all_menus_cache['file']['mnu_locklayout']['menu_items'];
}
switch(op) {
case this.lock_layout_levels.PREVENT_DOCKING:
docker.lockLayout(wcDocker.LOCK_LAYOUT_LEVEL.PREVENT_DOCKING);
break;
case this.lock_layout_levels.FULL:
docker.lockLayout(wcDocker.LOCK_LAYOUT_LEVEL.FULL);
break;
case this.lock_layout_levels.NONE:
docker.lockLayout(wcDocker.LOCK_LAYOUT_LEVEL.NONE);
break;
}
if(menu_items) {
_.each(menu_items, function(menu_item) {
if(menu_item.name != ('mnu_lock_'+op)) {
menu_item.change_checked(false);
} else {
menu_item.change_checked(true);
}
});
}
},
save_lock_layout: function(op) {
let browser = pgWindow.pgAdmin.Browser;
getApiInstance().put(
url_for('browser.lock_layout'),
JSON.stringify({
'value': op,
})
).then(()=> {
browser.cache_preferences('browser');
}).catch(function(error) {
Notify.pgNotifier('error', error, gettext('Failed to save the lock layout setting.'));
});
},
mnu_lock_docking: function() {
this.lock_layout(this.docker, this.lock_layout_levels.PREVENT_DOCKING);
this.save_lock_layout(this.lock_layout_levels.PREVENT_DOCKING);
},
mnu_lock_full: function() {
this.lock_layout(this.docker, this.lock_layout_levels.FULL);
this.save_lock_layout(this.lock_layout_levels.FULL);
},
mnu_lock_none: function() {
this.lock_layout(this.docker, this.lock_layout_levels.NONE);
this.save_lock_layout(this.lock_layout_levels.NONE);
},
});
export {pgBrowser};

View File

@ -7,12 +7,14 @@
// //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import {getNodeView, removeNodeView} from './node_view';
import Notify from '../../../static/js/helpers/Notifier';
import _ from 'lodash'; import _ from 'lodash';
import getApiInstance from '../../../static/js/api_instance'; import getApiInstance from '../../../static/js/api_instance';
import { removePanelView } from './panel_view'; import { BROWSER_PANELS } from './constants';
import { TAB_CHANGE } from './constants'; import React from 'react';
import ObjectNodeProperties from '../../../misc/properties/ObjectNodeProperties';
import ErrorBoundary from '../../../static/js/helpers/ErrorBoundary';
import toPx from '../../../static/js/to_px';
import usePreferences from '../../../preferences/static/js/store';
define('pgadmin.browser.node', [ define('pgadmin.browser.node', [
'sources/gettext', 'sources/pgadmin', 'sources/gettext', 'sources/pgadmin',
@ -21,9 +23,6 @@ define('pgadmin.browser.node', [
], function( ], function(
gettext, pgAdmin, generateUrl, commonUtils gettext, pgAdmin, generateUrl, commonUtils
) { ) {
let wcDocker = window.wcDocker;
const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {}; const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
// It has already been defined. // It has already been defined.
@ -89,8 +88,11 @@ define('pgadmin.browser.node', [
dialogHelp: '', dialogHelp: '',
epasHelp: false, epasHelp: false,
title: function(o, d) { title: function(d, action) {
return o.label + (d ? (' - ' + d.label) : ''); if(action == 'create') {
return gettext('Create - %s', this.label);
}
return d.label??'';
}, },
hasId: true, hasId: true,
/////// ///////
@ -282,114 +284,6 @@ define('pgadmin.browser.node', [
return true; return true;
} }
}, },
addUtilityPanel: function(width, height, docker) {
let body = window.document.body,
el = document.createElement('div'),
dockerObject = docker || pgBrowser.docker;
body.insertBefore(el, body.firstChild);
let new_width = screen.width < 700 ? screen.width * 0.95 : screen.width * 0.5,
new_height = screen.height < 500 ? screen.height * 0.95 : screen.height * 0.4;
if (!_.isUndefined(width) && !_.isNull(width)) {
new_width = width;
}
if (!_.isUndefined(height) && !_.isNull(height)) {
new_height = height;
}
let x = (body.offsetWidth - new_width) / 2;
let y = (body.offsetHeight - new_height) / 4;
let new_panel = dockerObject.addPanel(
'utility_props', window.wcDocker.DOCK.FLOAT, undefined, {
w: new_width,
h: new_height,
x: (x),
y: (y),
}
);
/*set movable false to prevent dialog from docking,
by setting this we can able to move the dialog but can't dock it
in to the frame. e.g: can't dock it in to properties and other tabs. */
setTimeout(function() {
new_panel.moveable(false);
}, 0);
body.removeChild(el);
return new_panel;
},
registerDockerPanel: function(docker, name, params) {
let w = docker || pgBrowser.docker,
p = w.findPanels(name);
if (p && p.length == 1)
return;
p = new pgBrowser.Panel({
name: name,
showTitle: true,
isCloseable: true,
isPrivate: true,
isLayoutMember: false,
canMaximise: true,
content: '<div class="obj_properties container-fluid h-100"></div>',
...params,
});
p.load(w);
},
registerUtilityPanel: function(docker) {
let w = docker || pgBrowser.docker,
p = w.findPanels('utility_props');
if (p && p.length == 1)
return;
let events = {};
p = new pgBrowser.Panel({
name: 'utility_props',
showTitle: true,
isCloseable: true,
isPrivate: true,
isLayoutMember: false,
canMaximise: true,
elContainer: true,
content: '<div class="obj_properties"></div>',
onCreate: function(myPanel, container) {
container.classList.add('pg-no-overflow');
},
events: events,
});
p.load(w);
},
register_node_panel: function() {
let w = pgBrowser.docker,
p = w.findPanels('node_props');
if (p && p.length == 1)
return;
let events = {};
p = new pgBrowser.Panel({
name: 'node_props',
showTitle: true,
isCloseable: true,
isPrivate: true,
isLayoutMember: false,
canMaximise: true,
elContainer: true,
content: '<div class="obj_properties"></div>',
onCreate: function(myPanel, container) {
container.classList.add('pg-no-overflow');
},
events: events,
});
p.load(pgBrowser.docker);
},
/* /*
* Default script type menu for node. * Default script type menu for node.
* *
@ -447,194 +341,132 @@ define('pgadmin.browser.node', [
**/ **/
show_obj_properties: function(args, item) { show_obj_properties: function(args, item) {
let t = pgBrowser.tree, let t = pgBrowser.tree,
i = (args && args.item) || item || t.selected(), nodeItem = (args && args.item) || item || t.selected(),
d = i ? t.itemData(i) : undefined, nodeData = nodeItem ? t.itemData(nodeItem) : undefined,
o = this, panelTitle = this.title(nodeData, args.action),
l = o.title.apply(this, [d]), treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(nodeItem);
p;
// Make sure - the properties dialog type registered if (!nodeData)
pgBrowser.Node.register_node_panel();
// No node selected.
if (!d)
return; return;
let self = this, const isParent = (_.isArray(this.parent_type) ?
isParent = (_.isArray(this.parent_type) ? (_d)=>{
function(_d) { return (_.indexOf(this.parent_type, _d._type) != -1);
return (_.indexOf(self.parent_type, _d._type) != -1); } : (_d)=>{
} : function(_d) { return (this.parent_type == _d._type);
return (self.parent_type == _d._type); });
}),
addPanel = function() {
let body = window.document.body,
el = document.createElement('div');
body.insertBefore(el, body.firstChild);
let w, h, x, y;
if(screen.width < 800) {
w = pgAdmin.toPx(el, '95%', 'width', true);
} else {
w = pgAdmin.toPx(
el, self.width || (pgBrowser.stdW.default + 'px'),
'width', true
);
/* Fit to standard sizes */
if(w <= pgBrowser.stdW.sm) {
w = pgBrowser.stdW.sm;
} else {
if(w <= pgBrowser.stdW.md) {
w = pgBrowser.stdW.md;
} else {
w = pgBrowser.stdW.lg;
}
}
}
if(screen.height < 600) {
h = pgAdmin.toPx(el, '95%', 'height', true);
} else {
h = pgAdmin.toPx(
el, self.height || (pgBrowser.stdH.default + 'px'),
'height', true
);
/* Fit to standard sizes */
if(h <= pgBrowser.stdH.sm) {
h = pgBrowser.stdH.sm;
} else {
if(h <= pgBrowser.stdH.md) {
h = pgBrowser.stdH.md;
} else {
h = pgBrowser.stdH.lg;
}
}
}
x = (body.offsetWidth - w) / 2;
y = (body.offsetHeight - h) / 4;
// If the screen resolution is higher, but - it is zoomed, dialog
// may be go out of window, and will not be accessible through the
// keyboard.
if (w > window.innerWidth) {
x = 0;
w = window.innerWidth;
}
if (h > window.innerHeight) {
y = 0;
h = window.innerHeight;
}
let new_panel = pgBrowser.docker.addPanel(
'node_props', wcDocker.DOCK.FLOAT, undefined, {
w: w + 'px',
h: h + 'px',
x: x + 'px',
y: y + 'px',
}
);
body.removeChild(el);
return new_panel;
};
if (args.action == 'create') { if (args.action == 'create') {
// If we've parent, we will get the information of it for // If we've parent, we will get the information of it for
// proper object manipulation. // proper object manipulation.
// if (this.parent_type && !isParent(nodeData)) {
// You know - we're working with RDBMS, relation is everything // actual parent of a table is schema, not Tables.
// for us. while (nodeItem && t.hasParent(nodeItem)) {
if (self.parent_type && !isParent(d)) { nodeItem = t.parent(nodeItem);
// In browser tree, I can be under any node, But - that let pd = t.itemData(nodeItem);
// does not mean, it is my parent.
//
// We have some group nodes too.
//
// i.e.
// Tables, Views, etc. nodes under Schema node
//
// And, actual parent of a table is schema, not Tables.
while (i && t.hasParent(i)) {
i = t.parent(i);
let pd = t.itemData(i);
if (isParent(pd)) { if (isParent(pd)) {
// Assign the data, this is my actual parent. // Assign the data, this is my actual parent.
d = pd; nodeData = pd;
break; break;
} }
} }
} }
// Seriously - I really don't have parent data present? // The only node who does not have parent is the Server Group
// if (!nodeData || (this.parent_type != null && !isParent(nodeData))) {
// The only node - which I know - who does not have parent
// node, is the Server Group (and, comes directly under root
// node - which has no parent.)
if (!d || (this.parent_type != null && !isParent(d))) {
// It should never come here. // It should never come here.
// If it is here, that means - we do have some bug in code. // If it is here, that means - we do have some bug in code.
return; return;
} }
l = gettext('Create - %s', this.label); const panelId = _.uniqueId(BROWSER_PANELS.EDIT_PROPERTIES);
if (this.type == 'server') { const onClose = (force=false)=>pgBrowser.docker.close(panelId, force);
l = gettext('Register - %s', this.label); const onSave = (newNodeData)=>{
} // Clear the cache for this node now.
p = addPanel(); setTimeout(()=>{
this.clear_cache.apply(this, item);
setTimeout(function() { }, 0);
o.showProperties(i, d, p, args.action); try {
}, 10); pgBrowser.Events.trigger(
} else { 'pgadmin:browser:tree:add', _.clone(newNodeData.node),
if (pgBrowser.Node.panels && pgBrowser.Node.panels[d.id] && _.clone(treeNodeInfo)
pgBrowser.Node.panels[d.id].$container) { );
p = pgBrowser.Node.panels[d.id]; } catch (e) {
/** TODO :: console.warn(e.stack || e);
* Run in edit mode (if asked) only when it is
* not already been running edit mode
**/
let mode = p.$container.attr('action-mode');
if (mode) {
let msg = gettext('Are you sure want to stop editing the properties of %s "%s"?');
if (args.action == 'edit') {
msg = gettext('Are you sure want to reset the current changes and re-open the panel for %s "%s"?');
}
Notify.confirm(
gettext('Edit in progress?'),
commonUtils.sprintf(msg, o.label.toLowerCase(), d.label),
function() {
setTimeout(function() {
o.showProperties(i, d, p, args.action);
}, 10);
},
null).show();
} else {
setTimeout(function() {
o.showProperties(i, d, p, args.action);
}, 10);
} }
} else { onClose();
pgBrowser.Node.panels = pgBrowser.Node.panels || {}; };
p = pgBrowser.Node.panels[d.id] = addPanel(); this.showPropertiesDialog(panelId, panelTitle, {
treeNodeInfo: treeNodeInfo,
item: nodeItem,
nodeData: nodeData,
actionType: 'create',
onSave: onSave,
onClose: onClose,
});
} else {
const panelId = BROWSER_PANELS.EDIT_PROPERTIES+nodeData.id;
const onClose = (force=false)=>pgBrowser.docker.close(panelId, force);
const onSave = (newNodeData)=>{
let _old = nodeData,
_new = newNodeData.node,
info = treeNodeInfo;
setTimeout(function() { // Clear the cache for this node now.
o.showProperties(i, d, p, args.action); setTimeout(()=>{
}, 10); this.clear_cache.apply(this, item);
}, 0);
pgBrowser.Events.trigger(
'pgadmin:browser:tree:update',
_old, _new, info, {
success: function(_item, _newNodeData, _oldNodeData) {
pgBrowser.Events.trigger(
'pgadmin:browser:node:updated', _item, _newNodeData,
_oldNodeData
);
pgBrowser.Events.trigger(
'pgadmin:browser:node:' + _newNodeData._type + ':updated',
_item, _newNodeData, _oldNodeData
);
},
}
);
onClose();
};
if(pgBrowser.docker.find(panelId)) {
let msg = gettext('Are you sure want to stop editing the properties of %s "%s"?');
if (args.action == 'edit') {
msg = gettext('Are you sure want to reset the current changes and re-open the panel for %s "%s"?');
}
pgAdmin.Browser.notifier.confirm(
gettext('Edit in progress?'),
commonUtils.sprintf(msg, this.label.toLowerCase(), nodeData.label),
()=>{
this.showPropertiesDialog(panelId, panelTitle, {
treeNodeInfo: treeNodeInfo,
item: nodeItem,
nodeData: nodeData,
actionType: 'edit',
onSave: onSave,
onClose: onClose,
}, true);
},
null
);
} else {
this.showPropertiesDialog(panelId, panelTitle, {
treeNodeInfo: treeNodeInfo,
item: nodeItem,
nodeData: nodeData,
actionType: 'edit',
onSave: onSave,
onClose: onClose,
});
} }
} }
p.title(l);
p.icon('icon-' + this.type);
// Make sure the properties dialog is visible
p.focus();
}, },
// Delete the selected object // Delete the selected object
delete_obj: function(args, item) { delete_obj: function(args, item) {
@ -668,7 +500,7 @@ define('pgadmin.browser.node', [
if (!(_.isFunction(obj.canDropCascade) ? if (!(_.isFunction(obj.canDropCascade) ?
obj.canDropCascade.apply(obj, [d, i]) : obj.canDropCascade)) { obj.canDropCascade.apply(obj, [d, i]) : obj.canDropCascade)) {
Notify.error( pgAdmin.Browser.notifier.error(
gettext('The %s "%s" cannot be dropped.', obj.label, d.label), gettext('The %s "%s" cannot be dropped.', obj.label, d.label),
10000 10000
); );
@ -685,24 +517,24 @@ define('pgadmin.browser.node', [
if (!(_.isFunction(obj.canDrop) ? if (!(_.isFunction(obj.canDrop) ?
obj.canDrop.apply(obj, [d, i]) : obj.canDrop)) { obj.canDrop.apply(obj, [d, i]) : obj.canDrop)) {
Notify.error( pgAdmin.Browser.notifier.error(
gettext('The %s "%s" cannot be dropped/removed.', obj.label, d.label), gettext('The %s "%s" cannot be dropped/removed.', obj.label, d.label),
10000 10000
); );
return; return;
} }
} }
Notify.confirm(title, msg, pgAdmin.Browser.notifier.confirm(title, msg,
function() { function() {
getApiInstance().delete( getApiInstance().delete(
obj.generate_url(i, input.url, d, true), obj.generate_url(i, input.url, d, true),
).then(({data: res})=> { ).then(({data: res})=> {
if(res.success == 2){ if(res.success == 2){
Notify.error(res.info, null); pgAdmin.Browser.notifier.error(res.info, null);
return; return;
} }
if (res.success == 0) { if (res.success == 0) {
Notify.alert(res.errormsg, res.info); pgAdmin.Browser.notifier.alert(res.errormsg, res.info);
} else { } else {
// Remove the node from tree and set collection node as selected. // Remove the node from tree and set collection node as selected.
let selectNextNode = true; let selectNextNode = true;
@ -727,7 +559,7 @@ define('pgadmin.browser.node', [
console.warn(e.stack || e); console.warn(e.stack || e);
} }
} }
Notify.alert(gettext('Error dropping/removing %s: "%s"', obj.label, objName), errmsg); pgAdmin.Browser.notifier.alert(gettext('Error dropping/removing %s: "%s"', obj.label, objName), errmsg);
}); });
} }
); );
@ -773,7 +605,7 @@ define('pgadmin.browser.node', [
// Callback to render query editor // Callback to render query editor
show_query_tool: function(args, item) { show_query_tool: function(args, item) {
let preference = pgBrowser.get_preference('sqleditor', 'copy_sql_to_query_tool'); let preference = usePreferences.getState().getPreferences('sqleditor', 'copy_sql_to_query_tool');
let t = pgBrowser.tree, let t = pgBrowser.tree,
i = item || t.selected(), i = item || t.selected(),
d = i ? t.itemData(i) : undefined; d = i ? t.itemData(i) : undefined;
@ -862,7 +694,7 @@ define('pgadmin.browser.node', [
pgBrowser.Node.callbacks.change_server_background(item, data); pgBrowser.Node.callbacks.change_server_background(item, data);
}, },
// Callback called - when a node is selected in browser tree. // Callback called - when a node is selected in browser tree.
selected: function(item, data, browser, _argsList, _event, actionSource) { selected: function(item, data, browser) {
// Show the information about the selected node in the below panels, // Show the information about the selected node in the below panels,
// which are visible at this time: // which are visible at this time:
// + Properties // + Properties
@ -870,34 +702,11 @@ define('pgadmin.browser.node', [
// + Dependents // + Dependents
// + Dependencies // + Dependencies
// + Statistics // + Statistics
let b = browser || pgBrowser, let b = browser || pgBrowser;
t = b.tree,
d = data || t.itemData(item);
// Update the menu items // Update the menu items
pgAdmin.Browser.enable_disable_menus.apply(b, [item]); pgAdmin.Browser.enable_disable_menus.apply(b, [item]);
if (d && b) {
if ('properties' in b.panels &&
b.panels['properties'] &&
b.panels['properties'].panel) {
if (actionSource != TAB_CHANGE) {
const propertiesPanel = b.panels['properties'].panel.$container.find('.obj_properties').first();
if (propertiesPanel) {
removePanelView(propertiesPanel[0]);
}
}
if (b.panels['properties'].panel.isVisible()) {
this.showProperties(item, d, b.panels['properties'].panel);
}
}
}
pgBrowser.Events.trigger('pgadmin:browser:tree:update-tree-state', pgBrowser.Events.trigger('pgadmin:browser:tree:update-tree-state',
item); item);
return true; return true;
@ -922,7 +731,7 @@ define('pgadmin.browser.node', [
}, },
opened: function(item) { opened: function(item) {
let tree = pgBrowser.tree, let tree = pgBrowser.tree,
auto_expand = pgBrowser.get_preference('browser', 'auto_expand_sole_children'); auto_expand = usePreferences.getState().getPreferences('browser', 'auto_expand_sole_children');
if (auto_expand && auto_expand.value && tree.children(item).length == 1) { if (auto_expand && auto_expand.value && tree.children(item).length == 1) {
// Automatically expand the child node, if a treeview node has only a single child. // Automatically expand the child node, if a treeview node has only a single child.
@ -957,127 +766,55 @@ define('pgadmin.browser.node', [
item); item);
}, },
}, },
/********************************************************************** showPropertiesDialog: function(panelId, panelTitle, dialogProps, update=false) {
* A hook (not a callback) to show object properties in given HTML const panelData = {
* element. id: panelId,
* title: panelTitle,
* This has been used for the showing, editing properties of the node. manualClose: true,
* This has also been used for creating a node. icon: `dialog-node-icon ${this.node_image?.(dialogProps.itemNodeData) ?? ('icon-' + this.type)}`,
**/ content: (
showProperties: function(item, data, panel, action) { <ErrorBoundary>
let that = this, <ObjectNodeProperties
j = panel.$container.find('.obj_properties').first(); panelId={panelId}
node={this}
formType="dialog"
{...dialogProps}
/>
</ErrorBoundary>
)
};
// Callback to show object properties let w = toPx(this.width || (pgBrowser.stdW.default + 'px'), 'width', true);
let properties = function() { let h = toPx(this.height || (pgBrowser.stdH.default + 'px'), 'height', true);
let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
getNodeView(
that.type, treeNodeInfo, 'properties', data, 'tab', j[0], this, onEdit
);
return;
}.bind(panel),
editFunc = function() { /* Fit to standard sizes */
let self = this; if(w <= pgBrowser.stdW.sm) {
if (action && action == 'properties') { w = pgBrowser.stdW.sm;
action = 'edit';
}
self.$container.attr('action-mode', action);
self.icon(
_.isFunction(that['node_image']) ?
(that['node_image']).apply(that, [data]) :
(that['node_image'] || ('icon-' + that.type))
);
/* Remove any dom rendered by getNodeView */
removeNodeView(j[0]);
/* getSchema is a schema for React. Get the react node view */
let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
getNodeView(
that.type, treeNodeInfo, action, data, 'dialog', j[0], this, onEdit,
(nodeData)=>{
if(nodeData.node) {
onSaveFunc(nodeData.node, treeNodeInfo);
if(nodeData.success === 0) {
Notify.alert(gettext('Error'),
gettext(nodeData.errormsg)
);
}
}
}
);
return;
}.bind(panel),
updateTreeItem = function(obj, tnode, node_info) {
let _old = data,
_new = tnode,
info = node_info;
// Clear the cache for this node now.
setTimeout(function() {
obj.clear_cache.apply(obj, item);
}, 0);
pgBrowser.Events.trigger(
'pgadmin:browser:tree:update',
_old, _new, info, {
success: function(_item, _newNodeData, _oldNodeData) {
pgBrowser.Events.trigger(
'pgadmin:browser:node:updated', _item, _newNodeData,
_oldNodeData
);
pgBrowser.Events.trigger(
'pgadmin:browser:node:' + _newNodeData._type + ':updated',
_item, _newNodeData, _oldNodeData
);
},
}
);
this.close();
},
saveNewNode = function(obj, tnode, node_info) {
// Clear the cache for this node now.
setTimeout(function() {
obj.clear_cache.apply(obj, item);
}, 0);
try {
pgBrowser.Events.trigger(
'pgadmin:browser:tree:add', _.clone(tnode),
_.clone(node_info)
);
} catch (e) {
console.warn(e.stack || e);
}
this.close();
}.bind(panel, that),
editInNewPanel = function() {
// Open edit in separate panel
setTimeout(function() {
that.callbacks.show_obj_properties.apply(that, [{
'action': 'edit',
'item': item,
}]);
}, 0);
},
onSaveFunc = updateTreeItem.bind(panel, that),
onEdit = editFunc.bind(panel);
if (action) {
if (action == 'create') {
onSaveFunc = saveNewNode;
}
if (action != 'properties') {
// We need to keep track edit/create mode for this panel.
editFunc();
} else {
properties();
}
} else { } else {
/* Show properties */ if(w <= pgBrowser.stdW.md) {
onEdit = editInNewPanel.bind(panel); w = pgBrowser.stdW.md;
properties(); } else {
w = pgBrowser.stdW.lg;
}
}
if(h <= pgBrowser.stdH.sm) {
h = pgBrowser.stdH.sm;
} else {
if(h <= pgBrowser.stdH.md) {
h = pgBrowser.stdH.md;
} else {
h = pgBrowser.stdH.lg;
}
}
if(update) {
dialogProps.onClose(true);
setTimeout(()=>{
pgBrowser.docker.openDialog(panelData, w, h);
}, 10);
} else {
pgBrowser.docker.openDialog(panelData, w, h);
} }
}, },
_find_parent_node: function(t, i, d) { _find_parent_node: function(t, i, d) {

View File

@ -1,225 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React from 'react';
import ReactDOM from 'react-dom';
import pgAdmin from 'sources/pgadmin';
import getApiInstance from 'sources/api_instance';
import {getHelpUrl, getEPASHelpUrl} from 'pgadmin.help';
import SchemaView from 'sources/SchemaView';
import { generateNodeUrl } from './node_ajax';
import Notify from '../../../static/js/helpers/Notifier';
import gettext from 'sources/gettext';
import 'wcdocker';
import Theme from '../../../static/js/Theme';
/* The entry point for rendering React based view in properties, called in node.js */
export function getNodeView(nodeType, treeNodeInfo, actionType, itemNodeData, formType, container, containerPanel, onEdit, onSave) {
let nodeObj = pgAdmin.Browser.Nodes[nodeType];
let serverInfo = treeNodeInfo && ('server' in treeNodeInfo) &&
pgAdmin.Browser.serverInfo && pgAdmin.Browser.serverInfo[treeNodeInfo.server._id];
let inCatalog = treeNodeInfo && ('catalog' in treeNodeInfo);
let urlBase = generateNodeUrl.call(nodeObj, treeNodeInfo, actionType, itemNodeData, false, nodeObj.url_jump_after_node);
const api = getApiInstance();
const url = (isNew)=>{
return urlBase + (isNew ? '' : itemNodeData._id);
};
let isDirty = false; // usefull for warnings
let warnOnCloseFlag = true;
const confirmOnCloseReset = pgAdmin.Browser.get_preferences_for_module('browser').confirm_on_properties_close;
let updatedData = ['table', 'partition'].includes(nodeType) && !_.isEmpty(itemNodeData.rows_cnt) ? {rows_cnt: itemNodeData.rows_cnt} : undefined;
let onError = (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);
}
};
/* Called when dialog is opened in edit mode, promise required */
let initData = ()=>new Promise((resolve, reject)=>{
if(actionType === 'create') {
resolve({});
} else {
api.get(url(false))
.then((res)=>{
resolve(res.data);
})
.catch((err)=>{
Notify.pgNotifier('error', err, '', function(msg) {
if (msg == 'CRYPTKEY_SET') {
return Promise.resolve(initData());
} else if (msg == 'CRYPTKEY_NOT_SET') {
reject(gettext('The master password is not set.'));
}
reject(err);
});
});
}
});
/* on save button callback, promise required */
const onSaveClick = (isNew, data)=>new Promise((resolve, reject)=>{
return api({
url: url(isNew),
method: isNew ? 'POST' : 'PUT',
data: data,
}).then((res)=>{
/* Don't warn the user before closing dialog */
warnOnCloseFlag = false;
resolve(res.data);
onSave && onSave(res.data);
}).catch((err)=>{
Notify.pgNotifier('error-noalert', err, '', function(msg) {
if (msg == 'CRYPTKEY_SET') {
return Promise.resolve(onSaveClick(isNew, data));
} else if (msg == 'CRYPTKEY_NOT_SET') {
reject(gettext('The master password is not set.'));
}
reject(err);
});
});
});
/* Called when switched to SQL tab, promise required */
const getSQLValue = (isNew, changedData)=>{
const msqlUrl = generateNodeUrl.call(nodeObj, treeNodeInfo, 'msql', itemNodeData, !isNew, nodeObj.url_jump_after_node);
return new Promise((resolve, reject)=>{
api({
url: msqlUrl,
method: 'GET',
params: changedData,
}).then((res)=>{
resolve(res.data.data);
}).catch((err)=>{
onError(err);
reject(err);
});
});
};
/* Callback for help button */
const onHelp = (isSqlHelp=false, isNew=false)=>{
if(isSqlHelp) {
let server = treeNodeInfo.server;
let helpUrl = pgAdmin.Browser.utils.pg_help_path;
let fullUrl = '';
if (server.server_type == 'ppas' && nodeObj.epasHelp) {
fullUrl = getEPASHelpUrl(server.version);
} else {
if (nodeObj.sqlCreateHelp == '' && nodeObj.sqlAlterHelp != '') {
fullUrl = getHelpUrl(helpUrl, nodeObj.sqlAlterHelp, server.version);
} else if (nodeObj.sqlCreateHelp != '' && nodeObj.sqlAlterHelp == '') {
fullUrl = getHelpUrl(helpUrl, nodeObj.sqlCreateHelp, server.version);
} else {
if (isNew) {
fullUrl = getHelpUrl(helpUrl, nodeObj.sqlCreateHelp, server.version);
} else {
fullUrl = getHelpUrl(helpUrl, nodeObj.sqlAlterHelp, server.version);
}
}
}
window.open(fullUrl, 'postgres_help');
} else {
window.open(nodeObj.dialogHelp, 'pgadmin_help');
}
};
/* A warning before closing the dialog with unsaved changes, based on preference */
let warnBeforeChangesLost = (yesCallback)=>{
let confirmOnClose = pgAdmin.Browser.get_preferences_for_module('browser').confirm_on_properties_close;
if (warnOnCloseFlag && confirmOnClose) {
if(isDirty){
Notify.confirm(
gettext('Warning'),
gettext('Changes will be lost. Are you sure you want to close the dialog?'),
function() {
yesCallback();
return true;
},
function() {
return true;
}
);
} else {
return true;
}
return false;
} else {
yesCallback();
return true;
}
};
/* Bind the wcDocker dialog close event and check if user should be warned */
if (containerPanel.closeable()) {
containerPanel.on(window.wcDocker.EVENT.CLOSING, warnBeforeChangesLost.bind(
containerPanel,
function() {
containerPanel.off(window.wcDocker.EVENT.CLOSING);
/* Always clean up the react mounted dom before closing */
removeNodeView(container);
containerPanel.close();
}
));
}
/* All other useful details can go with this object */
const viewHelperProps = {
mode: actionType,
serverInfo: serverInfo ? {
type: serverInfo.server_type,
version: serverInfo.version,
}: undefined,
inCatalog: inCatalog,
};
let schema = nodeObj.getSchema.call(nodeObj, treeNodeInfo, itemNodeData);
// Show/Hide security group for nodes under the catalog
if('catalog' in treeNodeInfo
&& formType !== 'tab') {
schema.filterGroups = [gettext('Security')];
}
/* Fire at will, mount the DOM */
ReactDOM.render(
<Theme>
<SchemaView
key={itemNodeData?._id}
formType={formType}
getInitData={initData}
updatedData={updatedData}
schema={schema}
viewHelperProps={viewHelperProps}
onSave={onSaveClick}
onClose={()=>containerPanel.close()}
onHelp={onHelp}
onEdit={onEdit}
onDataChange={(dataChanged)=>{
isDirty = dataChanged;
}}
confirmOnCloseReset={confirmOnCloseReset}
hasSQL={nodeObj.hasSQL && (actionType === 'create' || actionType === 'edit')}
getSQLValue={getSQLValue}
disableSqlHelp={nodeObj.sqlAlterHelp == '' && nodeObj.sqlCreateHelp == '' && !nodeObj.epasHelp}
disableDialogHelp={nodeObj.dialogHelp == undefined || nodeObj.dialogHelp == ''}
/>
</Theme>, container);
}
/* When switching from normal node to collection node, clean up the React mounted DOM */
export function removeNodeView(container) {
ReactDOM.unmountComponentAtNode(container);
}

View File

@ -1,290 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import { getPanelView } from './panel_view';
import _ from 'lodash';
define(
['sources/pgadmin', 'wcdocker'],
function(pgAdmin) {
let pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {},
wcDocker = window.wcDocker;
pgAdmin.Browser.Panel = function(options) {
let defaults = [
'name', 'title', 'width', 'height', 'showTitle', 'isCloseable',
'isPrivate', 'isLayoutMember', 'content', 'icon', 'events', 'onCreate', 'elContainer',
'canHide', 'limit', 'extraClasses', 'canMaximise',
];
_.extend(this, _.pick(options, defaults));
};
_.extend(pgAdmin.Browser.Panel.prototype, {
name: '',
title: '',
width: 300,
height: 600,
showTitle: true,
isCloseable: true,
isPrivate: false,
isLayoutMember: true,
content: '',
icon: '',
panel: null,
onCreate: null,
elContainer: false,
canMaximise: false,
limit: null,
extraClasses: null,
load: function(docker, title) {
let that = this;
if (!that.panel) {
docker.registerPanelType(that.name, {
title: that.title,
isPrivate: that.isPrivate,
limit: that.limit,
isLayoutMember: that.isLayoutMember,
onCreate: function(myPanel) {
myPanel.panelData = {
pgAdminName: that.name,
};
myPanel.initSize(that.width, that.height);
if (!that.showTitle)
myPanel.title(false);
else {
let title_elem = '<a href="#" tabindex="-1" class="panel-link-heading">' + (title || that.title) + '</a>';
myPanel.title(title_elem);
if (that.icon != '')
myPanel.icon(that.icon);
}
let container = document.createElement('div');
container.setAttribute('class', 'pg-panel-content');
container.innerHTML = that.content;
// Add extra classes
if (!_.isNull('extraClasses')) {
container.classList.add(that.extraClasses);
}
myPanel.maximisable(!!that.canMaximise);
myPanel.closeable(!!that.isCloseable);
myPanel.layout().addItem(container);
that.panel = myPanel;
if (that.events && _.isObject(that.events)) {
_.each(that.events, function(v, k) {
if (v && _.isFunction(v)) {
myPanel.on(k, v);
}
});
}
_.each([
wcDocker.EVENT.UPDATED, wcDocker.EVENT.VISIBILITY_CHANGED,
wcDocker.EVENT.BEGIN_DOCK, wcDocker.EVENT.END_DOCK,
wcDocker.EVENT.GAIN_FOCUS, wcDocker.EVENT.LOST_FOCUS,
wcDocker.EVENT.CLOSED, wcDocker.EVENT.BUTTON,
wcDocker.EVENT.ATTACHED, wcDocker.EVENT.DETACHED,
wcDocker.EVENT.MOVE_STARTED, wcDocker.EVENT.MOVE_ENDED,
wcDocker.EVENT.MOVED, wcDocker.EVENT.RESIZE_STARTED,
wcDocker.EVENT.RESIZE_ENDED, wcDocker.EVENT.RESIZED,
wcDocker.EVENT.SCROLLED,
], function(ev) {
myPanel.on(ev, that.eventFunc.bind(myPanel, ev));
});
if (that.onCreate && _.isFunction(that.onCreate)) {
that.onCreate.apply(that, [myPanel, container]);
}
// Prevent browser from opening the drag file.
// Using addEventListener to avoid conflict with jquery.drag
['dragover', 'drop'].forEach((eventName)=>{
container.addEventListener(eventName, function(event) {
event.stopPropagation();
event.preventDefault();
});
});
if (that.elContainer) {
myPanel.pgElContainer = container;
_.each([
wcDocker.EVENT.RESIZED, wcDocker.EVENT.ATTACHED,
wcDocker.EVENT.DETACHED, wcDocker.EVENT.VISIBILITY_CHANGED,
], function(ev) {
myPanel.on(ev, that.resizedContainer.bind(myPanel));
});
that.resizedContainer.apply(myPanel);
}
if (myPanel._type == 'dashboard' || myPanel._type == 'processes') {
getPanelView(
pgBrowser.tree,
container,
pgBrowser,
myPanel._type
);
}
// Re-render the dashboard panel when preference value 'show graph' gets changed.
pgBrowser.onPreferencesChange('dashboards', function() {
getPanelView(
pgBrowser.tree,
container,
pgBrowser,
myPanel._type
);
});
// Re-render the dashboard panel when preference value gets changed.
pgBrowser.onPreferencesChange('graphs', function() {
getPanelView(
pgBrowser.tree,
container,
pgBrowser,
myPanel._type
);
});
_.each([wcDocker.EVENT.CLOSED, wcDocker.EVENT.VISIBILITY_CHANGED],
function(ev) {
myPanel.on(ev, that.handleVisibility.bind(myPanel, ev));
});
pgBrowser.Events.on('pgadmin-browser:tree:selected', () => {
if(myPanel.isVisible() && myPanel._type !== 'properties') {
getPanelView(
pgBrowser.tree,
container,
pgBrowser,
myPanel._type
);
}
});
pgBrowser.Events.on('pgadmin:database:connected', () => {
if(myPanel.isVisible() && myPanel._type !== 'properties') {
getPanelView(
pgBrowser.tree,
container,
pgBrowser,
myPanel._type
);
}
});
pgBrowser.Events.on('pgadmin-browser:tree:refreshing', () => {
if(myPanel.isVisible() && myPanel._type !== 'properties') {
getPanelView(
pgBrowser.tree,
container,
pgBrowser,
myPanel._type
);
}
});
},
});
}
},
eventFunc: function(eventName) {
let name = this.panelData.pgAdminName;
try {
pgBrowser.Events.trigger(
'pgadmin-browser:panel', eventName, this, arguments
);
pgBrowser.Events.trigger(
'pgadmin-browser:panel:' + eventName, this, arguments
);
if (name) {
pgBrowser.Events.trigger(
'pgadmin-browser:panel-' + name, eventName, this, arguments
);
pgBrowser.Events.trigger(
'pgadmin-browser:panel-' + name + ':' + eventName, this, arguments
);
}
} catch (e) {
console.warn(e.stack || e);
}
},
resizedContainer: function() {
let p = this;
if (p.pgElContainer && !p.pgResizeTimeout) {
if (!p.isVisible()) {
clearTimeout(p.pgResizeTimeout);
p.pgResizeTimeout = null;
return;
}
p.pgResizeTimeout = setTimeout(
function() {
let w = p.width(),
elAttr = 'xs';
p.pgResizeTimeout = null;
/** Calculations based on https://getbootstrap.com/docs/4.1/layout/grid/#grid-options **/
if (w >= 480) {
elAttr = 'sm';
}
if (w >= 768) {
elAttr = 'md';
}
if (w >= 992) {
elAttr = 'lg';
}
if (w >=1200) {
elAttr = 'xl';
}
p.pgElContainer.setAttribute('el', elAttr);
},
100
);
}
},
handleVisibility: function(eventName) {
let selectedPanel = pgBrowser.docker.findPanels(this._type)[0];
let isPanelVisible = selectedPanel.isVisible();
let container = selectedPanel
.layout()
.scene()
.find('.pg-panel-content');
if (isPanelVisible && ['dashboard', 'statistics', 'dependencies', 'dependents', 'sql', 'processes'].includes(selectedPanel._type) ) {
if (eventName == 'panelVisibilityChanged') {
getPanelView(
pgBrowser.tree,
container[0],
pgBrowser,
this._type
);
}
}
if (eventName == 'panelClosed' && selectedPanel._type == 'dashboard') {
getPanelView(
pgBrowser.tree,
container[0],
pgBrowser,
this._type,
false
);
}
}
});
return pgAdmin.Browser.Panel;
});

View File

@ -1,148 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, 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 Dependencies from '../../../misc/dependencies/static/js/Dependencies';
import Dependents from '../../../misc/dependents/static/js/Dependents';
import Statistics from '../../../misc/statistics/static/js/Statistics';
import SQL from '../../../misc/sql/static/js/SQL';
import Dashboard from '../../../dashboard/static/js/Dashboard';
import _ from 'lodash';
import { CollectionNodeView } from '../../../misc/properties/CollectionNodeProperties';
import Processes from '../../../misc/bgprocess/static/js/Processes';
/* The entry point for rendering React based view in properties, called in node.js */
export function getPanelView(
tree,
container,
pgBrowser,
panelType,
panelVisible = true
) {
let item = !_.isNull(tree)? tree.selected(): null,
nodeData, node, treeNodeInfo, preferences, graphPref, dashPref;
if (item){
nodeData = tree.itemData(item);
node = nodeData && pgBrowser.Nodes[nodeData._type];
treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
dashPref = pgBrowser.get_preferences_for_module('dashboards');
graphPref = pgBrowser.get_preferences_for_module('graphs');
preferences = _.merge(dashPref, graphPref);
}
if (panelType == 'dashboard') {
ReactDOM.render(
<Theme>
<Dashboard
treeNodeInfo={treeNodeInfo}
pgBrowser={pgBrowser}
nodeData={nodeData}
node={node}
item={item}
preferences={preferences}
did={((!_.isUndefined(treeNodeInfo)) && (!_.isUndefined(treeNodeInfo['database']))) ? treeNodeInfo['database']._id: 0}
sid={!_.isUndefined(treeNodeInfo) && !_.isUndefined(treeNodeInfo['server']) ? treeNodeInfo['server']._id : ''}
serverConnected={!_.isUndefined(treeNodeInfo) && !_.isUndefined(treeNodeInfo['server']) ? treeNodeInfo.server.connected: false}
dbConnected={!_.isUndefined(treeNodeInfo) && !_.isUndefined(treeNodeInfo['database']) ? treeNodeInfo.database.connected: false}
panelVisible={panelVisible}
/>
</Theme>,
container
);
}
if (panelType == 'statistics') {
ReactDOM.render(
<Theme>
<Statistics
treeNodeInfo={treeNodeInfo}
pgBrowser={pgBrowser}
nodeData={nodeData}
node={node}
item={item}
/>
</Theme>,
container
);
}
if (panelType == 'properties' && nodeData?.is_collection) {
ReactDOM.render(
<Theme>
<CollectionNodeView
treeNodeInfo={treeNodeInfo}
item={item}
itemNodeData={nodeData}
node={node}
pgBrowser={pgBrowser}
/>
</Theme>,
container
);
}
if (panelType == 'dependencies') {
ReactDOM.render(
<Theme>
<Dependencies
treeNodeInfo={treeNodeInfo}
pgBrowser={pgBrowser}
nodeData={nodeData}
node={node}
item={item}
/>
</Theme>,
container
);
}
if (panelType == 'dependents') {
ReactDOM.render(
<Theme>
<Dependents
treeNodeInfo={treeNodeInfo}
pgBrowser={pgBrowser}
nodeData={nodeData}
node={node}
item={item}
/>
</Theme>,
container
);
}
if (panelType == 'sql') {
ReactDOM.render(
<Theme>
<SQL
treeNodeInfo={treeNodeInfo}
pgBrowser={pgBrowser}
nodeData={nodeData}
node={node}
item={item}
did={((!_.isUndefined(treeNodeInfo)) && (!_.isUndefined(treeNodeInfo['database']))) ? treeNodeInfo['database']._id: 0}
dbConnected={!_.isUndefined(treeNodeInfo) && !_.isUndefined(treeNodeInfo['database']) ? treeNodeInfo.database.connected: false}
/>
</Theme>,
container
);
}
if (panelType == 'processes') {
ReactDOM.render(
<Theme>
<Processes />
</Theme>,
container
);
}
}
/* When switching from normal node to collection node, clean up the React mounted DOM */
export function removePanelView(container) {
ReactDOM.unmountComponentAtNode(container);
}

View File

@ -1,148 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import pgAdmin from 'sources/pgadmin';
import url_for from 'sources/url_for';
import Notify from '../../../static/js/helpers/Notifier';
import { shortcutToString } from '../../../static/js/components/ShortcutTitle';
import gettext from 'sources/gettext';
import getApiInstance from '../../../static/js/api_instance';
const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
/* Add cache related methods and properties */
_.extend(pgBrowser, {
/* This will hold preference data (Works as a cache object)
* Here node will be a key and it's preference data will be value
*/
preferences_cache: [],
/* This will be used by poller of new tabs/windows to check
* if preference cache is updated in parent/window.opener.
*/
prefcache_version: 0,
/* Generate a unique version number */
generate_preference_version: function() {
return (new Date()).getTime();
},
preference_version: function(version) {
if(version) {
this.prefcache_version = version;
}
else {
return this.prefcache_version;
}
},
/* Get cached preference */
get_preference: function(module, preference){
const self = this;
return _.find(
self.preferences_cache, {'module': module, 'name': preference}
);
},
/* Get all the preferences of a module */
get_preferences_for_module: function(module) {
let self = this;
let preferences = {};
_.forEach(
_.filter(self.preferences_cache, {'module': module}),
(preference) => {
preferences[preference.name] = preference.value;
}
);
if(Object.keys(preferences).length > 0) {
return preferences;
}
},
/* Get preference of an id, id is numeric */
get_preference_for_id : function(id) {
let self = this;
/* find returns undefined if not found */
return _.find(self.preferences_cache, {'id': id});
},
// Get and cache the preferences
cache_preferences: function (modulesChanged) {
let self = this;
setTimeout(function() {
getApiInstance().get(url_for('preferences.get_all'))
.then(({data: res})=>{
self.preferences_cache = res;
self.preference_version(self.generate_preference_version());
pgBrowser.keyboardNavigation.init();
// Initialize Tree saving/reloading
pgBrowser.browserTreeState.init();
/* Once the cache is loaded after changing the preferences,
* notify the modules of the change
*/
if(modulesChanged) {
if(typeof modulesChanged === 'string'){
pgBrowser.Events.trigger('prefchange:'+modulesChanged);
} else {
_.each(modulesChanged, (val, key)=> {
pgBrowser.Events.trigger('prefchange:'+key);
});
}
}
})
.catch(function(error) {
Notify.pgRespErrorNotify(error);
});
}, 500);
},
triggerPreferencesChange: function(moduleChanged) {
pgBrowser.Events.trigger('prefchange:'+moduleChanged);
},
reflectPreferences: function(module) {
let obj = this;
//browser preference
if(module === 'browser') {
let browserPreferences = obj.get_preferences_for_module('browser');
let buttonList = obj?.panels?.browser?.panel?._buttonList;
buttonList.forEach(btn => {
let key = null;
switch(btn.name) {
case gettext('Query Tool'):
key = shortcutToString(browserPreferences.sub_menu_query_tool,null,true);
obj?.panels?.browser?.panel?.updateButton(gettext('Query Tool'), {key});
break;
case gettext('View Data'):
key = shortcutToString(browserPreferences.sub_menu_view_data,null,true);
obj?.panels?.browser?.panel?.updateButton(gettext('View Data'), {key});
break;
case gettext('Search objects'):
key = shortcutToString(browserPreferences.sub_menu_search_objects,null,true);
obj?.panels?.browser?.panel?.updateButton(gettext('Search objects'), {key});
}
});
}
},
onPreferencesChange: function(module, eventHandler) {
pgBrowser.Events?.on('prefchange:'+module, function(event) {
eventHandler(event);
});
},
});
export {pgBrowser};

View File

@ -1,44 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React, {Component} from 'react';
import PropTypes from 'prop-types';
// Allow us to render IFrame using React
// Here we will add the event listener on Iframe load event
export class Iframe extends Component {
static get propTypes() {
return {
id: PropTypes.string.isRequired,
srcURL: PropTypes.string.isRequired,
onLoad: PropTypes.func.isRequired,
};
}
render () {
const iframeStyle = {
border: '0',
display: 'block',
position:'absolute',
opacity:'0',
};
const {id, srcURL, onLoad} = this.props;
return (
<iframe
id={id}
src={srcURL}
onLoad={onLoad}
width={'20'}
height={'20'}
style={iframeStyle}
/>
);
}
}

View File

@ -1,140 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import gettext from 'sources/gettext';
import _ from 'lodash';
import pgAdmin from 'sources/pgadmin';
let _toolbarButtons = {};
let _browserPanel = null;
// Default Tool Bar Buttons.
let _defaultToolBarButtons = [
{
label: gettext('Search objects'),
ariaLabel: gettext('Search objects'),
btnClass: 'fa fa-search',
text: '',
toggled: false,
toggleClass: '',
parentClass: 'pg-toolbar-btn btn-primary-icon',
enabled: false,
},
{
label: gettext('Filtered Rows'),
ariaLabel: gettext('Filtered Rows'),
btnClass: 'pg-font-icon icon-row_filter',
text: '',
toggled: false,
toggleClass: '',
parentClass: 'pg-toolbar-btn btn-primary-icon',
enabled: false,
},
{
label: gettext('View Data'),
ariaLabel: gettext('View Data'),
btnClass: 'pg-font-icon sql-icon-lg icon-view_data',
text: '',
toggled: false,
toggleClass: '',
parentClass: 'pg-toolbar-btn btn-primary-icon',
enabled: false,
},
{
label: gettext('Query Tool'),
ariaLabel: gettext('Query Tool'),
btnClass: 'pg-font-icon icon-query_tool',
text: '',
toggled: false,
toggleClass: '',
parentClass: 'pg-toolbar-btn btn-primary-icon',
enabled: false,
}
];
if(pgAdmin['enable_psql']) {
_defaultToolBarButtons.unshift({
label: gettext('PSQL Tool'),
ariaLabel: gettext('PSQL Tool'),
btnClass: 'fas fa-terminal',
text: '',
toggled: false,
toggleClass: '',
parentClass: 'pg-toolbar-btn btn-primary-icon pg-toolbar-psql',
enabled: false,
});
}
// Place holder for non default tool bar buttons.
let _otherToolbarButtons = [];
// This function is used to add button into the browser panel.
function registerToolBarButton(btn) {
/* Sometimes the panel onCreate is called two times.
* Add buttons if not present in the panel also.
*/
if (!(btn.label in _toolbarButtons)
|| (_.findIndex(_browserPanel._buttonList,{name:btn.label}) < 0)) {
_browserPanel.addButton(
btn.label, btn.btnClass, btn.text, btn.label, btn.toggled,
btn.toggleClass, btn.parentClass, btn.enabled, btn.ariaLabel
);
_toolbarButtons[btn.label] = btn;
}
}
// This function is used to add tool bar button and
// listen on the button event.
export function initializeToolbar(panel, wcDocker) {
_browserPanel = panel;
// Iterate through default tool bar buttons and add them into the
// browser panel.
_.each(_defaultToolBarButtons, (btn) => {
registerToolBarButton(btn);
});
// Iterate through other tool bar buttons and add them into the
// browser panel.
_.each(_otherToolbarButtons, (btn) => {
registerToolBarButton(btn);
});
// Listen on button click event.
panel.on(wcDocker.EVENT.BUTTON, function(data) {
if ('name' in data && data.name === gettext('Query Tool'))
pgAdmin.Tools.SQLEditor.showQueryTool('', pgAdmin.Browser.tree.selected());
else if ('name' in data && data.name === gettext('View Data'))
pgAdmin.Tools.SQLEditor.showViewData({mnuid: 3}, pgAdmin.Browser.tree.selected());
else if ('name' in data && data.name === gettext('Filtered Rows'))
pgAdmin.Tools.SQLEditor.showFilteredRow({mnuid: 4}, pgAdmin.Browser.tree.selected());
else if ('name' in data && data.name === gettext('Search objects'))
pgAdmin.Tools.SearchObjects.show_search_objects('', pgAdmin.Browser.tree.selected());
else if ('name' in data && data.name === gettext('PSQL Tool')){
let input = {},
t = pgAdmin.Browser.tree,
i = input.item || t.selected(),
d = i ? t.itemData(i) : undefined;
pgAdmin.Browser.psql.psql_tool(d, i, true);
}
});
}
// This function is used to enable/disable the specific button
// based on their label.
export function enable(label, enable_btn) {
if (label in _toolbarButtons) {
_browserPanel.buttonEnable(label, enable_btn);
} else {
console.warn('Developer warning: No tool button found with label: ' + label);
}
}

View File

@ -1,150 +0,0 @@
#myInput {
box-sizing: border-box;
background-position: 14px 12px;
background-repeat: no-repeat;
font-size: 16px;
padding: 14px 20px 12px 45px;
border: none;
border-bottom: 1px solid #ddd;
}
#myInput:focus {outline: 3px solid #ddd;}
.custom-dropdown {
position: relative;
display: inline-block;
}
.custom-dropdown-content {
background-color: $color-bg;
min-width: 376px;
overflow: auto;
z-index: 1;
}
.custom-dropdown-content a {
color: $dropdown-link-color;
padding: 6px 12px 6px 16px;
text-decoration: none;
display: block;
cursor:pointer;
}
.custom-dropdown-content a:hover {
color: $black;
}
#myDropdown a:hover {background-color: $dropdown-link-hover-bg; color:$color-danger-fg !important;}
.search_icon{
color: $white;
cursor: pointer;
padding-right: 8px;
}
.hidden { display:none; }
.visible { display:block; }
.menu-groups, .help-groups{
background-color: $color-gray-light;
padding: 6px;
font-size: 12px;
font-weight: 600;
}
.menu-groups .fa, .fas{
font-weight:normal !important;
}
.help-groups .fa, .fas{
font-weight:600 !important;
}
.pad-12{
padding:12px;
}
.no-results{
font-size: 14px;
color: #697986;
text-align: center;
}
.no-padding{
padding:0 !important;
}
.menu-groups-a{
display:flex !important;
flex-direction:column;
padding: 6px 16px;
color: $dropdown-link-color;
}
.menu-groups-a span{
font-size: 0.9em;
font-weight: 100;
color: $quick-search-span-text;
}
#myDropdown a:hover span{
color: $color-danger-fg !important;
}
.search-icon{
background: $loader-icon-small center center no-repeat;
margin: auto !important;
height: 22px !important;
width: 130px !important;
background-position: left !important;
font-size: 14px;
color: $dropdown-link-color;
padding-left: 30px;
}
.help_loading_icon{
height: 16px;
}
#myDropdown ul {
list-style: none;
}
.border-right-search-icon{
border-right: 2px solid #fff;
}
.help_submenu{
left: 100%;
width: 20rem;
top:-0.4rem;
}
.help_menu{
min-width:300px;
}
.dropdown-item-input{
padding:4px;
}
.menu-groups-a span:focus{
color:$color-primary-fg;
}
.dropdown-item:hover, .dropdown-item:focus span{
color: $color-danger-fg !important;
}
.quick-search-tooltip{
position: absolute;
right: 10px;
margin-top: -2.1em;
font-size: 16px;
cursor:pointer;
color: $quick-search-info-icon;
}
#myDropdown .dropdown-divider{
height: auto;
border-top: 0;
}

View File

@ -92,14 +92,6 @@ require.onResourceLoad = function (context, map, depMaps) {
<div class="row"><div class="col-12 pg-sp-text">{{ _('Loading {0} v{1}...').format(config.APP_NAME, config.APP_VERSION) }}</div></div> <div class="row"><div class="col-12 pg-sp-text">{{ _('Loading {0} v{1}...').format(config.APP_NAME, config.APP_VERSION) }}</div></div>
</div> </div>
</div> </div>
{% if current_app.PGADMIN_RUNTIME | string() == 'False' %} <div id="root" style="height: 100%"></div>
<div id="main-menu-container"></div>
<div id="dockerContainer" class="pg-docker"></div>
{% else %}
<div id="dockerContainer" class="pg-docker pg-docker-native"></div>
{% endif %}
<div id="object-breadcrumbs"></div>
{% include 'browser/messages.html' %}
{% endblock %} {% endblock %}

View File

@ -1,18 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
define('pgadmin.browser.constants', [], function() {
return {
'INTERNAL': '{{ INTERNAL }}',
'LDAP': '{{ LDAP }}',
'KERBEROS': '{{ KERBEROS }}',
'OAUTH2': '{{ OAUTH2 }}'
}
});

View File

@ -20,7 +20,7 @@
{% endif %}label: "{{ item.label }}", applies: ["{{ key.lower() }}"], {% endif %}label: "{{ item.label }}", applies: ["{{ key.lower() }}"],
priority: {{ item.priority }}, priority: {{ item.priority }},
enable: "{{ item.enable }}", enable: "{{ item.enable }}",
{% if item.checked is defined %}checked: {% if (item.checked or item.name == 'mnu_lock_'+current_ui_lock) %}true{% else %}false{% endif %}, {% if item.checked is defined %}checked: {% if item.checked %}true{% else %}false{% endif %},
{% endif %} {% endif %}
{% if item.below is defined %}below: {% if item.below %}true{% else %}false{% endif %}, {% if item.below is defined %}below: {% if item.below %}true{% else %}false{% endif %},
{% endif %} {% endif %}
@ -41,9 +41,6 @@ define('pgadmin.browser.utils',
let pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {}; let pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
/* Add hooked-in panels by extensions */
pgBrowser['panels_items'] = '{{ current_app.panels|tojson }}';
pgBrowser['MainMenus'] = []; pgBrowser['MainMenus'] = [];
pgAdmin['csrf_token_header'] = '{{ current_app.config.get('WTF_CSRF_HEADERS')[0] }}'; pgAdmin['csrf_token_header'] = '{{ current_app.config.get('WTF_CSRF_HEADERS')[0] }}';

View File

@ -44,22 +44,6 @@ class DashboardModule(PgAdminModule):
stylesheets = [] stylesheets = []
return stylesheets return stylesheets
def get_panels(self):
return [
Panel(
name='dashboard',
priority=1,
title=gettext('Dashboard'),
icon='',
content='',
is_closeable=True,
is_private=False,
limit=1,
is_iframe=False,
can_hide=True
).__dict__
]
def register_preferences(self): def register_preferences(self):
""" """
register_preferences register_preferences

View File

@ -0,0 +1,74 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React from 'react';
import PropTypes from 'prop-types';
import { Box, Card, CardContent, CardHeader, makeStyles } from '@material-ui/core';
import EmptyPanelMessage from '../../../static/js/components/EmptyPanelMessage';
const useStyles = makeStyles((theme) => ({
chartCard: {
border: '1px solid '+theme.otherVars.borderColor,
},
chartCardContent: {
padding: '0.25rem 0.5rem',
height: '165px',
display: 'flex',
},
chartLegend: {
marginLeft: 'auto',
'& > div': {
display: 'flex',
fontWeight: 'normal',
'& .legend-value': {
marginLeft: '4px',
'& .legend-label': {
marginLeft: '4px',
}
}
}
}
}));
export default function ChartContainer(props) {
const classes = useStyles();
return (
<Card className={classes.chartCard} elevation={0} data-testid="chart-container">
<CardHeader title={<Box display="flex" justifyContent="space-between">
<div id={props.id}>{props.title}</div>
<div className={classes.chartLegend}>
<div className="d-flex">
{props.datasets?.map((datum)=>(
<div className="legend-value" key={datum.label}>
<span style={{backgroundColor: datum.borderColor}}>&nbsp;&nbsp;&nbsp;&nbsp;</span>
<span className="legend-label">{datum.label}</span>
</div>
))}
</div>
</div>
</Box>} />
<CardContent className={classes.chartCardContent}>
{!props.errorMsg && !props.isTest && props.children}
{props.errorMsg && <EmptyPanelMessage text={props.errorMsg}/>}
</CardContent>
</Card>
);
}
ChartContainer.propTypes = {
id: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
datasets: PropTypes.array.isRequired,
children: PropTypes.node.isRequired,
errorMsg: PropTypes.string,
isTest: PropTypes.bool
};

View File

@ -35,15 +35,6 @@ export default class ChartsDOM {
this.render(); this.render();
} }
reflectPreferences(preferences) {
this.preferences = preferences;
if(preferences.show_graphs) {
this.render();
} else {
this.unmount();
}
}
setPageVisible(visible) { setPageVisible(visible) {
this.pageVisible = visible; this.pageVisible = visible;
this.render(); this.render();

View File

@ -6,7 +6,6 @@
// This software is released under the PostgreSQL Licence // This software is released under the PostgreSQL Licence
// //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
// eslint-disable-next-line react/display-name
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -16,8 +15,7 @@ import { InputCheckbox } from '../../../static/js/components/FormComponents';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import url_for from 'sources/url_for'; import url_for from 'sources/url_for';
import Graphs from './Graphs'; import Graphs from './Graphs';
import Notify from '../../../static/js/helpers/Notifier'; import { Box, Tab, Tabs } from '@material-ui/core';
import { Box, Card, CardContent, CardHeader, Tab, Tabs } from '@material-ui/core';
import { PgIconButton } from '../../../static/js/components/Buttons'; import { PgIconButton } from '../../../static/js/components/Buttons';
import CancelIcon from '@material-ui/icons/Cancel'; import CancelIcon from '@material-ui/icons/Cancel';
import StopSharpIcon from '@material-ui/icons/StopSharp'; import StopSharpIcon from '@material-ui/icons/StopSharp';
@ -33,6 +31,11 @@ import Summary from './SystemStats/Summary';
import CPU from './SystemStats/CPU'; import CPU from './SystemStats/CPU';
import Memory from './SystemStats/Memory'; import Memory from './SystemStats/Memory';
import Storage from './SystemStats/Storage'; import Storage from './SystemStats/Storage';
import withStandardTabInfo from '../../../static/js/helpers/withStandardTabInfo';
import { BROWSER_PANELS } from '../../../browser/static/js/constants';
import { usePgAdmin } from '../../../static/js/BrowserComponent';
import usePreferences from '../../../preferences/static/js/store';
import ErrorBoundary from '../../../static/js/helpers/ErrorBoundary';
function parseData(data) { function parseData(data) {
let res = []; let res = [];
@ -45,13 +48,11 @@ function parseData(data) {
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
emptyPanel: { emptyPanel: {
height: '100%', width: '100%',
background: theme.palette.grey[400], background: theme.otherVars.emptySpaceBg,
overflow: 'auto', overflow: 'auto',
padding: '8px', padding: '8px',
display: 'flex', display: 'flex',
flexDirection: 'column',
flexGrow: 1,
}, },
fixedSizeList: { fixedSizeList: {
overflowX: 'hidden !important', overflowX: 'hidden !important',
@ -91,12 +92,20 @@ const useStyles = makeStyles((theme) => ({
fontSize: '0.875rem', fontSize: '0.875rem',
}, },
panelContent: { panelContent: {
...theme.mixins.panelBorder, ...theme.mixins.panelBorder.all,
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
overflow: 'hidden !important', overflow: 'hidden !important',
height: '100%', height: '100%',
minHeight: '400px' width: '100%',
minHeight: '400px',
padding: '8px'
},
mainTabs: {
...theme.mixins.panelBorder.all,
height: '100%',
display: 'flex',
flexDirection: 'column'
}, },
arrowButton: { arrowButton: {
fontSize: '2rem !important', fontSize: '2rem !important',
@ -139,16 +148,8 @@ const useStyles = makeStyles((theme) => ({
} }
})); }));
/* eslint-disable react/display-name */ function Dashboard({
export default function Dashboard({ nodeItem, nodeData, node, treeNodeInfo,
nodeData,
node,
item,
pgBrowser,
preferences,
sid,
did,
treeNodeInfo,
...props ...props
}) { }) {
const classes = useStyles(); const classes = useStyles();
@ -169,6 +170,16 @@ export default function Dashboard({
const systemStatsTabChanged = (e, tabVal) => { const systemStatsTabChanged = (e, tabVal) => {
setSystemStatsTabVal(tabVal); setSystemStatsTabVal(tabVal);
}; };
const pgAdmin = usePgAdmin();
const did = treeNodeInfo?.database?._id ?? 0;
const sid = treeNodeInfo?.server?._id ?? 0;
const dbConnected = treeNodeInfo?.database?.connected ?? false;
const serverConnected = treeNodeInfo?.server?.connected ?? false;
const prefStore = usePreferences();
const preferences = _.merge(
usePreferences().getPreferencesForModule('dashboards'),
usePreferences().getPreferencesForModule('graphs')
);
if (!did) { if (!did) {
tabs.push(gettext('Configuration')); tabs.push(gettext('Configuration'));
@ -268,7 +279,7 @@ export default function Dashboard({
) )
return; return;
let url = action_url + '/' + row.values.pid; let url = action_url + '/' + row.values.pid;
Notify.confirm( pgAdmin.Browser.notifier.confirm(
title, title,
txtConfirm, txtConfirm,
function () { function () {
@ -276,14 +287,14 @@ export default function Dashboard({
.delete(url) .delete(url)
.then(function (res) { .then(function (res) {
if (res.data == gettext('Success')) { if (res.data == gettext('Success')) {
Notify.success(txtSuccess); pgAdmin.Browser.notifier.success(txtSuccess);
setRefresh(!refresh); setRefresh(!refresh);
} else { } else {
Notify.error(txtError); pgAdmin.Browser.notifier.error(txtError);
} }
}) })
.catch(function (error) { .catch(function (error) {
Notify.alert( pgAdmin.Browser.notifier.alert(
gettext('Failed to retrieve data from the server.'), gettext('Failed to retrieve data from the server.'),
error.message error.message
); );
@ -335,7 +346,7 @@ export default function Dashboard({
if (!canTakeAction(row, 'cancel')) if (!canTakeAction(row, 'cancel'))
return; return;
let url = action_url + '/' + row.values.pid; let url = action_url + '/' + row.values.pid;
Notify.confirm( pgAdmin.Browser.notifier.confirm(
title, title,
txtConfirm, txtConfirm,
function () { function () {
@ -343,16 +354,16 @@ export default function Dashboard({
.delete(url) .delete(url)
.then(function (res) { .then(function (res) {
if (res.data == gettext('Success')) { if (res.data == gettext('Success')) {
Notify.success(txtSuccess); pgAdmin.Browser.notifier.success(txtSuccess);
setRefresh(!refresh); setRefresh(!refresh);
} else { } else {
Notify.error(txtError); pgAdmin.Browser.notifier.error(txtError);
setRefresh(!refresh); setRefresh(!refresh);
} }
}) })
.catch(function (error) { .catch(function (error) {
Notify.alert( pgAdmin.Browser.notifier.alert(
gettext('Failed to retrieve data from the server.'), gettext('Failed to retrieve data from the server.'),
error.message error.message
); );
@ -711,7 +722,7 @@ export default function Dashboard({
'You cannot terminate background worker processes.' 'You cannot terminate background worker processes.'
); );
} }
Notify.info(txtMessage); pgAdmin.Browser.notifier.info(txtMessage);
return false; return false;
// If it is the last active connection on maintenance db then error out // If it is the last active connection on maintenance db then error out
} else if ( } else if (
@ -727,11 +738,11 @@ export default function Dashboard({
'You are not allowed to terminate the main active session.' 'You are not allowed to terminate the main active session.'
); );
} }
Notify.error(txtMessage); pgAdmin.Browser.notifier.error(txtMessage);
return false; return false;
} else if (is_cancel_session && row.original.state == 'idle') { } else if (is_cancel_session && row.original.state == 'idle') {
// If this session is already idle then do nothing // If this session is already idle then do nothing
Notify.info(gettext('The session is already in idle state.')); pgAdmin.Browser.notifier.info(gettext('The session is already in idle state.'));
return false; return false;
} else if (can_signal_backend) { } else if (can_signal_backend) {
// user with membership of 'pg_signal_backend' can terminate the session of non admin user. // user with membership of 'pg_signal_backend' can terminate the session of non admin user.
@ -753,7 +764,7 @@ export default function Dashboard({
'Superuser privileges are required to terminate another users query.' 'Superuser privileges are required to terminate another users query.'
); );
} }
Notify.error(txtMessage); pgAdmin.Browser.notifier.error(txtMessage);
return false; return false;
} }
}; };
@ -773,7 +784,7 @@ export default function Dashboard({
setTabVal(0); setTabVal(0);
} }
if (sid && props.serverConnected) { if (sid && serverConnected) {
if (tabVal === 0) { if (tabVal === 0) {
url = url_for('dashboard.activity'); url = url_for('dashboard.activity');
} else if (tabVal === 1) { } else if (tabVal === 1) {
@ -785,11 +796,11 @@ export default function Dashboard({
} }
message = gettext('Loading dashboard...'); message = gettext('Loading dashboard...');
if (did && !props.dbConnected) return; if (did && !dbConnected) return;
if (did) url += sid + '/' + did; if (did) url += sid + '/' + did;
else url += sid; else url += sid;
if (did && !props.dbConnected) return; if (did && !dbConnected) return;
if (did && did > 0) ssExtensionCheckUrl += '/' + sid + '/' + did; if (did && did > 0) ssExtensionCheckUrl += '/' + sid + '/' + did;
else ssExtensionCheckUrl += '/' + sid; else ssExtensionCheckUrl += '/' + sid;
@ -804,7 +815,7 @@ export default function Dashboard({
setdashData(parseData(res.data)); setdashData(parseData(res.data));
}) })
.catch((error) => { .catch((error) => {
Notify.alert( pgAdmin.Browser.notifier.alert(
gettext('Failed to retrieve data from the server.'), gettext('Failed to retrieve data from the server.'),
_.isUndefined(error.response) ? error.message : error.response.data.errormsg _.isUndefined(error.response) ? error.message : error.response.data.errormsg
); );
@ -842,7 +853,7 @@ export default function Dashboard({
if (message != '') { if (message != '') {
setMsg(message); setMsg(message);
} }
}, [nodeData, tabVal, did, preferences, refresh, props.dbConnected, mainTabVal]); }, [nodeData, tabVal, treeNodeInfo, prefStore, refresh, mainTabVal]);
const filteredDashData = useMemo(()=>{ const filteredDashData = useMemo(()=>{
if (tabVal == 0 && activeOnly) { if (tabVal == 0 && activeOnly) {
@ -872,7 +883,7 @@ export default function Dashboard({
const showDefaultContents = () => { const showDefaultContents = () => {
return ( return (
sid && !props.serverConnected ? ( sid && !serverConnected ? (
<Box className={classes.dashboardPanel}> <Box className={classes.dashboardPanel}>
<div className={classes.emptyPanel}> <div className={classes.emptyPanel}>
<EmptyPanelMessage text={msg}/> <EmptyPanelMessage text={msg}/>
@ -880,10 +891,10 @@ export default function Dashboard({
</Box> </Box>
) : ( ) : (
<WelcomeDashboard <WelcomeDashboard
pgBrowser={pgBrowser} pgBrowser={pgAdmin.Browser}
node={node} node={node}
itemData={nodeData} itemData={nodeData}
item={item} item={nodeItem}
sid={sid} sid={sid}
did={did} did={did}
/> />
@ -912,150 +923,148 @@ export default function Dashboard({
return ( return (
<> <>
{sid && props.serverConnected ? ( {sid && serverConnected ? (
<Box className={classes.dashboardPanel}> <Box className={classes.dashboardPanel}>
<Box className={classes.emptyPanel}> <Box className={classes.panelContent}>
<Box className={classes.panelContent}> <Box className={classes.mainTabs}>
<Box height="100%" display="flex" flexDirection="column"> <Box>
<Box> <Tabs
<Tabs value={mainTabVal}
value={mainTabVal} onChange={mainTabChanged}
onChange={mainTabChanged} >
> {mainTabs.map((tabValue) => {
{mainTabs.map((tabValue) => { return <Tab key={tabValue} label={tabValue} />;
return <Tab key={tabValue} label={tabValue} />; })}
})} </Tabs>
</Tabs>
</Box>
{/* General Statistics */}
<TabPanel value={mainTabVal} index={0} classNameRoot={classes.tabPanel}>
{!_.isUndefined(preferences) && preferences.show_graphs && (
<Graphs
key={sid + did}
preferences={preferences}
sid={sid}
did={did}
pageVisible={props.panelVisible}
></Graphs>
)}
{!_.isUndefined(preferences) && preferences.show_activity && (
<Box className={classes.panelContent}>
<Box
className={classes.cardHeader}
title={props.dbConnected ? gettext('Database activity') : gettext('Server activity')}
>
{props.dbConnected ? gettext('Database activity') : gettext('Server activity')}{' '}
</Box>
<Box height="100%" display="flex" flexDirection="column">
<Box>
<Tabs
value={tabVal}
onChange={tabChanged}
>
{tabs.map((tabValue) => {
return <Tab key={tabValue} label={tabValue} />;
})}
<RefreshButton/>
</Tabs>
</Box>
<TabPanel value={tabVal} index={0} classNameRoot={classes.tabPanel}>
<PgTable
caveTable={false}
CustomHeader={CustomActiveOnlyHeader}
columns={activityColumns}
data={filteredDashData}
schema={schemaDict}
></PgTable>
</TabPanel>
<TabPanel value={tabVal} index={1} classNameRoot={classes.tabPanel}>
<PgTable
caveTable={false}
columns={databaseLocksColumns}
data={dashData}
></PgTable>
</TabPanel>
<TabPanel value={tabVal} index={2} classNameRoot={classes.tabPanel}>
<PgTable
caveTable={false}
columns={databasePreparedColumns}
data={dashData}
></PgTable>
</TabPanel>
<TabPanel value={tabVal} index={3} classNameRoot={classes.tabPanel}>
<PgTable
caveTable={false}
columns={serverConfigColumns}
data={dashData}
></PgTable>
</TabPanel>
</Box>
</Box>
)}
</TabPanel>
{/* System Statistics */}
<TabPanel value={mainTabVal} index={1} classNameRoot={classes.tabPanel}>
<Box height="100%" display="flex" flexDirection="column">
{ssMsg === 'installed' && did === ldid ?
<>
<Box>
<Tabs
value={systemStatsTabVal}
onChange={systemStatsTabChanged}
>
{systemStatsTabs.map((tabValue) => {
return <Tab key={tabValue} label={tabValue} />;
})}
</Tabs>
</Box>
<TabPanel value={systemStatsTabVal} index={0} classNameRoot={classes.tabPanel}>
<Summary
key={sid + did}
preferences={preferences}
sid={sid}
did={did}
pageVisible={props.panelVisible}
serverConnected={props.serverConnected}
/>
</TabPanel>
<TabPanel value={systemStatsTabVal} index={1} classNameRoot={classes.tabPanel}>
<CPU
key={sid + did}
preferences={preferences}
sid={sid}
did={did}
pageVisible={props.panelVisible}
serverConnected={props.serverConnected}
/>
</TabPanel>
<TabPanel value={systemStatsTabVal} index={2} classNameRoot={classes.tabPanel}>
<Memory
key={sid + did}
preferences={preferences}
sid={sid}
did={did}
pageVisible={props.panelVisible}
serverConnected={props.serverConnected}
/>
</TabPanel>
<TabPanel value={systemStatsTabVal} index={3} classNameRoot={classes.tabPanel}>
<Storage
key={sid + did}
preferences={preferences}
sid={sid}
did={did}
pageVisible={props.panelVisible}
serverConnected={props.serverConnected}
systemStatsTabVal={systemStatsTabVal}
/>
</TabPanel>
</> :
<div className={classes.emptyPanel}>
<EmptyPanelMessage text={ssMsg}/>
</div>
}
</Box>
</TabPanel>
</Box> </Box>
{/* General Statistics */}
<TabPanel value={mainTabVal} index={0} classNameRoot={classes.tabPanel}>
{!_.isUndefined(preferences) && preferences.show_graphs && (
<Graphs
key={sid + did}
preferences={preferences}
sid={sid}
did={did}
pageVisible={props.isActive}
></Graphs>
)}
{!_.isUndefined(preferences) && preferences.show_activity && (
<Box className={classes.panelContent}>
<Box
className={classes.cardHeader}
title={dbConnected ? gettext('Database activity') : gettext('Server activity')}
>
{dbConnected ? gettext('Database activity') : gettext('Server activity')}{' '}
</Box>
<Box height="100%" display="flex" flexDirection="column">
<Box>
<Tabs
value={tabVal}
onChange={tabChanged}
>
{tabs.map((tabValue) => {
return <Tab key={tabValue} label={tabValue} />;
})}
<RefreshButton/>
</Tabs>
</Box>
<TabPanel value={tabVal} index={0} classNameRoot={classes.tabPanel}>
<PgTable
caveTable={false}
CustomHeader={CustomActiveOnlyHeader}
columns={activityColumns}
data={filteredDashData}
schema={schemaDict}
></PgTable>
</TabPanel>
<TabPanel value={tabVal} index={1} classNameRoot={classes.tabPanel}>
<PgTable
caveTable={false}
columns={databaseLocksColumns}
data={dashData}
></PgTable>
</TabPanel>
<TabPanel value={tabVal} index={2} classNameRoot={classes.tabPanel}>
<PgTable
caveTable={false}
columns={databasePreparedColumns}
data={dashData}
></PgTable>
</TabPanel>
<TabPanel value={tabVal} index={3} classNameRoot={classes.tabPanel}>
<PgTable
caveTable={false}
columns={serverConfigColumns}
data={dashData}
></PgTable>
</TabPanel>
</Box>
</Box>
)}
</TabPanel>
{/* System Statistics */}
<TabPanel value={mainTabVal} index={1} classNameRoot={classes.tabPanel}>
<Box height="100%" display="flex" flexDirection="column">
{ssMsg === 'installed' && did === ldid ?
<ErrorBoundary>
<Box>
<Tabs
value={systemStatsTabVal}
onChange={systemStatsTabChanged}
>
{systemStatsTabs.map((tabValue) => {
return <Tab key={tabValue} label={tabValue} />;
})}
</Tabs>
</Box>
<TabPanel value={systemStatsTabVal} index={0} classNameRoot={classes.tabPanel}>
<Summary
key={sid + did}
preferences={preferences}
sid={sid}
did={did}
pageVisible={props.isActive}
serverConnected={serverConnected}
/>
</TabPanel>
<TabPanel value={systemStatsTabVal} index={1} classNameRoot={classes.tabPanel}>
<CPU
key={sid + did}
preferences={preferences}
sid={sid}
did={did}
pageVisible={props.isActive}
serverConnected={serverConnected}
/>
</TabPanel>
<TabPanel value={systemStatsTabVal} index={2} classNameRoot={classes.tabPanel}>
<Memory
key={sid + did}
preferences={preferences}
sid={sid}
did={did}
pageVisible={props.isActive}
serverConnected={serverConnected}
/>
</TabPanel>
<TabPanel value={systemStatsTabVal} index={3} classNameRoot={classes.tabPanel}>
<Storage
key={sid + did}
preferences={preferences}
sid={sid}
did={did}
pageVisible={props.isActive}
serverConnected={serverConnected}
systemStatsTabVal={systemStatsTabVal}
/>
</TabPanel>
</ErrorBoundary> :
<div className={classes.emptyPanel}>
<EmptyPanelMessage text={ssMsg}/>
</div>
}
</Box>
</TabPanel>
</Box> </Box>
</Box> </Box>
</Box> </Box>
@ -1069,48 +1078,14 @@ Dashboard.propTypes = {
itemData: PropTypes.object, itemData: PropTypes.object,
nodeData: PropTypes.object, nodeData: PropTypes.object,
treeNodeInfo: PropTypes.object, treeNodeInfo: PropTypes.object,
item: PropTypes.object, nodeItem: PropTypes.object,
pgBrowser: PropTypes.object,
preferences: PropTypes.object, preferences: PropTypes.object,
sid: PropTypes.string, sid: PropTypes.string,
did: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]), did: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]),
row: PropTypes.object, row: PropTypes.object,
serverConnected: PropTypes.bool, serverConnected: PropTypes.bool,
dbConnected: PropTypes.bool, dbConnected: PropTypes.bool,
panelVisible: PropTypes.bool, isActive: PropTypes.bool,
}; };
export function ChartContainer(props) { export default withStandardTabInfo(Dashboard, BROWSER_PANELS.DASHBOARD);
const classes = useStyles();
return (
<Card className={classes.chartCard} elevation={0}>
<CardHeader title={<Box display="flex" justifyContent="space-between">
<div id={props.id}>{props.title}</div>
<div className={classes.chartLegend}>
<div className="d-flex">
{props.datasets?.map((datum)=>(
<div className="legend-value" key={datum.label}>
<span style={{backgroundColor: datum.borderColor}}>&nbsp;&nbsp;&nbsp;&nbsp;</span>
<span className="legend-label">{datum.label}</span>
</div>
))}
</div>
</div>
</Box>} />
<CardContent className={classes.chartCardContent}>
{!props.errorMsg && !props.isTest && props.children}
{props.errorMsg && <EmptyPanelMessage text={props.errorMsg}/>}
</CardContent>
</Card>
);
}
ChartContainer.propTypes = {
id: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
datasets: PropTypes.array.isRequired,
children: PropTypes.node.isRequired,
errorMsg: PropTypes.string,
isTest: PropTypes.bool
};

View File

@ -8,7 +8,7 @@
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import React, { useEffect, useRef, useState, useReducer, useMemo } from 'react'; import React, { useEffect, useRef, useState, useReducer, useMemo } from 'react';
import { DATA_POINT_SIZE } from 'sources/chartjs'; import { DATA_POINT_SIZE } from 'sources/chartjs';
import {ChartContainer} from './Dashboard'; import ChartContainer from './ChartContainer';
import url_for from 'sources/url_for'; import url_for from 'sources/url_for';
import axios from 'axios'; import axios from 'axios';
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';

View File

@ -13,7 +13,7 @@ import gettext from 'sources/gettext';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import {getGCD, getEpoch} from 'sources/utils'; import {getGCD, getEpoch} from 'sources/utils';
import {ChartContainer} from '../Dashboard'; import ChartContainer from '../ChartContainer';
import { Box, Grid } from '@material-ui/core'; import { Box, Grid } from '@material-ui/core';
import { DATA_POINT_SIZE } from 'sources/chartjs'; import { DATA_POINT_SIZE } from 'sources/chartjs';
import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart'; import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart';

View File

@ -12,7 +12,7 @@ import gettext from 'sources/gettext';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import {getGCD, getEpoch} from 'sources/utils'; import {getGCD, getEpoch} from 'sources/utils';
import {ChartContainer} from '../Dashboard'; import ChartContainer from '../ChartContainer';
import { Box, Grid } from '@material-ui/core'; import { Box, Grid } from '@material-ui/core';
import { DATA_POINT_SIZE } from 'sources/chartjs'; import { DATA_POINT_SIZE } from 'sources/chartjs';
import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart'; import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart';

View File

@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import url_for from 'sources/url_for'; import url_for from 'sources/url_for';
import {getGCD, getEpoch} from 'sources/utils'; import {getGCD, getEpoch} from 'sources/utils';
import {ChartContainer} from '../Dashboard'; import ChartContainer from '../ChartContainer';
import { Grid } from '@material-ui/core'; import { Grid } from '@material-ui/core';
import { DATA_POINT_SIZE } from 'sources/chartjs'; import { DATA_POINT_SIZE } from 'sources/chartjs';
import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart'; import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart';

View File

@ -13,7 +13,7 @@ import { makeStyles } from '@material-ui/core/styles';
import url_for from 'sources/url_for'; import url_for from 'sources/url_for';
import getApiInstance from 'sources/api_instance'; import getApiInstance from 'sources/api_instance';
import {getGCD, getEpoch} from 'sources/utils'; import {getGCD, getEpoch} from 'sources/utils';
import {ChartContainer} from '../Dashboard'; import ChartContainer from '../ChartContainer';
import { Grid } from '@material-ui/core'; import { Grid } from '@material-ui/core';
import { DATA_POINT_SIZE } from 'sources/chartjs'; import { DATA_POINT_SIZE } from 'sources/chartjs';
import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart'; import StreamingChart from '../../../../static/js/components/PgChart/StreamingChart';
@ -277,7 +277,7 @@ Summary.propTypes = {
enablePoll: PropTypes.bool, enablePoll: PropTypes.bool,
}; };
export function SummaryWrapper(props) { function SummaryWrapper(props) {
const classes = useStyles(); const classes = useStyles();
const options = useMemo(()=>({ const options = useMemo(()=>({
showDataPoints: props.showDataPoints, showDataPoints: props.showDataPoints,

View File

@ -10,11 +10,11 @@
import React from 'react'; import React from 'react';
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';
import _ from 'lodash'; import _ from 'lodash';
import { Link, BrowserRouter } from 'react-router-dom';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import pgAdmin from 'sources/pgadmin'; import pgAdmin from 'sources/pgadmin';
import PgAdminLogo from './PgAdminLogo'; import PgAdminLogo from './PgAdminLogo';
import { Link } from '@material-ui/core';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
emptyPanel: { emptyPanel: {
@ -119,118 +119,116 @@ export default function WelcomeDashboard({ pgBrowser }) {
const classes = useStyles(); const classes = useStyles();
return ( return (
<BrowserRouter> <div className={classes.emptyPanel}>
<div className={classes.emptyPanel}> <div className={classes.dashboardContainer}>
<div className={classes.dashboardContainer}> <div className={classes.row}>
<div className={classes.row}> <div className={classes.cardColumn}>
<div className={classes.cardColumn}> <div className={classes.card}>
<div className={classes.card}> <div className={classes.cardHeader}>{gettext('Welcome')}</div>
<div className={classes.cardHeader}>{gettext('Welcome')}</div> <div className={classes.cardBody}>
<div className={classes.cardBody}> <PgAdminLogo />
<PgAdminLogo /> <h4>
<h4> {gettext('Feature rich')} | {gettext('Maximises PostgreSQL')}{' '}
{gettext('Feature rich')} | {gettext('Maximises PostgreSQL')}{' '} | {gettext('Open Source')}{' '}
| {gettext('Open Source')}{' '} </h4>
</h4> <p>
<p> {gettext(
{gettext( 'pgAdmin is an Open Source administration and management tool for the PostgreSQL database. It includes a graphical administration interface, an SQL query tool, a procedural code debugger and much more. The tool is designed to answer the needs of developers, DBAs and system administrators alike.'
'pgAdmin is an Open Source administration and management tool for the PostgreSQL database. It includes a graphical administration interface, an SQL query tool, a procedural code debugger and much more. The tool is designed to answer the needs of developers, DBAs and system administrators alike.' )}
)} </p>
</p>
</div>
</div> </div>
</div> </div>
</div> </div>
<div className={classes.row}> </div>
<div className={classes.cardColumn}> <div className={classes.row}>
<div className={classes.card}> <div className={classes.cardColumn}>
<div className={classes.cardHeader}>{gettext('Quick Links')}</div> <div className={classes.card}>
<div className={classes.cardBody}> <div className={classes.cardHeader}>{gettext('Quick Links')}</div>
<div className={classes.rowContent}> <div className={classes.cardBody}>
<div className={classes.dashboardLink}> <div className={classes.rowContent}>
<Link to="#" onClick={() => { AddNewServer(pgBrowser); }} className={classes.link}> <div className={classes.dashboardLink}>
<span <Link onClick={() => { AddNewServer(pgBrowser); }} className={classes.link}>
className="fa fa-4x dashboard-icon fa-server" <span
aria-hidden="true" className="fa fa-4x dashboard-icon fa-server"
></span> aria-hidden="true"
<br /> ></span>
{gettext('Add New Server')} <br />
</Link> {gettext('Add New Server')}
</div> </Link>
<div className={classes.dashboardLink}> </div>
<Link to="#" onClick={() => pgAdmin.Preferences.show()} className={classes.link}> <div className={classes.dashboardLink}>
<span <Link onClick={() => pgAdmin.Preferences.show()} className={classes.link}>
id="mnu_preferences" <span
className="fa fa-4x dashboard-icon fa-cogs" id="mnu_preferences"
aria-hidden="true" className="fa fa-4x dashboard-icon fa-cogs"
></span> aria-hidden="true"
<br /> ></span>
{gettext('Configure pgAdmin')} <br />
</Link> {gettext('Configure pgAdmin')}
</div> </Link>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className={classes.row}> </div>
<div className={classes.cardColumn}> <div className={classes.row}>
<div className={classes.card}> <div className={classes.cardColumn}>
<div className={classes.cardHeader}>{gettext('Getting Started')}</div> <div className={classes.card}>
<div className={classes.cardBody}> <div className={classes.cardHeader}>{gettext('Getting Started')}</div>
<div className={classes.rowContent}> <div className={classes.cardBody}>
<div className={classes.gettingStartedLink}> <div className={classes.rowContent}>
<a <div className={classes.gettingStartedLink}>
href="https://www.postgresql.org/docs" <a
target="postgres_help" href="https://www.postgresql.org/docs"
className={classes.link} target="postgres_help"
> className={classes.link}
<span >
className="fa fa-4x dashboard-icon dashboard-pg-doc" <span
aria-hidden="true" className="fa fa-4x dashboard-icon dashboard-pg-doc"
></span> aria-hidden="true"
<br /> ></span>
{gettext('PostgreSQL Documentation')} <br />
</a> {gettext('PostgreSQL Documentation')}
</div> </a>
<div className={classes.gettingStartedLink}> </div>
<a href="https://www.pgadmin.org" target="pgadmin_website" className={classes.link}> <div className={classes.gettingStartedLink}>
<span <a href="https://www.pgadmin.org" target="pgadmin_website" className={classes.link}>
className="fa fa-4x dashboard-icon fa-globe" <span
aria-hidden="true" className="fa fa-4x dashboard-icon fa-globe"
></span> aria-hidden="true"
<br /> ></span>
{gettext('pgAdmin Website')} <br />
</a> {gettext('pgAdmin Website')}
</div> </a>
<div className={classes.gettingStartedLink}> </div>
<a <div className={classes.gettingStartedLink}>
href="https://planet.postgresql.org" <a
target="planet_website" href="https://planet.postgresql.org"
className={classes.link} target="planet_website"
> className={classes.link}
<span >
className="fa fa-4x dashboard-icon fa-book" <span
aria-hidden="true" className="fa fa-4x dashboard-icon fa-book"
></span> aria-hidden="true"
<br /> ></span>
{gettext('Planet PostgreSQL')} <br />
</a> {gettext('Planet PostgreSQL')}
</div> </a>
<div className={classes.gettingStartedLink}> </div>
<a <div className={classes.gettingStartedLink}>
href="https://www.postgresql.org/community" <a
target="postgres_website" href="https://www.postgresql.org/community"
className={classes.link} target="postgres_website"
> className={classes.link}
<span >
className="fa fa-4x dashboard-icon fa-users" <span
aria-hidden="true" className="fa fa-4x dashboard-icon fa-users"
></span> aria-hidden="true"
<br /> ></span>
{gettext('Community Support')} <br />
</a> {gettext('Community Support')}
</div> </a>
</div> </div>
</div> </div>
</div> </div>
@ -238,7 +236,7 @@ export default function WelcomeDashboard({ pgBrowser }) {
</div> </div>
</div> </div>
</div> </div>
</BrowserRouter> </div>
); );
} }

View File

@ -11723,7 +11723,7 @@ msgstr ""
#: pgadmin/static/js/Dialogs/ConnectServerContent.jsx:136 #: pgadmin/static/js/Dialogs/ConnectServerContent.jsx:136
#: pgadmin/static/js/Dialogs/MasterPasswordContent.jsx:142 #: pgadmin/static/js/Dialogs/MasterPasswordContent.jsx:142
#: pgadmin/static/js/Dialogs/NamedRestoreContent.jsx:88 #: pgadmin/static/js/Dialogs/NamedRestoreContent.jsx:88
#: pgadmin/static/js/Dialogs/RenamePanelContent.jsx:115 #: pgadmin/static/js/Dialogs/RenameTabContent.jsx:115
#: pgadmin/static/js/helpers/ModalProvider.jsx:52 #: pgadmin/static/js/helpers/ModalProvider.jsx:52
#: pgadmin/static/js/helpers/ModalProvider.jsx:75 #: pgadmin/static/js/helpers/ModalProvider.jsx:75
#: pgadmin/static/js/helpers/Notifier.jsx:107 #: pgadmin/static/js/helpers/Notifier.jsx:107
@ -11772,7 +11772,7 @@ msgstr ""
msgid "Enter the name of the restore point to add" msgid "Enter the name of the restore point to add"
msgstr "" msgstr ""
#: pgadmin/static/js/Dialogs/RenamePanelContent.jsx:85 #: pgadmin/static/js/Dialogs/RenameTabContent.jsx:85
msgid "Title cannot be empty" msgid "Title cannot be empty"
msgstr "" msgstr ""

View File

@ -19,13 +19,16 @@ from pgadmin.utils.csrf import pgCSRFProtect
from pgadmin.utils.session import cleanup_session_files from pgadmin.utils.session import cleanup_session_files
from pgadmin.misc.themes import get_all_themes from pgadmin.misc.themes import get_all_themes
from pgadmin.utils.constants import MIMETYPE_APP_JS, UTILITIES_ARRAY from pgadmin.utils.constants import MIMETYPE_APP_JS, UTILITIES_ARRAY
from pgadmin.utils.ajax import precondition_required, make_json_response from pgadmin.utils.ajax import precondition_required, make_json_response, \
from pgadmin.utils.heartbeat import log_server_heartbeat,\ internal_server_error
from pgadmin.utils.heartbeat import log_server_heartbeat, \
get_server_heartbeat, stop_server_heartbeat get_server_heartbeat, stop_server_heartbeat
import config import config
import subprocess import time
import os
import json import json
import os
from urllib.request import urlopen
from pgadmin.settings import get_setting, store_setting
MODULE_NAME = 'misc' MODULE_NAME = 'misc'
@ -72,7 +75,7 @@ class MiscModule(PgAdminModule):
'value': theme, 'value': theme,
'preview_src': url_for( 'preview_src': url_for(
'static', filename='js/generated/img/' + 'static', filename='js/generated/img/' +
theme_data['preview_img'] theme_data['preview_img']
) )
}) })
@ -97,7 +100,8 @@ class MiscModule(PgAdminModule):
""" """
return ['misc.ping', 'misc.index', 'misc.cleanup', return ['misc.ping', 'misc.index', 'misc.cleanup',
'misc.validate_binary_path', 'misc.log_heartbeat', 'misc.validate_binary_path', 'misc.log_heartbeat',
'misc.stop_heartbeat', 'misc.get_heartbeat'] 'misc.stop_heartbeat', 'misc.get_heartbeat',
'misc.upgrade_check']
def register(self, app, options): def register(self, app, options):
""" """
@ -200,24 +204,6 @@ def get_heartbeat(sid):
status=200) status=200)
@blueprint.route("/explain/explain.js")
def explain_js():
"""
explain_js()
Returns:
javascript for the explain module
"""
return Response(
response=render_template(
"explain/js/explain.js",
_=gettext
),
status=200,
mimetype=MIMETYPE_APP_JS
)
########################################################################## ##########################################################################
# A special URL used to shut down the server # A special URL used to shut down the server
########################################################################## ##########################################################################
@ -266,3 +252,56 @@ def validate_binary_path():
return precondition_required(gettext('Invalid binary path.')) return precondition_required(gettext('Invalid binary path.'))
return make_json_response(data=gettext(version_str), status=200) return make_json_response(data=gettext(version_str), status=200)
@blueprint.route("/upgrade_check", endpoint="upgrade_check", methods=['GET'])
@login_required
def upgrade_check():
# Get the current version info from the website, and flash a message if
# the user is out of date, and the check is enabled.
ret = {
"outdated": False,
}
if config.UPGRADE_CHECK_ENABLED:
last_check = get_setting('LastUpdateCheck', default='0')
today = time.strftime('%Y%m%d')
if int(last_check) < int(today):
data = None
url = '%s?version=%s' % (
config.UPGRADE_CHECK_URL, config.APP_VERSION)
current_app.logger.debug('Checking version data at: %s' % url)
try:
# Do not wait for more than 5 seconds.
# It stuck on rendering the browser.html, while working in the
# broken network.
if os.path.exists(config.CA_FILE):
response = urlopen(url, data, 5, cafile=config.CA_FILE)
else:
response = urlopen(url, data, 5)
current_app.logger.debug(
'Version check HTTP response code: %d' % response.getcode()
)
if response.getcode() == 200:
data = json.loads(response.read().decode('utf-8'))
current_app.logger.debug('Response data: %s' % data)
except Exception:
current_app.logger.exception(
'Exception when checking for update')
return internal_server_error('Failed to check for update')
if data is not None and \
data[config.UPGRADE_CHECK_KEY]['version_int'] > \
config.APP_VERSION_INT:
ret = {
"outdated": True,
"current_version": config.APP_VERSION,
"upgrade_version": data[config.UPGRADE_CHECK_KEY][
'version'],
"product_name": config.APP_NAME,
"download_url": data[config.UPGRADE_CHECK_KEY][
'download_url']
}
store_setting('LastUpdateCheck', today)
return make_json_response(data=ret)

View File

@ -0,0 +1,21 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
export const BgProcessManagerEvents = {
LIST_UPDATED: 'LIST_UPDATED',
};
export const BgProcessManagerProcessState = {
PROCESS_NOT_STARTED: 0,
PROCESS_STARTED: 1,
PROCESS_FINISHED: 2,
PROCESS_TERMINATED: 3,
/* Supported by front end only */
PROCESS_TERMINATING: 10,
PROCESS_FAILED: 11,
};

View File

@ -6,32 +6,18 @@
// This software is released under the PostgreSQL Licence // This software is released under the PostgreSQL Licence
// //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import getApiInstance, { parseApiError } from '../../../../static/js/api_instance'; import getApiInstance, { parseApiError } from '../../../../static/js/api_instance';
import url_for from 'sources/url_for'; import url_for from 'sources/url_for';
import Notifier from '../../../../static/js/helpers/Notifier';
import EventBus from '../../../../static/js/helpers/EventBus'; import EventBus from '../../../../static/js/helpers/EventBus';
import * as BgProcessNotify from './BgProcessNotify';
import showDetails from './showDetails';
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';
import { BROWSER_PANELS } from '../../../../browser/static/js/constants';
import * as BgProcessNotify from './BgProcessNotify';
import pgAdmin from 'sources/pgadmin';
import { processesPanelData } from '../../../../static/js/BrowserComponent';
import { BgProcessManagerEvents, BgProcessManagerProcessState } from './BgProcessConstants';
const WORKER_INTERVAL = 1000; const WORKER_INTERVAL = 1000;
export const BgProcessManagerEvents = {
LIST_UPDATED: 'LIST_UPDATED',
};
export const BgProcessManagerProcessState = {
PROCESS_NOT_STARTED: 0,
PROCESS_STARTED: 1,
PROCESS_FINISHED: 2,
PROCESS_TERMINATED: 3,
/* Supported by front end only */
PROCESS_TERMINATING: 10,
PROCESS_FAILED: 11,
};
export default class BgProcessManager { export default class BgProcessManager {
static instance; static instance;
@ -148,7 +134,7 @@ export default class BgProcessManager {
this._eventManager.fireEvent(BgProcessManagerEvents.LIST_UPDATED); this._eventManager.fireEvent(BgProcessManagerEvents.LIST_UPDATED);
}) })
.catch((err)=>{ .catch((err)=>{
Notifier.error(parseApiError(err)); pgAdmin.Browser.notifier.error(parseApiError(err));
}); });
} }
@ -169,16 +155,12 @@ export default class BgProcessManager {
/* Object not available */ /* Object not available */
removeJob(jobId); removeJob(jobId);
} else { } else {
Notifier.error(parseApiError(err)); pgAdmin.Browser.notifier.error(parseApiError(err));
} }
}); });
}); });
} }
viewJobDetails(jobId) {
showDetails(this.procList.find((p)=>p.id==jobId));
}
updateCloudDetails(jobId) { updateCloudDetails(jobId) {
this.api.put(url_for('bgprocess.update_cloud_details', { this.api.put(url_for('bgprocess.update_cloud_details', {
pid: jobId, pid: jobId,
@ -186,7 +168,7 @@ export default class BgProcessManager {
.then((res)=>{ .then((res)=>{
let _server = res.data?.data?.node; let _server = res.data?.data?.node;
if(!_server) { if(!_server) {
Notifier.error(gettext('Cloud server deployment is pending')); pgAdmin.Browser.notifier.error(gettext('Cloud server deployment is pending'));
return; return;
} }
let _server_path = '/browser/server_group_' + _server.gid + '/' + _server.id, let _server_path = '/browser/server_group_' + _server.gid + '/' + _server.id,
@ -209,7 +191,7 @@ export default class BgProcessManager {
}) })
.catch((err)=>{ .catch((err)=>{
if(err.response?.status != 410) { if(err.response?.status != 410) {
Notifier.error(gettext('Failed Cloud Deployment.')); pgAdmin.Browser.notifier.error(gettext('Failed Cloud Deployment.'));
} }
}); });
} }
@ -223,14 +205,12 @@ export default class BgProcessManager {
} }
openProcessesPanel() { openProcessesPanel() {
let processPanel = this.pgBrowser.docker.findPanels('processes'); let processPanel = this.pgBrowser.docker.find(BROWSER_PANELS.PROCESSES);
if(processPanel.length > 0) { if(!processPanel) {
processPanel = processPanel[0]; pgAdmin.Browser.docker.openTab(processesPanelData, BROWSER_PANELS.MAIN, 'middle', true);
} else { } else {
let propertiesPanel = this.pgBrowser.docker.findPanels('properties'); this.pgBrowser.docker.focus(BROWSER_PANELS.PROCESSES);
processPanel = this.pgBrowser.docker.addPanel('processes', window.wcDocker.DOCK.STACKED, propertiesPanel[0]);
} }
processPanel.focus();
} }
registerListener(event, callback) { registerListener(event, callback) {

View File

@ -1,13 +1,13 @@
import { Box, makeStyles } from '@material-ui/core'; import { Box, makeStyles } from '@material-ui/core';
import React from 'react'; import React from 'react';
import Notifier from '../../../../static/js/helpers/Notifier';
import CloseIcon from '@material-ui/icons/CloseRounded'; import CloseIcon from '@material-ui/icons/CloseRounded';
import { DefaultButton, PgIconButton } from '../../../../static/js/components/Buttons'; import { DefaultButton, PgIconButton } from '../../../../static/js/components/Buttons';
import clsx from 'clsx'; import clsx from 'clsx';
import DescriptionOutlinedIcon from '@material-ui/icons/DescriptionOutlined'; import DescriptionOutlinedIcon from '@material-ui/icons/DescriptionOutlined';
import { BgProcessManagerProcessState } from './BgProcessManager'; import { BgProcessManagerProcessState } from './BgProcessConstants';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';
import pgAdmin from 'sources/pgadmin';
const useStyles = makeStyles((theme)=>({ const useStyles = makeStyles((theme)=>({
@ -77,7 +77,7 @@ ProcessNotifyMessage.propTypes = {
export function processStarted(desc, onViewProcess) { export function processStarted(desc, onViewProcess) {
Notifier.notify( pgAdmin.Browser.notifier.notify(
<ProcessNotifyMessage title={gettext('Process started')} desc={desc} onViewProcess={onViewProcess} dataTestSuffix="start"/>, <ProcessNotifyMessage title={gettext('Process started')} desc={desc} onViewProcess={onViewProcess} dataTestSuffix="start"/>,
null null
); );
@ -94,7 +94,7 @@ export function processCompleted(desc, process_state, onViewProcess) {
success = false; success = false;
} }
Notifier.notify( pgAdmin.Browser.notifier.notify(
<ProcessNotifyMessage title={title} desc={desc} onViewProcess={onViewProcess} success={success} dataTestSuffix="end"/>, <ProcessNotifyMessage title={title} desc={desc} onViewProcess={onViewProcess} success={success} dataTestSuffix="end"/>,
null null
); );

View File

@ -13,7 +13,7 @@ import url_for from 'sources/url_for';
import { Box, makeStyles } from '@material-ui/core'; import { Box, makeStyles } from '@material-ui/core';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { MESSAGE_TYPE, NotifierMessage } from '../../../../static/js/components/FormComponents'; import { MESSAGE_TYPE, NotifierMessage } from '../../../../static/js/components/FormComponents';
import { BgProcessManagerProcessState } from './BgProcessManager'; import { BgProcessManagerProcessState } from './BgProcessConstants';
import { DefaultButton, PgIconButton } from '../../../../static/js/components/Buttons'; import { DefaultButton, PgIconButton } from '../../../../static/js/components/Buttons';
import HighlightOffRoundedIcon from '@material-ui/icons/HighlightOffRounded'; import HighlightOffRoundedIcon from '@material-ui/icons/HighlightOffRounded';
import AccessTimeRoundedIcon from '@material-ui/icons/AccessTimeRounded'; import AccessTimeRoundedIcon from '@material-ui/icons/AccessTimeRounded';

View File

@ -7,13 +7,12 @@
// //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import React, { useEffect, useMemo } from 'react'; import React, { useCallback, useEffect, useMemo } from 'react';
import PgTable from 'sources/components/PgTable'; import PgTable from 'sources/components/PgTable';
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import pgAdmin from 'sources/pgadmin'; import { BgProcessManagerEvents, BgProcessManagerProcessState } from './BgProcessConstants';
import { BgProcessManagerEvents, BgProcessManagerProcessState } from './BgProcessManager';
import { PgIconButton } from '../../../../static/js/components/Buttons'; import { PgIconButton } from '../../../../static/js/components/Buttons';
import CancelIcon from '@material-ui/icons/Cancel'; import CancelIcon from '@material-ui/icons/Cancel';
import DescriptionOutlinedIcon from '@material-ui/icons/DescriptionOutlined'; import DescriptionOutlinedIcon from '@material-ui/icons/DescriptionOutlined';
@ -21,7 +20,10 @@ import DeleteIcon from '@material-ui/icons/Delete';
import HelpIcon from '@material-ui/icons/HelpRounded'; import HelpIcon from '@material-ui/icons/HelpRounded';
import url_for from 'sources/url_for'; import url_for from 'sources/url_for';
import { Box } from '@material-ui/core'; import { Box } from '@material-ui/core';
import Notifier from '../../../../static/js/helpers/Notifier'; import { usePgAdmin } from '../../../../static/js/BrowserComponent';
import { BROWSER_PANELS } from '../../../../browser/static/js/constants';
import ErrorBoundary from '../../../../static/js/helpers/ErrorBoundary';
import ProcessDetails from './ProcessDetails';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
@ -93,13 +95,33 @@ const ProcessStateTextAndColor = {
[BgProcessManagerProcessState.PROCESS_TERMINATING]: [gettext('Terminating...'), 'bgTerm'], [BgProcessManagerProcessState.PROCESS_TERMINATING]: [gettext('Terminating...'), 'bgTerm'],
[BgProcessManagerProcessState.PROCESS_FAILED]: [gettext('Failed'), 'bgFailed'], [BgProcessManagerProcessState.PROCESS_FAILED]: [gettext('Failed'), 'bgFailed'],
}; };
export default function Processes() { export default function Processes() {
const classes = useStyles(); const classes = useStyles();
const pgAdmin = usePgAdmin();
const [tableData, setTableData] = React.useState([]); const [tableData, setTableData] = React.useState([]);
const [selectedRows, setSelectedRows] = React.useState([]); const [selectedRows, setSelectedRows] = React.useState([]);
let columns = useMemo(()=>[ const onViewDetailsClick = useCallback((p)=>{
const panelTitle = gettext('Process Watcher - %s', p.type_desc);
const panelId = BROWSER_PANELS.PROCESS_DETAILS+''+p.id;
pgAdmin.Browser.docker.openDialog({
id: panelId,
title: panelTitle,
content: (
<ErrorBoundary>
<ProcessDetails
data={p}
closeModal={()=>{
pgAdmin.Browser.docker.close(panelId);
}}
/>
</ErrorBoundary>
)
}, pgAdmin.Browser.stdW.md);
}, []);
const columns = useMemo(()=>[
{ {
accessor: 'stop_process', accessor: 'stop_process',
Header: () => null, Header: () => null,
@ -149,7 +171,7 @@ export default function Processes() {
noBorder noBorder
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
pgAdmin.Browser.BgProcessManager.viewJobDetails(row.original.id); onViewDetailsClick(row.original);
}} }}
aria-label="View details" aria-label="View details"
title={gettext('View details')} title={gettext('View details')}
@ -266,7 +288,7 @@ export default function Processes() {
aria-label="Acknowledge and Remove" aria-label="Acknowledge and Remove"
title={gettext('Acknowledge and Remove')} title={gettext('Acknowledge and Remove')}
onClick={() => { onClick={() => {
Notifier.confirm(gettext('Remove Processes'), gettext('Are you sure you want to remove the selected processes?'), ()=>{ pgAdmin.Browser.notifier.confirm(gettext('Remove Processes'), gettext('Are you sure you want to remove the selected processes?'), ()=>{
pgAdmin.Browser.BgProcessManager.acknowledge(selectedRows.map((p)=>p.original.id)); pgAdmin.Browser.BgProcessManager.acknowledge(selectedRows.map((p)=>p.original.id));
}); });
}} }}

View File

@ -1,31 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import pgAdmin from 'sources/pgadmin';
import Theme from '../../../../static/js/Theme';
import ProcessDetails from './ProcessDetails';
import gettext from 'sources/gettext';
export default function showDetails(p) {
let pgBrowser = pgAdmin.Browser;
// Register dialog panel
pgBrowser.Node.registerUtilityPanel();
let panel = pgBrowser.Node.addUtilityPanel(pgBrowser.stdW.md),
j = panel.$container.find('.obj_properties').first();
panel.title(gettext('Process Watcher - %s', p.type_desc));
panel.focus();
panel.on(window.wcDocker.EVENT.CLOSED, ()=>{
ReactDOM.unmountComponentAtNode(j[0]);
});
ReactDOM.render(
<Theme>
<ProcessDetails
data={p}
closeModal={()=>{
panel.close();
}}
/>
</Theme>, j[0]);
}

View File

@ -16,7 +16,6 @@ import Wizard from '../../../../static/js/helpers/wizard/Wizard';
import WizardStep from '../../../../static/js/helpers/wizard/WizardStep'; import WizardStep from '../../../../static/js/helpers/wizard/WizardStep';
import {FormFooterMessage, MESSAGE_TYPE } from '../../../../static/js/components/FormComponents'; import {FormFooterMessage, MESSAGE_TYPE } from '../../../../static/js/components/FormComponents';
import getApiInstance from '../../../../static/js/api_instance'; import getApiInstance from '../../../../static/js/api_instance';
import Notifier from '../../../../static/js/helpers/Notifier';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import pgAdmin from 'sources/pgadmin'; import pgAdmin from 'sources/pgadmin';
import {ToggleButtons, FinalSummary} from './cloud_components'; import {ToggleButtons, FinalSummary} from './cloud_components';
@ -29,6 +28,7 @@ import {AzureCredentials, AzureInstanceDetails, AzureDatabaseDetails, checkClust
import { GoogleCredentials, GoogleInstanceDetails, GoogleDatabaseDetails, validateGoogleStep2, validateGoogleStep3 } from './google'; import { GoogleCredentials, GoogleInstanceDetails, GoogleDatabaseDetails, validateGoogleStep2, validateGoogleStep3 } from './google';
import EventBus from '../../../../static/js/helpers/EventBus'; import EventBus from '../../../../static/js/helpers/EventBus';
import { CLOUD_PROVIDERS, CLOUD_PROVIDERS_LABELS } from './cloud_constants'; import { CLOUD_PROVIDERS, CLOUD_PROVIDERS_LABELS } from './cloud_constants';
import { LAYOUT_EVENTS } from '../../../../static/js/helpers/Layout';
const useStyles = makeStyles(() => const useStyles = makeStyles(() =>
@ -64,7 +64,7 @@ const useStyles = makeStyles(() =>
export const CloudWizardEventsContext = React.createContext(); export const CloudWizardEventsContext = React.createContext();
export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel}) { export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanelId}) {
const classes = useStyles(); const classes = useStyles();
const eventBus = React.useRef(new EventBus()); const eventBus = React.useRef(new EventBus());
@ -98,16 +98,27 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel})
const [verificationURI, setVerificationURI] = React.useState(''); const [verificationURI, setVerificationURI] = React.useState('');
const [verificationCode, setVerificationCode] = React.useState(''); const [verificationCode, setVerificationCode] = React.useState('');
const authInterval = React.useRef();
React.useEffect(()=>{ React.useEffect(()=>{
eventBus.current.registerListener('SET_ERROR_MESSAGE_FOR_CLOUD_WIZARD', (msg) => { eventBus.current.registerListener('SET_ERROR_MESSAGE_FOR_CLOUD_WIZARD', (msg) => {
setErrMsg(msg); setErrMsg(msg);
}); });
}, []);
React.useEffect(()=>{
eventBus.current.registerListener('SET_CRED_VERIFICATION_INITIATED', (initiated) => { eventBus.current.registerListener('SET_CRED_VERIFICATION_INITIATED', (initiated) => {
setVerificationIntiated(initiated); setVerificationIntiated(initiated);
}); });
const onWizardClosing = (panelId)=>{
if(panelId == cloudPanelId) {
clearInterval(authInterval.current);
onClose();
}
};
pgAdmin.Browser.docker.eventBus.registerListener(LAYOUT_EVENTS.CLOSING, onWizardClosing);
return ()=>{
pgAdmin.Browser.docker.eventBus.deregisterListener(LAYOUT_EVENTS.CLOSING, onWizardClosing);
};
}, []); }, []);
React.useEffect(() => { React.useEffect(() => {
@ -119,7 +130,7 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel})
} }
}) })
.catch((error) => { .catch((error) => {
Notifier.error(gettext(`Error while getting the host ip: ${error.response.data.errormsg}`)); pgAdmin.Browser.notifier.error(gettext(`Error while getting the host ip: ${error.response.data.errormsg}`));
}); });
}, [cloudProvider]); }, [cloudProvider]);
@ -173,7 +184,7 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel})
onClose(); onClose();
}) })
.catch((error) => { .catch((error) => {
Notifier.error(gettext(`Error while saving cloud wizard data: ${error.response.data.errormsg}`)); pgAdmin.Browser.notifier.error(gettext(`Error while saving cloud wizard data: ${error.response.data.errormsg}`));
}); });
}; };
@ -366,37 +377,33 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel})
let child = window.open(verificationURI, 'edb_biganimal_authentication'); let child = window.open(verificationURI, 'edb_biganimal_authentication');
let _url = url_for('biganimal.verification_ack') ; let _url = url_for('biganimal.verification_ack') ;
let countdown = 60; let countdown = 60;
const myInterval = setInterval(() => { authInterval.current = setInterval(() => {
axiosApi.get(_url) axiosApi.get(_url)
.then((res) => { .then((res) => {
if (res.data && res.data.success == 1 ) { if (res.data && res.data.success == 1 ) {
setErrMsg([MESSAGE_TYPE.SUCCESS, gettext('Authentication completed successfully. Click the Next button to proceed.')]); setErrMsg([MESSAGE_TYPE.SUCCESS, gettext('Authentication completed successfully. Click the Next button to proceed.')]);
setVerificationIntiated(true); setVerificationIntiated(true);
clearInterval(myInterval); clearInterval(authInterval.current);
} else if (res.data && res.data.success == 0 && res.data.errormsg == 'access_denied') { } else if (res.data && res.data.success == 0 && res.data.errormsg == 'access_denied') {
setErrMsg([MESSAGE_TYPE.INFO, gettext('Verification failed. Access Denied...')]); setErrMsg([MESSAGE_TYPE.INFO, gettext('Verification failed. Access Denied...')]);
setVerificationIntiated(false); setVerificationIntiated(false);
clearInterval(myInterval); clearInterval(authInterval.current);
} else if (res.data && res.data.success == 0 && res.data.errormsg == 'forbidden') { } else if (res.data && res.data.success == 0 && res.data.errormsg == 'forbidden') {
setErrMsg([MESSAGE_TYPE.INFO, gettext('Authentication completed successfully but you do not have permission to create the cluster.')]); setErrMsg([MESSAGE_TYPE.INFO, gettext('Authentication completed successfully but you do not have permission to create the cluster.')]);
setVerificationIntiated(false); setVerificationIntiated(false);
clearInterval(myInterval); clearInterval(authInterval.current);
} else if (child.closed && !verificationIntiated && countdown <= 0) { } else if (child.closed && !verificationIntiated && countdown <= 0) {
setVerificationIntiated(false); setVerificationIntiated(false);
setErrMsg([MESSAGE_TYPE.ERROR, gettext('Authentication is aborted.')]); setErrMsg([MESSAGE_TYPE.ERROR, gettext('Authentication is aborted.')]);
clearInterval(myInterval); clearInterval(authInterval.current);
} }
authInterval.current = null;
}) })
.catch((error) => { .catch((error) => {
setErrMsg([MESSAGE_TYPE.ERROR, gettext(`Error while verifying EDB BigAnimal: ${error.response.data.errormsg}`)]); setErrMsg([MESSAGE_TYPE.ERROR, gettext(`Error while verifying EDB BigAnimal: ${error.response.data.errormsg}`)]);
}); });
countdown = countdown - 1; countdown = countdown - 1;
}, 1000); }, 1000);
cloudPanel.on(window.wcDocker.EVENT.CLOSED, function() {
clearInterval(myInterval);
});
}; };
@ -575,5 +582,6 @@ CloudWizard.propTypes = {
nodeInfo: PropTypes.object, nodeInfo: PropTypes.object,
nodeData: PropTypes.object, nodeData: PropTypes.object,
onClose: PropTypes.func, onClose: PropTypes.func,
cloudPanel: PropTypes.object cloudPanel: PropTypes.object,
cloudPanelId: PropTypes.string,
}; };

View File

@ -7,12 +7,10 @@
// //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom';
import Theme from 'sources/Theme';
import CloudWizard from './CloudWizard'; import CloudWizard from './CloudWizard';
import getApiInstance from '../../../../static/js/api_instance'; import getApiInstance from '../../../../static/js/api_instance';
import Notifier from '../../../../static/js/helpers/Notifier'; import { BROWSER_PANELS } from '../../../../browser/static/js/constants';
import pgAdmin from 'sources/pgadmin';
// Cloud Wizard // Cloud Wizard
define('pgadmin.misc.cloud', [ define('pgadmin.misc.cloud', [
@ -74,30 +72,26 @@ define('pgadmin.misc.cloud', [
d = this.d = i ? t.itemData(i) : undefined, d = this.d = i ? t.itemData(i) : undefined,
info = this.info = pgBrowser.tree.getTreeNodeHierarchy(i); info = this.info = pgBrowser.tree.getTreeNodeHierarchy(i);
// Register dialog panel const panelTitle = gettext('Deploy Cloud Instance');
pgBrowser.Node.registerUtilityPanel(); const panelId = BROWSER_PANELS.CLOUD_WIZARD;
let panel = pgBrowser.Node.addUtilityPanel(930, 650), pgAdmin.Browser.docker.openDialog({
j = panel.$container.find('.obj_properties').first(); id: panelId,
panel.title(gettext('Deploy Cloud Instance')); title: panelTitle,
manualClose: true,
panel.on(window.wcDocker.EVENT.CLOSED, function() { content: (
const axiosApi = getApiInstance(); <CloudWizard nodeInfo={info} nodeData={d} cloudPanelId={panelId}
let _url = url_for('cloud.clear_cloud_session');
axiosApi.post(_url)
.then(() => {/*This is intentional (SonarQube)*/})
.catch((error) => {
Notifier.error(gettext(`Error while clearing cloud wizard data: ${error.response.data.errormsg}`));
});
});
ReactDOM.render(
<Theme>
<CloudWizard nodeInfo={info} nodeData={d} cloudPanel={panel}
onClose={() => { onClose={() => {
ReactDOM.unmountComponentAtNode(j[0]); const axiosApi = getApiInstance();
panel.close(); let _url = url_for('cloud.clear_cloud_session');
axiosApi.post(_url)
.then(() => {/*This is intentional (SonarQube)*/})
.catch((error) => {
pgAdmin.Browser.notifier.error(gettext(`Error while clearing cloud wizard data: ${error.response.data.errormsg}`));
});
pgAdmin.Browser.docker.close(panelId, true);
}}/> }}/>
</Theme>, j[0]); )
}, pgAdmin.Browser.stdW.lg, pgAdmin.Browser.stdH.lg);
}, },
}; };

View File

@ -12,13 +12,15 @@ import React, { useEffect } from 'react';
import PgTable from 'sources/components/PgTable'; import PgTable from 'sources/components/PgTable';
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Notify from '../../../../static/js/helpers/Notifier';
import getApiInstance from 'sources/api_instance'; import getApiInstance from 'sources/api_instance';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import { getURL } from '../../../static/utils/utils'; import { getURL } from '../../../static/utils/utils';
import Loader from 'sources/components/Loader'; import Loader from 'sources/components/Loader';
import EmptyPanelMessage from '../../../../static/js/components/EmptyPanelMessage'; import EmptyPanelMessage from '../../../../static/js/components/EmptyPanelMessage';
import { parseApiError } from '../../../../static/js/api_instance'; import { parseApiError } from '../../../../static/js/api_instance';
import withStandardTabInfo from '../../../../static/js/helpers/withStandardTabInfo';
import { BROWSER_PANELS } from '../../../../browser/static/js/constants';
import { usePgAdmin } from '../../../../static/js/BrowserComponent';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
emptyPanel: { emptyPanel: {
@ -72,11 +74,13 @@ function parseData(data, node) {
return data; return data;
} }
export default function Dependencies({ nodeData, item, node, ...props }) { function Dependencies({ nodeData, nodeItem, node, treeNodeInfo, isActive, isStale, setIsStale }) {
const classes = useStyles(); const classes = useStyles();
const [tableData, setTableData] = React.useState([]); const [tableData, setTableData] = React.useState([]);
const [loaderText, setLoaderText] = React.useState(''); const [loaderText, setLoaderText] = React.useState('');
const [msg, setMsg] = React.useState(''); const [msg, setMsg] = React.useState('');
const pgAdmin = usePgAdmin();
let columns = [ let columns = [
{ {
Header: 'Type', Header: 'Type',
@ -103,14 +107,18 @@ export default function Dependencies({ nodeData, item, node, ...props }) {
]; ];
useEffect(() => { useEffect(() => {
if(!isStale || !isActive) {
return;
}
let message = gettext('Please select an object in the tree view.'); let message = gettext('Please select an object in the tree view.');
if (node) { if (node) {
let url = getURL( let url = getURL(
nodeData, nodeData,
true, true,
props.treeNodeInfo, treeNodeInfo,
node, node,
item, nodeItem,
'dependency' 'dependency'
); );
message = gettext( message = gettext(
@ -134,7 +142,7 @@ export default function Dependencies({ nodeData, item, node, ...props }) {
} }
}) })
.catch((e) => { .catch((e) => {
Notify.alert( pgAdmin.Browser.notifier.alert(
gettext('Failed to retrieve data from the server.'), gettext('Failed to retrieve data from the server.'),
parseApiError(e) parseApiError(e)
); );
@ -147,10 +155,8 @@ export default function Dependencies({ nodeData, item, node, ...props }) {
setMsg(message); setMsg(message);
setLoaderText(''); setLoaderText('');
} }
return () => { setIsStale(false);
setTableData([]); }, [isActive, isStale]);
};
}, [nodeData]);
return ( return (
<> <>
@ -178,5 +184,10 @@ Dependencies.propTypes = {
nodeData: PropTypes.object, nodeData: PropTypes.object,
treeNodeInfo: PropTypes.object, treeNodeInfo: PropTypes.object,
node: PropTypes.func, node: PropTypes.func,
item: PropTypes.object, nodeItem: PropTypes.object,
isActive: PropTypes.bool,
isStale: PropTypes.bool,
setIsStale: PropTypes.func,
}; };
export default withStandardTabInfo(Dependencies, BROWSER_PANELS.DEPENDENCIES);

View File

@ -12,13 +12,15 @@ import React, { useEffect } from 'react';
import PgTable from 'sources/components/PgTable'; import PgTable from 'sources/components/PgTable';
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Notify from '../../../../static/js/helpers/Notifier';
import getApiInstance from 'sources/api_instance'; import getApiInstance from 'sources/api_instance';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import { getURL } from '../../../static/utils/utils'; import { getURL } from '../../../static/utils/utils';
import Loader from 'sources/components/Loader'; import Loader from 'sources/components/Loader';
import EmptyPanelMessage from '../../../../static/js/components/EmptyPanelMessage'; import EmptyPanelMessage from '../../../../static/js/components/EmptyPanelMessage';
import { parseApiError } from '../../../../static/js/api_instance'; import { parseApiError } from '../../../../static/js/api_instance';
import withStandardTabInfo from '../../../../static/js/helpers/withStandardTabInfo';
import { BROWSER_PANELS } from '../../../../browser/static/js/constants';
import { usePgAdmin } from '../../../../static/js/BrowserComponent';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
emptyPanel: { emptyPanel: {
@ -72,11 +74,12 @@ function parseData(data, node) {
return data; return data;
} }
export default function Dependents({ nodeData, item, node, ...props }) { function Dependents({ nodeData, nodeItem, node, treeNodeInfo, isActive, isStale, setIsStale }) {
const classes = useStyles(); const classes = useStyles();
const [tableData, setTableData] = React.useState([]); const [tableData, setTableData] = React.useState([]);
const [loaderText, setLoaderText] = React.useState(''); const [loaderText, setLoaderText] = React.useState('');
const [msg, setMsg] = React.useState(''); const [msg, setMsg] = React.useState('');
const pgAdmin = usePgAdmin();
let columns = [ let columns = [
{ {
@ -104,14 +107,18 @@ export default function Dependents({ nodeData, item, node, ...props }) {
]; ];
useEffect(() => { useEffect(() => {
if(!isStale || !isActive) {
return;
}
let message = gettext('Please select an object in the tree view.'); let message = gettext('Please select an object in the tree view.');
if (node) { if (node) {
let url = getURL( let url = getURL(
nodeData, nodeData,
true, true,
props.treeNodeInfo, treeNodeInfo,
node, node,
item, nodeItem,
'dependent' 'dependent'
); );
message = gettext( message = gettext(
@ -134,7 +141,7 @@ export default function Dependents({ nodeData, item, node, ...props }) {
} }
}) })
.catch((e) => { .catch((e) => {
Notify.alert( pgAdmin.Browser.notifier.alert(
gettext('Failed to retrieve data from the server.'), gettext('Failed to retrieve data from the server.'),
parseApiError(e) parseApiError(e)
); );
@ -148,10 +155,8 @@ export default function Dependents({ nodeData, item, node, ...props }) {
setMsg(message); setMsg(message);
} }
return () => { setIsStale(false);
setTableData([]); }, [isActive, isStale]);
};
}, [nodeData, item]);
return ( return (
<> <>
@ -180,5 +185,10 @@ Dependents.propTypes = {
nodeData: PropTypes.object, nodeData: PropTypes.object,
treeNodeInfo: PropTypes.object, treeNodeInfo: PropTypes.object,
node: PropTypes.func, node: PropTypes.func,
item: PropTypes.object, nodeItem: PropTypes.object,
isActive: PropTypes.bool,
isStale: PropTypes.bool,
setIsStale: PropTypes.func,
}; };
export default withStandardTabInfo(Dependents, BROWSER_PANELS.DEPENDENTS);

View File

@ -8,10 +8,10 @@
// //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';
import Notifier from '../../../../static/js/helpers/Notifier';
import React from 'react'; import React from 'react';
import FileManager from './components/FileManager'; import FileManager from './components/FileManager';
import { getBrowser } from '../../../../static/js/utils'; import { getBrowser } from '../../../../static/js/utils';
import pgAdmin from 'sources/pgadmin';
export default class FileManagerModule { export default class FileManagerModule {
static instance; static instance;
@ -56,7 +56,7 @@ export default class FileManagerModule {
} }
showInternal(params, onOK, onCancel, modalObj) { showInternal(params, onOK, onCancel, modalObj) {
const modal = modalObj || Notifier; const modal = modalObj || pgAdmin.Browser.notifier;
let title = params.dialog_title; let title = params.dialog_title;
if(!title) { if(!title) {
if(params.dialog_type == 'create_file') { if(params.dialog_type == 'create_file') {

View File

@ -130,7 +130,7 @@ export default function GridView({items, operation, onItemSelect, onItemEnter})
} }
return ( return (
<Box flexGrow={1} overflow="hidden auto"> <Box flexGrow={1} overflow="hidden auto" id="grid">
<ul ref={gridRef} className={classes.grid}> <ul ref={gridRef} className={classes.grid}>
{items.map((item, i)=>( {items.map((item, i)=>(
<ItemView key={item.Filename} idx={i} row={item} selected={selectedIdx==i} onItemSelect={setSelectedIdx} <ItemView key={item.Filename} idx={i} row={item} selected={selectedIdx==i} onItemSelect={setSelectedIdx}

View File

@ -153,13 +153,13 @@ export default function ListView({items, operation, ...props}) {
}, [operation]); }, [operation]);
useEffect(()=>{ useEffect(()=>{
gridRef.current.selectCell({idx: 0, rowIdx: 0}); gridRef.current?.selectCell({idx: 0, rowIdx: 0});
}, [gridRef.current?.element]); }, [gridRef.current?.element]);
return ( return (
<PgReactDataGrid <PgReactDataGrid
gridRef={gridRef} gridRef={gridRef}
id="files" id="list"
className={classes.grid} className={classes.grid}
hasSelectColumn={false} hasSelectColumn={false}
columns={columns} columns={columns}

View File

@ -14,7 +14,6 @@ import FileManagerModule from './FileManagerModule';
/* Do not add let, var, const to this variable */ /* Do not add let, var, const to this variable */
__webpack_public_path__ = window.resourceBasePath; __webpack_public_path__ = window.resourceBasePath;
/* eslint-enable */ /* eslint-enable */
if(!pgAdmin.Tools) { if(!pgAdmin.Tools) {
pgAdmin.Tools = {}; pgAdmin.Tools = {};
} }

View File

@ -7,14 +7,11 @@
// //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import React from 'react'; import React from 'react';
import pgAdmin from 'sources/pgadmin';
import getApiInstance from 'sources/api_instance'; import getApiInstance from 'sources/api_instance';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import { Box, Switch } from '@material-ui/core'; import { Box, Switch } from '@material-ui/core';
import { generateCollectionURL } from '../../browser/static/js/node_ajax'; import { generateCollectionURL } from '../../browser/static/js/node_ajax';
import Notify from '../../static/js/helpers/Notifier';
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';
import 'wcdocker';
import PgTable from 'sources/components/PgTable'; import PgTable from 'sources/components/PgTable';
import Theme from 'sources/Theme'; import Theme from 'sources/Theme';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -25,6 +22,7 @@ import DeleteForeverIcon from '@material-ui/icons/DeleteForever';
import EmptyPanelMessage from '../../static/js/components/EmptyPanelMessage'; import EmptyPanelMessage from '../../static/js/components/EmptyPanelMessage';
import Loader from 'sources/components/Loader'; import Loader from 'sources/components/Loader';
import { evalFunc } from '../../static/js/utils'; import { evalFunc } from '../../static/js/utils';
import { usePgAdmin } from '../../static/js/BrowserComponent';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
emptyPanel: { emptyPanel: {
@ -77,14 +75,17 @@ const useStyles = makeStyles((theme) => ({
} }
})); }));
export function CollectionNodeView({ export default function CollectionNodeProperties({
node, node,
treeNodeInfo, treeNodeInfo,
itemNodeData, nodeData,
item, nodeItem,
pgBrowser isActive,
isStale,
setIsStale
}) { }) {
const classes = useStyles(); const classes = useStyles();
const pgAdmin = usePgAdmin();
const [data, setData] = React.useState([]); const [data, setData] = React.useState([]);
const [infoMsg, setInfoMsg] = React.useState('Please select an object in the tree view.'); const [infoMsg, setInfoMsg] = React.useState('Please select an object in the tree view.');
@ -96,7 +97,7 @@ export function CollectionNodeView({
//Reload the collection node on refresh or change in children count //Reload the collection node on refresh or change in children count
React.useEffect(() => { React.useEffect(() => {
setReload(!reload); setReload(!reload);
}, [item?._children]); }, [nodeItem?._children]);
const [pgTableColumns, setPgTableColumns] = React.useState([ const [pgTableColumns, setPgTableColumns] = React.useState([
{ {
@ -122,9 +123,9 @@ export function CollectionNodeView({
const onDrop = (type) => { const onDrop = (type) => {
let selRowModels = selectedObject, let selRowModels = selectedObject,
selRows = [], selRows = [],
selItem = pgBrowser.tree.selected(), selItem = pgAdmin.Browser.tree.selected(),
selectedItemData = selItem ? pgBrowser.tree.itemData(selItem) : null, selectedItemData = selItem ? pgAdmin.Browser.tree.itemData(selItem) : null,
selNode = selectedItemData && pgBrowser.Nodes[selectedItemData._type], selNode = selectedItemData && pgAdmin.Browser.Nodes[selectedItemData._type],
url = undefined, url = undefined,
msg = undefined, msg = undefined,
title = undefined; title = undefined;
@ -140,7 +141,7 @@ export function CollectionNodeView({
} }
if (selRows.length === 0) { if (selRows.length === 0) {
Notify.alert( pgAdmin.Browser.notifier.alert(
gettext('Delete Multiple'), gettext('Delete Multiple'),
gettext('Please select at least one object to delete.') gettext('Please select at least one object to delete.')
); );
@ -178,14 +179,14 @@ export function CollectionNodeView({
.then(function (res) { .then(function (res) {
setLoaderText(''); setLoaderText('');
if (res.success == 0) { if (res.success == 0) {
Notify.alert(res.errormsg, res.info); pgAdmin.Browser.notifier.alert(res.errormsg, res.info);
} }
pgAdmin.Browser.tree.refresh(selItem); pgAdmin.Browser.tree.refresh(selItem);
setReload(!reload); setReload(!reload);
}) })
.catch(function (error) { .catch(function (error) {
setLoaderText(''); setLoaderText('');
Notify.alert( pgAdmin.Browser.notifier.alert(
gettext('Error deleting %s', selectedItemData._label.toLowerCase()), gettext('Error deleting %s', selectedItemData._label.toLowerCase()),
_.isUndefined(error.response) ? error.message : error.response.data.errormsg _.isUndefined(error.response) ? error.message : error.response.data.errormsg
); );
@ -193,7 +194,7 @@ export function CollectionNodeView({
}; };
if (confirm) { if (confirm) {
Notify.confirm(title, msg, dropNodeProperties, null); pgAdmin.Browser.notifier.confirm(title, msg, dropNodeProperties, null);
} else { } else {
dropNodeProperties(); dropNodeProperties();
} }
@ -203,9 +204,9 @@ export function CollectionNodeView({
if (node){ if (node){
let nodeObj = let nodeObj =
pgAdmin.Browser.Nodes[itemNodeData?._type.replace('coll-', '')]; pgAdmin.Browser.Nodes[nodeData?._type.replace('coll-', '')];
let url = generateCollectionURL.call(nodeObj, item, 'properties'); let url = generateCollectionURL.call(nodeObj, nodeItem, 'properties');
const api = getApiInstance(); const api = getApiInstance();
@ -213,8 +214,8 @@ export function CollectionNodeView({
let column = {}; let column = {};
setLoaderText(gettext('Loading...')); setLoaderText(gettext('Loading...'));
if (itemNodeData._type.indexOf('coll-') > -1 && !_.isUndefined(nodeObj.getSchema)) { if (nodeData._type.indexOf('coll-') > -1 && !_.isUndefined(nodeObj.getSchema)) {
schemaRef.current = nodeObj.getSchema?.call(nodeObj, treeNodeInfo, itemNodeData); schemaRef.current = nodeObj.getSchema?.call(nodeObj, treeNodeInfo, nodeData);
schemaRef.current?.fields.forEach((field) => { schemaRef.current?.fields.forEach((field) => {
if (node.columns.indexOf(field.id) > -1) { if (node.columns.indexOf(field.id) > -1) {
if (field.label.indexOf('?') > -1) { if (field.label.indexOf('?') > -1) {
@ -257,6 +258,10 @@ export function CollectionNodeView({
}); });
} }
if(!isStale || !isActive) {
return;
}
api({ api({
url: url, url: url,
type: 'GET', type: 'GET',
@ -271,18 +276,19 @@ export function CollectionNodeView({
setLoaderText(''); setLoaderText('');
}) })
.catch((err) => { .catch((err) => {
Notify.alert( pgAdmin.Browser.notifier.alert(
gettext('Failed to retrieve data from the server.'), gettext('Failed to retrieve data from the server.'),
gettext(err.message) gettext(err.message)
); );
}); });
setIsStale(false);
} }
}, [itemNodeData, node, item, reload]); }, [nodeData, node, nodeItem, reload]);
const CustomHeader = () => { const CustomHeader = () => {
const canDrop = evalFunc(node, node.canDrop, itemNodeData, item, treeNodeInfo); const canDrop = evalFunc(node, node.canDrop, nodeData, nodeItem, treeNodeInfo);
const canDropCascade = evalFunc(node, node.canDropCascade, itemNodeData, item, treeNodeInfo); const canDropCascade = evalFunc(node, node.canDropCascade, nodeData, nodeItem, treeNodeInfo);
const canDropForce = evalFunc(node, node.canDropForce, itemNodeData, item, treeNodeInfo); const canDropForce = evalFunc(node, node.canDropForce, nodeData, nodeItem, treeNodeInfo);
return ( return (
<Box > <Box >
<PgIconButton <PgIconButton
@ -337,7 +343,7 @@ export function CollectionNodeView({
{data.length > 0 ? {data.length > 0 ?
( (
<PgTable <PgTable
isSelectRow={!('catalog' in treeNodeInfo) && (itemNodeData.label !== 'Catalogs') && _.isUndefined(node?.canSelect)} isSelectRow={!('catalog' in treeNodeInfo) && (nodeData.label !== 'Catalogs') && _.isUndefined(node?.canSelect)}
CustomHeader={CustomHeader} CustomHeader={CustomHeader}
className={classes.autoResizer} className={classes.autoResizer}
columns={pgTableColumns} columns={pgTableColumns}
@ -361,17 +367,19 @@ export function CollectionNodeView({
); );
} }
CollectionNodeView.propTypes = { CollectionNodeProperties.propTypes = {
node: PropTypes.func, node: PropTypes.func,
itemData: PropTypes.object, itemData: PropTypes.object,
itemNodeData: PropTypes.object, nodeData: PropTypes.object,
treeNodeInfo: PropTypes.object, treeNodeInfo: PropTypes.object,
item: PropTypes.object, nodeItem: PropTypes.object,
pgBrowser: PropTypes.object,
preferences: PropTypes.object, preferences: PropTypes.object,
sid: PropTypes.number, sid: PropTypes.number,
did: PropTypes.number, did: PropTypes.number,
row: PropTypes.object, row: PropTypes.object,
serverConnected: PropTypes.bool, serverConnected: PropTypes.bool,
value: PropTypes.bool, value: PropTypes.bool,
isActive: PropTypes.bool,
isStale: PropTypes.bool,
setIsStale: PropTypes.func,
}; };

View File

@ -0,0 +1,247 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React, { useEffect, useMemo, useRef } from 'react';
import getApiInstance from 'sources/api_instance';
import {getHelpUrl, getEPASHelpUrl} from 'pgadmin.help';
import SchemaView from 'sources/SchemaView';
import gettext from 'sources/gettext';
import { generateNodeUrl } from '../../browser/static/js/node_ajax';
import { usePgAdmin } from '../../static/js/BrowserComponent';
import { LAYOUT_EVENTS, LayoutDockerContext } from '../../static/js/helpers/Layout';
import usePreferences from '../../preferences/static/js/store';
import PropTypes from 'prop-types';
export default function ObjectNodeProperties({panelId, node, treeNodeInfo, nodeData, actionType, formType, onEdit, onSave, onClose,
isActive, isStale, setIsStale}) {
const layoutDocker = React.useContext(LayoutDockerContext);
const nodeType = nodeData?._type;
const pgAdmin = usePgAdmin();
let serverInfo = treeNodeInfo && ('server' in treeNodeInfo) &&
pgAdmin.Browser.serverInfo && pgAdmin.Browser.serverInfo[treeNodeInfo.server._id];
let inCatalog = treeNodeInfo && ('catalog' in treeNodeInfo);
let urlBase = generateNodeUrl.call(node, treeNodeInfo, actionType, nodeData, false, node.url_jump_after_node);
const api = getApiInstance();
const url = (isNew)=>{
return urlBase + (isNew ? '' : nodeData._id);
};
const isDirty = useRef(false); // usefull for warnings
let warnOnCloseFlag = true;
const confirmOnCloseReset = usePreferences().getPreferencesForModule('browser').confirm_on_properties_close;
let updatedData = ['table', 'partition'].includes(nodeType) && !_.isEmpty(nodeData.rows_cnt) ? {rows_cnt: nodeData.rows_cnt} : undefined;
let onError = (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);
}
};
/* Called when dialog is opened in edit mode, promise required */
let initData = ()=>new Promise((resolve, reject)=>{
if(actionType === 'create') {
resolve({});
} else {
// Do not call the API if tab is not active.
if(!isActive && actionType == 'properties') {
return;
}
api.get(url(false))
.then((res)=>{
resolve(res.data);
})
.catch((err)=>{
pgAdmin.Browser.notifier.pgNotifier('error', err, '', function(msg) {
if (msg == 'CRYPTKEY_SET') {
return Promise.resolve(initData());
} else if (msg == 'CRYPTKEY_NOT_SET') {
reject(gettext('The master password is not set.'));
}
reject(err);
});
})
.then(()=>{
// applies only for properties tab
setIsStale?.(false);
});
}
});
/* on save button callback, promise required */
const onSaveClick = (isNew, data)=>new Promise((resolve, reject)=>{
return api({
url: url(isNew),
method: isNew ? 'POST' : 'PUT',
data: data,
}).then((res)=>{
/* Don't warn the user before closing dialog */
warnOnCloseFlag = false;
resolve(res.data);
onSave?.(res.data);
}).catch((err)=>{
pgAdmin.Browser.notifier.pgNotifier('error-noalert', err, '', function(msg) {
if (msg == 'CRYPTKEY_SET') {
return Promise.resolve(onSaveClick(isNew, data));
} else if (msg == 'CRYPTKEY_NOT_SET') {
reject(gettext('The master password is not set.'));
}
reject(err);
});
});
});
/* Called when switched to SQL tab, promise required */
const getSQLValue = (isNew, changedData)=>{
const msqlUrl = generateNodeUrl.call(node, treeNodeInfo, 'msql', nodeData, !isNew, node.url_jump_after_node);
return new Promise((resolve, reject)=>{
api({
url: msqlUrl,
method: 'GET',
params: changedData,
}).then((res)=>{
resolve(res.data.data);
}).catch((err)=>{
onError(err);
reject(err);
});
});
};
/* Callback for help button */
const onHelp = (isSqlHelp=false, isNew=false)=>{
if(isSqlHelp) {
let server = treeNodeInfo.server;
let helpUrl = pgAdmin.Browser.utils.pg_help_path;
let fullUrl = '';
if (server.server_type == 'ppas' && node.epasHelp) {
fullUrl = getEPASHelpUrl(server.version);
} else {
if (node.sqlCreateHelp == '' && node.sqlAlterHelp != '') {
fullUrl = getHelpUrl(helpUrl, node.sqlAlterHelp, server.version);
} else if (node.sqlCreateHelp != '' && node.sqlAlterHelp == '') {
fullUrl = getHelpUrl(helpUrl, node.sqlCreateHelp, server.version);
} else {
if (isNew) {
fullUrl = getHelpUrl(helpUrl, node.sqlCreateHelp, server.version);
} else {
fullUrl = getHelpUrl(helpUrl, node.sqlAlterHelp, server.version);
}
}
}
window.open(fullUrl, 'postgres_help');
} else {
window.open(node.dialogHelp, 'pgadmin_help');
}
};
/* A warning before closing the dialog with unsaved changes, based on preference */
const warnBeforeChangesLost = (id)=>{
if(panelId != id) {
warnBeforeChangesLost();
}
if (warnOnCloseFlag && confirmOnCloseReset) {
if(isDirty.current) {
pgAdmin.Browser.notifier.confirm(
gettext('Warning'),
gettext('Changes will be lost. Are you sure you want to close the dialog?'),
function() {
onClose(true);
},
null
);
} else {
onClose(true);
}
} else {
onClose(true);
}
};
useEffect(()=>{
if(formType == 'dialog') {
/* Bind the close event and check if user should be warned */
layoutDocker.eventBus.registerListener(LAYOUT_EVENTS.CLOSING, warnBeforeChangesLost);
}
return ()=>{
layoutDocker.eventBus.deregisterListener(LAYOUT_EVENTS.CLOSING, warnBeforeChangesLost);
};
}, []);
/* All other useful details can go with this object */
const viewHelperProps = {
mode: actionType,
serverInfo: serverInfo ? {
type: serverInfo.server_type,
version: serverInfo.version,
}: undefined,
inCatalog: inCatalog,
};
let schema = node.getSchema.call(node, treeNodeInfo, nodeData);
// Show/Hide security group for nodes under the catalog
if('catalog' in treeNodeInfo
&& formType !== 'tab') {
schema.filterGroups = [gettext('Security')];
}
const key = useMemo(()=>{
if(!isActive && isStale || actionType != 'properties') {
return nodeData?._id;
} else if(isActive && isStale) {
return nodeData?._id + '0';
}
return nodeData?._id + '1';
}, [isActive, isStale, nodeData?._id]);
/* Fire at will, mount the DOM */
return (
<SchemaView
key={key}
formType={formType}
getInitData={initData}
updatedData={updatedData}
schema={schema}
viewHelperProps={viewHelperProps}
onSave={onSaveClick}
onClose={()=>onClose()}
onHelp={onHelp}
onEdit={onEdit}
onDataChange={(dataChanged)=>{
isDirty.current = dataChanged;
}}
confirmOnCloseReset={confirmOnCloseReset}
hasSQL={node.hasSQL && (actionType === 'create' || actionType === 'edit')}
getSQLValue={getSQLValue}
disableSqlHelp={node.sqlAlterHelp == '' && node.sqlCreateHelp == '' && !node.epasHelp}
disableDialogHelp={node.dialogHelp == undefined || node.dialogHelp == ''}
/>
);
}
ObjectNodeProperties.propTypes = {
panelId: PropTypes.string,
node: PropTypes.func,
treeNodeInfo: PropTypes.object,
nodeData: PropTypes.object,
actionType: PropTypes.string,
formType: PropTypes.string,
onEdit: PropTypes.func,
onSave: PropTypes.func,
onClose: PropTypes.func,
isActive: PropTypes.bool,
isStale: PropTypes.bool,
setIsStale: PropTypes.func,
};

View File

@ -0,0 +1,74 @@
import React from 'react';
import CollectionNodeProperties from './CollectionNodeProperties';
import ErrorBoundary from '../../static/js/helpers/ErrorBoundary';
import withStandardTabInfo from '../../static/js/helpers/withStandardTabInfo';
import { BROWSER_PANELS } from '../../browser/static/js/constants';
import ObjectNodeProperties from './ObjectNodeProperties';
import EmptyPanelMessage from '../../static/js/components/EmptyPanelMessage';
import gettext from 'sources/gettext';
import { Box, makeStyles } from '@material-ui/core';
import { usePgAdmin } from '../../static/js/BrowserComponent';
import PropTypes from 'prop-types';
const useStyles = makeStyles((theme) => ({
root: {
height: '100%',
background: theme.otherVars.emptySpaceBg,
display: 'flex',
flexDirection: 'column'
},
}));
function Properties(props) {
const isCollection = props.nodeData?._type?.startsWith('coll-');
const classes = useStyles();
const pgAdmin = usePgAdmin();
if(!props.node) {
return (
<Box className={classes.root}>
<Box margin={'4px auto'}>
<EmptyPanelMessage text={gettext('Please select an object in the tree view.')} />
</Box>
</Box>
);
}
if(isCollection) {
return (
<Box className={classes.root}>
<ErrorBoundary>
<CollectionNodeProperties
{...props}
/>
</ErrorBoundary>
</Box>
);
} else {
return (
<Box className={classes.root}>
<ErrorBoundary>
<ObjectNodeProperties
{...props}
actionType='properties'
formType="tab"
onEdit={()=>{
pgAdmin.Browser.Node.callbacks.show_obj_properties.call(
props.node, {action: 'edit'}
);
}}
/>
</ErrorBoundary>
</Box>
);
}
}
Properties.propTypes = {
node: PropTypes.func,
treeNodeInfo: PropTypes.object,
nodeData: PropTypes.object,
nodeItem: PropTypes.object,
};
export default withStandardTabInfo(Properties, BROWSER_PANELS.PROPERTIES);

View File

@ -11,11 +11,13 @@ import React, { useEffect } from 'react';
import { generateNodeUrl } from '../../../../browser/static/js/node_ajax'; import { generateNodeUrl } from '../../../../browser/static/js/node_ajax';
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Notify from '../../../../static/js/helpers/Notifier';
import getApiInstance from 'sources/api_instance'; import getApiInstance from 'sources/api_instance';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import CodeMirror from '../../../../static/js/components/CodeMirror'; import CodeMirror from '../../../../static/js/components/CodeMirror';
import Loader from 'sources/components/Loader'; import Loader from 'sources/components/Loader';
import withStandardTabInfo from '../../../../static/js/helpers/withStandardTabInfo';
import { BROWSER_PANELS } from '../../../../browser/static/js/constants';
import { usePgAdmin } from '../../../../static/js/BrowserComponent';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
textArea: { textArea: {
@ -28,26 +30,29 @@ const useStyles = makeStyles((theme) => ({
}, },
})); }));
export default function SQL({ nodeData, node, did, ...props }) { function SQL({nodeData, node, treeNodeInfo, isActive, isStale, setIsStale}) {
const classes = useStyles(); const classes = useStyles();
const did = ((!_.isUndefined(treeNodeInfo)) && (!_.isUndefined(treeNodeInfo['database']))) ? treeNodeInfo['database']._id: 0;
const dbConnected = !_.isUndefined(treeNodeInfo) && !_.isUndefined(treeNodeInfo['database']) ? treeNodeInfo.database.connected: false;
const [nodeSQL, setNodeSQL] = React.useState(''); const [nodeSQL, setNodeSQL] = React.useState('');
const [loaderText, setLoaderText] = React.useState(''); const [loaderText, setLoaderText] = React.useState('');
const [msg, setMsg] = React.useState(''); const pgAdmin = usePgAdmin();
useEffect(() => { useEffect(() => {
if(!isStale || !isActive) {
return;
}
let sql = '-- ' + gettext('Please select an object in the tree view.'); let sql = '-- ' + gettext('Please select an object in the tree view.');
if (node) { if(node) {
let url = generateNodeUrl.call( let url = generateNodeUrl.call(
node, node,
props.treeNodeInfo, treeNodeInfo,
'sql', 'sql',
nodeData, nodeData,
true, true,
node.url_jump_after_node node.url_jump_after_node
); );
setLoaderText('Loading...'); if (did && !dbConnected){
if (did && !props.dbConnected){
setLoaderText('');
return; return;
} }
sql = sql =
@ -55,6 +60,7 @@ export default function SQL({ nodeData, node, did, ...props }) {
if (node.hasSQL) { if (node.hasSQL) {
const api = getApiInstance(); const api = getApiInstance();
setLoaderText('Loading...');
api({ api({
url: url, url: url,
type: 'GET', type: 'GET',
@ -64,37 +70,34 @@ export default function SQL({ nodeData, node, did, ...props }) {
setNodeSQL(res.data); setNodeSQL(res.data);
setLoaderText(''); setLoaderText('');
} else { } else {
setMsg(sql); setNodeSQL(sql);
} }
}) })
.catch((e) => { .catch((e) => {
Notify.alert( pgAdmin.Browser.notifier.alert(
gettext('Error'), gettext('Error'),
gettext(e.response.data.errormsg) gettext(e.response.data.errormsg)
); );
// show failed message. // show failed message.
setMsg(gettext('Failed to retrieve data from the server.')); setNodeSQL([gettext('Failed to retrieve data from the server.'), true]);
setLoaderText('');
}).then(()=>{
setLoaderText(''); setLoaderText('');
}); });
}else{
setMsg(sql);
setLoaderText('');
} }
} }
if (sql != '') { if (sql != '') {
setMsg(sql); setNodeSQL(sql);
} }
return () => { setIsStale(false);
setNodeSQL([]); }, [isStale, isActive]);
};
}, [nodeData, props.dbConnected]);
return ( return (
<> <>
<Loader message={loaderText}/> <Loader message={loaderText}/>
<CodeMirror <CodeMirror
className={classes.textArea} className={classes.textArea}
value={nodeSQL.length > 0 ? nodeSQL : msg} value={nodeSQL}
readonly={true} readonly={true}
options={{ options={{
lineNumbers: true, lineNumbers: true,
@ -111,5 +114,10 @@ SQL.propTypes = {
treeNodeInfo: PropTypes.object, treeNodeInfo: PropTypes.object,
node: PropTypes.func, node: PropTypes.func,
dbConnected: PropTypes.bool, dbConnected: PropTypes.bool,
did: PropTypes.number did: PropTypes.number,
isActive: PropTypes.bool,
isStale: PropTypes.bool,
setIsStale: PropTypes.func,
}; };
export default withStandardTabInfo(SQL, BROWSER_PANELS.SQL);

View File

@ -12,13 +12,15 @@ import React, { useEffect } from 'react';
import PgTable from 'sources/components/PgTable'; import PgTable from 'sources/components/PgTable';
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Notify from '../../../../static/js/helpers/Notifier';
import getApiInstance from 'sources/api_instance'; import getApiInstance from 'sources/api_instance';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import { getURL } from '../../../static/utils/utils'; import { getURL } from '../../../static/utils/utils';
import Loader from 'sources/components/Loader'; import Loader from 'sources/components/Loader';
import EmptyPanelMessage from '../../../../static/js/components/EmptyPanelMessage'; import EmptyPanelMessage from '../../../../static/js/components/EmptyPanelMessage';
import { compareSizeVals, toPrettySize } from '../../../../static/js/utils'; import { compareSizeVals, toPrettySize } from '../../../../static/js/utils';
import withStandardTabInfo from '../../../../static/js/helpers/withStandardTabInfo';
import { BROWSER_PANELS } from '../../../../browser/static/js/constants';
import { usePgAdmin } from '../../../../static/js/BrowserComponent';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
emptyPanel: { emptyPanel: {
@ -155,7 +157,8 @@ function createSingleLineStatistics(data, prettifyFields) {
return res; return res;
} }
export default function Statistics({ nodeData, item, node, ...props }) { // {nodeData, node, treeNodeInfo, isActive, isStale, setIsStale}
function Statistics({ nodeData, nodeItem, node, treeNodeInfo, isActive, isStale, setIsStale }) {
const classes = useStyles(); const classes = useStyles();
const [tableData, setTableData] = React.useState([]); const [tableData, setTableData] = React.useState([]);
@ -177,13 +180,18 @@ export default function Statistics({ nodeData, item, node, ...props }) {
disableGlobalFilter: false, disableGlobalFilter: false,
}, },
]); ]);
const pgAdmin = usePgAdmin();
useEffect(() => { useEffect(() => {
if(!isStale || !isActive) {
return;
}
let url, let url,
message = gettext('Please select an object in the tree view.'); message = gettext('Please select an object in the tree view.');
if (node) { if (node) {
url = getURL(nodeData, true, props.treeNodeInfo, node, item, 'stats'); url = getURL(nodeData, true, treeNodeInfo, node, nodeItem, 'stats');
message = gettext('No statistics are available for the selected object.'); message = gettext('No statistics are available for the selected object.');
@ -207,7 +215,7 @@ export default function Statistics({ nodeData, item, node, ...props }) {
setLoaderText(''); setLoaderText('');
if (err?.response?.data?.info == 'CRYPTKEY_MISSING') { if (err?.response?.data?.info == 'CRYPTKEY_MISSING') {
Notify.pgNotifier('error', err.request, 'The master password is not set', function(mesg) { pgAdmin.Browser.notifier.pgNotifier('error', err.request, 'The master password is not set', function(mesg) {
setTimeout(function() { setTimeout(function() {
if (mesg == 'CRYPTKEY_SET') { if (mesg == 'CRYPTKEY_SET') {
setMsg('No statistics are available for the selected object.'); setMsg('No statistics are available for the selected object.');
@ -217,7 +225,7 @@ export default function Statistics({ nodeData, item, node, ...props }) {
}, 100); }, 100);
}); });
} else { } else {
Notify.alert( pgAdmin.Browser.notifier.alert(
gettext('Failed to retrieve data from the server.'), gettext('Failed to retrieve data from the server.'),
gettext(err.message) gettext(err.message)
); );
@ -232,10 +240,8 @@ export default function Statistics({ nodeData, item, node, ...props }) {
if (message != '') { if (message != '') {
setMsg(message); setMsg(message);
} }
return () => { setIsStale(false);
setTableData([]); }, [isStale, isActive]);
};
}, [nodeData]);
return ( return (
<> <>
@ -260,7 +266,12 @@ export default function Statistics({ nodeData, item, node, ...props }) {
Statistics.propTypes = { Statistics.propTypes = {
res: PropTypes.array, res: PropTypes.array,
nodeData: PropTypes.object, nodeData: PropTypes.object,
item: PropTypes.object, nodeItem: PropTypes.object,
treeNodeInfo: PropTypes.object, treeNodeInfo: PropTypes.object,
node: PropTypes.func, node: PropTypes.func,
isActive: PropTypes.bool,
isStale: PropTypes.bool,
setIsStale: PropTypes.func,
}; };
export default withStandardTabInfo(Statistics, BROWSER_PANELS.STATISTICS);

View File

@ -21,13 +21,12 @@ import CloseSharpIcon from '@material-ui/icons/CloseSharp';
import HelpIcon from '@material-ui/icons/HelpRounded'; import HelpIcon from '@material-ui/icons/HelpRounded';
import SaveSharpIcon from '@material-ui/icons/SaveSharp'; import SaveSharpIcon from '@material-ui/icons/SaveSharp';
import clsx from 'clsx'; import clsx from 'clsx';
import Notify from '../../../../static/js/helpers/Notifier';
import pgAdmin from 'sources/pgadmin'; import pgAdmin from 'sources/pgadmin';
import { DefaultButton, PgIconButton, PrimaryButton } from '../../../../static/js/components/Buttons'; import { DefaultButton, PgIconButton, PrimaryButton } from '../../../../static/js/components/Buttons';
import BaseUISchema from 'sources/SchemaView/base_schema.ui'; import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import { getBinaryPathSchema } from '../../../../browser/server_groups/servers/static/js/binary_path.ui'; import { getBinaryPathSchema } from '../../../../browser/server_groups/servers/static/js/binary_path.ui';
import { _set_dynamic_tab } from '../../../../tools/sqleditor/static/js/show_query_tool';
import { getBrowserAccesskey } from '../../../../static/js/components/ShortcutTitle'; import { getBrowserAccesskey } from '../../../../static/js/components/ShortcutTitle';
import usePreferences from '../store';
class PreferencesSchema extends BaseUISchema { class PreferencesSchema extends BaseUISchema {
constructor(initValues = {}, schemaFields = []) { constructor(initValues = {}, schemaFields = []) {
@ -168,6 +167,7 @@ export default function PreferencesComponent({ ...props }) {
const [loadTree, setLoadTree] = React.useState(0); const [loadTree, setLoadTree] = React.useState(0);
const api = getApiInstance(); const api = getApiInstance();
const firstTreeElement = React.useRef(''); const firstTreeElement = React.useRef('');
const preferencesStore = usePreferences();
useEffect(() => { useEffect(() => {
const pref_url = url_for('preferences.index'); const pref_url = url_for('preferences.index');
@ -230,7 +230,7 @@ export default function PreferencesComponent({ ...props }) {
// set Preferences schema // set Preferences schema
prefSchema.current = new PreferencesSchema(preferencesValues, preferencesData); prefSchema.current = new PreferencesSchema(preferencesValues, preferencesData);
}).catch((err) => { }).catch((err) => {
Notify.alert(err); pgAdmin.Browser.notifier.alert(err);
}); });
}, []); }, []);
function setPreferences(node, subNode, nodeData, preferencesValues, preferencesData) { function setPreferences(node, subNode, nodeData, preferencesValues, preferencesData) {
@ -328,8 +328,8 @@ export default function PreferencesComponent({ ...props }) {
element.canDelete = false; element.canDelete = false;
element.canEdit = false; element.canEdit = false;
element.editable = false; element.editable = false;
if (pgAdmin.Browser.get_preference(node.label.toLowerCase(), element.name)?.value) { if (preferencesStore.getPreferences(node.label.toLowerCase(), element.name)?.value) {
let temp = pgAdmin.Browser.get_preference(node.label.toLowerCase(), element.name).value; let temp = preferencesStore.getPreferences(node.label.toLowerCase(), element.name).value;
preferencesValues[element.id] = temp; preferencesValues[element.id] = temp;
} else { } else {
preferencesValues[element.id] = element.value; preferencesValues[element.id] = element.value;
@ -547,44 +547,19 @@ export default function PreferencesComponent({ ...props }) {
return s.name=='show_system_objects'||s.name=='show_empty_coll_nodes'||s.name.startsWith('show_node_')||s.name=='hide_shared_server'||s.name=='show_user_defined_templates'; return s.name=='show_system_objects'||s.name=='show_empty_coll_nodes'||s.name.startsWith('show_node_')||s.name=='hide_shared_server'||s.name=='show_user_defined_templates';
}); });
let requires_refresh = false; let requires_refresh = false;
/* Find the modules changed */
let modulesChanged = {};
for (const [key] of Object.entries(data.current)) { for (const [key] of Object.entries(data.current)) {
let pref = pgAdmin.Browser.get_preference_for_id(Number(key)); let pref = preferencesStore.getPreferenceForId(Number(key));
if (pref['name'] == 'dynamic_tabs') {
_set_dynamic_tab(pgAdmin.Browser, !pref['value']);
}
if (!modulesChanged[pref.module]) {
modulesChanged[pref.module] = true;
}
requires_refresh = checkRefreshRequired(pref, requires_refresh); requires_refresh = checkRefreshRequired(pref, requires_refresh);
// Sync the lock layout menu with preferences
if (pref.name == 'lock_layout') {
let fileMenu = pgAdmin.Browser.MainMenus.find((menu) => menu.name == 'file');
let layoutSubMenu = fileMenu['menuItems'].find(menu => menu.name == 'mnu_locklayout');
layoutSubMenu['menu_items'].forEach(item => {
if (item.name === 'mnu_lock_'+save_data[0]['value']) {
item.checked = true;
} else {
item.checked = false;
}
});
pgAdmin.Browser.Events.trigger('pgadmin:nw-refresh-menu-item', 'lock_layout');
}
} }
if (requiresTreeRefresh) { if (requiresTreeRefresh) {
Notify.confirm( pgAdmin.Browser.notifier.confirm(
gettext('Object explorer refresh required'), gettext('Object explorer refresh required'),
gettext('An object explorer refresh is required. Do you wish to refresh it now?'), gettext('An object explorer refresh is required. Do you wish to refresh it now?'),
function () { function () {
pgAdmin.Browser.tree.destroy({ pgAdmin.Browser.tree.destroy({
success: function () { success: function () {
pgAdmin.Browser.initializeBrowserTree(pgAdmin.Browser); // pgAdmin.Browser.initializeBrowserTree(pgAdmin.Browser);
return true; return true;
}, },
}); });
@ -598,7 +573,7 @@ export default function PreferencesComponent({ ...props }) {
} }
if (requires_refresh) { if (requires_refresh) {
Notify.confirm( pgAdmin.Browser.notifier.confirm(
gettext('Refresh required'), gettext('Refresh required'),
gettext('A page refresh is required to apply the theme. Do you wish to refresh the page now?'), gettext('A page refresh is required to apply the theme. Do you wish to refresh the page now?'),
function () { function () {
@ -612,10 +587,10 @@ export default function PreferencesComponent({ ...props }) {
); );
} }
// Refresh preferences cache // Refresh preferences cache
pgAdmin.Browser.cache_preferences(modulesChanged); preferencesStore.cache();
props.closeModal(); props.closeModal();
}).catch((err) => { }).catch((err) => {
Notify.alert(err.response.data); pgAdmin.Browser.notifier.alert(err.response.data);
}); });
} }

View File

@ -10,8 +10,8 @@
import React from 'react'; import React from 'react';
import gettext from 'sources/gettext'; import gettext from 'sources/gettext';
import PreferencesComponent from './components/PreferencesComponent'; import PreferencesComponent from './components/PreferencesComponent';
import Notify from '../../../static/js/helpers/Notifier';
import PreferencesTree from './components/PreferencesTree'; import PreferencesTree from './components/PreferencesTree';
import pgAdmin from 'sources/pgadmin';
export default class Preferences { export default class Preferences {
static instance; static instance;
@ -50,7 +50,7 @@ export default class Preferences {
show() { show() {
// Render Preferences component // Render Preferences component
Notify.showModal(gettext('Preferences'), (closeModal) => { pgAdmin.Browser.notifier.showModal(gettext('Preferences'), (closeModal) => {
return <PreferencesComponent return <PreferencesComponent
renderTree={(prefTreeData) => { renderTree={(prefTreeData) => {
// Render preferences tree component // Render preferences tree component

View File

@ -0,0 +1,81 @@
import {create} from 'zustand';
import getApiInstance from '../../../static/js/api_instance';
import url_for from 'sources/url_for';
import pgAdmin from 'sources/pgadmin';
const usePreferences = create((set, get)=>({
data: {},
version: 0,
isLoading: true,
failed: false,
getPreferences: (module, preference)=>{
return _.find(
get().data, {'module': module, 'name': preference}
);
},
getPreferencesForModule: function(module) {
let preferences = {};
_.forEach(
_.filter(get().data, {'module': module}),
(preference) => {
preferences[preference.name] = preference.value;
}
);
return preferences;
},
/* Get preference of an id, id is numeric */
getPreferenceForId : function(id) {
return _.find(get().data, {'id': id});
},
cache: async ()=>{
try {
const res = await getApiInstance().get(url_for('preferences.get_all'));
set({data: res.data, version: (new Date()).getTime(), isLoading: false});
} catch (error) {
set({data: {}, version: (new Date()).getTime(), isLoading: false, failed: true});
pgAdmin.Browser.notifier.pgRespErrorNotify(error);
}
}
}));
export default usePreferences;
// Setup two way broadcast channel
// This will help to sync preferences in iframes/tabs of Query tool, debugger, etc.
const preferenceChangeBroadcast = new BroadcastChannel('preference-change');
export function setupPreferenceBroadcast() {
const broadcast = (state)=>{
preferenceChangeBroadcast.postMessage({
data: state.data,
version: state.version,
});
};
// broadcast when state changed.
usePreferences.subscribe((state)=>{
broadcast(state);
});
// if asked for sync from a tab then broadcast once.
preferenceChangeBroadcast.onmessage = (ev)=>{
if(ev.data == 'sync') {
broadcast(usePreferences.getState());
}
};
}
export function listenPreferenceBroadcast() {
preferenceChangeBroadcast.onmessage = (ev)=>{
const currState = usePreferences.getState();
if(currState.version < ev.data.version) {
usePreferences.setState({
...usePreferences.getState(),
...ev.data,
});
}
};
// initial sync
preferenceChangeBroadcast.postMessage('sync');
}

View File

@ -7,13 +7,7 @@
// //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import getApiInstance from '../../../static/js/api_instance'; define('pgadmin.settings', ['sources/pgadmin'], function(pgAdmin) {
import Notify from '../../../static/js/helpers/Notifier';
import { getBrowser } from '../../../static/js/utils';
define('pgadmin.settings', [
'sources/pgadmin', 'sources/gettext', 'sources/url_for',
], function(pgAdmin, gettext, url_for) {
// This defines the Preference/Options Dialog for pgAdmin IV. // This defines the Preference/Options Dialog for pgAdmin IV.
pgAdmin = pgAdmin || window.pgAdmin || {}; pgAdmin = pgAdmin || window.pgAdmin || {};
@ -35,34 +29,7 @@ define('pgadmin.settings', [
// We will force unload method to not to save current layout // We will force unload method to not to save current layout
// and reload the window // and reload the window
show: function() { show: function() {
Notify.confirm(gettext('Reset layout'), pgAdmin.Browser.docker.resetLayout();
gettext('Are you sure you want to reset the current layout? This will cause the application to reload and any un-saved data will be lost.'),
function() {
const reloadingIndicator = document.createElement('div');
reloadingIndicator.setAttribute('id', 'reloading-indicator');
document.body.appendChild(reloadingIndicator);
// Delete the record from database as well, then only reload page
getApiInstance().delete(url_for('settings.reset_layout'))
.then(()=>{
window.onbeforeunload = null;
// Now reload page
location.reload(true);
let {name: browser} = getBrowser();
if(browser == 'Nwjs') {
pgAdmin.Browser.create_menus();
}
})
.catch(()=>{
console.warn(
'Something went wrong on server while resetting layout.'
);
});
},
function() {
// Do nothing as user canceled the operation.
}
);
}, },
}; };

View File

@ -9,9 +9,8 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import BrowserComponent from '../js/BrowserComponent';
import MainMenuFactory from '../../browser/static/js/MainMenuFactory'; import MainMenuFactory from '../../browser/static/js/MainMenuFactory';
import AppMenuBar from '../js/AppMenuBar';
import ObjectBreadcrumbs from '../js/components/ObjectBreadcrumbs';
import Theme from '../js/Theme'; import Theme from '../js/Theme';
define('app', [ define('app', [
@ -51,18 +50,25 @@ define('app', [
// Create menus after all modules are initialized. // Create menus after all modules are initialized.
MainMenuFactory.createMainMenus(); MainMenuFactory.createMainMenus();
const menuContainerEle = document.querySelector('#main-menu-container'); // const menuContainerEle = document.querySelector('#main-menu-container');
if(menuContainerEle) { // if(menuContainerEle) {
ReactDOM.render( // ReactDOM.render(
<Theme> // <Theme>
<AppMenuBar /> // <AppMenuBar />
</Theme>, menuContainerEle // </Theme>, menuContainerEle
); // );
} // }
// ReactDOM.render(
// <Theme>
// <ObjectBreadcrumbs pgAdmin={pgAdmin} />
// </Theme>, document.querySelector('#object-breadcrumbs')
// );
ReactDOM.render( ReactDOM.render(
<Theme> <Theme>
<ObjectBreadcrumbs pgAdmin={pgAdmin} /> <BrowserComponent pgAdmin={pgAdmin} />
</Theme>, document.querySelector('#object-breadcrumbs') </Theme>,
document.querySelector('#root')
); );
}); });

View File

@ -13,7 +13,3 @@
@import 'node_modules/@simonwep/pickr/dist/themes/monolith.min.css'; @import 'node_modules/@simonwep/pickr/dist/themes/monolith.min.css';
@import 'node_modules/uplot/dist/uPlot.min.css'; @import 'node_modules/uplot/dist/uPlot.min.css';
/* wcDocker dependencies */
@import 'node_modules/webcabin-docker/Build/wcDocker.css';
@import 'node_modules/jquery-contextmenu/dist/jquery.contextMenu.css';

View File

@ -1 +0,0 @@
/* This dummy CSS required by wcdocker */

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