mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Port Dependent, dependencies, statistics panel to React. Fixes #7016
This commit is contained in:
parent
3e86ed5d21
commit
094129e2be
@ -13,6 +13,7 @@ New features
|
||||
Housekeeping
|
||||
************
|
||||
|
||||
| `Issue #7016 <https://redmine.postgresql.org/issues/7016>`_ - Port Dependent, dependencies, statistics panel to React.
|
||||
| `Issue #7017 <https://redmine.postgresql.org/issues/7017>`_ - Port Import/Export dialog to React.
|
||||
|
||||
Bug fixes
|
||||
|
@ -1,6 +1,5 @@
|
||||
.icon-rule{
|
||||
background-image: url('{{ url_for('NODE-rule.static', filename='img/rule.svg') }}') !important;
|
||||
border-radius: 10px;
|
||||
background-size: 20px !important;
|
||||
background-repeat: no-repeat;
|
||||
align-content: center;
|
||||
@ -11,7 +10,6 @@
|
||||
|
||||
.icon-rule-bad{
|
||||
background-image: url('{{ url_for('NODE-rule.static', filename='img/rule-bad.svg') }}') !important;
|
||||
border-radius: 10px;
|
||||
background-size: 20px !important;
|
||||
background-repeat: no-repeat;
|
||||
align-content: center;
|
||||
|
@ -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 = {
|
||||
|
@ -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 (_.isNull(pgBrowser.tree)) return;
|
||||
|
||||
if(_.isUndefined(module.toggleVisibility))
|
||||
return;
|
||||
let selectedPanel = pgBrowser.docker.findPanels(this._type)[0];
|
||||
let isPanelVisible = selectedPanel.isVisible();
|
||||
var $container = selectedPanel
|
||||
.layout()
|
||||
.scene()
|
||||
.find('.pg-panel-content');
|
||||
|
||||
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 (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 (isPanelVisible) {
|
||||
if (eventName == 'panelClosed') {
|
||||
removePanelView($container[0]);
|
||||
} else if (eventName == 'panelVisibilityChanged') {
|
||||
getPanelView(
|
||||
pgBrowser.tree,
|
||||
$container[0],
|
||||
pgBrowser,
|
||||
this._type
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
77
web/pgadmin/browser/static/js/panel_view.jsx
Normal file
77
web/pgadmin/browser/static/js/panel_view.jsx
Normal file
@ -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(
|
||||
<Theme>
|
||||
<Statistics
|
||||
treeNodeInfo={treeNodeInfo}
|
||||
pgBrowser={pgBrowser}
|
||||
nodeData={nodeData}
|
||||
node={node}
|
||||
item={item}
|
||||
/>
|
||||
</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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* When switching from normal node to collection node, clean up the React mounted DOM */
|
||||
export function removePanelView(container) {
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
}
|
175
web/pgadmin/misc/dependencies/static/js/Dependencies.jsx
Normal file
175
web/pgadmin/misc/dependencies/static/js/Dependencies.jsx
Normal file
@ -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 ? (
|
||||
<PgTable
|
||||
className={classes.autoResizer}
|
||||
columns={columns}
|
||||
data={tableData}
|
||||
msg={msg}
|
||||
type={gettext('panel')}
|
||||
></PgTable>
|
||||
) : (
|
||||
<div className={classes.emptyPanel}>
|
||||
<div className={classes.panelIcon}>
|
||||
<i className="fa fa-exclamation-circle"></i>
|
||||
<span className={classes.panelMessage}>{gettext(msg)}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Dependencies.propTypes = {
|
||||
res: PropTypes.array,
|
||||
nodeData: PropTypes.object,
|
||||
treeNodeInfo: PropTypes.object,
|
||||
node: PropTypes.func,
|
||||
};
|
@ -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($('<i>', {
|
||||
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;
|
||||
});
|
176
web/pgadmin/misc/dependents/static/js/Dependents.jsx
Normal file
176
web/pgadmin/misc/dependents/static/js/Dependents.jsx
Normal file
@ -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 ? (
|
||||
<PgTable
|
||||
className={classes.autoResizer}
|
||||
columns={columns}
|
||||
data={tableData}
|
||||
msg={msg}
|
||||
type={gettext('panel')}
|
||||
></PgTable>
|
||||
) : (
|
||||
<div className={classes.emptyPanel}>
|
||||
<div className={classes.panelIcon}>
|
||||
<i className="fa fa-exclamation-circle"></i>
|
||||
<span className={classes.panelMessage}>{gettext(msg)}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Dependents.propTypes = {
|
||||
res: PropTypes.array,
|
||||
nodeData: PropTypes.object,
|
||||
treeNodeInfo: PropTypes.object,
|
||||
node: PropTypes.func,
|
||||
};
|
@ -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($('<i>', {
|
||||
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;
|
||||
});
|
235
web/pgadmin/misc/statistics/static/js/Statistics.jsx
Normal file
235
web/pgadmin/misc/statistics/static/js/Statistics.jsx
Normal file
@ -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 ? (
|
||||
<PgTable
|
||||
className={classes.autoResizer}
|
||||
columns={columns}
|
||||
data={tableData}
|
||||
msg={msg}
|
||||
type={'panel'}
|
||||
></PgTable>
|
||||
) : (
|
||||
<div className={classes.emptyPanel}>
|
||||
<div className={classes.panelIcon}>
|
||||
<i className="fa fa-exclamation-circle"></i>
|
||||
<span className={classes.panelMessage}>{gettext(msg)}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Statistics.propTypes = {
|
||||
res: PropTypes.array,
|
||||
nodeData: PropTypes.object,
|
||||
item: PropTypes.object,
|
||||
treeNodeInfo: PropTypes.object,
|
||||
node: PropTypes.func,
|
||||
};
|
@ -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;
|
||||
});
|
@ -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%',
|
||||
}
|
||||
}),
|
||||
);
|
||||
@ -171,9 +189,9 @@ export default function PgTable({ columns, data, isSelectRow, ...props }) {
|
||||
return [...CLOUMNS];
|
||||
}
|
||||
});
|
||||
hooks.useInstanceBeforeDimensions.push(({ tmpHeaderGroups }) => {
|
||||
hooks.useInstanceBeforeDimensions.push(({ headerGroups }) => {
|
||||
// fix the parent group of the selection button to not be resizable
|
||||
const selectionGroupHeader = tmpHeaderGroups[0].headers[0];
|
||||
const selectionGroupHeader = headerGroups[0].headers[0];
|
||||
selectionGroupHeader.resizable = false;
|
||||
});
|
||||
}
|
||||
@ -235,15 +253,26 @@ export default function PgTable({ columns, data, isSelectRow, ...props }) {
|
||||
);
|
||||
// Render the UI for your table
|
||||
return (
|
||||
<AutoSizer className={classes.autoResizer}>
|
||||
{({ height }) => (
|
||||
<AutoSizer className={(props.type ==='panel' ? props.className: classes.autoResizer)}>
|
||||
{({ height}) => (
|
||||
<div {...getTableProps()} className={classes.table}>
|
||||
<div>
|
||||
{headerGroups.map(headerGroup => (
|
||||
{headerGroups.map((headerGroup) => (
|
||||
<div key={''} {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map(column => (
|
||||
<div key={column.id} {...column.getHeaderProps()} className={clsx(classes.tableCellHeader, column.className)}>
|
||||
<div {...(column.sortble ? column.getSortByToggleProps() : {})}>
|
||||
{headerGroup.headers.map((column) => (
|
||||
<div
|
||||
key={column.id}
|
||||
{...column.getHeaderProps()}
|
||||
className={clsx(
|
||||
classes.tableCellHeader,
|
||||
column.className
|
||||
)}
|
||||
>
|
||||
<div
|
||||
{...(column.sortble
|
||||
? column.getSortByToggleProps()
|
||||
: {})}
|
||||
>
|
||||
{column.render('Header')}
|
||||
<span>
|
||||
{column.isSorted
|
||||
@ -252,21 +281,23 @@ export default function PgTable({ columns, data, isSelectRow, ...props }) {
|
||||
: ' 🔼'
|
||||
: ''}
|
||||
</span>
|
||||
{column.resizable &&
|
||||
{column.resizable && (
|
||||
<div
|
||||
{...column.getResizerProps()}
|
||||
className={classes.resizer}
|
||||
/>}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{/* <span className={classes.extraTable}></span> */}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div {...getTableBodyProps()}>
|
||||
|
||||
<div {...getTableBodyProps()} className={classes}>
|
||||
<FixedSizeList
|
||||
className={classes.fixedSizeList}
|
||||
height={height - 75}
|
||||
itemCount={rows.length}
|
||||
itemSize={35}
|
||||
@ -274,7 +305,6 @@ export default function PgTable({ columns, data, isSelectRow, ...props }) {
|
||||
>
|
||||
{RenderRow}
|
||||
</FixedSizeList>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@ -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',
|
||||
|
@ -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'),
|
||||
|
Loading…
Reference in New Issue
Block a user