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//////////////////////////////////////////////////////////////9 10import React, { useEffect, useMemo, useRef } from 'react';11 12import getApiInstance from 'sources/api_instance';13import {getHelpUrl, getEPASHelpUrl} from 'pgadmin.help';14import SchemaView from 'sources/SchemaView';15import gettext from 'sources/gettext';16import { generateNodeUrl } from '../../browser/static/js/node_ajax';17import { usePgAdmin } from '../../static/js/BrowserComponent';18import { LAYOUT_EVENTS, LayoutDockerContext } from '../../static/js/helpers/Layout';19import usePreferences from '../../preferences/static/js/store';20import PropTypes from 'prop-types';21 22export default function ObjectNodeProperties({panelId, node, treeNodeInfo, nodeData, actionType, formType, onEdit, onSave, onClose,23 isActive, setIsStale, isStale}) {24 const layoutDocker = React.useContext(LayoutDockerContext);25 const nodeType = nodeData?._type;26 const pgAdmin = usePgAdmin();27 let serverInfo = treeNodeInfo && ('server' in treeNodeInfo) &&28 pgAdmin.Browser.serverInfo && pgAdmin.Browser.serverInfo[treeNodeInfo.server._id];29 let inCatalog = treeNodeInfo && ('catalog' in treeNodeInfo);30 let isActionTypeCopy = actionType == 'copy';31 // If the actionType is set to 'copy' it is necessary to retrieve the details32 // of the existing node. Therefore, specify the actionType as 'edit' to33 // facilitate this process.34 let urlBase = generateNodeUrl.call(node, treeNodeInfo, isActionTypeCopy ? 'edit' : actionType, nodeData, false, node.url_jump_after_node);35 const api = getApiInstance();36 // To check node data is updated or not37 const staleCounter = useRef(0);38 const url = (isNew)=>{39 return urlBase + (isNew ? '' : nodeData._id);40 };41 const isDirty = useRef(false); // useful for warnings42 let warnOnCloseFlag = true;43 const confirmOnCloseReset = usePreferences().getPreferencesForModule('browser').confirm_on_properties_close;44 let updatedData = ['table', 'partition'].includes(nodeType) && !_.isEmpty(nodeData.rows_cnt) ? {rows_cnt: nodeData.rows_cnt} : undefined;45 let schema = node.getSchema(treeNodeInfo, nodeData);46 47 // We only have two actionTypes, 'create' and 'edit' to initiate the dialog,48 // so if isActionTypeCopy is true, we should revert back to "create" since49 // we are duplicating the node.50 if (isActionTypeCopy) {51 actionType = 'create';52 }53 54 let onError = (err)=> {55 if(err.response){56 console.error('error resp', err.response);57 } else if(err.request){58 console.error('error req', err.request);59 } else if(err.message){60 console.error('error msg', err.message);61 }62 };63 64 /* Called when dialog is opened in edit mode, promise required */65 let initData = ()=>new Promise((resolve, reject)=>{66 if(actionType === 'create' && !isActionTypeCopy) {67 resolve({});68 } else {69 // Do not call the API if tab is not active.70 if(!isActive && actionType == 'properties') {71 return;72 }73 api.get(url(false))74 .then((res)=>{75 let data = res.data;76 if (isActionTypeCopy) {77 // Delete the idAttribute while copying the node.78 delete data[schema.idAttribute];79 data = node.copy(data);80 }81 resolve(data);82 })83 .catch((err)=>{84 pgAdmin.Browser.notifier.pgNotifier('error', err, gettext('Failed to fetch data'), function(msg) {85 if (msg == 'CRYPTKEY_SET') {86 return Promise.resolve(initData());87 } else if (msg == 'CRYPTKEY_NOT_SET') {88 reject(gettext('The master password is not set.'));89 }90 reject(err);91 });92 93 })94 .then(()=>{95 // applies only for properties tab96 setIsStale?.(false);97 });98 }99 });100 101 /* on save button callback, promise required */102 const onSaveClick = (isNew, data)=>new Promise((resolve, reject)=>{103 return api({104 url: url(isNew),105 method: isNew ? 'POST' : 'PUT',106 data: data,107 }).then((res)=>{108 /* Don't warn the user before closing dialog */109 warnOnCloseFlag = false;110 resolve(res.data);111 onSave?.(res.data);112 }).catch((err)=>{113 pgAdmin.Browser.notifier.pgNotifier('error-noalert', err, '', function(msg) {114 if (msg == 'CRYPTKEY_SET') {115 return Promise.resolve(onSaveClick(isNew, data));116 } else if (msg == 'CRYPTKEY_NOT_SET') {117 reject(gettext('The master password is not set.'));118 }119 reject(err);120 });121 });122 });123 124 /* Called when switched to SQL tab, promise required */125 const getSQLValue = (isNew, changedData)=>{126 const msqlUrl = generateNodeUrl.call(node, treeNodeInfo, 'msql', nodeData, !isNew, node.url_jump_after_node);127 return new Promise((resolve, reject)=>{128 api({129 url: msqlUrl,130 method: 'GET',131 params: changedData,132 }).then((res)=>{133 resolve(res.data.data);134 }).catch((err)=>{135 onError(err);136 reject(err);137 });138 });139 };140 141 /* Callback for help button */142 const onHelp = (isSqlHelp=false, isNew=false)=>{143 if(isSqlHelp) {144 let server = treeNodeInfo.server;145 let helpUrl = pgAdmin.Browser.utils.pg_help_path;146 let fullUrl = '';147 148 if (server.server_type == 'ppas' && node.epasHelp) {149 fullUrl = getEPASHelpUrl(server.version, node.epasURL);150 } else if (node.sqlCreateHelp == '' && node.sqlAlterHelp != '') {151 fullUrl = getHelpUrl(helpUrl, node.sqlAlterHelp, server.version);152 } else if (node.sqlCreateHelp != '' && node.sqlAlterHelp == '') {153 fullUrl = getHelpUrl(helpUrl, node.sqlCreateHelp, server.version);154 } else if (isNew) {155 fullUrl = getHelpUrl(helpUrl, node.sqlCreateHelp, server.version);156 } else {157 fullUrl = getHelpUrl(helpUrl, node.sqlAlterHelp, server.version);158 }159 160 window.open(fullUrl, 'postgres_help');161 } else {162 window.open(node.dialogHelp, 'pgadmin_help');163 }164 };165 166 /* A warning before closing the dialog with unsaved changes, based on preference */167 const warnBeforeChangesLost = (id)=>{168 if(panelId != id) {169 warnBeforeChangesLost();170 }171 if (warnOnCloseFlag && confirmOnCloseReset) {172 if(isDirty.current) {173 pgAdmin.Browser.notifier.confirm(174 gettext('Warning'),175 gettext('Changes will be lost. Are you sure you want to close the dialog?'),176 function() {177 onClose(true);178 },179 null180 );181 } else {182 onClose(true);183 }184 } else {185 onClose(true);186 }187 };188 189 useEffect(()=>{190 if(formType == 'dialog') {191 /* Bind the close event and check if user should be warned */192 layoutDocker.eventBus.registerListener(LAYOUT_EVENTS.CLOSING, warnBeforeChangesLost);193 }194 return ()=>{195 layoutDocker.eventBus.deregisterListener(LAYOUT_EVENTS.CLOSING, warnBeforeChangesLost);196 };197 }, []);198 199 /* All other useful details can go with this object */200 const viewHelperProps = {201 mode: actionType,202 serverInfo: serverInfo ? {203 type: serverInfo.server_type,204 version: serverInfo.version,205 }: undefined,206 inCatalog: inCatalog,207 };208 209 // Show/Hide security group for nodes under the catalog210 if('catalog' in treeNodeInfo211 && formType !== 'tab') {212 schema.filterGroups = [gettext('Security')];213 }214 // Reset stale counter.215 useMemo(()=> {216 staleCounter.current = 0;217 }, [nodeData?._id]);218 219 const key = useMemo(()=>{220 // If node data is updated increase the counter to show updated data.221 if(isStale) {222 staleCounter.current += 1;223 }224 if( actionType != 'properties' || isActive) {225 // Not required any action.226 } else {227 initData = ()=>Promise.resolve({});228 }229 230 return nodeData?._id + '-' + staleCounter.current;231 }, [isActive, nodeData?._id, isStale]);232 233 /* Fire at will, mount the DOM */234 return (235 <SchemaView236 key={key}237 formType={formType}238 getInitData={initData}239 updatedData={updatedData}240 schema={schema}241 viewHelperProps={viewHelperProps}242 onSave={onSaveClick}243 onClose={()=>onClose()}244 onHelp={onHelp}245 onEdit={onEdit}246 onDataChange={(dataChanged)=>{247 isDirty.current = dataChanged;248 }}249 confirmOnCloseReset={confirmOnCloseReset}250 hasSQL={node.hasSQL && (actionType === 'create' || actionType === 'edit')}251 getSQLValue={getSQLValue}252 disableSqlHelp={node.sqlAlterHelp == '' && node.sqlCreateHelp == '' && !node.epasHelp}253 disableDialogHelp={node.dialogHelp == undefined || node.dialogHelp == ''}254 />255 );256}257 258ObjectNodeProperties.propTypes = {259 panelId: PropTypes.string,260 node: PropTypes.func,261 treeNodeInfo: PropTypes.object,262 nodeData: PropTypes.object,263 actionType: PropTypes.string,264 formType: PropTypes.string,265 onEdit: PropTypes.func,266 onSave: PropTypes.func,267 onClose: PropTypes.func,268 isActive: PropTypes.bool,269 setIsStale: PropTypes.func,270 isStale: PropTypes.bool,271};272 