codekingpro/portable-devtools
114k
1/////////////////////////////////////////////////////////////2//3// pgAdmin 4 - PostgreSQL Tools4//5// Copyright (C) 2013 - 2024, The pgAdmin Development Team6// This software is released under the PostgreSQL Licence7//8//////////////////////////////////////////////////////////////9import React from 'react';10import getApiInstance from 'sources/api_instance';11import { makeStyles } from '@mui/styles';12import { Box } from '@mui/material';13import { generateCollectionURL } from '../../browser/static/js/node_ajax';14import gettext from 'sources/gettext';15import PgTable from 'sources/components/PgTable';16import Theme from 'sources/Theme';17import PropTypes from 'prop-types';18import { PgButtonGroup, PgIconButton } from '../../static/js/components/Buttons';19import DeleteIcon from '@mui/icons-material/Delete';20import DeleteSweepIcon from '@mui/icons-material/DeleteSweep';21import DeleteForeverIcon from '@mui/icons-material/DeleteForever';22import EmptyPanelMessage from '../../static/js/components/EmptyPanelMessage';23import Loader from 'sources/components/Loader';24import { evalFunc } from '../../static/js/utils';25import { usePgAdmin } from '../../static/js/BrowserComponent';26import { getSwitchCell } from '../../static/js/components/PgTable';27 28const useStyles = makeStyles((theme) => ({29 emptyPanel: {30 minHeight: '100%',31 minWidth: '100%',32 background: theme.otherVars.emptySpaceBg,33 overflow: 'auto',34 padding: '8px',35 display: 'flex',36 },37 panelIcon: {38 width: '80%',39 margin: '0 auto',40 marginTop: '25px !important',41 position: 'relative',42 textAlign: 'center',43 },44 panelMessage: {45 marginLeft: '0.5rem',46 fontSize: '0.875rem',47 },48 searchPadding: {49 flex: 2.550 },51 searchInput: {52 flex: 1,53 margin: '4 0 4 0',54 borderLeft: 'none',55 paddingLeft: 556 },57 propertiesPanel: {58 height: '100%'59 },60 autoResizer: {61 height: '100% !important',62 width: '100% !important',63 background: theme.palette.grey[400],64 padding: '8px',65 overflow: 'hidden !important',66 overflowX: 'auto !important'67 },68}));69 70export default function CollectionNodeProperties({71 node,72 treeNodeInfo,73 nodeData,74 nodeItem,75 isActive,76 isStale,77 setIsStale78}) {79 const classes = useStyles();80 const pgAdmin = usePgAdmin();81 82 const [data, setData] = React.useState([]);83 const [infoMsg, setInfoMsg] = React.useState('Please select an object in the tree view.');84 const [selectedObject, setSelectedObject] = React.useState([]);85 const [loaderText, setLoaderText] = React.useState('');86 const schemaRef = React.useRef();87 88 const [pgTableColumns, setPgTableColumns] = React.useState([89 {90 Header: 'properties',91 accessor: 'Properties',92 sortable: true,93 resizable: true,94 disableGlobalFilter: false,95 },96 {97 Header: 'value',98 accessor: 'value',99 sortable: true,100 resizable: true,101 disableGlobalFilter: false,102 },103 ]);104 105 const getTableSelectedRows = (selRows) => {106 setSelectedObject(selRows);107 };108 109 const onDrop = (type) => {110 let selRowModels = selectedObject,111 selRows = [],112 selItem = pgAdmin.Browser.tree.selected(),113 selectedItemData = selItem ? pgAdmin.Browser.tree.itemData(selItem) : null,114 selNode = selectedItemData && pgAdmin.Browser.Nodes[selectedItemData._type],115 url, msg, title;116 117 if (selNode?.type == 'coll-constraints') {118 // In order to identify the constraint type, the type should be passed to the server119 selRows = selRowModels.map((row) => ({120 id: row.original.oid,121 _type: row.original._type,122 }));123 } else {124 selRows = selRowModels.map((row) => row.original[schemaRef.current.idAttribute]);125 }126 127 if (selRows.length === 0) {128 pgAdmin.Browser.notifier.alert(129 gettext('Delete Multiple'),130 gettext('Please select at least one object to delete.')131 );132 return;133 }134 135 if (!selNode) return;136 137 if (type === 'dropCascade') {138 url = selNode.generate_url(selItem, 'delete');139 msg = gettext(140 'Are you sure you want to delete all the selected objects and all the objects that depend on them?'141 );142 title = gettext('Delete CASCADE multiple objects?');143 } else if (type === 'dropForce') {144 url = selNode.generate_url(selItem, 'delete');145 msg = gettext(146 'Delete databases with the force option will attempt to terminate all the existing connections to the selected databases. Are you sure you want to proceed?'147 );148 title = gettext('Delete FORCE multiple objects?');149 } else {150 url = selNode.generate_url(selItem, 'drop');151 msg = gettext('Are you sure you want to delete all the selected objects?');152 title = gettext('Delete multiple objects?');153 }154 155 const api = getApiInstance();156 let dropNodeProperties = function () {157 setLoaderText(gettext('Deleting Objects...'));158 api159 .delete(url, {160 data: JSON.stringify({ ids: selRows }),161 contentType: 'application/json; charset=utf-8',162 })163 .then(function (res) {164 if (res.success == 0) {165 pgAdmin.Browser.notifier.alert(res.errormsg, res.info);166 }167 pgAdmin.Browser.tree.refresh(selItem);168 setIsStale(true);169 })170 .catch(function (error) {171 pgAdmin.Browser.notifier.alert(172 gettext('Error deleting %s', selectedItemData._label.toLowerCase()),173 _.isUndefined(error.response) ? error.message : error.response.data.errormsg174 );175 })176 .then(()=>{177 setLoaderText('');178 });179 };180 181 if (confirm) {182 pgAdmin.Browser.notifier.confirm(title, msg, dropNodeProperties, null);183 } else {184 dropNodeProperties();185 }186 };187 188 React.useEffect(() => {189 if (node) {190 191 let nodeObj =192 pgAdmin.Browser.Nodes[nodeData?._type.replace('coll-', '')];193 194 let url = generateCollectionURL.call(nodeObj, nodeItem, 'properties');195 196 const api = getApiInstance();197 198 let tableColumns = [];199 let column = {};200 if(!isStale || !isActive) {201 return;202 }203 204 setLoaderText(gettext('Loading...'));205 206 if (!_.isUndefined(nodeObj.getSchema)) {207 schemaRef.current = nodeObj.getSchema?.(treeNodeInfo, nodeData);208 schemaRef.current?.fields.forEach((field) => {209 if (node.columns.indexOf(field.id) > -1) {210 if (field.label.indexOf('?') > -1) {211 column = {212 Header: field.label,213 accessor: field.id,214 sortable: true,215 resizable: true,216 disableGlobalFilter: false,217 minWidth: 0,218 Cell: getSwitchCell()219 };220 } else {221 column = {222 Header: field.label,223 accessor: field.id,224 sortable: true,225 resizable: true,226 disableGlobalFilter: false,227 minWidth: 0,228 };229 }230 tableColumns.push(column);231 }232 });233 }else{234 node.columns.forEach((field) => {235 column = {236 Header: field,237 accessor: field,238 sortable: true,239 resizable: true,240 disableGlobalFilter: false,241 minWidth: 0,242 };243 tableColumns.push(column);244 });245 }246 247 248 api({249 url: url,250 type: 'GET',251 })252 .then((res) => {253 res.data.forEach((element) => {254 element['icon'] = '';255 });256 setPgTableColumns(tableColumns);257 setData(res.data);258 setInfoMsg('No properties are available for the selected object.');259 setLoaderText('');260 })261 .catch((err) => {262 pgAdmin.Browser.notifier.alert(263 gettext('Failed to retrieve data from the server.'),264 gettext(err.message)265 );266 });267 setIsStale(false);268 }269 }, [nodeData, node, nodeItem, isStale, isActive]);270 271 const CustomHeader = () => {272 const canDrop = evalFunc(node, node.canDrop, nodeData, nodeItem, treeNodeInfo);273 const canDropCascade = evalFunc(node, node.canDropCascade, nodeData, nodeItem, treeNodeInfo);274 const canDropForce = evalFunc(node, node.canDropForce, nodeData, nodeItem, treeNodeInfo);275 return (276 <Box >277 <PgButtonGroup size="small">278 <PgIconButton279 icon={<DeleteIcon style={{height: '1.35rem'}}/>}280 aria-label="Delete"281 title={gettext('Delete')}282 onClick={() => {283 onDrop('drop');284 }}285 disabled={286 (selectedObject.length > 0)287 ? !canDrop288 : true289 }290 ></PgIconButton>291 {node.type !== 'coll-database' ? <PgIconButton292 icon={<DeleteSweepIcon style={{height: '1.5rem'}} />}293 aria-label="Delete Cascade"294 title={gettext('Delete (Cascade)')}295 onClick={() => {296 onDrop('dropCascade');297 }}298 disabled={299 (selectedObject.length > 0)300 ? !canDropCascade301 : true302 }303 ></PgIconButton> :304 <PgIconButton305 icon={<DeleteForeverIcon style={{height: '1.4rem'}} />}306 aria-label="Delete Force"307 title={gettext('Delete (Force)')}308 onClick={() => {309 onDrop('dropForce');310 }}311 disabled={312 (selectedObject.length > 0)313 ? !canDropForce314 : true315 }316 ></PgIconButton>}317 </PgButtonGroup>318 </Box>);319 };320 321 return (322 <Theme className='obj_properties'>323 <Loader message={loaderText}/>324 <Box className={classes.propertiesPanel}>325 {data.length > 0 ?326 (327 <PgTable328 isSelectRow={!('catalog' in treeNodeInfo) && (nodeData.label !== 'Catalogs') && _.isUndefined(node?.canSelect)}329 CustomHeader={CustomHeader}330 className={classes.autoResizer}331 columns={pgTableColumns}332 data={data}333 type={'panel'}334 isSearch={false}335 getSelectedRows={getTableSelectedRows}336 />337 )338 :339 (340 <div className={classes.emptyPanel}>341 <EmptyPanelMessage text={gettext(infoMsg)}/>342 </div>343 )344 }345 </Box>346 </Theme>347 );348}349 350CollectionNodeProperties.propTypes = {351 node: PropTypes.func,352 nodeData: PropTypes.object,353 treeNodeInfo: PropTypes.object,354 nodeItem: PropTypes.object,355 isActive: PropTypes.bool,356 isStale: PropTypes.bool,357 setIsStale: PropTypes.func,358};359 