diff --git a/web/pgadmin/browser/static/js/node_ajax.js b/web/pgadmin/browser/static/js/node_ajax.js
index 2ed51663c..14a9afa3c 100644
--- a/web/pgadmin/browser/static/js/node_ajax.js
+++ b/web/pgadmin/browser/static/js/node_ajax.js
@@ -12,6 +12,25 @@ import getApiInstance from '../../../static/js/api_instance';
import {generate_url} from 'sources/browser/generate_url';
import pgAdmin from 'sources/pgadmin';
+/* It generates the URL based on collection node selected */
+export function generateCollectionURL(item, type) {
+ var opURL = {
+ 'properties': 'obj',
+ 'children': 'nodes',
+ 'drop': 'obj',
+ };
+ let nodeObj= this;
+ var collectionPickFunction = function (treeInfoValue, treeInfoKey) {
+ return (treeInfoKey != nodeObj.type);
+ };
+ var treeInfo = pgAdmin.Browser.tree.getTreeNodeHierarchy(item);
+ var actionType = type in opURL ? opURL[type] : type;
+ return generate_url(
+ pgAdmin.Browser.URL, treeInfo, actionType, nodeObj.node,
+ collectionPickFunction
+ );
+}
+
/* It generates the URL based on tree node selected */
export function generateNodeUrl(treeNodeInfo, actionType, itemNodeData, withId, jumpAfterNode) {
let opURL = {
diff --git a/web/pgadmin/browser/static/js/panel.js b/web/pgadmin/browser/static/js/panel.js
index 7f408d63b..ff1444f3d 100644
--- a/web/pgadmin/browser/static/js/panel.js
+++ b/web/pgadmin/browser/static/js/panel.js
@@ -7,6 +7,8 @@
//
//////////////////////////////////////////////////////////////
+import { getPanelView, removePanelView } from './panel_view';
+
define(
['underscore', 'sources/pgadmin', 'jquery', 'wcdocker'],
function(_, pgAdmin, $) {
@@ -127,6 +129,19 @@ define(
myPanel.on(ev, that.handleVisibility.bind(myPanel, ev));
});
}
+
+ pgBrowser.Events.on('pgadmin-browser:tree:selected', () => {
+
+ if(myPanel.isVisible()) {
+ removePanelView($container[0]);
+ getPanelView(
+ pgBrowser.tree,
+ $container[0],
+ pgBrowser,
+ myPanel._type
+ );
+ }
+ });
},
});
}
@@ -193,26 +208,43 @@ define(
handleVisibility: function(eventName) {
// Supported modules
let type_module = {
- 'dashboard': pgAdmin.Dashboard,
- 'statistics': pgBrowser.NodeStatistics,
- 'dependencies': pgBrowser.NodeDependencies,
- 'dependents': pgBrowser.NodeDependents,
+ dashboard: pgAdmin.Dashboard,
};
-
let module = type_module[this._type];
- if(_.isUndefined(module))
- return;
-
- if(_.isUndefined(module.toggleVisibility))
- return;
+ if (_.isNull(pgBrowser.tree)) return;
+
+ let selectedPanel = pgBrowser.docker.findPanels(this._type)[0];
+ let isPanelVisible = selectedPanel.isVisible();
+ var $container = selectedPanel
+ .layout()
+ .scene()
+ .find('.pg-panel-content');
+
+ if (this._type === 'dashboard') {
+ if (eventName == 'panelClosed') {
+ module.toggleVisibility.call(module, false, true);
+ } else if (eventName == 'panelVisibilityChanged') {
+ module.toggleVisibility.call(
+ module,
+ pgBrowser.docker.findPanels(this._type)[0].isVisible(),
+ false
+ );
+ }
+ }
- if (eventName == 'panelClosed') {
- /* Pass the closed flag also */
- module.toggleVisibility.call(module, false, true);
- } else if (eventName == 'panelVisibilityChanged') {
- module.toggleVisibility.call(module, pgBrowser.docker.findPanels(this._type)[0].isVisible(), false);
+ if (isPanelVisible) {
+ if (eventName == 'panelClosed') {
+ removePanelView($container[0]);
+ } else if (eventName == 'panelVisibilityChanged') {
+ getPanelView(
+ pgBrowser.tree,
+ $container[0],
+ pgBrowser,
+ this._type
+ );
+ }
}
- },
+ }
});
diff --git a/web/pgadmin/browser/static/js/panel_view.jsx b/web/pgadmin/browser/static/js/panel_view.jsx
new file mode 100644
index 000000000..cf0a5a07e
--- /dev/null
+++ b/web/pgadmin/browser/static/js/panel_view.jsx
@@ -0,0 +1,77 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Theme from 'sources/Theme';
+import Dependencies from '../../../misc/dependencies/static/js/Dependencies';
+import Dependents from '../../../misc/dependents/static/js/Dependents';
+import Statistics from '../../../misc/statistics/static/js/Statistics';
+
+/* The entry point for rendering React based view in properties, called in node.js */
+export function getPanelView(
+ tree,
+ container,
+ pgBrowser,
+ panelType
+) {
+ let item = tree.selected(),
+ nodeData = item && tree.itemData(item),
+ node = item && nodeData && pgBrowser.Nodes[nodeData._type],
+ treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
+
+
+ if (panelType == 'statistics') {
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+ }
+ if (panelType == 'dependencies') {
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+ }
+ if (panelType == 'dependents') {
+ ReactDOM.render(
+
+
+ ,
+ container
+ );
+ }
+}
+
+/* When switching from normal node to collection node, clean up the React mounted DOM */
+export function removePanelView(container) {
+ ReactDOM.unmountComponentAtNode(container);
+}
diff --git a/web/pgadmin/misc/dependencies/static/js/Dependencies.jsx b/web/pgadmin/misc/dependencies/static/js/Dependencies.jsx
new file mode 100644
index 000000000..994106c09
--- /dev/null
+++ b/web/pgadmin/misc/dependencies/static/js/Dependencies.jsx
@@ -0,0 +1,175 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import _ from 'lodash';
+import React, { useEffect } from 'react';
+import { generateNodeUrl } from '../../../../browser/static/js/node_ajax';
+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';
+
+const useStyles = makeStyles((theme) => ({
+ emptyPanel: {
+ minHeight: '100%',
+ minWidth: '100%',
+ background: theme.palette.grey[400],
+ overflow: 'auto',
+ padding: '7.5px',
+ },
+ panelIcon: {
+ width: '80%',
+ margin: '0 auto',
+ marginTop: '25px !important',
+ position: 'relative',
+ textAlign: 'center',
+ },
+ panelMessage: {
+ marginLeft: '0.5rem',
+ fontSize: '0.875rem',
+ },
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflow: 'auto !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+}));
+
+function parseData(data, node) {
+ // Update the icon
+ data.forEach((element) => {
+ if (element.icon == null || element.icon == '') {
+ if (node) {
+ element.icon = _.isFunction(node['node_image'])
+ ? node['node_image'].apply(node, [null, null])
+ : node['node_image'] || 'icon-' + element.type;
+ } else {
+ element.icon = 'icon-' + element.type;
+ }
+ if (element.icon) {
+ element['icon'] = {
+ type: element.icon,
+ };
+ }
+ }
+ });
+ return data;
+}
+
+export default function Dependencies({ nodeData, node, ...props }) {
+ const classes = useStyles();
+ const [tableData, setTableData] = React.useState([]);
+
+ const [msg, setMsg] = React.useState('');
+ var columns = [
+ {
+ Header: 'Type',
+ accessor: 'type',
+ sortble: true,
+ resizable: false,
+ disableGlobalFilter: true,
+ },
+ {
+ Header: 'Name',
+ accessor: 'name',
+ sortble: true,
+ resizable: false,
+ disableGlobalFilter: true,
+ },
+ {
+ Header: 'Restriction',
+ accessor: 'field',
+ sortble: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ minWidth: 280,
+ },
+ ];
+
+ useEffect(() => {
+ let message = gettext('Please select an object in the tree view.');
+ if (node) {
+ let url = generateNodeUrl.call(
+ node,
+ props.treeNodeInfo,
+ 'dependency',
+ nodeData,
+ true,
+ node.url_jump_after_node
+ );
+ message = gettext(
+ 'No dependency information is available for the selected object.'
+ );
+ if (node.hasDepends) {
+ const api = getApiInstance();
+
+ api({
+ url: url,
+ type: 'GET',
+ })
+ .then((res) => {
+ if (res.data.length > 0) {
+ let data = parseData(res.data, node);
+ setTableData(data);
+ } else {
+ setMsg(message);
+ }
+ })
+ .catch((e) => {
+ Notify.alert(
+ gettext('Failed to retrieve data from the server.'),
+ gettext(e.message)
+ );
+ // show failed message.
+ setMsg(gettext('Failed to retrieve data from the server.'));
+ });
+ }
+ }
+ if (message != '') {
+ setMsg(message);
+ }
+ return () => {
+ setTableData([]);
+ };
+ }, [nodeData]);
+
+ return (
+ <>
+ {tableData.length > 0 ? (
+
+ ) : (
+
+ )}
+ >
+ );
+}
+
+Dependencies.propTypes = {
+ res: PropTypes.array,
+ nodeData: PropTypes.object,
+ treeNodeInfo: PropTypes.object,
+ node: PropTypes.func,
+};
diff --git a/web/pgadmin/misc/dependencies/static/js/dependencies.js b/web/pgadmin/misc/dependencies/static/js/dependencies.js
deleted file mode 100644
index e655db28a..000000000
--- a/web/pgadmin/misc/dependencies/static/js/dependencies.js
+++ /dev/null
@@ -1,437 +0,0 @@
-/////////////////////////////////////////////////////////////
-//
-// pgAdmin 4 - PostgreSQL Tools
-//
-// Copyright (C) 2013 - 2022, The pgAdmin Development Team
-// This software is released under the PostgreSQL Licence
-//
-//////////////////////////////////////////////////////////////
-
-import Notify from '../../../../static/js/helpers/Notifier';
-
-define('misc.dependencies', [
- 'sources/gettext', 'underscore', 'jquery', 'backbone',
- 'pgadmin', 'pgadmin.browser', 'pgadmin.alertifyjs', 'pgadmin.backgrid',
- 'sources/utils',
-], function(gettext, _, $, Backbone, pgAdmin, pgBrowser, Alertify, Backgrid, pgadminUtils) {
-
- if (pgBrowser.NodeDependencies)
- return pgBrowser.NodeDependencies;
-
- var wcDocker = window.wcDocker;
-
- pgBrowser.NodeDependencies = pgBrowser.NodeDependencies || {};
-
- _.extend(pgBrowser.NodeDependencies, {
- init: function() {
- if (this.initialized) {
- return;
- }
-
- this.initialized = true;
- this.dependenciesPanel = pgBrowser.docker.findPanels('dependencies')[0];
- /* Parameter is used to set the proper label of the
- * backgrid header cell.
- */
- _.bindAll(this, 'showDependencies', '__updateCollection', '__loadMoreRows',
- '__appendGridToPanel', 'toggleVisibility');
-
- // Defining Backbone Model for Dependencies.
- var Model = Backbone.Model.extend({
- defaults: {
- icon: 'icon-unknown',
- type: undefined,
- name: undefined,
- /* field contains 'Database Name' for 'Tablespace and Role node',
- * for other node it contains 'Restriction'.
- */
- field: undefined,
- },
- // This function is used to fetch/set the icon for the type(Function, Role, Database, ....)
- parse: function(res) {
- var node = pgBrowser.Nodes[res.type];
- if(res.icon == null || res.icon == '') {
- if (node) {
- res.icon = _.isFunction(node['node_image']) ?
- (node['node_image']).apply(node, [null, null]) :
- (node['node_image'] || ('icon-' + res.type));
- } else {
- res.icon = ('icon-' + res.type);
- }
- }
- res.type = pgadminUtils.titleize(res.type.replace(/_/g, ' '), true);
- return res;
- },
- });
-
- // Defining Backbone Collection for Dependencies.
- this.dependenciesCollection = new(Backbone.Collection.extend({
- model: Model,
- }))(null);
-
- if(this.dependenciesPanel) this.toggleVisibility(this.dependenciesPanel.isVisible());
- },
-
- toggleVisibility: function(visible, closed=false) {
- if (visible) {
- this.dependenciesPanel = pgBrowser.docker.findPanels('dependencies')[0];
- var t = pgBrowser.tree,
- i = t.selected(),
- d = i && t.itemData(i),
- n = i && d && pgBrowser.Nodes[d._type];
-
- this.showDependencies(i, d, n);
-
- // We will start listening the tree selection event.
- pgBrowser.Events.on('pgadmin-browser:tree:selected', this.showDependencies);
- } else {
- if(closed) {
- $(this.dependenciesPanel).data('node-prop', '');
- }
- // We don't need to listen the tree item selection event.
- pgBrowser.Events.off('pgadmin-browser:tree:selected', this.showDependencies);
- }
- },
-
- /* Function is used to create and render backgrid with
- * empty collection. We just want to add backgrid into the
- * panel only once.
- */
- __appendGridToPanel: function() {
- var $container = this.dependenciesPanel.layout().scene().find('.pg-panel-content'),
- $gridContainer = $container.find('.pg-panel-dependencies-container'),
- grid = new Backgrid.Grid({
- emptyText: gettext('No data found'),
- columns: [{
- name: 'type',
- label: gettext('Type'),
- // Extend it to render the icon as per the type.
- cell: Backgrid.Cell.extend({
- render: function() {
- Backgrid.Cell.prototype.render.apply(this, arguments);
- this.$el.prepend($('', {
- class: 'wcTabIcon ' + this.model.get('icon'),
- }));
- return this;
- },
- }),
- editable: false,
- },
- {
- name: 'name',
- label: gettext('Name'),
- cell: 'string',
- editable: false,
- },
- {
- name: 'field',
- label: gettext('field'),
- cell: 'string',
- editable: false,
- },
- ],
-
- collection: this.dependenciesCollection,
- className: 'backgrid table presentation table-bordered table-noouter-border table-hover',
- });
-
- // Condition is used to save grid object to change the label of the header.
- this.dependenciesGrid = grid;
-
- $gridContainer.empty();
- $gridContainer.append(grid.render().el);
-
- return true;
- },
-
- // Fetch the actual data and update the collection
- showDependencies: function(item, data, node) {
- if (!node) {
- return;
- }
-
- /**
- * We can't start fetching the statistics immediately, it is possible -
- * the user is just using keyboards to select the node, and just
- * traversing through.
- *
- * We will wait for some time before fetching
- **/
- if (this.timeout) {
- clearTimeout(this.timeout);
- }
- this.timeout = setTimeout(() => {
- this.__updateCollection(node.generate_url(item, 'dependency', data, true), node, item, data._type);
- }, 400);
- },
-
- // Fetch the actual data and update the collection
- __updateCollection: function(url, node, item, node_type) {
- let self = this,
- msg = gettext('Please select an object in the tree view.'),
- panel = this.dependenciesPanel,
- $container = panel.layout().scene().find('.pg-panel-content'),
- $msgContainer = $container.find('.pg-panel-depends-message'),
- $gridContainer = $container.find('.pg-panel-dependencies-container'),
- treeHierarchy = pgBrowser.tree.getTreeNodeHierarchy(item);
-
- if (node) {
- /* We fetch the Dependencies and Dependencies tab only for
- * those node who set the parameter hasDepends to true.
- */
- msg = gettext('No dependency information is available for the selected object.');
- if (node.hasDepends) {
- // Avoid unnecessary reloads
- var cache_flag = {
- node_type: node_type,
- url: url,
- };
- if (_.isEqual($(panel).data('node-prop'), cache_flag)) {
- return;
- }
- // Cache the current IDs for next time
- $(panel).data('node-prop', cache_flag);
-
- /* Updating the label for the 'field' type of the backbone model.
- * Label should be "Database" if the node type is tablespace or role
- * and dependencies tab is selected. For other nodes and dependencies tab
- * it should be 'Restriction'.
- */
-
- self.__appendGridToPanel();
- this.dependenciesGrid.columns.models[2].set({
- 'label': gettext('Restriction'),
- });
-
- // Hide message container and show grid container.
- $msgContainer.addClass('d-none');
- $gridContainer.removeClass('d-none');
-
- var timer = '';
- var ajaxHook = function() {
- $.ajax({
- url: url,
- type: 'GET',
- beforeSend: function(xhr) {
- xhr.setRequestHeader(pgAdmin.csrf_token_header, pgAdmin.csrf_token);
- // Generate a timer for the request
- timer = setTimeout(function() {
- // notify user if request is taking longer than 1 second
-
- $msgContainer.text(gettext('Fetching dependency information from the server...'));
- $msgContainer.removeClass('d-none');
- msg = '';
-
- }, 1000);
- },
- })
- .done(function(res) {
- clearTimeout(timer);
-
- if (res.length > 0) {
-
- if (!$msgContainer.hasClass('d-none')) {
- $msgContainer.addClass('d-none');
- }
- $gridContainer.removeClass('d-none');
-
- self.dependenciesData = res;
-
- // Load only 100 rows
- self.dependenciesCollection.reset(self.dependenciesData.splice(0, 100), {parse: true});
-
- // Load more rows on scroll down
- pgBrowser.Events.on(
- 'pgadmin-browser:panel-dependencies:' +
- wcDocker.EVENT.SCROLLED,
- self.__loadMoreRows
- );
- } else {
- // Do not listen the scroll event
- pgBrowser.Events.off(
- 'pgadmin-browser:panel-dependencies:' +
- wcDocker.EVENT.SCROLLED
- );
-
- self.dependenciesCollection.reset({silent: true});
- $msgContainer.text(msg);
- $msgContainer.removeClass('d-none');
-
- if (!$gridContainer.hasClass('d-none')) {
- $gridContainer.addClass('d-none');
- }
- }
-
-
- })
- .fail(function(xhr, error, message) {
- var _label = treeHierarchy[node_type].label;
- pgBrowser.Events.trigger(
- 'pgadmin:node:retrieval:error', 'depends', xhr, error, message
- );
- if (!Alertify.pgHandleItemError(xhr, error, message, {
- item: item,
- info: treeHierarchy,
- })) {
- Notify.pgNotifier(
- error, xhr,
- gettext('Error retrieving data from the server: %s', message || _label),
- function(alertMsg) {
- if(alertMsg === 'CRYPTKEY_SET') {
- ajaxHook();
- } else {
- console.warn(arguments);
- }
- });
- }
- // show failed message.
- $msgContainer.text(gettext('Failed to retrieve data from the server.'));
- });
- };
- ajaxHook();
- }
- }
- if (msg != '') {
- $msgContainer.text(msg);
- $msgContainer.removeClass('d-none');
- if (!$gridContainer.hasClass('d-none')) {
- $gridContainer.addClass('d-none');
- }
- }
- },
- showReactDependencies: function(item, data, node) {
- let self = this,
- msg = gettext('Please select an object in the tree view.'),
- panel = this.dependenciesPanel,
- $container = panel.layout().scene().find('.pg-panel-content'),
- $msgContainer = $container.find('.pg-panel-depends-message'),
- $gridContainer = $container.find('.pg-panel-dependencies-container'),
- treeHierarchy = pgBrowser.tree.getTreeNodeHierarchy(item),
- n_type = data._type,
- url = node.generate_url_react(item, 'dependency');
-
- if (node) {
- /* We fetch the Dependencies and Dependencies tab only for
- * those node who set the parameter hasDepends to true.
- */
- msg = gettext('No dependency information is available for the selected object.');
- if (node.hasDepends) {
- /* Updating the label for the 'field' type of the backbone model.
- * Label should be "Database" if the node type is tablespace or role
- * and dependencies tab is selected. For other nodes and dependencies tab
- * it should be 'Restriction'.
- */
-
- this.dependenciesGrid.columns.models[2].set({
- 'label': gettext('Restriction'),
- });
-
- // Hide message container and show grid container.
- $msgContainer.addClass('d-none');
- $gridContainer.removeClass('d-none');
-
- var timer = '';
- $.ajax({
- url: url,
- type: 'GET',
- beforeSend: function(xhr) {
- xhr.setRequestHeader(pgAdmin.csrf_token_header, pgAdmin.csrf_token);
- // Generate a timer for the request
- timer = setTimeout(function() {
- // notify user if request is taking longer than 1 second
-
- $msgContainer.text(gettext('Fetching dependency information from the server...'));
- $msgContainer.removeClass('d-none');
- msg = '';
-
- }, 1000);
- },
- })
- .done(function(res) {
- clearTimeout(timer);
-
- if (res.length > 0) {
-
- if (!$msgContainer.hasClass('d-none')) {
- $msgContainer.addClass('d-none');
- }
- $gridContainer.removeClass('d-none');
-
- self.dependenciesData = res;
-
- // Load only 100 rows
- self.dependenciesCollection.reset(self.dependenciesData.splice(0, 100), {parse: true});
-
- // Load more rows on scroll down
- pgBrowser.Events.on(
- 'pgadmin-browser:panel-dependencies:' +
- wcDocker.EVENT.SCROLLED,
- self.__loadMoreRows
- );
-
- } else {
- // Do not listen the scroll event
- pgBrowser.Events.off(
- 'pgadmin-browser:panel-dependencies:' +
- wcDocker.EVENT.SCROLLED
- );
-
- self.dependenciesCollection.reset({silent: true});
- $msgContainer.text(msg);
- $msgContainer.removeClass('d-none');
-
- if (!$gridContainer.hasClass('d-none')) {
- $gridContainer.addClass('d-none');
- }
- }
-
-
- })
- .fail(function(xhr, error, message) {
- var _label = treeHierarchy[n_type].label;
- pgBrowser.Events.trigger(
- 'pgadmin:node:retrieval:error', 'depends', xhr, error, message
- );
- if (!Alertify.pgHandleItemError(xhr, error, message, {
- item: item,
- info: treeHierarchy,
- })) {
- Notify.pgNotifier(
- error, xhr,
- gettext('Error retrieving data from the server: %s', message || _label),
- function(alertMsg) {
- if(alertMsg === 'CRYPTKEY_SET') {
- self.showDependencies(item, data, node);
- } else {
- console.warn(arguments);
- }
- });
- }
- // show failed message.
- $msgContainer.text(gettext('Failed to retrieve data from the server.'));
- });
- }
- }
- if (msg != '') {
- $msgContainer.text(msg);
- $msgContainer.removeClass('d-none');
- if (!$gridContainer.hasClass('d-none')) {
- $gridContainer.addClass('d-none');
- }
- }
- },
-
- __loadMoreRows: function() {
- if (this.dependenciesPanel.length < 1) return ;
-
- let elem = this.dependenciesPanel.$container.find('.pg-panel-dependencies-container').closest('.wcFrameCenter')[0];
- if ((elem.scrollHeight - 10) < elem.scrollTop + elem.offsetHeight) {
- if (this.dependenciesData.length > 0) {
- this.dependenciesCollection.add(this.dependenciesData.splice(0, 100), {parse: true});
- }
- }
- },
- });
-
- return pgBrowser.NodeDependencies;
-});
diff --git a/web/pgadmin/misc/dependents/static/js/Dependents.jsx b/web/pgadmin/misc/dependents/static/js/Dependents.jsx
new file mode 100644
index 000000000..112429b12
--- /dev/null
+++ b/web/pgadmin/misc/dependents/static/js/Dependents.jsx
@@ -0,0 +1,176 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import _ from 'lodash';
+import React, { useEffect } from 'react';
+import { generateNodeUrl } from '../../../../browser/static/js/node_ajax';
+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';
+
+const useStyles = makeStyles((theme) => ({
+ emptyPanel: {
+ minHeight: '100%',
+ minWidth: '100%',
+ background: theme.palette.grey[400],
+ overflow: 'auto',
+ padding: '7.5px',
+ },
+ panelIcon: {
+ width: '80%',
+ margin: '0 auto',
+ marginTop: '25px !important',
+ position: 'relative',
+ textAlign: 'center',
+ },
+ panelMessage: {
+ marginLeft: '0.5rem',
+ fontSize: '0.875rem',
+ },
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflow: 'auto !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+}));
+
+function parseData(data, node) {
+ // Update the icon
+ data.forEach((element) => {
+ if (element.icon == null || element.icon == '') {
+ if (node) {
+ element.icon = _.isFunction(node['node_image'])
+ ? node['node_image'].apply(node, [null, null])
+ : node['node_image'] || 'icon-' + element.type;
+ } else {
+ element.icon = 'icon-' + element.type;
+ }
+ if (element.icon) {
+ element['icon'] = {
+ type: element.icon,
+ };
+ }
+ }
+ });
+ return data;
+}
+
+export default function Dependents({ nodeData, node, ...props }) {
+ const classes = useStyles();
+ const [tableData, setTableData] = React.useState([]);
+
+ const [msg, setMsg] = React.useState('');
+
+ var columns = [
+ {
+ Header: 'Type',
+ accessor: 'type',
+ sortble: true,
+ resizable: false,
+ disableGlobalFilter: true,
+ },
+ {
+ Header: 'Name',
+ accessor: 'name',
+ sortble: true,
+ resizable: false,
+ disableGlobalFilter: true,
+ },
+ {
+ Header: 'Restriction',
+ accessor: 'field',
+ sortble: true,
+ resizable: true,
+ disableGlobalFilter: false,
+ minWidth: 280,
+ },
+ ];
+
+ useEffect(() => {
+ let message = gettext('Please select an object in the tree view.');
+ if (node) {
+ let url = generateNodeUrl.call(
+ node,
+ props.treeNodeInfo,
+ 'dependent',
+ nodeData,
+ true,
+ node.url_jump_after_node
+ );
+ message = gettext(
+ 'No dependant information is available for the selected object.'
+ );
+ if (node.hasDepends && !nodeData.is_collection) {
+ const api = getApiInstance();
+ api({
+ url: url,
+ type: 'GET',
+ })
+ .then((res) => {
+ if (res.data.length > 0) {
+ let data = parseData(res.data, node);
+ setTableData(data);
+ } else {
+ setMsg(message);
+ }
+ })
+ .catch((e) => {
+ Notify.alert(
+ gettext('Failed to retrieve data from the server.'),
+ gettext(e.message)
+ );
+ // show failed message.
+ setMsg(gettext('Failed to retrieve data from the server.'));
+ });
+ }
+ }
+ if (message != '') {
+ setMsg(message);
+ }
+
+ return () => {
+ setTableData([]);
+ };
+ }, [nodeData]);
+
+ return (
+ <>
+ {tableData.length > 0 ? (
+
+ ) : (
+
+ )}
+ >
+ );
+}
+
+Dependents.propTypes = {
+ res: PropTypes.array,
+ nodeData: PropTypes.object,
+ treeNodeInfo: PropTypes.object,
+ node: PropTypes.func,
+};
diff --git a/web/pgadmin/misc/dependents/static/js/dependents.js b/web/pgadmin/misc/dependents/static/js/dependents.js
deleted file mode 100644
index 717ca1e4a..000000000
--- a/web/pgadmin/misc/dependents/static/js/dependents.js
+++ /dev/null
@@ -1,321 +0,0 @@
-/////////////////////////////////////////////////////////////
-//
-// pgAdmin 4 - PostgreSQL Tools
-//
-// Copyright (C) 2013 - 2022, The pgAdmin Development Team
-// This software is released under the PostgreSQL Licence
-//
-//////////////////////////////////////////////////////////////
-
-import Notify from '../../../../static/js/helpers/Notifier';
-
-define('misc.dependents', [
- 'sources/gettext', 'underscore', 'jquery', 'backbone',
- 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.alertifyjs', 'pgadmin.backgrid',
- 'sources/utils',
-], function(gettext, _, $, Backbone, pgAdmin, pgBrowser, Alertify, Backgrid, pgadminUtils) {
-
- if (pgBrowser.NodeDependents)
- return pgBrowser.NodeDependents;
-
- var wcDocker = window.wcDocker;
-
- pgBrowser.NodeDependents = pgBrowser.NodeDependents || {};
-
- _.extend(pgBrowser.NodeDependents, {
- init: function() {
- if (this.initialized) {
- return;
- }
-
- this.initialized = true;
- this.dependentsPanel = pgBrowser.docker.findPanels('dependents')[0];
- /* Parameter is used to set the proper label of the
- * backgrid header cell.
- */
- _.bindAll(this, 'showDependents', '__updateCollection', '__loadMoreRows',
- '__appendGridToPanel', 'toggleVisibility');
-
- // Defining Backbone Model for Dependents.
- var Model = Backbone.Model.extend({
- defaults: {
- icon: 'icon-unknown',
- type: undefined,
- name: undefined,
- /* field contains 'Database Name' for 'Tablespace and Role node',
- * for other node it contains 'Restriction'.
- */
- field: undefined,
- },
- // This function is used to fetch/set the icon for the type(Function, Role, Database, ....)
- parse: function(res) {
- var node = pgBrowser.Nodes[res.type];
- if(res.icon == null || res.icon == '') {
- if (node) {
- res.icon = _.isFunction(node['node_image']) ?
- (node['node_image']).apply(node, [null, null]) :
- (node['node_image'] || ('icon-' + res.type));
- } else {
- res.icon = ('icon-' + res.type);
- }
- }
- res.type = pgadminUtils.titleize(res.type.replace(/_/g, ' '), true);
- return res;
- },
- });
-
- // Defining Backbone Collection for Dependents.
- this.dependentCollection = new(Backbone.Collection.extend({
- model: Model,
- }))(null);
-
- if(this.dependentsPanel) this.toggleVisibility(this.dependentsPanel.isVisible());
- },
-
- toggleVisibility: function(visible, closed=false) {
- if (visible) {
- this.dependentsPanel = pgBrowser.docker.findPanels('dependents')[0];
- var t = pgBrowser.tree,
- i = t && t.selected(),
- d = i && t.itemData(i),
- n = i && d && pgBrowser.Nodes[d._type];
-
- this.showDependents(i, d, n);
-
- // We will start listening the tree selection event.
- pgBrowser.Events.on('pgadmin-browser:tree:selected', this.showDependents);
- } else {
- if(closed) {
- $(this.dependentsPanel).data('node-prop', '');
- }
- // We don't need to listen the tree item selection event.
- pgBrowser.Events.off('pgadmin-browser:tree:selected', this.showDependents);
- }
- },
-
- /* Function is used to create and render backgrid with
- * empty collection. We just want to add backgrid into the
- * panel only once.
- */
- __appendGridToPanel: function() {
- var $container = this.dependentsPanel.layout().scene().find('.pg-panel-content'),
- $gridContainer = $container.find('.pg-panel-dependents-container'),
- grid = new Backgrid.Grid({
- emptyText: gettext('No data found'),
- columns: [{
- name: 'type',
- label: gettext('Type'),
- // Extend it to render the icon as per the type.
- cell: Backgrid.Cell.extend({
- render: function() {
- Backgrid.Cell.prototype.render.apply(this, arguments);
- this.$el.prepend($('', {
- class: 'wcTabIcon ' + this.model.get('icon'),
- }));
- return this;
- },
- }),
- editable: false,
- },
- {
- name: 'name',
- label: gettext('Name'),
- cell: 'string',
- editable: false,
- },
- {
- name: 'field',
- label: gettext('field'),
- cell: 'string',
- editable: false,
- },
- ],
-
- collection: this.dependentCollection,
- className: 'backgrid table presentation table-bordered table-noouter-border table-hover',
- });
-
- // Condition is used to save grid object to change the label of the header.
- this.dependentGrid = grid;
-
- $gridContainer.empty();
- $gridContainer.append(grid.render().el);
-
- return true;
- },
-
- // Fetch the actual data and update the collection
- showDependents: function(item, data, node) {
- if (!node) {
- return;
- }
-
- /**
- * We can't start fetching the statistics immediately, it is possible -
- * the user is just using keyboards to select the node, and just
- * traversing through.
- *
- * We will wait for some time before fetching
- **/
- if (this.timeout) {
- clearTimeout(this.timeout);
- }
- this.timeout = setTimeout(() => {
- this.__updateCollection(node.generate_url(item, 'dependent', data, true), node, item, data._type);
- }, 400);
- },
-
- // Fetch the actual data and update the collection
- __updateCollection: function(url, node, item, node_type) {
- let self = this,
- msg = gettext('Please select an object in the tree view.'),
- panel = this.dependentsPanel,
- $container = panel.layout().scene().find('.pg-panel-content'),
- $msgContainer = $container.find('.pg-panel-depends-message'),
- $gridContainer = $container.find('.pg-panel-dependents-container'),
- treeHierarchy = pgBrowser.tree.getTreeNodeHierarchy(item);
-
- if (node) {
- /* We fetch the Dependencies and Dependents tab only for
- * those node who set the parameter hasDepends to true.
- */
- msg = gettext('No dependent information is available for the selected object.');
- if (node.hasDepends) {
- // Avoid unnecessary reloads
- var cache_flag = {
- node_type: node_type,
- url: url,
- };
- if (_.isEqual($(panel).data('node-prop'), cache_flag)) {
- return;
- }
- // Cache the current IDs for next time
- $(panel).data('node-prop', cache_flag);
-
- /* Updating the label for the 'field' type of the backbone model.
- * Label should be "Database" if the node type is tablespace or role
- * and dependent tab is selected. For other nodes and dependencies tab
- * it should be 'Restriction'.
- */
- this.__appendGridToPanel();
- if (node.type == 'tablespace' || node.type == 'role') {
- this.dependentGrid.columns.models[2].set({
- 'label': gettext('Database'),
- });
- } else {
- this.dependentGrid.columns.models[2].set({
- 'label': gettext('Restriction'),
- });
- }
-
- // Hide message container and show grid container.
- $msgContainer.addClass('d-none');
- $gridContainer.removeClass('d-none');
-
- var timer = '';
- var ajaxHook = function() {
- $.ajax({
- url: url,
- type: 'GET',
- beforeSend: function(xhr) {
- xhr.setRequestHeader(pgAdmin.csrf_token_header, pgAdmin.csrf_token);
- // Generate a timer for the request
- timer = setTimeout(function() {
- // notify user if request is taking longer than 1 second
-
- $msgContainer.text(gettext('Fetching dependent information from the server...'));
- $msgContainer.removeClass('d-none');
- msg = '';
-
- }, 1000);
- },
- })
- .done(function(res) {
- clearTimeout(timer);
-
- if (res.length > 0) {
-
- if (!$msgContainer.hasClass('d-none')) {
- $msgContainer.addClass('d-none');
- }
- $gridContainer.removeClass('d-none');
-
- self.dependentData = res;
-
- // Load only 100 rows
- self.dependentCollection.reset(self.dependentData.splice(0, 100), {parse: true});
-
- // Load more rows on scroll down
- pgBrowser.Events.on(
- 'pgadmin-browser:panel-dependents:' +
- wcDocker.EVENT.SCROLLED,
- self.__loadMoreRows
- );
-
- } else {
- // Do not listen the scroll event
- pgBrowser.Events.off(
- 'pgadmin-browser:panel-dependents:' +
- wcDocker.EVENT.SCROLLED
- );
-
- self.dependentCollection.reset({silent: true});
- $msgContainer.text(msg);
- $msgContainer.removeClass('d-none');
-
- if (!$gridContainer.hasClass('d-none')) {
- $gridContainer.addClass('d-none');
- }
- }
-
-
- })
- .fail(function(xhr, error, message) {
- var _label = treeHierarchy[node_type].label;
- pgBrowser.Events.trigger(
- 'pgadmin:node:retrieval:error', 'depends', xhr, error, message
- );
- if (!Alertify.pgHandleItemError(xhr, error, message, {
- item: item,
- info: treeHierarchy,
- })) {
- Notify.pgNotifier(
- error, xhr,
- gettext('Error retrieving data from the server: %s', message || _label),
- function(alertMsg) {
- if(alertMsg === 'CRYPTKEY_SET') {
- ajaxHook();
- } else {
- console.warn(arguments);
- }
- });
- }
- // show failed message.
- $msgContainer.text(gettext('Failed to retrieve data from the server.'));
- });
- };
- ajaxHook();
- }
- }
- if (msg != '') {
- $msgContainer.text(msg);
- $msgContainer.removeClass('d-none');
- if (!$gridContainer.hasClass('d-none')) {
- $gridContainer.addClass('d-none');
- }
- }
- },
- __loadMoreRows: function() {
- if (this.dependentsPanel.length < 1) return ;
-
- let elem = this.dependentsPanel.$container.find('.pg-panel-dependents-container').closest('.wcFrameCenter')[0];
- if ((elem.scrollHeight - 10) < elem.scrollTop + elem.offsetHeight) {
- if (this.dependentData.length > 0) {
- this.dependentCollection.add(this.dependentData.splice(0, 100), {parse: true});
- }
- }
- },
- });
-
- return pgBrowser.NodeDependents;
-});
diff --git a/web/pgadmin/misc/statistics/static/js/Statistics.jsx b/web/pgadmin/misc/statistics/static/js/Statistics.jsx
new file mode 100644
index 000000000..f2fb316f0
--- /dev/null
+++ b/web/pgadmin/misc/statistics/static/js/Statistics.jsx
@@ -0,0 +1,235 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import _ from 'lodash';
+import React, { useEffect } from 'react';
+import {
+ generateNodeUrl,
+ generateCollectionURL,
+} from '../../../../browser/static/js/node_ajax';
+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 sizePrettify from 'sources/size_prettify';
+
+const useStyles = makeStyles((theme) => ({
+ emptyPanel: {
+ minHeight: '100%',
+ minWidth: '100%',
+ background: theme.palette.grey[400],
+ overflow: 'auto',
+ padding: '7.5px',
+ },
+ panelIcon: {
+ width: '80%',
+ margin: '0 auto',
+ marginTop: '25px !important',
+ position: 'relative',
+ textAlign: 'center',
+ },
+ panelMessage: {
+ marginLeft: '0.5rem',
+ fontSize: '0.875rem',
+ },
+ autoResizer: {
+ height: '100% !important',
+ width: '100% !important',
+ background: theme.palette.grey[400],
+ padding: '7.5px',
+ overflow: 'auto !important',
+ minHeight: '100%',
+ minWidth: '100%',
+ },
+}));
+
+function getColumn(data, singleLineStatistics) {
+ let columns = [];
+ if (!singleLineStatistics) {
+ if (!_.isUndefined(data)) {
+ data.forEach((row) => {
+ var column = {
+ Header: row.name,
+ accessor: row.name,
+ sortble: true,
+ resizable: false,
+ disableGlobalFilter: true,
+ };
+ columns.push(column);
+ });
+ }
+ } else {
+ columns = [
+ {
+ Header: 'Statictics',
+ accessor: 'name',
+ sortble: true,
+ resizable: false,
+ disableGlobalFilter: true,
+ },
+ {
+ Header: 'Value',
+ accessor: 'value',
+ sortble: true,
+ resizable: false,
+ disableGlobalFilter: true,
+ },
+ ];
+ }
+ return columns;
+}
+
+function getTableData(res, node) {
+ let nodeStats = [],
+ colData;
+ if (res.data.data) {
+ let data = res.data.data;
+ if (node.hasCollectiveStatistics || data['rows'].length > 1) {
+ data.rows.forEach((row) => {
+ nodeStats.push({ ...row, icon: '' });
+ });
+ colData = getColumn(data.columns, false);
+ } else {
+ nodeStats = createSingleLineStatistics(data, node.statsPrettifyFields);
+ colData = getColumn(data.columns, true);
+ }
+ }
+ return [nodeStats, colData];
+}
+function createSingleLineStatistics(data, prettifyFields) {
+ var row = data['rows'][0],
+ columns = data['columns'],
+ res = [],
+ name,
+ value;
+
+ for (var idx in columns) {
+ name = columns[idx]['name'];
+ if (row && row[name]) {
+ value =
+ _.indexOf(prettifyFields, name) != -1
+ ? sizePrettify(row[name])
+ : row[name];
+ } else {
+ value = null;
+ }
+
+ res.push({
+ name: name,
+ value: value,
+ icon: '',
+ });
+ }
+
+ return res;
+}
+
+export default function Statistics({ nodeData, item, node, ...props }) {
+ const classes = useStyles();
+ const [tableData, setTableData] = React.useState([]);
+
+ const [msg, setMsg] = React.useState('');
+ const [columns, setColumns] = React.useState([
+ {
+ Header: 'Statictics',
+ accessor: 'name',
+ sortble: true,
+ resizable: false,
+ disableGlobalFilter: true,
+ },
+ {
+ Header: 'Value',
+ accessor: 'value',
+ sortble: true,
+ resizable: false,
+ disableGlobalFilter: true,
+ },
+ ]);
+
+ useEffect(() => {
+ let url,
+ message = gettext('Please select an object in the tree view.');
+ if (node) {
+ if (nodeData.is_collection) {
+ url = generateCollectionURL.call(node, item, 'stats');
+ } else {
+ url = generateNodeUrl.call(
+ node,
+ props.treeNodeInfo,
+ 'stats',
+ nodeData,
+ true,
+ node.url_jump_after_node
+ );
+ }
+
+ message = gettext('No statistics are available for the selected object.');
+
+ const api = getApiInstance();
+ if (node.hasStatistics) {
+ api({
+ url: url,
+ type: 'GET',
+ })
+ .then((res) => {
+ let [nodeStats, colData] = getTableData(res, node);
+ setTableData(nodeStats);
+ setColumns(colData);
+ })
+ .catch((e) => {
+ Notify.alert(
+ gettext('Failed to retrieve data from the server.'),
+ gettext(e.message)
+ );
+ // show failed message.
+ setMsg(gettext('Failed to retrieve data from the server.'));
+ });
+ } else {
+ setMsg(message);
+ }
+ }
+ if (message != '') {
+ setMsg(message);
+ }
+ return () => {
+ setTableData([]);
+ };
+ }, [nodeData]);
+
+ return (
+ <>
+ {tableData.length > 0 ? (
+
+ ) : (
+
+ )}
+ >
+ );
+}
+
+Statistics.propTypes = {
+ res: PropTypes.array,
+ nodeData: PropTypes.object,
+ item: PropTypes.object,
+ treeNodeInfo: PropTypes.object,
+ node: PropTypes.func,
+};
diff --git a/web/pgadmin/misc/statistics/static/js/statistics.js b/web/pgadmin/misc/statistics/static/js/statistics.js
deleted file mode 100644
index 744e36dbf..000000000
--- a/web/pgadmin/misc/statistics/static/js/statistics.js
+++ /dev/null
@@ -1,432 +0,0 @@
-/////////////////////////////////////////////////////////////
-//
-// pgAdmin 4 - PostgreSQL Tools
-//
-// Copyright (C) 2013 - 2022, The pgAdmin Development Team
-// This software is released under the PostgreSQL Licence
-//
-//////////////////////////////////////////////////////////////
-
-import Notify from '../../../../static/js/helpers/Notifier';
-
-define('misc.statistics', [
- 'sources/gettext', 'underscore', 'jquery', 'backbone',
- 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backgrid', 'alertify', 'sources/size_prettify',
- 'sources/misc/statistics/statistics',
-], function(
- gettext, _, $, Backbone, pgAdmin, pgBrowser, Backgrid, Alertify, sizePrettify,
- statisticsHelper
-) {
-
- if (pgBrowser.NodeStatistics)
- return pgBrowser.NodeStatistics;
-
- pgBrowser.NodeStatistics = pgBrowser.NodeStatistics || {};
-
- if (pgBrowser.NodeStatistics.initialized) {
- return pgBrowser.NodeStatistics;
- }
-
- var SizeFormatter = Backgrid.SizeFormatter = function() {/*This is intentional (SonarQube)*/};
- _.extend(SizeFormatter.prototype, {
- /**
- Takes a raw value from a model and returns the human readable formatted
- string for display.
-
- @member Backgrid.SizeFormatter
- @param {*} rawData
- @param {Backbone.Model} model Used for more complicated formatting
- @return {*}
- */
- fromRaw: function(rawData) {
- return sizePrettify(rawData);
- },
- toRaw: function(formattedData) {
- return formattedData;
- },
- });
-
- var PGBooleanCell = Backgrid.Extension.SwitchCell.extend({
- defaults: _.extend({}, Backgrid.Extension.SwitchCell.prototype.defaults),
- }),
- typeCellMapper = {
- // boolean
- 16: PGBooleanCell,
- // int8
- 20: Backgrid.IntegerCell,
- // int2
- 21: Backgrid.IntegerCell,
- // int4
- 23: Backgrid.IntegerCell,
- // float4
- 700: Backgrid.NumberCell,
- // float8
- 701: Backgrid.NumberCell,
- // numeric
- 1700: Backgrid.NumberCell,
- // abstime
- 702: Backgrid.DatetimeCell,
- // reltime
- 703: Backgrid.DatetimeCell,
- // date
- 1082: Backgrid.DatetimeCell.extend({
- includeDate: true,
- includeTime: false,
- includeMilli: false,
- }),
- // time
- 1083: Backgrid.DatetimeCell.extend({
- includeDate: false,
- includeTime: true,
- includeMilli: true,
- }),
- // timestamp
- 1114: Backgrid.DatetimeCell.extend({
- includeDate: true,
- includeTime: true,
- includeMilli: true,
- }),
- // timestamptz
- 1184: 'string'
- /* Backgrid.DatetimeCell.extend({
- includeDate: true, includeTime: true, includeMilli: true
- }) */
- ,
- 1266: 'string',
- /* Backgrid.DatetimeCell.extend({
- includeDate: false, includeTime: true, includeMilli: true
- }) */
- },
- GRID_CLASSES = 'backgrid presentation table table-bordered table-noouter-border table-hover',
- wcDocker = window.wcDocker;
-
- _.extend(
- PGBooleanCell.prototype.defaults.options, {
- onText: gettext('True'),
- offText: gettext('False'),
- onColor: 'success',
- offColor: 'ternary',
- size: 'mini',
- }
- );
-
- _.extend(pgBrowser.NodeStatistics, {
- init: function() {
- if (this.initialized) {
- return;
- }
- this.initialized = true;
- _.bindAll(
- this,
- 'showStatistics', 'toggleVisibility',
- '__createMultiLineStatistics', '__createSingleLineStatistics', '__loadMoreRows');
-
- _.extend(
- this, {
- collection: new(Backbone.Collection)(null),
- statistic_columns: [{
- editable: false,
- name: 'statistics',
- label: gettext('Statistics'),
- cell: 'string',
- headerCell: Backgrid.Extension.CustomHeaderCell,
- cellHeaderClasses: 'width_percent_25',
- }, {
- editable: false,
- name: 'value',
- label: gettext('Value'),
- cell: 'string',
- }],
- columns: null,
- grid: null,
- });
-
- this.panel = pgBrowser.docker.findPanels('statistics');
- if(this.panel.length > 0) this.toggleVisibility(this.panel[0].isVisible());
- },
-
- toggleVisibility: function(visible, closed=false) {
- if (visible) {
- this.panel = pgBrowser.docker.findPanels('statistics');
- var t = pgBrowser.tree,
- i = t.selected(),
- d = i && t.itemData(i),
- n = i && d && pgBrowser.Nodes[d._type];
-
- pgBrowser.NodeStatistics.showStatistics.apply(
- pgBrowser.NodeStatistics, [i, d, n]
- );
-
- // We will start listening the tree selection event.
- pgBrowser.Events.on(
- 'pgadmin-browser:tree:selected',
- pgBrowser.NodeStatistics.showStatistics
- );
- pgBrowser.Events.on(
- 'pgadmin-browser:tree:refreshing',
- pgBrowser.NodeStatistics.refreshStatistics,
- this
- );
- } else {
- if(closed) {
- $(this.panel[0]).data('node-prop', '');
- }
- // We don't need to listen the tree item selection event.
- pgBrowser.Events.off(
- 'pgadmin-browser:tree:selected',
- pgBrowser.NodeStatistics.showStatistics
- );
- pgBrowser.Events.off(
- 'pgadmin-browser:tree:refreshing',
- pgBrowser.NodeStatistics.refreshStatistics,
- this
- );
- }
- },
-
- // Fetch the actual data and update the collection
- __updateCollection: function(url, node, item, node_type) {
- var $container = this.panel[0].layout().scene().find('.pg-panel-content'),
- $msgContainer = $container.find('.pg-panel-statistics-message'),
- $gridContainer = $container.find('.pg-panel-statistics-container'),
- panel = this.panel,
- self = this,
- msg = '',
- n_type = node_type;
-
- if (node) {
- msg = gettext('No statistics are available for the selected object.');
- /* We fetch the statistics only for those node who set the parameter
- * showStatistics function.
- */
-
- // Avoid unnecessary reloads
- var treeHierarchy = pgBrowser.tree.getTreeNodeHierarchy(item);
- var cache_flag = {
- node_type: node_type,
- url: url,
- };
- if (_.isEqual($(panel[0]).data('node-prop'), cache_flag)) {
- return;
- }
- // Cache the current IDs for next time
- $(panel[0]).data('node-prop', cache_flag);
-
- if (statisticsHelper.nodeHasStatistics(pgBrowser, node, item)) {
- msg = '';
- var timer;
- // Set the url, fetch the data and update the collection
- var ajaxHook = function() {
- $.ajax({
- url: url,
- type: 'GET',
- beforeSend: function(xhr) {
- xhr.setRequestHeader(
- pgAdmin.csrf_token_header, pgAdmin.csrf_token
- );
- // Generate a timer for the request
- timer = setTimeout(function() {
- // notify user if request is taking longer than 1 second
-
- $msgContainer.text(gettext('Retrieving data from the server...'));
- $msgContainer.removeClass('d-none');
- if (self.grid) {
- self.grid.remove();
- }
- }, 1000);
- },
- })
- .done(function(res) {
- // clear timer and reset message.
- clearTimeout(timer);
- $msgContainer.text('');
- if (res.data) {
- var data = self.data = res.data;
- if (node.hasCollectiveStatistics || data['rows'].length > 1) {
- // Listen scroll event to load more rows
- pgBrowser.Events.on(
- 'pgadmin-browser:panel-statistics:' +
- wcDocker.EVENT.SCROLLED,
- self.__loadMoreRows
- );
- self.__createMultiLineStatistics.call(self, data, node.statsPrettifyFields);
- } else {
- // Do not listen the scroll event
- pgBrowser.Events.off(
- 'pgadmin-browser:panel-statistics:' +
- wcDocker.EVENT.SCROLLED,
- self.__loadMoreRows
- );
- self.__createSingleLineStatistics.call(self, data, node.statsPrettifyFields);
- }
-
- if (self.grid) {
- delete self.grid;
- self.grid = null;
- }
-
- self.grid = new Backgrid.Grid({
- emptyText: gettext('No data found'),
- columns: self.columns,
- collection: self.collection,
- className: GRID_CLASSES,
- });
- self.grid.render();
- $gridContainer.empty();
- $gridContainer.append(self.grid.$el);
-
- if (!$msgContainer.hasClass('d-none')) {
- $msgContainer.addClass('d-none');
- }
- $gridContainer.removeClass('d-none');
-
- } else if (res.info) {
- if (!$gridContainer.hasClass('d-none')) {
- $gridContainer.addClass('d-none');
- }
- $msgContainer.text(res.info);
- $msgContainer.removeClass('d-none');
- }
- })
- .fail(function(xhr, error, message) {
- var _label = treeHierarchy[n_type].label;
- pgBrowser.Events.trigger(
- 'pgadmin:node:retrieval:error', 'statistics', xhr, error, message, item
- );
- if (!Alertify.pgHandleItemError(xhr, error, message, {
- item: item,
- info: treeHierarchy,
- })) {
- Notify.pgNotifier(
- error, xhr,
- gettext('Error retrieving the information - %s', message || _label),
- function(alertMsg) {
- if(alertMsg === 'CRYPTKEY_SET') {
- ajaxHook();
- } else {
- console.warn(arguments);
- }
- }
- );
- }
- // show failed message.
- $msgContainer.text(gettext('Failed to retrieve data from the server.'));
- });
- };
-
- ajaxHook();
- }
- }
- if (msg != '') {
- // Hide the grid container and show the default message container
- if (!$gridContainer.hasClass('d-none'))
- $gridContainer.addClass('d-none');
- $msgContainer.removeClass('d-none');
-
- $msgContainer.text(msg);
- }
- },
- refreshStatistics: function(item, data, node) {
- var that = this,
- cache_flag = {
- node_type: data._type,
- url: node.generate_url(item, 'stats', data, true),
- };
-
- if (_.isEqual($(that.panel[0]).data('node-prop'), cache_flag)) {
- // Reset the current item selection
- $(that.panel[0]).data('node-prop', '');
- that.showStatistics(item, data, node);
- }
- },
- showStatistics: function(item, data, node) {
- var self = this;
- if (!node) {
- return;
- }
- /**
- * We can't start fetching the statistics immediately, it is possible -
- * the user is just using keyboards to select the node, and just
- * traversing through.
- *
- * We will wait for some time before fetching the statistics for the
- * selected node.
- **/
- if (self.timeout) {
- clearTimeout(self.timeout);
- }
- self.timeout = setTimeout(
- function() {
- self.__updateCollection.call(
- self, node.generate_url(item, 'stats', data, true), node, item, data._type
- );
- }, 400);
- },
-
- __createMultiLineStatistics: function(data, prettifyFields) {
- var rows = data['rows'],
- columns = data['columns'];
-
- this.columns = [];
- for (var idx in columns) {
- var rawColumn = columns[idx],
- cell_type = typeCellMapper[rawColumn['type_code']] || 'string';
-
- // Don't show PID comma separated
- if (rawColumn['name'] == 'PID') {
- cell_type = cell_type.extend({
- orderSeparator: '',
- });
- }
-
- var col = {
- editable: false,
- name: rawColumn['name'],
- cell: cell_type,
- };
- if (_.indexOf(prettifyFields, rawColumn['name']) != -1) {
- col['formatter'] = SizeFormatter;
- }
- this.columns.push(col);
-
- }
-
- this.collection.reset(rows.splice(0, 50));
- },
-
- __loadMoreRows: function() {
- let elem = $('.pg-panel-statistics-container').closest('.wcFrameCenter')[0];
- if ((elem.scrollHeight - 10) < elem.scrollTop + elem.offsetHeight) {
- var rows = this.data['rows'];
- if (rows.length > 0) {
- this.collection.add(rows.splice(0, 50));
- }
- }
- },
-
- __createSingleLineStatistics: function(data, prettifyFields) {
- var row = data['rows'][0],
- columns = data['columns'],
- res = [],
- name;
-
- this.columns = this.statistic_columns;
- for (var idx in columns) {
- name = (columns[idx])['name'];
- let val = null;
- if (row && row[name]) {
- val = _.indexOf(prettifyFields, name) != -1 ? sizePrettify(row[name]) : row[name];
- }
- res.push({
- 'statistics': name,
- // Check if row is undefined?
- 'value': val,
- });
- }
-
- this.collection.reset(res);
- },
- });
-
- return pgBrowser.NodeStatistics;
-});
diff --git a/web/pgadmin/static/js/components/PgTable.jsx b/web/pgadmin/static/js/components/PgTable.jsx
index 7a56ab83d..f2739c775 100644
--- a/web/pgadmin/static/js/components/PgTable.jsx
+++ b/web/pgadmin/static/js/components/PgTable.jsx
@@ -30,14 +30,23 @@ const useStyles = makeStyles((theme) => ({
height: '100% !important',
width: '100% !important',
},
+ fixedSizeList: {
+ position: 'relative',
+ direction: 'ltr',
+ overflowX: 'hidden !important',
+ overflow: 'overlay !important'
+ },
table: {
+ flexGrow:1,
+ minHeight:0,
borderSpacing: 0,
width: '100%',
overflow: 'hidden',
- height: '100%',
- backgroundColor: theme.otherVars.tableBg,
- ...theme.mixins.panelBorder,
- //backgroundColor: theme.palette.background.default,
+ borderRadius: theme.shape.borderRadius,
+ },
+ extraTable:{
+ backgroundColor: theme.palette.grey[400],
+ flexGrow:1,
},
tableCell: {
@@ -49,6 +58,10 @@ const useStyles = makeStyles((theme) => ({
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
+ backgroundColor: theme.otherVars.tableBg,
+ // ...theme.mixins.panelBorder.top,
+ ...theme.mixins.panelBorder.left,
+
},
selectCell: {
textAlign: 'center'
@@ -60,8 +73,11 @@ const useStyles = makeStyles((theme) => ({
overflowY: 'auto',
overflowX: 'hidden',
alignContent: 'center',
+ backgroundColor: theme.otherVars.tableBg,
...theme.mixins.panelBorder.bottom,
...theme.mixins.panelBorder.right,
+ ...theme.mixins.panelBorder.top,
+ ...theme.mixins.panelBorder.left,
},
resizer: {
display: 'inline-block',
@@ -75,8 +91,10 @@ const useStyles = makeStyles((theme) => ({
touchAction: 'none',
},
cellIcon: {
- paddingLeft: '1.5em',
- height: 35
+ paddingLeft: '1.8em',
+ paddingTop: '0.35em',
+ height: 35,
+ backgroundPosition: '1%',
}
}),
);
@@ -235,15 +253,26 @@ export default function PgTable({ columns, data, isSelectRow, ...props }) {
);
// Render the UI for your table
return (
-
- {({ height }) => (
+
+ {({ height}) => (
- {headerGroups.map(headerGroup => (
+ {headerGroups.map((headerGroup) => (
- {headerGroup.headers.map(column => (
-
-
+ {headerGroup.headers.map((column) => (
+
+
{column.render('Header')}
{column.isSorted
@@ -252,21 +281,23 @@ export default function PgTable({ columns, data, isSelectRow, ...props }) {
: ' 🔼'
: ''}
- {column.resizable &&
+ {column.resizable && (
}
+ />
+ )}
))}
+ {/*
*/}
))}
-
)}
diff --git a/web/webpack.config.js b/web/webpack.config.js
index bf7293c68..3181ecfe3 100644
--- a/web/webpack.config.js
+++ b/web/webpack.config.js
@@ -455,9 +455,6 @@ module.exports = [{
'pure|pgadmin.dashboard',
'pure|pgadmin.browser.quick_search',
'pure|pgadmin.tools.user_management',
- 'pure|pgadmin.browser.object_statistics',
- 'pure|pgadmin.browser.dependencies',
- 'pure|pgadmin.browser.dependents',
'pure|pgadmin.browser.object_sql',
'pure|pgadmin.browser.bgprocess',
'pure|pgadmin.node.server_group',
diff --git a/web/webpack.shim.js b/web/webpack.shim.js
index 6896ade63..0430047c0 100644
--- a/web/webpack.shim.js
+++ b/web/webpack.shim.js
@@ -207,10 +207,7 @@ var webpackShimConfig = {
'pgadmin.browser.dialog': path.join(__dirname, './pgadmin/browser/static/js/dialog'),
'pgadmin.browser.node': path.join(__dirname, './pgadmin/browser/static/js/node'),
'pgadmin.browser.node.ui': path.join(__dirname, './pgadmin/browser/static/js/node.ui'),
- 'pgadmin.browser.dependencies': path.join(__dirname, './pgadmin/misc/dependencies/static/js/dependencies'),
- 'pgadmin.browser.dependents': path.join(__dirname, './pgadmin/misc/dependents/static/js/dependents'),
'pgadmin.browser.object_sql': path.join(__dirname, './pgadmin/misc/sql/static/js/sql'),
- 'pgadmin.browser.object_statistics': path.join(__dirname, './pgadmin/misc/statistics/static/js/statistics'),
'pgadmin.browser.panel': path.join(__dirname, './pgadmin/browser/static/js/panel'),
'pgadmin.browser.toolbar': path.join(__dirname, './pgadmin/browser/static/js/toolbar'),
'pgadmin.browser.server.privilege': path.join(__dirname, './pgadmin/browser/server_groups/servers/static/js/privilege'),