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
run: |
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 \
webpack.* \
karma.conf.js \
jest.config.js \
babel.* \
./pgadmin/static/js/generated/.cache
#########################################################################

View File

@ -42,7 +42,7 @@ linter:
cd web && yarn run linter
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:
cd web && yarn run audit
@ -77,10 +77,10 @@ check-feature: install-node bundle
cd web && python regression/runtests.py --pkg feature_tests
check-js: install-node linter
cd web && yarn run karma start --single-run
cd web && yarn run test:js-once
check-js-coverage:
cd web && yarn run test:karma-coverage
cd web && yarn run test:js-coverage
# Include all clean sub-targets in clean
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
PostgreSQL (http://www.postgresql.org) database.
PostgreSQL (http://www.postgresql.org) database.
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
@ -9,9 +9,8 @@ tarball or a git checkout.
## Architecture
pgAdmin 4 is written as a web application in Python, using jQuery and Bootstrap
for the client side processing and UI. On the server side, Flask is being
utilised.
pgAdmin 4 is written as a web application with Python(Flask) on the server side
and ReactJS, HTML5 with CSS for the client side processing and UI.
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/
@ -22,7 +21,7 @@ which will execute the Python server and display the UI.
To build the runtime, the following packages must be installed:
* NodeJS 12+
* NodeJS 16+
* Yarn
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
$ python3 -m venv venv
```
2. Now activate the virtual environment:
```bash
$ source venv/bin/activate
```
3. Some of the components used by pgAdmin require a very recent version of *pip*,
so update that to the latest:
```bash
$ pip install --upgrade pip
```
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
packages:
@ -80,20 +79,20 @@ simple - adapt as required for your distribution:
```bash
(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
additional requirements from web/regression/requirements.txt:
```bash
(venv) $ pip install -r $PGADMIN4_SRC/web/regression/requirements.txt
```
5. Create a local configuration file for pgAdmin. Edit
$PGADMIN4_SRC/web/config_local.py and add any desired configuration options
(use the config.py file as a reference - any settings duplicated in
config_local.py will override those in config.py). A typical development
configuration may look like:
```python
from config import *
@ -126,7 +125,7 @@ simple - adapt as required for your distribution:
'pgadmin4-server.db'
)
```
This configuration allows easy switching between server and desktop modes
for testing.
@ -137,13 +136,13 @@ simple - adapt as required for your distribution:
```bash
(venv) $ python3 $PGADMIN4_SRC/web/setup.py
```
or by starting pgAdmin 4:
```bash
(venv) $ python3 $PGADMIN4_SRC/web/pgAdmin4.py
```
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
command line interaction with the setup program.
@ -228,7 +227,7 @@ To build a source tarball:
(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:
```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
**security (at) pgadmin (dot) org**.
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,
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
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
a web application, which is written using NWjs (Node Webkit).

View File

@ -64,7 +64,7 @@ All HTML must be HTML 5 compliant.
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
is rendered from templates using Jinja2 (often to inject the users settings) or
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. |
+-------------------------+---------------------------------------------------------------------------------------------------------+
| *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 |
| | 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
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
collections which are empty will be hidden from browser tree.
* When the *Show system objects?* switch is set to *True*, the client will

View File

@ -2,7 +2,7 @@
html{
background-color: #fff;
}
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;
-webkit-font-smoothing: antialiased;
background-color: #fff;
height: 100vh;
}
div.body {

View File

@ -224,7 +224,7 @@ _copy_code() {
cp "${SOURCEDIR}/pkg/linux/config_distro.py" "${SERVERROOT}/usr/${APP_NAME}/web/"
cd "${SERVERROOT}/usr/${APP_NAME}/web/" || exit
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 "feature_tests" -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/"
cd "${BUNDLE_DIR}/Contents/Resources/web" || exit
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 "feature_tests" -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,
'es6': true,
'amd': true,
'jasmine': true,
},
'extends': [
'eslint:recommended',

View File

@ -12,7 +12,6 @@ module.exports = {
'browser': true,
'es6': true,
'amd': true,
'jasmine': true,
},
'extends': [
'eslint:recommended',
@ -41,10 +40,11 @@ module.exports = {
'plugins': [
'react',
'@babel',
'jest'
],
'overrides': [
{
'files': ['**/*.ts', '**/*.tsx'],
'files': ['**/*.{ts,tsx}'],
'plugins': [
'@typescript-eslint',
],
@ -55,10 +55,20 @@ module.exports = {
'@typescript-eslint/no-this-alias': ['off'],
}
},
{
'files': ['**/*{spec,test}.{js,jsx}', './regression/javascript/**/*.{js}'],
'extends': ['eslint:recommended'],
'env': {
'jest': true
}
},
],
'globals': {
'_': true,
'module': true,
'__dirname': true,
'global': true,
'jest': true
},
'rules': {
'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/plugin-proposal-object-rest-spread": "^7.10.1",
"@babel/plugin-syntax-jsx": "^7.16.0",
"@babel/plugin-transform-runtime": "^7.22.15",
"@babel/preset-env": "^7.10.2",
"@babel/preset-typescript": "^7.22.5",
"@emotion/core": "^10.0.14",
@ -20,44 +21,35 @@
"@emotion/styled": "^11.11.0",
"@emotion/utils": "^1.0.0",
"@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/parser": "^5.57.0",
"@wojtekmaj/enzyme-adapter-react-17": "^0.8.0",
"autoprefixer": "^10.2.4",
"axios-mock-adapter": "^1.17.0",
"babel-loader": "^8.1.0",
"browserify": "^17.0.0",
"buffer": "^6.0.3",
"copy-webpack-plugin": "^11.0.0",
"core-js": "^3.2.1",
"cross-env": "^7.0.3",
"css-loader": "^6.7.2",
"css-minimizer-webpack-plugin": "^5.0.0",
"enzyme": "^3.11.0",
"eslint": "^8.37.0",
"eslint-plugin-jest": "^27.4.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.3.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",
"imagemin": "^8.0.1",
"imagemin-mozjpeg": "^10.0.0",
"imagemin-optipng": "^8.0.0",
"imports-loader": "^4.0.1",
"is-docker": "^2.1.1",
"istanbul-instrumenter-loader": "^3.0.1",
"jasmine-core": "3.10.1",
"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",
"jest": "^29.6.4",
"jest-environment-jsdom": "^29.6.4",
"loader-utils": "^3.2.1",
"mini-css-extract-plugin": "^2.7.6",
"postcss-loader": "^7.1.0",
@ -87,6 +79,7 @@
"@date-io/core": "^1.3.6",
"@date-io/date-fns": "1.x",
"@emotion/sheet": "^1.0.1",
"@fortawesome/fontawesome-free": "latest",
"@material-ui/core": "4.12.4",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "4.0.0-alpha.61",
@ -103,7 +96,6 @@
"axios": "^1.4.0",
"babelify": "~10.0.0",
"bignumber.js": "^9.0.1",
"bootstrap": "^4.3.1",
"brace": "^0.11.1",
"browserfs": "^1.4.3",
"chart.js": "^3.0.0",
@ -120,11 +112,8 @@
"immutability-helper": "^3.0.0",
"insert-if": "^1.1.0",
"ip-address": "^7.1.0",
"jquery": "^3.6.0",
"jquery-contextmenu": "^2.9.2",
"json-bignumber": "^1.0.1",
"jsoneditor": "^9.5.4",
"karma-coverage": "^2.0.3",
"leaflet": "^1.5.1",
"lodash": "4.*",
"ml-matrix": "^6.5.0",
@ -150,10 +139,11 @@
"react-dom": "^17.0.1",
"react-draggable": "^4.4.4",
"react-dropzone": "^14.2.1",
"react-frame-component": "^5.2.6",
"react-leaflet": "^3.2.2",
"react-new-window": "^1.0.1",
"react-resize-detector": "^9.1.0",
"react-rnd": "^10.3.5",
"react-router-dom": "^6.2.2",
"react-select": "^5.7.2",
"react-table": "^7.6.3",
"react-timer-hook": "^3.0.5",
@ -163,16 +153,15 @@
"socket.io-client": "^4.5.0",
"split.js": "^1.5.10",
"styled-components": "^5.2.1",
"tempusdominus-core": "^5.19.3",
"uplot": "^1.6.24",
"uplot-react": "^1.1.4",
"valid-filename": "^2.0.1",
"webcabin-docker": "https://github.com/pgadmin-org/wcdocker#460fc6d90ba170bb177faaa8277f5fbb8279522a",
"wkx": "^0.5.0",
"xterm": "^4.11.0",
"xterm-addon-fit": "^0.5.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": {
"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:dev": "yarn run linter && yarn run webpacker",
"bundle:analyze": "cross-env NODE_ENV=production ANALYZE=true yarn run bundle:dev",
"bundle": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=3072 yarn run bundle:dev",
"test:karma-once": "yarn run linter && yarn run karma start --single-run",
"test:karma": "yarn run linter && yarn run karma start",
"test:karma-coverage": "yarn run test:karma-once --reporters coverage,progress",
"bundle": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=2048 yarn run bundle:dev",
"test:js-once": "yarn run linter && yarn run jest --maxWorkers=50%",
"test:js": "yarn run test:js-once --watch",
"test:js-coverage": "yarn run test:js-once --collect-coverage",
"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",
"auditjs-html": "yarn audit --json | yarn run yarn-audit-html --output ../auditjs.html",
"auditjs": "yarn audit --groups dependencies",

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@
import url_for from 'sources/url_for';
import userInfo from 'pgadmin.user_management.current_user';
import pgConst from 'pgadmin.browser.constants';
import {AUTH_METHODS} from 'pgadmin.browser.constants';
function fetch_ticket() {
// Fetch the Kerberos Updated ticket through SPNEGO
@ -52,7 +52,7 @@ function fetch_ticket_lifetime () {
function validate_kerberos_ticket() {
// Ping pgAdmin server every 10 seconds
// 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() {
let newPromise = fetch_ticket_lifetime();

View File

@ -88,61 +88,6 @@ PASS_ERROR = gettext('Error: {error}\n {pass_error}').format(
class BrowserModule(PgAdminModule):
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):
register_browser_preferences(self)
@ -156,7 +101,6 @@ class BrowserModule(PgAdminModule):
'browser.check_master_password',
'browser.set_master_password',
'browser.reset_master_password',
'browser.lock_layout',
]
def register(self, app, options):
@ -416,47 +360,6 @@ def _get_supported_browser():
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("/")
@pgCSRFProtect.exempt
@login_required
@ -491,15 +394,6 @@ def index():
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
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
prefs = Preferences.module('browser')
current_ui_lock = prefs.preference('lock_layout').get()
# Try to fetch current libpq version from the driver
try:
from config import PG_DEFAULT_DRIVER
@ -672,7 +565,6 @@ def utils():
auth_source=auth_source,
heartbeat_timeout=config.SERVER_HEARTBEAT_TIMEOUT,
password_length_min=config.PASSWORD_LENGTH_MIN,
current_ui_lock=current_ui_lock,
shared_storage_list=shared_storage_list,
restricted_shared_storage_list=[] if current_user.has_role(
"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")
@pgCSRFProtect.exempt
@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
# We can't access app context here so cannot
# use app.config['SECURITY_CHANGEABLE']

View File

@ -15,12 +15,6 @@ from pgadmin.utils.constants import PREF_LABEL_DISPLAY,\
from flask import current_app
import config
LOCK_LAYOUT_LEVEL = {
'PREVENT_DOCKING': 'docking',
'FULL': 'full',
'NONE': 'none'
}
def register_browser_preferences(self):
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(
'properties', 'table_row_count_threshold',
gettext("Count rows if estimated less than"), 'integer', 2000,

View File

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

View File

@ -8,7 +8,6 @@
//////////////////////////////////////////////////////////////
import CheckConstraintSchema from './check_constraint.ui';
import Notify from '../../../../../../../../../../static/js/helpers/Notifier';
import _ from 'lodash';
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))
.then(({data: res})=>{
if (res.success == 1) {
Notify.success(res.info);
pgAdmin.Browser.notifier.success(res.info);
t.removeIcon(i);
data.valid = true;
data.icon = 'icon-check_constraint';
@ -83,7 +82,7 @@ define('pgadmin.node.check_constraint', [
}
})
.catch((error)=>{
Notify.pgRespErrorNotify(error);
pgAdmin.Browser.notifier.pgRespErrorNotify(error);
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 { getNodeAjaxOptions, getNodeListByName } from '../../../../../../../../../static/js/node_ajax';
import TableSchema from '../../../../static/js/table.ui';
import Notify from '../../../../../../../../../../static/js/helpers/Notifier';
import pgAdmin from 'sources/pgadmin';
function getData(data) {
let res = [];
@ -275,7 +275,7 @@ export default class ExclusionConstraintSchema extends BaseUISchema {
options: this.fieldOptions.amname,
deferredDepChange: (state, source, topState, actionObj)=>{
return new Promise((resolve)=>{
Notify.confirm(
pgAdmin.Browser.notifier.confirm(
gettext('Change access method?'),
gettext('Changing access method will clear columns collection'),
function () {

View File

@ -8,7 +8,6 @@
//////////////////////////////////////////////////////////////
import { getNodeForeignKeySchema } from './foreign_key.ui';
import Notify from '../../../../../../../../../../static/js/helpers/Notifier';
import _ from 'lodash';
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))
.then(({data: res})=>{
if (res.success == 1) {
Notify.success(res.info);
pgAdmin.Browser.notifier.success(res.info);
t.removeIcon(i);
data.valid = true;
data.icon = 'icon-foreign_key';
@ -79,7 +78,7 @@ define('pgadmin.node.foreign_key', [
}
})
.catch((error)=>{
Notify.pgRespErrorNotify(error);
pgAdmin.Browser.notifier.pgRespErrorNotify(error);
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 _ from 'lodash';
import { isEmptyString } from 'sources/validators';
import Notify from '../../../../../../../../../static/js/helpers/Notifier';
import pgAdmin from 'sources/pgadmin';
function inSchema(node_info) {
@ -476,7 +476,7 @@ export default class IndexSchema extends BaseUISchema {
};
if((state.amname != actionObj?.oldState.amname) && state.columns?.length > 0) {
return new Promise((resolve)=>{
Notify.confirm(
pgAdmin.Browser.notifier.confirm(
gettext('Warning'),
gettext('Changing access method will clear columns collection. Do you want to continue?'),
function () {

View File

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

View File

@ -7,15 +7,14 @@
//
//////////////////////////////////////////////////////////////
import RuleSchema from './rule.ui';
import Notify from '../../../../../../../../../static/js/helpers/Notifier';
import _ from 'lodash';
import getApiInstance from '../../../../../../../../../static/js/api_instance';
define('pgadmin.node.rule', [
'sources/gettext', 'sources/url_for', 'jquery',
'sources/gettext', 'sources/url_for',
'sources/pgadmin', 'pgadmin.browser',
'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
@ -137,14 +136,14 @@ define('pgadmin.node.rule', [
let data = d;
getApiInstance().put(obj.generate_url(i, 'obj' , d, true), {'is_enable_rule' : 'O'})
.then(()=>{
Notify.success('Rule updated.');
pgAdmin.Browser.notifier.success('Rule updated.');
t.removeIcon(i);
data.icon = 'icon-rule';
t.addIcon(i, {icon: data.icon});
t.updateAndReselectNode(i, data);
})
.catch((error)=>{
Notify.pgRespErrorNotify(error);
pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.refresh(i);
});
},
@ -162,14 +161,14 @@ define('pgadmin.node.rule', [
let data = d;
getApiInstance().put(obj.generate_url(i, 'obj' , d, true), {'is_enable_rule' : 'D'})
.then(()=>{
Notify.success('Rule updated');
pgAdmin.Browser.notifier.success('Rule updated');
t.removeIcon(i);
data.icon = 'icon-rule-bad';
t.addIcon(i, {icon: data.icon});
t.updateAndReselectNode(i, data);
})
.catch((error)=>{
Notify.pgRespErrorNotify(error);
pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.refresh(i);
});
},

View File

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

View File

@ -7,20 +7,19 @@
//////////////////////////////////////////////////////////////
import { getNodeTableSchema } from './table.ui';
import Notify from '../../../../../../../../static/js/helpers/Notifier';
import _ from 'lodash';
import getApiInstance from '../../../../../../../../static/js/api_instance';
define('pgadmin.node.table', [
'pgadmin.tables.js/enable_disable_triggers',
'sources/gettext', 'sources/url_for', 'jquery',
'sources/gettext', 'sources/url_for',
'sources/pgadmin', 'pgadmin.browser',
'pgadmin.node.schema.dir/child','pgadmin.node.schema.dir/schema_child_tree_node',
'pgadmin.browser.collection', 'pgadmin.node.column',
'pgadmin.node.constraints',
], function(
tableFunctions,
gettext, url_for, $, pgAdmin, pgBrowser, SchemaChild, SchemaChildTreeNode
gettext, url_for, pgAdmin, pgBrowser, SchemaChild, SchemaChildTreeNode
) {
if (!pgBrowser.Nodes['coll-table']) {
@ -145,7 +144,6 @@ define('pgadmin.node.table', [
enable_triggers_on_table: function(args) {
tableFunctions.enableTriggers(
pgBrowser.tree,
Notify,
this.generate_url.bind(this),
args
);
@ -154,7 +152,6 @@ define('pgadmin.node.table', [
disable_triggers_on_table: function(args) {
tableFunctions.disableTriggers(
pgBrowser.tree,
Notify,
this.generate_url.bind(this),
args
);
@ -183,7 +180,7 @@ define('pgadmin.node.table', [
if (!d)
return false;
Notify.confirm(
pgAdmin.Browser.notifier.confirm(
gettext('Truncate Table'),
gettext('Are you sure you want to truncate table %s?', d.label),
function () {
@ -191,18 +188,18 @@ define('pgadmin.node.table', [
getApiInstance().put(obj.generate_url(i, 'truncate' , d, true), params)
.then(({data: res})=>{
if (res.success == 1) {
Notify.success(res.info);
pgAdmin.Browser.notifier.success(res.info);
t.removeIcon(i);
data.icon = data.is_partitioned ? 'icon-partition': 'icon-table';
t.addIcon(i, {icon: data.icon});
t.updateAndReselectNode(i, data);
}
if (res.success == 2) {
Notify.error(res.info);
pgAdmin.Browser.notifier.error(res.info);
}
})
.catch((error)=>{
Notify.pgRespErrorNotify(error);
pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.refresh(i);
});
}, function() {/*This is intentional (SonarQube)*/}
@ -218,7 +215,7 @@ define('pgadmin.node.table', [
if (!d)
return false;
Notify.confirm(
pgAdmin.Browser.notifier.confirm(
gettext('Reset statistics'),
gettext('Are you sure you want to reset the statistics for table "%s"?', d._label),
function () {
@ -226,7 +223,7 @@ define('pgadmin.node.table', [
getApiInstance().delete(obj.generate_url(i, 'reset' , d, true))
.then(({data: res})=>{
if (res.success == 1) {
Notify.success(res.info);
pgAdmin.Browser.notifier.success(res.info);
t.removeIcon(i);
data.icon = data.is_partitioned ? 'icon-partition': 'icon-table';
t.addIcon(i, {icon: data.icon});
@ -234,7 +231,7 @@ define('pgadmin.node.table', [
}
})
.catch((error)=>{
Notify.pgRespErrorNotify(error);
pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.refresh(i);
});
},
@ -257,12 +254,12 @@ define('pgadmin.node.table', [
// Fetch the total rows of a table
getApiInstance().get(obj.generate_url(i, 'count_rows' , newD, true))
.then(({data: res})=>{
Notify.success(res.info, null);
pgAdmin.Browser.notifier.success(res.info, null);
d.rows_cnt = res.data.total_rows;
t.updateAndReselectNode(i, d);
})
.catch((error)=>{
Notify.pgRespErrorNotify(error);
pgAdmin.Browser.notifier.pgRespErrorNotify(error);
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 { getNodeExclusionConstraintSchema } from '../../constraints/exclusion_constraint/static/js/exclusion_constraint.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) {
const spcname = ()=>getNodeListByName('tablespace', treeNodeInfo, itemNodeData, {}, (m)=>{
@ -622,7 +622,7 @@ export default class TableSchema extends BaseUISchema {
group: 'advanced', min_version: 90600,
depChange: (state)=>{
if (state.rlspolicy && this.origData.rlspolicy != state.rlspolicy) {
Notify.alert(
pgAdmin.Browser.notifier.alert(
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')
);
@ -752,7 +752,7 @@ export default class TableSchema extends BaseUISchema {
};
if(!isEmptyString(state.typname) && isEmptyString(actionObj.oldState.typname)) {
return new Promise((resolve)=>{
Notify.confirm(
pgAdmin.Browser.notifier.confirm(
gettext('Remove column definitions?'),
gettext('Changing \'Of type\' will remove column definitions.'),
function () {

View File

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

View File

@ -11,17 +11,16 @@ import MViewSchema from './mview.ui';
import { getNodeListByName, getNodeAjaxOptions } from '../../../../../../../static/js/node_ajax';
import { getNodePrivilegeRoleSchema } from '../../../../../static/js/privilege.ui';
import { getNodeVacuumSettingsSchema } from '../../../../../static/js/vacuum.ui';
import Notify from '../../../../../../../../static/js/helpers/Notifier';
import _ from 'lodash';
import getApiInstance from '../../../../../../../../static/js/api_instance';
define('pgadmin.node.mview', [
'sources/gettext', 'sources/url_for', 'jquery',
'sources/gettext', 'sources/url_for',
'sources/pgadmin', 'pgadmin.browser',
'pgadmin.node.schema.dir/child',
'pgadmin.node.schema.dir/schema_child_tree_node', 'sources/utils',
], function(
gettext, url_for, $, pgAdmin, pgBrowser,
gettext, url_for, pgAdmin, pgBrowser,
schemaChild, schemaChildTreeNode, commonUtils
) {
@ -176,7 +175,7 @@ define('pgadmin.node.mview', [
if (pgBrowser.tree.hasParent(j)) {
j = pgBrowser.tree.parent(j);
} 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;
}
}
@ -193,7 +192,7 @@ define('pgadmin.node.mview', [
api.get(obj.generate_url(i, 'check_utility_exists' , d, true))
.then(({data: res})=>{
if (!res.success) {
Notify.alert(
pgAdmin.Browser.notifier.alert(
gettext('Utility not found'),
res.errormsg
);
@ -206,20 +205,20 @@ define('pgadmin.node.mview', [
//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);
} else {
Notify.alert(
pgAdmin.Browser.notifier.alert(
gettext('Failed to create materialized view refresh job.'),
refreshed_res.errormsg
);
}
})
.catch((error)=>{
Notify.pgRespErrorNotify(
pgAdmin.Browser.notifier.pgRespErrorNotify(
error, gettext('Failed to create materialized view refresh job.')
);
});
})
.catch(()=>{
Notify.alert(
pgAdmin.Browser.notifier.alert(
gettext('Utility not found'),
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 { getNodeVariableSchema } from '../../../static/js/variable.ui';
import DatabaseSchema from './database.ui';
import Notify from '../../../../../../static/js/helpers/Notifier';
import { showServerPassword } from '../../../../../../static/js/Dialogs/index';
import _ from 'lodash';
import getApiInstance, { parseApiError } from '../../../../../../static/js/api_instance';
define('pgadmin.node.database', [
'sources/gettext', 'sources/url_for', 'jquery',
'sources/gettext', 'sources/url_for',
'sources/pgadmin', 'pgadmin.browser.utils',
'pgadmin.authenticate.kerberos', 'pgadmin.browser.collection',
], function(gettext, url_for, $, pgAdmin, pgBrowser, Kerberos) {
], function(gettext, url_for, pgAdmin, pgBrowser, Kerberos) {
function canDeleteWithForce(itemNodeData, item) {
let treeData = pgBrowser.tree.getTreeNodeHierarchy(item),
@ -189,7 +188,7 @@ define('pgadmin.node.database', [
connect(self, d, t, i, true);
return;
}
Notify.confirm(
pgAdmin.Browser.notifier.confirm(
gettext('Connection lost'),
gettext('Would you like to reconnect to the database?'),
function() {
@ -231,7 +230,7 @@ define('pgadmin.node.database', [
d = i ? t.itemData(i) : undefined;
if (d) {
Notify.confirm(
pgAdmin.Browser.notifier.confirm(
gettext('Disconnect from database'),
gettext('Are you sure you want to disconnect from database - %s?', d.label),
function() {
@ -244,7 +243,7 @@ define('pgadmin.node.database', [
if(res.data.info_prefix) {
res.info = `${_.escape(res.data.info_prefix)} - ${res.info}`;
}
Notify.success(res.info);
pgAdmin.Browser.notifier.success(res.info);
t.removeIcon(i);
data.connected = false;
data.icon = data.isTemplate ? 'icon-database-template-not-connected':'icon-database-not-connected';
@ -258,14 +257,14 @@ define('pgadmin.node.database', [
} else {
try {
Notify.error(res.errormsg);
pgAdmin.Browser.notifier.error(res.errormsg);
} catch (e) {
console.warn(e.stack || e);
}
t.unload(i);
}
}).catch(function(error) {
Notify.pgRespErrorNotify(error);
pgAdmin.Browser.notifier.pgRespErrorNotify(error);
t.unload(i);
});
},
@ -408,7 +407,7 @@ define('pgadmin.node.database', [
tree.setInode(_item);
let dbIcon = data.isTemplate ? 'icon-database-template-not-connected':'icon-database-not-connected';
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 {
@ -418,7 +417,7 @@ define('pgadmin.node.database', [
tree.addIcon(_item, {icon: dbIcon});
}
Notify.pgNotifier('error', error, 'Error', function(msg) {
pgAdmin.Browser.notifier.pgNotifier('error', error, 'Error', function(msg) {
setTimeout(function() {
if (msg == 'CRYPTKEY_SET') {
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}`;
}
if(res.data.already_connected) {
Notify.info(res.info);
pgAdmin.Browser.notifier.info(res.info);
} else {
Notify.success(res.info);
pgAdmin.Browser.notifier.success(res.info);
}
pgBrowser.Events.trigger(
'pgadmin:database:connected', _item, _data

View File

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

View File

@ -10,13 +10,13 @@
import { getNodeAjaxOptions } from '../../../../../static/js/node_ajax';
import PgaJobSchema from './pga_job.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 pgAdmin from 'sources/pgadmin';
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',
], function(gettext, url_for, $, pgBrowser) {
], function(gettext, url_for, pgBrowser) {
if (!pgBrowser.Nodes['coll-pga_job']) {
pgBrowser.Nodes['coll-pga_job'] =
@ -95,10 +95,10 @@ define('pgadmin.node.pga_job', [
getApiInstance().put(
obj.generate_url(i, 'run_now', d, true),
).then(({data: res})=> {
Notify.success(res.info);
pgAdmin.Browser.notifier.success(res.info);
t.unload(i);
}).catch(function(error) {
Notify.pgRespErrorNotify(error);
pgAdmin.Browser.notifier.pgRespErrorNotify(error);
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 { getNodeListByName, generateNodeUrl } from '../../../../../static/js/node_ajax';
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 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 ) {
return new RoleReassign(
{
@ -211,13 +198,7 @@ export function showRoleReassign() {
treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item),
itemNodeData = pgBrowser.tree.findNodeByDomElement(item).getData();
pgBrowser.Node.registerUtilityPanel();
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);
const urlBase = generateNodeUrl.call( pgAdmin.Browser.Nodes[data._type], treeNodeInfo, 'reassign', data, true);
let schema = getUISchema(treeNodeInfo, itemNodeData),
sqlHelpUrl = '',
@ -227,6 +208,9 @@ export function showRoleReassign() {
'filename': 'role_reassign_dialog.html',
});
getUtilityView(
schema, treeNodeInfo, 'create', 'dialog', j[0], panel, saveCallBack, extraData, 'Reassign/Drop', baseUrl, sqlHelpUrl, helpUrl);
}
pgAdmin.Browser.Events.trigger('pgadmin:utility:show', item,
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 BaseUISchema from 'sources/SchemaView/base_schema.ui';
import getApiInstance from '../../../../../static/js/api_instance';
import Notify from '../../../../../static/js/helpers/Notifier';
import pgAdmin from 'sources/pgadmin';
export function getBinaryPathSchema() {
@ -60,15 +59,15 @@ export default class BinaryPathSchema extends BaseUISchema {
validate: (data) => {
const api = getApiInstance();
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 {
api.post(url_for('misc.validate_binary_path'),
JSON.stringify({ 'utility_path': data }))
.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) {
Notify.pgNotifier('error', error, gettext('Failed to validate binary path.'));
pgAdmin.Browser.notifier.pgNotifier('error', error, gettext('Failed to validate binary path.'));
});
}
return true;

View File

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

View File

@ -7,45 +7,25 @@
//
//////////////////////////////////////////////////////////////
import React from 'react';
import { generateNodeUrl } from './node_ajax';
import MainMenuFactory from './MainMenuFactory';
import _ from 'lodash';
import Notify, {initializeModalProvider, initializeNotifier} from '../../../static/js/helpers/Notifier';
import { checkMasterPassword } from '../../../static/js/Dialogs/index';
import { checkMasterPassword, showQuickSearch } from '../../../static/js/Dialogs/index';
import { pgHandleItemError } from '../../../static/js/utils';
import { Search } from './quick_search/trigger_search';
import { send_heartbeat, stop_heartbeat } from './heartbeat';
import getApiInstance from '../../../static/js/api_instance';
import { copyToClipboard } from '../../../static/js/clipboard';
import { TAB_CHANGE } from './constants';
import usePreferences, { setupPreferenceBroadcast } from '../../../preferences/static/js/store';
import checkNodeVisibility from '../../../static/js/check_node_visibility';
define('pgadmin.browser', [
'sources/gettext', 'sources/url_for', 'jquery',
'sources/pgadmin', 'bundled_codemirror',
'sources/check_node_visibility', './toolbar', 'pgadmin.help',
'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',
'sources/gettext', 'sources/url_for', 'sources/pgadmin', 'bundled_codemirror',
'sources/csrf', 'pgadmin.authenticate.kerberos',
'pgadmin.browser.utils', 'pgadmin.browser.messages',
'pgadmin.browser.node', 'pgadmin.browser.collection', 'pgadmin.browser.activity',
'sources/codemirror/addon/fold/pgadmin-sqlfoldcode',
'pgadmin.browser.keyboard', 'sources/tree/pgadmin_tree_save_state',
/* wcDocker dependencies */
'bootstrap', 'jquery-contextmenu', 'wcdocker',
], function(
gettext, url_for, $,
pgAdmin, codemirror,
checkNodeVisibility, toolBar, help, csrfToken, pgadminUtils, pgWindow,
Kerberos, InitTree,
gettext, url_for, pgAdmin, codemirror, csrfToken, Kerberos,
) {
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 pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
@ -55,104 +35,10 @@ define('pgadmin.browser', [
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(pgAdmin.Browser, {
// The base url for browser
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,
// Reversed Engineer query for the selected database node object goes
// here
@ -215,102 +101,7 @@ define('pgadmin.browser', [
},
},
// Default 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,
}),
},
panels: {},
// We also support showing dashboards, HTML file, external URL
frames: {},
/* Menus */
@ -336,40 +127,6 @@ define('pgadmin.browser', [
MainMenus: [],
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: {
/* name, label (pair) */
'register': {
@ -490,8 +247,6 @@ define('pgadmin.browser', [
enable_disable_menus: function(item) {
let obj = this;
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.
// They will just require to check, whether they are
@ -529,64 +284,8 @@ define('pgadmin.browser', [
obj.initialized = true;
// Cache preferences
obj.cache_preferences();
obj.add_panels();
// 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');
});
usePreferences.getState().cache();
setupPreferenceBroadcast();
setTimeout(function() {
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:refresh', obj.onRefreshTreeNodeReact.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();
/* User UI activity */
@ -629,8 +327,8 @@ define('pgadmin.browser', [
obj.register_to_activity_listener(document);
obj.start_inactivity_timeout_daemon();
},
onResizeEnded: function() {
if (this.tree) this.tree.resizeTree();
uiloaded: function() {
this.check_version_update();
},
check_corrupted_db_file: function() {
getApiInstance().get(
@ -638,7 +336,7 @@ define('pgadmin.browser', [
).then(({data: res})=> {
if(res.data.length > 0) {
Notify.alert(
pgAdmin.Browser.notifier.alert(
'Warning',
'pgAdmin detected unrecoverable corruption in it\'s SQLite configuration database. ' +
'The database has been backed up and recreated with default settings. '+
@ -649,7 +347,7 @@ define('pgadmin.browser', [
);
}
}).catch(function(error) {
Notify.alert(error);
pgAdmin.Browser.notifier.alert(error);
});
},
check_master_password: function(on_resp_callback) {
@ -664,7 +362,7 @@ define('pgadmin.browser', [
}
}
}).catch(function(error) {
Notify.pgRespErrorNotify(error);
pgAdmin.Browser.notifier.pgRespErrorNotify(error);
});
},
@ -677,7 +375,7 @@ define('pgadmin.browser', [
self.set_master_password('');
}
}).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);
},
bind_beforeunload: function() {
$(window).on('beforeunload', function(e) {
/* Can open you in new tab */
let openerBrowser = pgWindow.default.pgAdmin.Browser;
check_version_update: function() {
getApiInstance().get(
url_for('misc.upgrade_check')
).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'),
confirm_on_refresh_close = openerBrowser.get_preference('browser', 'confirm_on_refresh_close');
}).catch(function() {
// 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)
pgAdmin.Browser.browserTreeState.save_state();
@ -709,7 +427,9 @@ define('pgadmin.browser', [
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*/
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;
}
});
@ -730,8 +450,7 @@ define('pgadmin.browser', [
// Add menus of module/extension at appropriate menu
add_menus: function(menus) {
let self = this,
pgMenu = this.all_menus_cache;
let pgMenu = this.all_menus_cache;
_.each(menus, function(m) {
_.each(m.applies, function(a) {
@ -741,12 +460,12 @@ define('pgadmin.browser', [
// If current node is not visible in browser tree
// then return from here
if(!checkNodeVisibility(self, m.node)) {
if(!checkNodeVisibility(m.node)) {
return;
} else if(_.has(m, 'module') && !_.isUndefined(m.module)) {
// If module to which this menu applies is not visible in
// browser tree then also we do not display menu
if(!checkNodeVisibility(self, m.module.type)) {
if(!checkNodeVisibility(m.module.type)) {
return;
}
}
@ -770,14 +489,9 @@ define('pgadmin.browser', [
}
// This is to handel quick search callback
if(gettext(_m.label) == gettext('Quick Search')) {
if(_m.name == 'mnu_quick_search_help') {
_m.callback = () => {
// Render Search component
Notify.showModal(gettext('Quick Search'), (closeModal) => {
return <Search closeModal={closeModal}/>;
},
{ isFullScreen: false, isResizeable: false, showFullScreen: false, isFullWidth: false, showTitle: false}
);
showQuickSearch();
};
}
@ -1683,7 +1397,6 @@ define('pgadmin.browser', [
if (!n) {
ctx.t.destroy({
success: function() {
initializeBrowserTree(ctx.b);
ctx.t = ctx.b.tree;
ctx.i = null;
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') {
fetchNodeInfo(__i, __d, __n);
} else {
@ -1974,7 +1687,7 @@ define('pgadmin.browser', [
}
fetchNodeInfo(_callback);
}).catch(function(error) {
Notify.pgRespErrorNotify(error);
pgAdmin.Browser.notifier.pgRespErrorNotify(error);
fetchNodeInfo(_callback);
});
};

View File

@ -6,7 +6,6 @@
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import {getPanelView} from './panel_view';
import _ from 'lodash';
define([
@ -84,15 +83,6 @@ define([
canDrop: true,
canDropCascade: true,
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) {
/*
* 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 url_for from 'sources/url_for';
import getApiInstance from '../../../static/js/api_instance';
import Notifier from '../../../static/js/helpers/Notifier';
import pgAdmin from 'sources/pgadmin';
const axiosApi = getApiInstance();
@ -28,9 +27,9 @@ export function send_heartbeat(_server_id, _item) {
})
.catch((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 {
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);
});

View File

@ -13,6 +13,7 @@ import Mousetrap from 'mousetrap';
import * as commonUtils from '../../../static/js/utils';
import gettext from 'sources/gettext';
import pgWindow from 'sources/window';
import usePreferences from '../../../preferences/static/js/store';
const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
@ -20,51 +21,53 @@ pgBrowser.keyboardNavigation = pgBrowser.keyboardNavigation || {};
_.extend(pgBrowser.keyboardNavigation, {
init: function() {
Mousetrap.reset();
if (pgBrowser.preferences_cache.length > 0) {
this.keyboardShortcut = {
...(pgBrowser.get_preference('browser', 'main_menu_file')?.value) && {'file_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'main_menu_file')?.value)},
...(pgBrowser.get_preference('browser', 'main_menu_object')?.value) && {'object_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'main_menu_object')?.value)},
...(pgBrowser.get_preference('browser', 'main_menu_tools')?.value) && {'tools_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'main_menu_tools')?.value)},
...(pgBrowser.get_preference('browser', 'main_menu_help')?.value) && {'help_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'main_menu_help')?.value)},
'left_tree_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'browser_tree').value),
'tabbed_panel_backward': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'tabbed_panel_backward').value),
'tabbed_panel_forward': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'tabbed_panel_forward').value),
'sub_menu_query_tool': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_query_tool').value),
'sub_menu_view_data': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_view_data').value),
'sub_menu_search_objects': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_search_objects').value),
'sub_menu_properties': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_properties').value),
'sub_menu_create': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_create').value),
'sub_menu_delete': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_delete').value),
'sub_menu_refresh': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_refresh').value),
'context_menu': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'context_menu').value),
'direct_debugging': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'direct_debugging').value),
'add_grid_row': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'add_grid_row').value),
'open_quick_search': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'open_quick_search').value),
usePreferences.subscribe((prefStore)=>{
Mousetrap.reset();
if (prefStore.version > 0) {
this.keyboardShortcut = {
...(prefStore.getPreferences('browser', 'main_menu_file')?.value) && {'file_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'main_menu_file')?.value)},
...(prefStore.getPreferences('browser', 'main_menu_object')?.value) && {'object_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'main_menu_object')?.value)},
...(prefStore.getPreferences('browser', 'main_menu_tools')?.value) && {'tools_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'main_menu_tools')?.value)},
...(prefStore.getPreferences('browser', 'main_menu_help')?.value) && {'help_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'main_menu_help')?.value)},
'left_tree_shortcut': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'browser_tree').value),
'tabbed_panel_backward': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'tabbed_panel_backward').value),
'tabbed_panel_forward': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'tabbed_panel_forward').value),
'sub_menu_query_tool': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_query_tool').value),
'sub_menu_view_data': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_view_data').value),
'sub_menu_search_objects': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_search_objects').value),
'sub_menu_properties': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_properties').value),
'sub_menu_create': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_create').value),
'sub_menu_delete': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_delete').value),
'sub_menu_refresh': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'sub_menu_refresh').value),
'context_menu': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'context_menu').value),
'direct_debugging': commonUtils.parseShortcutValue(prefStore.getPreferences('browser', 'direct_debugging').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 = {
...(pgBrowser.get_preference('browser', 'main_menu_file')?.value) && {'bindMainMenu': {
'shortcuts': [this.keyboardShortcut.file_shortcut,
this.keyboardShortcut.object_shortcut, this.keyboardShortcut.tools_shortcut,
this.keyboardShortcut.help_shortcut],
}}, // Main menu
'bindRightPanel': {'shortcuts': [this.keyboardShortcut.tabbed_panel_backward, this.keyboardShortcut.tabbed_panel_forward]}, // Main window panels
'bindLeftTree': {'shortcuts': this.keyboardShortcut.left_tree_shortcut}, // Main menu,
'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,
'bindSubMenuSearchObjects': {'shortcuts': this.keyboardShortcut.sub_menu_search_objects}, // Sub menu - Open search objects,
'bindSubMenuProperties': {'shortcuts': this.keyboardShortcut.sub_menu_properties}, // Sub menu - Edit Properties,
'bindSubMenuCreate': {'shortcuts': this.keyboardShortcut.sub_menu_create}, // Sub menu - Create 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,
'bindContextMenu': {'shortcuts': this.keyboardShortcut.context_menu}, // Sub menu - Open context menu,
'bindDirectDebugging': {'shortcuts': this.keyboardShortcut.direct_debugging}, // Sub menu - Direct Debugging
'bindAddGridRow': {'shortcuts': this.keyboardShortcut.add_grid_row}, // Subnode Grid Add Row
'bindOpenQuickSearch': {'shortcuts': this.keyboardShortcut.open_quick_search}, // Subnode Grid Refresh Row
};
this.bindShortcuts();
}
};
this.shortcutMethods = {
...(prefStore.getPreferences('browser', 'main_menu_file')?.value) && {'bindMainMenu': {
'shortcuts': [this.keyboardShortcut.file_shortcut,
this.keyboardShortcut.object_shortcut, this.keyboardShortcut.tools_shortcut,
this.keyboardShortcut.help_shortcut],
}}, // Main menu
'bindRightPanel': {'shortcuts': [this.keyboardShortcut.tabbed_panel_backward, this.keyboardShortcut.tabbed_panel_forward]}, // Main window panels
'bindLeftTree': {'shortcuts': this.keyboardShortcut.left_tree_shortcut}, // Main menu,
'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,
'bindSubMenuSearchObjects': {'shortcuts': this.keyboardShortcut.sub_menu_search_objects}, // Sub menu - Open search objects,
'bindSubMenuProperties': {'shortcuts': this.keyboardShortcut.sub_menu_properties}, // Sub menu - Edit Properties,
'bindSubMenuCreate': {'shortcuts': this.keyboardShortcut.sub_menu_create}, // Sub menu - Create 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,
'bindContextMenu': {'shortcuts': this.keyboardShortcut.context_menu}, // Sub menu - Open context menu,
'bindDirectDebugging': {'shortcuts': this.keyboardShortcut.direct_debugging}, // Sub menu - Direct Debugging
'bindAddGridRow': {'shortcuts': this.keyboardShortcut.add_grid_row}, // Subnode Grid Add Row
'bindOpenQuickSearch': {'shortcuts': this.keyboardShortcut.open_quick_search}, // Subnode Grid Refresh Row
};
this.bindShortcuts();
}
});
},
bindShortcuts: function() {
const self = this;
@ -116,7 +119,7 @@ _.extend(pgBrowser.keyboardNavigation, {
}
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) {

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 getApiInstance from '../../../static/js/api_instance';
import { removePanelView } from './panel_view';
import { TAB_CHANGE } from './constants';
import { BROWSER_PANELS } 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', [
'sources/gettext', 'sources/pgadmin',
@ -21,9 +23,6 @@ define('pgadmin.browser.node', [
], function(
gettext, pgAdmin, generateUrl, commonUtils
) {
let wcDocker = window.wcDocker;
const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
// It has already been defined.
@ -89,8 +88,11 @@ define('pgadmin.browser.node', [
dialogHelp: '',
epasHelp: false,
title: function(o, d) {
return o.label + (d ? (' - ' + d.label) : '');
title: function(d, action) {
if(action == 'create') {
return gettext('Create - %s', this.label);
}
return d.label??'';
},
hasId: true,
///////
@ -282,114 +284,6 @@ define('pgadmin.browser.node', [
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.
*
@ -447,194 +341,132 @@ define('pgadmin.browser.node', [
**/
show_obj_properties: function(args, item) {
let t = pgBrowser.tree,
i = (args && args.item) || item || t.selected(),
d = i ? t.itemData(i) : undefined,
o = this,
l = o.title.apply(this, [d]),
p;
nodeItem = (args && args.item) || item || t.selected(),
nodeData = nodeItem ? t.itemData(nodeItem) : undefined,
panelTitle = this.title(nodeData, args.action),
treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(nodeItem);
// Make sure - the properties dialog type registered
pgBrowser.Node.register_node_panel();
// No node selected.
if (!d)
if (!nodeData)
return;
let self = this,
isParent = (_.isArray(this.parent_type) ?
function(_d) {
return (_.indexOf(self.parent_type, _d._type) != -1);
} : function(_d) {
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;
};
const isParent = (_.isArray(this.parent_type) ?
(_d)=>{
return (_.indexOf(this.parent_type, _d._type) != -1);
} : (_d)=>{
return (this.parent_type == _d._type);
});
if (args.action == 'create') {
// If we've parent, we will get the information of it for
// proper object manipulation.
//
// You know - we're working with RDBMS, relation is everything
// for us.
if (self.parent_type && !isParent(d)) {
// In browser tree, I can be under any node, But - that
// 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 (this.parent_type && !isParent(nodeData)) {
// actual parent of a table is schema, not Tables.
while (nodeItem && t.hasParent(nodeItem)) {
nodeItem = t.parent(nodeItem);
let pd = t.itemData(nodeItem);
if (isParent(pd)) {
// Assign the data, this is my actual parent.
d = pd;
nodeData = pd;
break;
}
}
}
// Seriously - I really don't have parent data present?
//
// 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))) {
// The only node who does not have parent is the Server Group
if (!nodeData || (this.parent_type != null && !isParent(nodeData))) {
// It should never come here.
// If it is here, that means - we do have some bug in code.
return;
}
l = gettext('Create - %s', this.label);
if (this.type == 'server') {
l = gettext('Register - %s', this.label);
}
p = addPanel();
setTimeout(function() {
o.showProperties(i, d, p, args.action);
}, 10);
} else {
if (pgBrowser.Node.panels && pgBrowser.Node.panels[d.id] &&
pgBrowser.Node.panels[d.id].$container) {
p = pgBrowser.Node.panels[d.id];
/** TODO ::
* 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);
const panelId = _.uniqueId(BROWSER_PANELS.EDIT_PROPERTIES);
const onClose = (force=false)=>pgBrowser.docker.close(panelId, force);
const onSave = (newNodeData)=>{
// Clear the cache for this node now.
setTimeout(()=>{
this.clear_cache.apply(this, item);
}, 0);
try {
pgBrowser.Events.trigger(
'pgadmin:browser:tree:add', _.clone(newNodeData.node),
_.clone(treeNodeInfo)
);
} catch (e) {
console.warn(e.stack || e);
}
} else {
pgBrowser.Node.panels = pgBrowser.Node.panels || {};
p = pgBrowser.Node.panels[d.id] = addPanel();
onClose();
};
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() {
o.showProperties(i, d, p, args.action);
}, 10);
// Clear the cache for this node now.
setTimeout(()=>{
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_obj: function(args, item) {
@ -668,7 +500,7 @@ define('pgadmin.browser.node', [
if (!(_.isFunction(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),
10000
);
@ -685,24 +517,24 @@ define('pgadmin.browser.node', [
if (!(_.isFunction(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),
10000
);
return;
}
}
Notify.confirm(title, msg,
pgAdmin.Browser.notifier.confirm(title, msg,
function() {
getApiInstance().delete(
obj.generate_url(i, input.url, d, true),
).then(({data: res})=> {
if(res.success == 2){
Notify.error(res.info, null);
pgAdmin.Browser.notifier.error(res.info, null);
return;
}
if (res.success == 0) {
Notify.alert(res.errormsg, res.info);
pgAdmin.Browser.notifier.alert(res.errormsg, res.info);
} else {
// Remove the node from tree and set collection node as selected.
let selectNextNode = true;
@ -727,7 +559,7 @@ define('pgadmin.browser.node', [
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
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,
i = item || t.selected(),
d = i ? t.itemData(i) : undefined;
@ -862,7 +694,7 @@ define('pgadmin.browser.node', [
pgBrowser.Node.callbacks.change_server_background(item, data);
},
// 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,
// which are visible at this time:
// + Properties
@ -870,34 +702,11 @@ define('pgadmin.browser.node', [
// + Dependents
// + Dependencies
// + Statistics
let b = browser || pgBrowser,
t = b.tree,
d = data || t.itemData(item);
let b = browser || pgBrowser;
// Update the menu items
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',
item);
return true;
@ -922,7 +731,7 @@ define('pgadmin.browser.node', [
},
opened: function(item) {
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) {
// Automatically expand the child node, if a treeview node has only a single child.
@ -957,127 +766,55 @@ define('pgadmin.browser.node', [
item);
},
},
/**********************************************************************
* A hook (not a callback) to show object properties in given HTML
* element.
*
* This has been used for the showing, editing properties of the node.
* This has also been used for creating a node.
**/
showProperties: function(item, data, panel, action) {
let that = this,
j = panel.$container.find('.obj_properties').first();
showPropertiesDialog: function(panelId, panelTitle, dialogProps, update=false) {
const panelData = {
id: panelId,
title: panelTitle,
manualClose: true,
icon: `dialog-node-icon ${this.node_image?.(dialogProps.itemNodeData) ?? ('icon-' + this.type)}`,
content: (
<ErrorBoundary>
<ObjectNodeProperties
panelId={panelId}
node={this}
formType="dialog"
{...dialogProps}
/>
</ErrorBoundary>
)
};
// Callback to show object properties
let properties = function() {
let treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
getNodeView(
that.type, treeNodeInfo, 'properties', data, 'tab', j[0], this, onEdit
);
return;
}.bind(panel),
let w = toPx(this.width || (pgBrowser.stdW.default + 'px'), 'width', true);
let h = toPx(this.height || (pgBrowser.stdH.default + 'px'), 'height', true);
editFunc = function() {
let self = this;
if (action && action == 'properties') {
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();
}
/* Fit to standard sizes */
if(w <= pgBrowser.stdW.sm) {
w = pgBrowser.stdW.sm;
} else {
/* Show properties */
onEdit = editInNewPanel.bind(panel);
properties();
if(w <= pgBrowser.stdW.md) {
w = pgBrowser.stdW.md;
} 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) {

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>
</div>
{% if current_app.PGADMIN_RUNTIME | string() == 'False' %}
<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' %}
<div id="root" style="height: 100%"></div>
{% 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() }}"],
priority: {{ item.priority }},
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 %}
{% if item.below is defined %}below: {% if item.below %}true{% else %}false{% endif %},
{% endif %}
@ -41,9 +41,6 @@ define('pgadmin.browser.utils',
let pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
/* Add hooked-in panels by extensions */
pgBrowser['panels_items'] = '{{ current_app.panels|tojson }}';
pgBrowser['MainMenus'] = [];
pgAdmin['csrf_token_header'] = '{{ current_app.config.get('WTF_CSRF_HEADERS')[0] }}';

View File

@ -44,22 +44,6 @@ class DashboardModule(PgAdminModule):
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):
"""
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();
}
reflectPreferences(preferences) {
this.preferences = preferences;
if(preferences.show_graphs) {
this.render();
} else {
this.unmount();
}
}
setPageVisible(visible) {
this.pageVisible = visible;
this.render();

View File

@ -6,7 +6,6 @@
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
// eslint-disable-next-line react/display-name
import React, { useEffect, useMemo, useState } from 'react';
import gettext from 'sources/gettext';
import PropTypes from 'prop-types';
@ -16,8 +15,7 @@ import { InputCheckbox } from '../../../static/js/components/FormComponents';
import { makeStyles } from '@material-ui/core/styles';
import url_for from 'sources/url_for';
import Graphs from './Graphs';
import Notify from '../../../static/js/helpers/Notifier';
import { Box, Card, CardContent, CardHeader, Tab, Tabs } from '@material-ui/core';
import { Box, Tab, Tabs } from '@material-ui/core';
import { PgIconButton } from '../../../static/js/components/Buttons';
import CancelIcon from '@material-ui/icons/Cancel';
import StopSharpIcon from '@material-ui/icons/StopSharp';
@ -33,6 +31,11 @@ import Summary from './SystemStats/Summary';
import CPU from './SystemStats/CPU';
import Memory from './SystemStats/Memory';
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) {
let res = [];
@ -45,13 +48,11 @@ function parseData(data) {
const useStyles = makeStyles((theme) => ({
emptyPanel: {
height: '100%',
background: theme.palette.grey[400],
width: '100%',
background: theme.otherVars.emptySpaceBg,
overflow: 'auto',
padding: '8px',
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
},
fixedSizeList: {
overflowX: 'hidden !important',
@ -91,12 +92,20 @@ const useStyles = makeStyles((theme) => ({
fontSize: '0.875rem',
},
panelContent: {
...theme.mixins.panelBorder,
...theme.mixins.panelBorder.all,
display: 'flex',
flexDirection: 'column',
overflow: 'hidden !important',
height: '100%',
minHeight: '400px'
width: '100%',
minHeight: '400px',
padding: '8px'
},
mainTabs: {
...theme.mixins.panelBorder.all,
height: '100%',
display: 'flex',
flexDirection: 'column'
},
arrowButton: {
fontSize: '2rem !important',
@ -139,16 +148,8 @@ const useStyles = makeStyles((theme) => ({
}
}));
/* eslint-disable react/display-name */
export default function Dashboard({
nodeData,
node,
item,
pgBrowser,
preferences,
sid,
did,
treeNodeInfo,
function Dashboard({
nodeItem, nodeData, node, treeNodeInfo,
...props
}) {
const classes = useStyles();
@ -169,6 +170,16 @@ export default function Dashboard({
const systemStatsTabChanged = (e, 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) {
tabs.push(gettext('Configuration'));
@ -268,7 +279,7 @@ export default function Dashboard({
)
return;
let url = action_url + '/' + row.values.pid;
Notify.confirm(
pgAdmin.Browser.notifier.confirm(
title,
txtConfirm,
function () {
@ -276,14 +287,14 @@ export default function Dashboard({
.delete(url)
.then(function (res) {
if (res.data == gettext('Success')) {
Notify.success(txtSuccess);
pgAdmin.Browser.notifier.success(txtSuccess);
setRefresh(!refresh);
} else {
Notify.error(txtError);
pgAdmin.Browser.notifier.error(txtError);
}
})
.catch(function (error) {
Notify.alert(
pgAdmin.Browser.notifier.alert(
gettext('Failed to retrieve data from the server.'),
error.message
);
@ -335,7 +346,7 @@ export default function Dashboard({
if (!canTakeAction(row, 'cancel'))
return;
let url = action_url + '/' + row.values.pid;
Notify.confirm(
pgAdmin.Browser.notifier.confirm(
title,
txtConfirm,
function () {
@ -343,16 +354,16 @@ export default function Dashboard({
.delete(url)
.then(function (res) {
if (res.data == gettext('Success')) {
Notify.success(txtSuccess);
pgAdmin.Browser.notifier.success(txtSuccess);
setRefresh(!refresh);
} else {
Notify.error(txtError);
pgAdmin.Browser.notifier.error(txtError);
setRefresh(!refresh);
}
})
.catch(function (error) {
Notify.alert(
pgAdmin.Browser.notifier.alert(
gettext('Failed to retrieve data from the server.'),
error.message
);
@ -711,7 +722,7 @@ export default function Dashboard({
'You cannot terminate background worker processes.'
);
}
Notify.info(txtMessage);
pgAdmin.Browser.notifier.info(txtMessage);
return false;
// If it is the last active connection on maintenance db then error out
} else if (
@ -727,11 +738,11 @@ export default function Dashboard({
'You are not allowed to terminate the main active session.'
);
}
Notify.error(txtMessage);
pgAdmin.Browser.notifier.error(txtMessage);
return false;
} else if (is_cancel_session && row.original.state == 'idle') {
// 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;
} else if (can_signal_backend) {
// 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.'
);
}
Notify.error(txtMessage);
pgAdmin.Browser.notifier.error(txtMessage);
return false;
}
};
@ -773,7 +784,7 @@ export default function Dashboard({
setTabVal(0);
}
if (sid && props.serverConnected) {
if (sid && serverConnected) {
if (tabVal === 0) {
url = url_for('dashboard.activity');
} else if (tabVal === 1) {
@ -785,11 +796,11 @@ export default function Dashboard({
}
message = gettext('Loading dashboard...');
if (did && !props.dbConnected) return;
if (did && !dbConnected) return;
if (did) url += sid + '/' + did;
else url += sid;
if (did && !props.dbConnected) return;
if (did && !dbConnected) return;
if (did && did > 0) ssExtensionCheckUrl += '/' + sid + '/' + did;
else ssExtensionCheckUrl += '/' + sid;
@ -804,7 +815,7 @@ export default function Dashboard({
setdashData(parseData(res.data));
})
.catch((error) => {
Notify.alert(
pgAdmin.Browser.notifier.alert(
gettext('Failed to retrieve data from the server.'),
_.isUndefined(error.response) ? error.message : error.response.data.errormsg
);
@ -842,7 +853,7 @@ export default function Dashboard({
if (message != '') {
setMsg(message);
}
}, [nodeData, tabVal, did, preferences, refresh, props.dbConnected, mainTabVal]);
}, [nodeData, tabVal, treeNodeInfo, prefStore, refresh, mainTabVal]);
const filteredDashData = useMemo(()=>{
if (tabVal == 0 && activeOnly) {
@ -872,7 +883,7 @@ export default function Dashboard({
const showDefaultContents = () => {
return (
sid && !props.serverConnected ? (
sid && !serverConnected ? (
<Box className={classes.dashboardPanel}>
<div className={classes.emptyPanel}>
<EmptyPanelMessage text={msg}/>
@ -880,10 +891,10 @@ export default function Dashboard({
</Box>
) : (
<WelcomeDashboard
pgBrowser={pgBrowser}
pgBrowser={pgAdmin.Browser}
node={node}
itemData={nodeData}
item={item}
item={nodeItem}
sid={sid}
did={did}
/>
@ -912,150 +923,148 @@ export default function Dashboard({
return (
<>
{sid && props.serverConnected ? (
{sid && serverConnected ? (
<Box className={classes.dashboardPanel}>
<Box className={classes.emptyPanel}>
<Box className={classes.panelContent}>
<Box height="100%" display="flex" flexDirection="column">
<Box>
<Tabs
value={mainTabVal}
onChange={mainTabChanged}
>
{mainTabs.map((tabValue) => {
return <Tab key={tabValue} label={tabValue} />;
})}
</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 className={classes.panelContent}>
<Box className={classes.mainTabs}>
<Box>
<Tabs
value={mainTabVal}
onChange={mainTabChanged}
>
{mainTabs.map((tabValue) => {
return <Tab key={tabValue} label={tabValue} />;
})}
</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.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>
@ -1069,48 +1078,14 @@ Dashboard.propTypes = {
itemData: PropTypes.object,
nodeData: PropTypes.object,
treeNodeInfo: PropTypes.object,
item: PropTypes.object,
pgBrowser: PropTypes.object,
nodeItem: PropTypes.object,
preferences: PropTypes.object,
sid: PropTypes.string,
did: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]),
row: PropTypes.object,
serverConnected: PropTypes.bool,
dbConnected: PropTypes.bool,
panelVisible: PropTypes.bool,
isActive: PropTypes.bool,
};
export function ChartContainer(props) {
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
};
export default withStandardTabInfo(Dashboard, BROWSER_PANELS.DASHBOARD);

View File

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

View File

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

View File

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

View File

@ -11723,7 +11723,7 @@ msgstr ""
#: pgadmin/static/js/Dialogs/ConnectServerContent.jsx:136
#: pgadmin/static/js/Dialogs/MasterPasswordContent.jsx:142
#: 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:75
#: pgadmin/static/js/helpers/Notifier.jsx:107
@ -11772,7 +11772,7 @@ msgstr ""
msgid "Enter the name of the restore point to add"
msgstr ""
#: pgadmin/static/js/Dialogs/RenamePanelContent.jsx:85
#: pgadmin/static/js/Dialogs/RenameTabContent.jsx:85
msgid "Title cannot be empty"
msgstr ""

View File

@ -19,13 +19,16 @@ from pgadmin.utils.csrf import pgCSRFProtect
from pgadmin.utils.session import cleanup_session_files
from pgadmin.misc.themes import get_all_themes
from pgadmin.utils.constants import MIMETYPE_APP_JS, UTILITIES_ARRAY
from pgadmin.utils.ajax import precondition_required, make_json_response
from pgadmin.utils.heartbeat import log_server_heartbeat,\
from pgadmin.utils.ajax import precondition_required, make_json_response, \
internal_server_error
from pgadmin.utils.heartbeat import log_server_heartbeat, \
get_server_heartbeat, stop_server_heartbeat
import config
import subprocess
import os
import time
import json
import os
from urllib.request import urlopen
from pgadmin.settings import get_setting, store_setting
MODULE_NAME = 'misc'
@ -72,7 +75,7 @@ class MiscModule(PgAdminModule):
'value': theme,
'preview_src': url_for(
'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',
'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):
"""
@ -200,24 +204,6 @@ def get_heartbeat(sid):
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
##########################################################################
@ -266,3 +252,56 @@ def validate_binary_path():
return precondition_required(gettext('Invalid binary path.'))
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
//
//////////////////////////////////////////////////////////////
import getApiInstance, { parseApiError } from '../../../../static/js/api_instance';
import url_for from 'sources/url_for';
import Notifier from '../../../../static/js/helpers/Notifier';
import EventBus from '../../../../static/js/helpers/EventBus';
import * as BgProcessNotify from './BgProcessNotify';
import showDetails from './showDetails';
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;
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 {
static instance;
@ -148,7 +134,7 @@ export default class BgProcessManager {
this._eventManager.fireEvent(BgProcessManagerEvents.LIST_UPDATED);
})
.catch((err)=>{
Notifier.error(parseApiError(err));
pgAdmin.Browser.notifier.error(parseApiError(err));
});
}
@ -169,16 +155,12 @@ export default class BgProcessManager {
/* Object not available */
removeJob(jobId);
} else {
Notifier.error(parseApiError(err));
pgAdmin.Browser.notifier.error(parseApiError(err));
}
});
});
}
viewJobDetails(jobId) {
showDetails(this.procList.find((p)=>p.id==jobId));
}
updateCloudDetails(jobId) {
this.api.put(url_for('bgprocess.update_cloud_details', {
pid: jobId,
@ -186,7 +168,7 @@ export default class BgProcessManager {
.then((res)=>{
let _server = res.data?.data?.node;
if(!_server) {
Notifier.error(gettext('Cloud server deployment is pending'));
pgAdmin.Browser.notifier.error(gettext('Cloud server deployment is pending'));
return;
}
let _server_path = '/browser/server_group_' + _server.gid + '/' + _server.id,
@ -209,7 +191,7 @@ export default class BgProcessManager {
})
.catch((err)=>{
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() {
let processPanel = this.pgBrowser.docker.findPanels('processes');
if(processPanel.length > 0) {
processPanel = processPanel[0];
let processPanel = this.pgBrowser.docker.find(BROWSER_PANELS.PROCESSES);
if(!processPanel) {
pgAdmin.Browser.docker.openTab(processesPanelData, BROWSER_PANELS.MAIN, 'middle', true);
} else {
let propertiesPanel = this.pgBrowser.docker.findPanels('properties');
processPanel = this.pgBrowser.docker.addPanel('processes', window.wcDocker.DOCK.STACKED, propertiesPanel[0]);
this.pgBrowser.docker.focus(BROWSER_PANELS.PROCESSES);
}
processPanel.focus();
}
registerListener(event, callback) {

View File

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

View File

@ -13,7 +13,7 @@ import url_for from 'sources/url_for';
import { Box, makeStyles } from '@material-ui/core';
import PropTypes from 'prop-types';
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 HighlightOffRoundedIcon from '@material-ui/icons/HighlightOffRounded';
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 gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import { makeStyles } from '@material-ui/core/styles';
import pgAdmin from 'sources/pgadmin';
import { BgProcessManagerEvents, BgProcessManagerProcessState } from './BgProcessManager';
import { BgProcessManagerEvents, BgProcessManagerProcessState } from './BgProcessConstants';
import { PgIconButton } from '../../../../static/js/components/Buttons';
import CancelIcon from '@material-ui/icons/Cancel';
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 url_for from 'sources/url_for';
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) => ({
@ -93,13 +95,33 @@ const ProcessStateTextAndColor = {
[BgProcessManagerProcessState.PROCESS_TERMINATING]: [gettext('Terminating...'), 'bgTerm'],
[BgProcessManagerProcessState.PROCESS_FAILED]: [gettext('Failed'), 'bgFailed'],
};
export default function Processes() {
const classes = useStyles();
const pgAdmin = usePgAdmin();
const [tableData, setTableData] = 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',
Header: () => null,
@ -149,7 +171,7 @@ export default function Processes() {
noBorder
onClick={(e) => {
e.preventDefault();
pgAdmin.Browser.BgProcessManager.viewJobDetails(row.original.id);
onViewDetailsClick(row.original);
}}
aria-label="View details"
title={gettext('View details')}
@ -266,7 +288,7 @@ export default function Processes() {
aria-label="Acknowledge and Remove"
title={gettext('Acknowledge and Remove')}
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));
});
}}

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

View File

@ -7,12 +7,10 @@
//
//////////////////////////////////////////////////////////////
import React from 'react';
import ReactDOM from 'react-dom';
import Theme from 'sources/Theme';
import CloudWizard from './CloudWizard';
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
define('pgadmin.misc.cloud', [
@ -74,30 +72,26 @@ define('pgadmin.misc.cloud', [
d = this.d = i ? t.itemData(i) : undefined,
info = this.info = pgBrowser.tree.getTreeNodeHierarchy(i);
// Register dialog panel
pgBrowser.Node.registerUtilityPanel();
let panel = pgBrowser.Node.addUtilityPanel(930, 650),
j = panel.$container.find('.obj_properties').first();
panel.title(gettext('Deploy Cloud Instance'));
panel.on(window.wcDocker.EVENT.CLOSED, function() {
const axiosApi = getApiInstance();
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}
const panelTitle = gettext('Deploy Cloud Instance');
const panelId = BROWSER_PANELS.CLOUD_WIZARD;
pgAdmin.Browser.docker.openDialog({
id: panelId,
title: panelTitle,
manualClose: true,
content: (
<CloudWizard nodeInfo={info} nodeData={d} cloudPanelId={panelId}
onClose={() => {
ReactDOM.unmountComponentAtNode(j[0]);
panel.close();
const axiosApi = getApiInstance();
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 gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import Notify from '../../../../static/js/helpers/Notifier';
import getApiInstance from 'sources/api_instance';
import { makeStyles } from '@material-ui/core/styles';
import { getURL } from '../../../static/utils/utils';
import Loader from 'sources/components/Loader';
import EmptyPanelMessage from '../../../../static/js/components/EmptyPanelMessage';
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) => ({
emptyPanel: {
@ -72,11 +74,13 @@ function parseData(data, node) {
return data;
}
export default function Dependencies({ nodeData, item, node, ...props }) {
function Dependencies({ nodeData, nodeItem, node, treeNodeInfo, isActive, isStale, setIsStale }) {
const classes = useStyles();
const [tableData, setTableData] = React.useState([]);
const [loaderText, setLoaderText] = React.useState('');
const [msg, setMsg] = React.useState('');
const pgAdmin = usePgAdmin();
let columns = [
{
Header: 'Type',
@ -103,14 +107,18 @@ export default function Dependencies({ nodeData, item, node, ...props }) {
];
useEffect(() => {
if(!isStale || !isActive) {
return;
}
let message = gettext('Please select an object in the tree view.');
if (node) {
let url = getURL(
nodeData,
true,
props.treeNodeInfo,
treeNodeInfo,
node,
item,
nodeItem,
'dependency'
);
message = gettext(
@ -134,7 +142,7 @@ export default function Dependencies({ nodeData, item, node, ...props }) {
}
})
.catch((e) => {
Notify.alert(
pgAdmin.Browser.notifier.alert(
gettext('Failed to retrieve data from the server.'),
parseApiError(e)
);
@ -147,10 +155,8 @@ export default function Dependencies({ nodeData, item, node, ...props }) {
setMsg(message);
setLoaderText('');
}
return () => {
setTableData([]);
};
}, [nodeData]);
setIsStale(false);
}, [isActive, isStale]);
return (
<>
@ -178,5 +184,10 @@ Dependencies.propTypes = {
nodeData: PropTypes.object,
treeNodeInfo: PropTypes.object,
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 gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import Notify from '../../../../static/js/helpers/Notifier';
import getApiInstance from 'sources/api_instance';
import { makeStyles } from '@material-ui/core/styles';
import { getURL } from '../../../static/utils/utils';
import Loader from 'sources/components/Loader';
import EmptyPanelMessage from '../../../../static/js/components/EmptyPanelMessage';
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) => ({
emptyPanel: {
@ -72,11 +74,12 @@ function parseData(data, node) {
return data;
}
export default function Dependents({ nodeData, item, node, ...props }) {
function Dependents({ nodeData, nodeItem, node, treeNodeInfo, isActive, isStale, setIsStale }) {
const classes = useStyles();
const [tableData, setTableData] = React.useState([]);
const [loaderText, setLoaderText] = React.useState('');
const [msg, setMsg] = React.useState('');
const pgAdmin = usePgAdmin();
let columns = [
{
@ -104,14 +107,18 @@ export default function Dependents({ nodeData, item, node, ...props }) {
];
useEffect(() => {
if(!isStale || !isActive) {
return;
}
let message = gettext('Please select an object in the tree view.');
if (node) {
let url = getURL(
nodeData,
true,
props.treeNodeInfo,
treeNodeInfo,
node,
item,
nodeItem,
'dependent'
);
message = gettext(
@ -134,7 +141,7 @@ export default function Dependents({ nodeData, item, node, ...props }) {
}
})
.catch((e) => {
Notify.alert(
pgAdmin.Browser.notifier.alert(
gettext('Failed to retrieve data from the server.'),
parseApiError(e)
);
@ -148,10 +155,8 @@ export default function Dependents({ nodeData, item, node, ...props }) {
setMsg(message);
}
return () => {
setTableData([]);
};
}, [nodeData, item]);
setIsStale(false);
}, [isActive, isStale]);
return (
<>
@ -180,5 +185,10 @@ Dependents.propTypes = {
nodeData: PropTypes.object,
treeNodeInfo: PropTypes.object,
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 Notifier from '../../../../static/js/helpers/Notifier';
import React from 'react';
import FileManager from './components/FileManager';
import { getBrowser } from '../../../../static/js/utils';
import pgAdmin from 'sources/pgadmin';
export default class FileManagerModule {
static instance;
@ -56,7 +56,7 @@ export default class FileManagerModule {
}
showInternal(params, onOK, onCancel, modalObj) {
const modal = modalObj || Notifier;
const modal = modalObj || pgAdmin.Browser.notifier;
let title = params.dialog_title;
if(!title) {
if(params.dialog_type == 'create_file') {

View File

@ -130,7 +130,7 @@ export default function GridView({items, operation, onItemSelect, onItemEnter})
}
return (
<Box flexGrow={1} overflow="hidden auto">
<Box flexGrow={1} overflow="hidden auto" id="grid">
<ul ref={gridRef} className={classes.grid}>
{items.map((item, i)=>(
<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]);
useEffect(()=>{
gridRef.current.selectCell({idx: 0, rowIdx: 0});
gridRef.current?.selectCell({idx: 0, rowIdx: 0});
}, [gridRef.current?.element]);
return (
<PgReactDataGrid
gridRef={gridRef}
id="files"
id="list"
className={classes.grid}
hasSelectColumn={false}
columns={columns}

View File

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

View File

@ -7,14 +7,11 @@
//
//////////////////////////////////////////////////////////////
import React from 'react';
import pgAdmin from 'sources/pgadmin';
import getApiInstance from 'sources/api_instance';
import { makeStyles } from '@material-ui/core/styles';
import { Box, Switch } from '@material-ui/core';
import { generateCollectionURL } from '../../browser/static/js/node_ajax';
import Notify from '../../static/js/helpers/Notifier';
import gettext from 'sources/gettext';
import 'wcdocker';
import PgTable from 'sources/components/PgTable';
import Theme from 'sources/Theme';
import PropTypes from 'prop-types';
@ -25,6 +22,7 @@ import DeleteForeverIcon from '@material-ui/icons/DeleteForever';
import EmptyPanelMessage from '../../static/js/components/EmptyPanelMessage';
import Loader from 'sources/components/Loader';
import { evalFunc } from '../../static/js/utils';
import { usePgAdmin } from '../../static/js/BrowserComponent';
const useStyles = makeStyles((theme) => ({
emptyPanel: {
@ -77,14 +75,17 @@ const useStyles = makeStyles((theme) => ({
}
}));
export function CollectionNodeView({
export default function CollectionNodeProperties({
node,
treeNodeInfo,
itemNodeData,
item,
pgBrowser
nodeData,
nodeItem,
isActive,
isStale,
setIsStale
}) {
const classes = useStyles();
const pgAdmin = usePgAdmin();
const [data, setData] = React.useState([]);
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
React.useEffect(() => {
setReload(!reload);
}, [item?._children]);
}, [nodeItem?._children]);
const [pgTableColumns, setPgTableColumns] = React.useState([
{
@ -122,9 +123,9 @@ export function CollectionNodeView({
const onDrop = (type) => {
let selRowModels = selectedObject,
selRows = [],
selItem = pgBrowser.tree.selected(),
selectedItemData = selItem ? pgBrowser.tree.itemData(selItem) : null,
selNode = selectedItemData && pgBrowser.Nodes[selectedItemData._type],
selItem = pgAdmin.Browser.tree.selected(),
selectedItemData = selItem ? pgAdmin.Browser.tree.itemData(selItem) : null,
selNode = selectedItemData && pgAdmin.Browser.Nodes[selectedItemData._type],
url = undefined,
msg = undefined,
title = undefined;
@ -140,7 +141,7 @@ export function CollectionNodeView({
}
if (selRows.length === 0) {
Notify.alert(
pgAdmin.Browser.notifier.alert(
gettext('Delete Multiple'),
gettext('Please select at least one object to delete.')
);
@ -178,14 +179,14 @@ export function CollectionNodeView({
.then(function (res) {
setLoaderText('');
if (res.success == 0) {
Notify.alert(res.errormsg, res.info);
pgAdmin.Browser.notifier.alert(res.errormsg, res.info);
}
pgAdmin.Browser.tree.refresh(selItem);
setReload(!reload);
})
.catch(function (error) {
setLoaderText('');
Notify.alert(
pgAdmin.Browser.notifier.alert(
gettext('Error deleting %s', selectedItemData._label.toLowerCase()),
_.isUndefined(error.response) ? error.message : error.response.data.errormsg
);
@ -193,7 +194,7 @@ export function CollectionNodeView({
};
if (confirm) {
Notify.confirm(title, msg, dropNodeProperties, null);
pgAdmin.Browser.notifier.confirm(title, msg, dropNodeProperties, null);
} else {
dropNodeProperties();
}
@ -203,9 +204,9 @@ export function CollectionNodeView({
if (node){
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();
@ -213,8 +214,8 @@ export function CollectionNodeView({
let column = {};
setLoaderText(gettext('Loading...'));
if (itemNodeData._type.indexOf('coll-') > -1 && !_.isUndefined(nodeObj.getSchema)) {
schemaRef.current = nodeObj.getSchema?.call(nodeObj, treeNodeInfo, itemNodeData);
if (nodeData._type.indexOf('coll-') > -1 && !_.isUndefined(nodeObj.getSchema)) {
schemaRef.current = nodeObj.getSchema?.call(nodeObj, treeNodeInfo, nodeData);
schemaRef.current?.fields.forEach((field) => {
if (node.columns.indexOf(field.id) > -1) {
if (field.label.indexOf('?') > -1) {
@ -257,6 +258,10 @@ export function CollectionNodeView({
});
}
if(!isStale || !isActive) {
return;
}
api({
url: url,
type: 'GET',
@ -271,18 +276,19 @@ export function CollectionNodeView({
setLoaderText('');
})
.catch((err) => {
Notify.alert(
pgAdmin.Browser.notifier.alert(
gettext('Failed to retrieve data from the server.'),
gettext(err.message)
);
});
setIsStale(false);
}
}, [itemNodeData, node, item, reload]);
}, [nodeData, node, nodeItem, reload]);
const CustomHeader = () => {
const canDrop = evalFunc(node, node.canDrop, itemNodeData, item, treeNodeInfo);
const canDropCascade = evalFunc(node, node.canDropCascade, itemNodeData, item, treeNodeInfo);
const canDropForce = evalFunc(node, node.canDropForce, itemNodeData, item, treeNodeInfo);
const canDrop = evalFunc(node, node.canDrop, nodeData, nodeItem, treeNodeInfo);
const canDropCascade = evalFunc(node, node.canDropCascade, nodeData, nodeItem, treeNodeInfo);
const canDropForce = evalFunc(node, node.canDropForce, nodeData, nodeItem, treeNodeInfo);
return (
<Box >
<PgIconButton
@ -337,7 +343,7 @@ export function CollectionNodeView({
{data.length > 0 ?
(
<PgTable
isSelectRow={!('catalog' in treeNodeInfo) && (itemNodeData.label !== 'Catalogs') && _.isUndefined(node?.canSelect)}
isSelectRow={!('catalog' in treeNodeInfo) && (nodeData.label !== 'Catalogs') && _.isUndefined(node?.canSelect)}
CustomHeader={CustomHeader}
className={classes.autoResizer}
columns={pgTableColumns}
@ -361,17 +367,19 @@ export function CollectionNodeView({
);
}
CollectionNodeView.propTypes = {
CollectionNodeProperties.propTypes = {
node: PropTypes.func,
itemData: PropTypes.object,
itemNodeData: PropTypes.object,
nodeData: PropTypes.object,
treeNodeInfo: PropTypes.object,
item: PropTypes.object,
pgBrowser: PropTypes.object,
nodeItem: PropTypes.object,
preferences: PropTypes.object,
sid: PropTypes.number,
did: PropTypes.number,
row: PropTypes.object,
serverConnected: 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 gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import Notify from '../../../../static/js/helpers/Notifier';
import getApiInstance from 'sources/api_instance';
import { makeStyles } from '@material-ui/core/styles';
import CodeMirror from '../../../../static/js/components/CodeMirror';
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) => ({
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 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 [loaderText, setLoaderText] = React.useState('');
const [msg, setMsg] = React.useState('');
const pgAdmin = usePgAdmin();
useEffect(() => {
if(!isStale || !isActive) {
return;
}
let sql = '-- ' + gettext('Please select an object in the tree view.');
if (node) {
if(node) {
let url = generateNodeUrl.call(
node,
props.treeNodeInfo,
treeNodeInfo,
'sql',
nodeData,
true,
node.url_jump_after_node
);
setLoaderText('Loading...');
if (did && !props.dbConnected){
setLoaderText('');
if (did && !dbConnected){
return;
}
sql =
@ -55,6 +60,7 @@ export default function SQL({ nodeData, node, did, ...props }) {
if (node.hasSQL) {
const api = getApiInstance();
setLoaderText('Loading...');
api({
url: url,
type: 'GET',
@ -64,37 +70,34 @@ export default function SQL({ nodeData, node, did, ...props }) {
setNodeSQL(res.data);
setLoaderText('');
} else {
setMsg(sql);
setNodeSQL(sql);
}
})
.catch((e) => {
Notify.alert(
pgAdmin.Browser.notifier.alert(
gettext('Error'),
gettext(e.response.data.errormsg)
);
// 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('');
});
}else{
setMsg(sql);
setLoaderText('');
}
}
if (sql != '') {
setMsg(sql);
setNodeSQL(sql);
}
return () => {
setNodeSQL([]);
};
}, [nodeData, props.dbConnected]);
setIsStale(false);
}, [isStale, isActive]);
return (
<>
<Loader message={loaderText}/>
<CodeMirror
className={classes.textArea}
value={nodeSQL.length > 0 ? nodeSQL : msg}
value={nodeSQL}
readonly={true}
options={{
lineNumbers: true,
@ -111,5 +114,10 @@ SQL.propTypes = {
treeNodeInfo: PropTypes.object,
node: PropTypes.func,
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 gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import Notify from '../../../../static/js/helpers/Notifier';
import getApiInstance from 'sources/api_instance';
import { makeStyles } from '@material-ui/core/styles';
import { getURL } from '../../../static/utils/utils';
import Loader from 'sources/components/Loader';
import EmptyPanelMessage from '../../../../static/js/components/EmptyPanelMessage';
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) => ({
emptyPanel: {
@ -155,7 +157,8 @@ function createSingleLineStatistics(data, prettifyFields) {
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 [tableData, setTableData] = React.useState([]);
@ -177,13 +180,18 @@ export default function Statistics({ nodeData, item, node, ...props }) {
disableGlobalFilter: false,
},
]);
const pgAdmin = usePgAdmin();
useEffect(() => {
if(!isStale || !isActive) {
return;
}
let url,
message = gettext('Please select an object in the tree view.');
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.');
@ -207,7 +215,7 @@ export default function Statistics({ nodeData, item, node, ...props }) {
setLoaderText('');
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() {
if (mesg == 'CRYPTKEY_SET') {
setMsg('No statistics are available for the selected object.');
@ -217,7 +225,7 @@ export default function Statistics({ nodeData, item, node, ...props }) {
}, 100);
});
} else {
Notify.alert(
pgAdmin.Browser.notifier.alert(
gettext('Failed to retrieve data from the server.'),
gettext(err.message)
);
@ -232,10 +240,8 @@ export default function Statistics({ nodeData, item, node, ...props }) {
if (message != '') {
setMsg(message);
}
return () => {
setTableData([]);
};
}, [nodeData]);
setIsStale(false);
}, [isStale, isActive]);
return (
<>
@ -260,7 +266,12 @@ export default function Statistics({ nodeData, item, node, ...props }) {
Statistics.propTypes = {
res: PropTypes.array,
nodeData: PropTypes.object,
item: PropTypes.object,
nodeItem: PropTypes.object,
treeNodeInfo: PropTypes.object,
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 SaveSharpIcon from '@material-ui/icons/SaveSharp';
import clsx from 'clsx';
import Notify from '../../../../static/js/helpers/Notifier';
import pgAdmin from 'sources/pgadmin';
import { DefaultButton, PgIconButton, PrimaryButton } from '../../../../static/js/components/Buttons';
import BaseUISchema from 'sources/SchemaView/base_schema.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 usePreferences from '../store';
class PreferencesSchema extends BaseUISchema {
constructor(initValues = {}, schemaFields = []) {
@ -168,6 +167,7 @@ export default function PreferencesComponent({ ...props }) {
const [loadTree, setLoadTree] = React.useState(0);
const api = getApiInstance();
const firstTreeElement = React.useRef('');
const preferencesStore = usePreferences();
useEffect(() => {
const pref_url = url_for('preferences.index');
@ -230,7 +230,7 @@ export default function PreferencesComponent({ ...props }) {
// set Preferences schema
prefSchema.current = new PreferencesSchema(preferencesValues, preferencesData);
}).catch((err) => {
Notify.alert(err);
pgAdmin.Browser.notifier.alert(err);
});
}, []);
function setPreferences(node, subNode, nodeData, preferencesValues, preferencesData) {
@ -328,8 +328,8 @@ export default function PreferencesComponent({ ...props }) {
element.canDelete = false;
element.canEdit = false;
element.editable = false;
if (pgAdmin.Browser.get_preference(node.label.toLowerCase(), element.name)?.value) {
let temp = pgAdmin.Browser.get_preference(node.label.toLowerCase(), element.name).value;
if (preferencesStore.getPreferences(node.label.toLowerCase(), element.name)?.value) {
let temp = preferencesStore.getPreferences(node.label.toLowerCase(), element.name).value;
preferencesValues[element.id] = temp;
} else {
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';
});
let requires_refresh = false;
/* Find the modules changed */
let modulesChanged = {};
for (const [key] of Object.entries(data.current)) {
let pref = pgAdmin.Browser.get_preference_for_id(Number(key));
if (pref['name'] == 'dynamic_tabs') {
_set_dynamic_tab(pgAdmin.Browser, !pref['value']);
}
if (!modulesChanged[pref.module]) {
modulesChanged[pref.module] = true;
}
let pref = preferencesStore.getPreferenceForId(Number(key));
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) {
Notify.confirm(
pgAdmin.Browser.notifier.confirm(
gettext('Object explorer refresh required'),
gettext('An object explorer refresh is required. Do you wish to refresh it now?'),
function () {
pgAdmin.Browser.tree.destroy({
success: function () {
pgAdmin.Browser.initializeBrowserTree(pgAdmin.Browser);
// pgAdmin.Browser.initializeBrowserTree(pgAdmin.Browser);
return true;
},
});
@ -598,7 +573,7 @@ export default function PreferencesComponent({ ...props }) {
}
if (requires_refresh) {
Notify.confirm(
pgAdmin.Browser.notifier.confirm(
gettext('Refresh required'),
gettext('A page refresh is required to apply the theme. Do you wish to refresh the page now?'),
function () {
@ -612,10 +587,10 @@ export default function PreferencesComponent({ ...props }) {
);
}
// Refresh preferences cache
pgAdmin.Browser.cache_preferences(modulesChanged);
preferencesStore.cache();
props.closeModal();
}).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 gettext from 'sources/gettext';
import PreferencesComponent from './components/PreferencesComponent';
import Notify from '../../../static/js/helpers/Notifier';
import PreferencesTree from './components/PreferencesTree';
import pgAdmin from 'sources/pgadmin';
export default class Preferences {
static instance;
@ -50,7 +50,7 @@ export default class Preferences {
show() {
// Render Preferences component
Notify.showModal(gettext('Preferences'), (closeModal) => {
pgAdmin.Browser.notifier.showModal(gettext('Preferences'), (closeModal) => {
return <PreferencesComponent
renderTree={(prefTreeData) => {
// 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';
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) {
define('pgadmin.settings', ['sources/pgadmin'], function(pgAdmin) {
// This defines the Preference/Options Dialog for pgAdmin IV.
pgAdmin = pgAdmin || window.pgAdmin || {};
@ -35,34 +29,7 @@ define('pgadmin.settings', [
// We will force unload method to not to save current layout
// and reload the window
show: function() {
Notify.confirm(gettext('Reset layout'),
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.
}
);
pgAdmin.Browser.docker.resetLayout();
},
};

View File

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

View File

@ -13,7 +13,3 @@
@import 'node_modules/@simonwep/pickr/dist/themes/monolith.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