///////////////////////////////////////////////////////////// // // pgAdmin 4 - PostgreSQL Tools // // Copyright (C) 2013 - 2025, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // ////////////////////////////////////////////////////////////// import React from 'react'; import { styled } from '@mui/material/styles'; import getApiInstance from 'sources/api_instance'; import { Box } from '@mui/material'; import { generateCollectionURL } from '../../browser/static/js/node_ajax'; import gettext from 'sources/gettext'; import PgTable from 'sources/components/PgTable'; import PropTypes from 'prop-types'; import { PgButtonGroup, PgIconButton } from '../../static/js/components/Buttons'; import DeleteIcon from '@mui/icons-material/Delete'; import DeleteSweepIcon from '@mui/icons-material/DeleteSweep'; import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; import EmptyPanelMessage from '../../static/js/components/EmptyPanelMessage'; import Loader from 'sources/components/Loader'; import { evalFunc } from '../../static/js/utils'; import { usePgAdmin } from '../../static/js/PgAdminProvider'; import { getSwitchCell } from '../../static/js/components/PgReactTableStyled'; import { parseApiError } from '../../static/js/api_instance'; const StyledBox = styled(Box)(({theme}) => ({ height: '100%', '&.CollectionNodeProperties-emptyPanel': { minHeight: '100%', minWidth: '100%', background: theme.otherVars.emptySpaceBg, overflow: 'auto', padding: '8px', display: 'flex', } })); function CustomHeader({node, nodeData, nodeItem, treeNodeInfo, selectedObject, onDrop}) { const canDrop = evalFunc(node, node.canDrop, nodeData, nodeItem, treeNodeInfo); const canDropCascade = evalFunc(node, node.canDropCascade, nodeData, nodeItem, treeNodeInfo); const canDropForce = evalFunc(node, node.canDropForce, nodeData, nodeItem, treeNodeInfo); return ( } aria-label="Delete" title={gettext('Drop')} onClick={() => { onDrop('drop'); }} disabled={ (Object.keys(selectedObject).length > 0) ? !canDrop : true } > {node.type !== 'coll-database' ? } aria-label="Drop Cascade" title={gettext('Drop (Cascade)')} onClick={() => { onDrop('dropCascade'); }} disabled={ (Object.keys(selectedObject).length > 0) ? !canDropCascade : true } > : } aria-label="Drop Force" title={gettext('Drop (Force)')} onClick={() => { onDrop('dropForce'); }} disabled={ (Object.keys(selectedObject).length > 0) ? !canDropForce : true } >} ); } CustomHeader.propTypes = { node: PropTypes.func, nodeData: PropTypes.object, treeNodeInfo: PropTypes.object, nodeItem: PropTypes.object, selectedObject: PropTypes.object, onDrop: PropTypes.func, }; export default function CollectionNodeProperties({ node, treeNodeInfo, nodeData, nodeItem, isActive, isStale, setIsStale }) { const pgAdmin = usePgAdmin(); const [data, setData] = React.useState([]); const [infoMsg, setInfoMsg] = React.useState('Please select an object in the tree view.'); const [selectedObject, setSelectedObject] = React.useState({}); const [loaderText, setLoaderText] = React.useState(''); const schemaRef = React.useRef(); const [pgTableColumns, setPgTableColumns] = React.useState([ { header: 'properties', accessorKey: 'Properties', enableSorting: true, enableResizing: true, enableFilters: true, }, { header: 'value', accessorKey: 'value', enableSorting: true, enableResizing: true, enableFilters: true, }, ]); const onDrop = (type) => { let selRows = [], selItem = pgAdmin.Browser.tree.selected(), selectedItemData = selItem ? pgAdmin.Browser.tree.itemData(selItem) : null, selNode = selectedItemData && pgAdmin.Browser.Nodes[selectedItemData._type], url, msg, title; selRows = Object.keys(selectedObject).map((i)=>(selNode?.type == 'coll-constraints' ? { id: data[i].oid, _type: data[i]._type, } : data[i][schemaRef.current.idAttribute])); if (selRows.length === 0) { pgAdmin.Browser.notifier.alert( gettext('Drop Multiple'), gettext('Please select at least one object to drop.') ); 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 if (type === 'dropForce') { url = selNode.generate_url(selItem, 'delete'); msg = gettext( 'Drop databases with the force option will attempt to terminate all the existing connections to the selected databases. Are you sure you want to proceed?' ); title = gettext('Drop FORCE 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 () { setLoaderText(gettext('Deleting Objects...')); api .delete(url, { data: JSON.stringify({ ids: selRows }), contentType: 'application/json; charset=utf-8', }) .then(function (res) { if (res.success == 0) { pgAdmin.Browser.notifier.alert(res.errormsg, res.info); } pgAdmin.Browser.tree.refresh(selItem); setIsStale(true); setSelectedObject({}); }) .catch(function (error) { pgAdmin.Browser.notifier.alert( gettext('Error deleting %s', selectedItemData._label.toLowerCase()), _.isUndefined(error.response) ? error.message : error.response.data.errormsg ); }) .then(()=>{ setLoaderText(''); }); }; if (confirm) { pgAdmin.Browser.notifier.confirmDelete(title, msg, dropNodeProperties, () => {}, gettext('Drop'), gettext('Cancel')); } else { dropNodeProperties(); } }; React.useEffect(() => { if (node) { let nodeObj = pgAdmin.Browser.Nodes[nodeData?._type.replace('coll-', '')]; let url = generateCollectionURL.call(nodeObj, nodeItem, 'properties'); const api = getApiInstance(); let tableColumns = []; let column = {}; if(!isStale || !isActive) { return; } setLoaderText(gettext('Loading...')); if (!_.isUndefined(nodeObj.getSchema)) { schemaRef.current = nodeObj.getSchema?.(treeNodeInfo, nodeData); schemaRef.current?.fields.forEach((field) => { if (node.columns.indexOf(field.id) > -1) { if (field.label.indexOf('?') > -1) { column = { header: field.label, accessorKey: field.id, enableSorting: true, enableResizing: true, enableFilters: true, cell: getSwitchCell() }; } else { column = { header: field.label, accessorKey: field.id, enableSorting: true, enableResizing: true, enableFilters: true, }; } tableColumns.push(column); } }); }else{ node.columns.forEach((field) => { column = { header: field, accessorKey: field, enableSorting: true, enableResizing: true, enableFilters: true, }; 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) => { setData([]); setLoaderText(''); pgAdmin.Browser.notifier.alert( gettext('Failed to retrieve data from the server.'), parseApiError(err) ); }); setIsStale(false); } }, [nodeData, node, nodeItem, isStale, isActive]); return ( <> {data.length > 0 ? ( } columns={pgTableColumns} data={data} type={'panel'} isSearch={false} selectedRows={selectedObject} setSelectedRows={setSelectedObject} /> ) : (
) }
); } CollectionNodeProperties.propTypes = { node: PropTypes.func, nodeData: PropTypes.object, treeNodeInfo: PropTypes.object, nodeItem: PropTypes.object, isActive: PropTypes.bool, isStale: PropTypes.bool, setIsStale: PropTypes.func, };