pgadmin4/web/pgadmin/misc/properties/CollectionNodeProperties.jsx

348 lines
9.8 KiB
JavaScript

/////////////////////////////////////////////////////////////
//
// 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 pgAdmin from 'sources/pgadmin';
import getApiInstance from 'sources/api_instance';
import { makeStyles } from '@material-ui/core/styles';
import { Box, Switch } from '@material-ui/core';
import { generateCollectionURL } from '../../browser/static/js/node_ajax';
import Notify from '../../static/js/helpers/Notifier';
import gettext from 'sources/gettext';
import 'wcdocker';
import PgTable from 'sources/components/PgTable';
import Theme from 'sources/Theme';
import PropTypes from 'prop-types';
import { PgIconButton } from '../../static/js/components/Buttons';
import DeleteIcon from '@material-ui/icons/Delete';
import DeleteSweepIcon from '@material-ui/icons/DeleteSweep';
import EmptyPanelMessage from '../../static/js/components/EmptyPanelMessage';
import Loader from 'sources/components/Loader';
const useStyles = makeStyles((theme) => ({
emptyPanel: {
minHeight: '100%',
minWidth: '100%',
background: theme.otherVars.emptySpaceBg,
overflow: 'auto',
padding: '8px',
display: 'flex',
},
panelIcon: {
width: '80%',
margin: '0 auto',
marginTop: '25px !important',
position: 'relative',
textAlign: 'center',
},
panelMessage: {
marginLeft: '0.5rem',
fontSize: '0.875rem',
},
searchPadding: {
flex: 2.5
},
searchInput: {
flex: 1,
margin: '4 0 4 0',
borderLeft: 'none',
paddingLeft: 5
},
propertiesPanel: {
height: '100%'
},
autoResizer: {
height: '100% !important',
width: '100% !important',
background: theme.palette.grey[400],
padding: '8px',
overflow: 'hidden !important',
overflowX: 'auto !important'
},
dropButton: {
marginRight: '8px !important'
},
readOnlySwitch: {
opacity: 0.75,
'& .MuiSwitch-track': {
opacity: theme.palette.action.disabledOpacity,
}
}
}));
export function CollectionNodeView({
node,
treeNodeInfo,
itemNodeData,
item,
pgBrowser
}) {
const classes = useStyles();
const [data, setData] = React.useState([]);
const [infoMsg, setInfoMsg] = React.useState('Please select an object in the tree view.');
const [selectedObject, setSelectedObject] = React.useState([]);
const [reload, setReload] = React.useState(false);
const [loaderText, setLoaderText] = React.useState('');
//Reload the collection node on refresh or change in children count
React.useEffect(() => {
setReload(!reload);
}, [item?._children]);
const [pgTableColumns, setPgTableColumns] = React.useState([
{
Header: 'properties',
accessor: 'Properties',
sortable: true,
resizable: true,
disableGlobalFilter: false,
},
{
Header: 'value',
accessor: 'value',
sortable: true,
resizable: true,
disableGlobalFilter: false,
},
]);
const getTableSelectedRows = (selRows) => {
setSelectedObject(selRows);
};
const onDrop = (type) => {
let selRowModels = selectedObject,
selRows = [],
selItem = pgBrowser.tree.selected(),
selectedItemData = selItem ? pgBrowser.tree.itemData(selItem) : null,
selNode = selectedItemData && pgBrowser.Nodes[selectedItemData._type],
url = undefined,
msg = undefined,
title = undefined;
if (selNode && selNode.type && selNode.type == 'coll-constraints') {
// In order to identify the constraint type, the type should be passed to the server
selRows = selRowModels.map((row) => ({
id: row.original.oid,
_type: row.original._type,
}));
} else {
selRows = selRowModels.map((row) => row.original.oid);
}
if (selRows.length === 0) {
Notify.alert(
gettext('Drop Multiple'),
gettext('Please select at least one object to delete.')
);
return;
}
if (!selNode) return;
if (type === 'dropCascade') {
url = selNode.generate_url(selItem, 'delete');
msg = gettext(
'Are you sure you want to drop all the selected objects and all the objects that depend on them?'
);
title = gettext('DROP CASCADE multiple objects?');
} else {
url = selNode.generate_url(selItem, 'drop');
msg = gettext('Are you sure you want to drop all the selected objects?');
title = gettext('DROP multiple objects?');
}
const api = getApiInstance();
let dropNodeProperties = function () {
api
.delete(url, {
data: JSON.stringify({ ids: selRows }),
contentType: 'application/json; charset=utf-8',
})
.then(function (res) {
if (res.success == 0) {
pgBrowser.report_error(res.errormsg, res.info);
}
pgAdmin.Browser.tree.refresh(selItem);
setReload(!reload);
})
.catch(function (error) {
Notify.alert(
gettext('Error dropping %s', selectedItemData._label.toLowerCase()),
_.isUndefined(error.response) ? error.message : error.response.data.errormsg
);
});
};
if (confirm) {
Notify.confirm(title, msg, dropNodeProperties, null);
} else {
dropNodeProperties();
}
};
React.useEffect(() => {
if (node){
let nodeObj =
pgAdmin.Browser.Nodes[itemNodeData?._type.replace('coll-', '')];
let url = generateCollectionURL.call(nodeObj, item, 'properties');
const api = getApiInstance();
let tableColumns = [];
let column = {};
setLoaderText('Loading...');
if (itemNodeData._type.indexOf('coll-') > -1 && !_.isUndefined(nodeObj.getSchema)) {
let schema = nodeObj.getSchema?.call(nodeObj, treeNodeInfo, itemNodeData);
schema.fields.forEach((field) => {
if (node.columns.indexOf(field.id) > -1) {
if (field.label.indexOf('?') > -1) {
column = {
Header: field.label,
accessor: field.id,
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 0,
// eslint-disable-next-line react/display-name
Cell: ({ value }) => {
return (<Switch color="primary" checked={value} className={classes.readOnlySwitch} value={value} readOnly title={String(value)} />);
}
};
} else {
column = {
Header: field.label,
accessor: field.id,
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 0,
};
}
tableColumns.push(column);
}
});
}else{
node.columns.forEach((field) => {
column = {
Header: field,
accessor: field,
sortable: true,
resizable: true,
disableGlobalFilter: false,
minWidth: 0,
};
tableColumns.push(column);
});
}
api({
url: url,
type: 'GET',
})
.then((res) => {
res.data.forEach((element) => {
element['icon'] = '';
});
setPgTableColumns(tableColumns);
setData(res.data);
setInfoMsg('No properties are available for the selected object.');
setLoaderText('');
})
.catch((err) => {
Notify.alert(
gettext('Failed to retrieve data from the server.'),
gettext(err.message)
);
});
}
}, [itemNodeData, node, item, reload]);
const CustomHeader = () => {
return (
<Box >
<PgIconButton
className={classes.dropButton}
icon={<DeleteIcon/>}
aria-label="Delete/Drop"
title={gettext('Delete/Drop')}
onClick={() => {
onDrop('drop');
}}
disabled={
(selectedObject.length > 0)
? !node.canDrop
: true
}
></PgIconButton>
<PgIconButton
className={classes.dropButton}
icon={<DeleteSweepIcon />}
aria-label="Drop Cascade"
title={gettext('Drop Cascade')}
onClick={() => {
onDrop('dropCascade');
}}
disabled={
(selectedObject.length > 0)
? !node.canDropCascade
: true
}
></PgIconButton>
</Box>);
};
return (
<Theme className='obj_properties'>
<Box className={classes.propertiesPanel}>
{data.length > 0 ?
(
<PgTable
isSelectRow={!('catalog' in treeNodeInfo) && (itemNodeData.label !== 'Catalogs') && _.isUndefined(node?.canSelect)}
CustomHeader={CustomHeader}
className={classes.autoResizer}
columns={pgTableColumns}
data={data}
type={'panel'}
isSearch={false}
getSelectedRows={getTableSelectedRows}
/>
)
:
(
<div className={classes.emptyPanel}>
{loaderText ? (<Loader message={loaderText}/>) :
<EmptyPanelMessage text={gettext(infoMsg)}/>
}
</div>
)
}
</Box>
</Theme>
);
}
CollectionNodeView.propTypes = {
node: PropTypes.func,
itemData: PropTypes.object,
itemNodeData: PropTypes.object,
treeNodeInfo: PropTypes.object,
item: PropTypes.object,
pgBrowser: PropTypes.object,
preferences: PropTypes.object,
sid: PropTypes.number,
did: PropTypes.number,
row: PropTypes.object,
serverConnected: PropTypes.bool,
value: PropTypes.bool,
};