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 _ from 'lodash';11import getApiInstance from '../../../static/js/api_instance';12import { BROWSER_PANELS } from './constants';13import React from 'react';14import ObjectNodeProperties from '../../../misc/properties/ObjectNodeProperties';15import ErrorBoundary from '../../../static/js/helpers/ErrorBoundary';16import toPx from '../../../static/js/to_px';17import usePreferences from '../../../preferences/static/js/store';18import { evalFunc } from '../../../static/js/utils';19 20define('pgadmin.browser.node', [21 'sources/gettext', 'sources/pgadmin',22 'sources/browser/generate_url', 'sources/utils',23 'pgadmin.browser.utils', 'pgadmin.browser.events',24], function(25 gettext, pgAdmin, generateUrl, commonUtils26) {27 const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};28 29 // It has already been defined.30 // Avoid running this script again.31 if (pgBrowser.Node)32 return pgBrowser.Node;33 34 pgBrowser.Nodes = pgBrowser.Nodes || {};35 36 // A helper (base) class for all the nodes, this has basic37 // operations/callbacks defined for basic operation.38 pgBrowser.Node = function() {/*This is intentional (SonarQube)*/};39 40 // Helper function to correctly set up the property chain, for subclasses.41 // Uses a hash of class properties to be extended.42 //43 // It is unlikely - we will instantiate an object for this class.44 pgBrowser.Node.extend = function(props, initialize) {45 let parent = this;46 let child;47 48 // The constructor function for the new subclass is defined to simply call49 // the parent's constructor.50 child = function() {51 return parent.apply(this, arguments);52 };53 54 // Add static properties to the constructor function, if supplied.55 _.extend(child, parent, _.omit(props, 'callbacks'));56 57 // Make sure - a child have all the callbacks of the parent.58 child.callbacks = _.extend({}, parent.callbacks, props.callbacks);59 60 // Let's not bind the callbacks, or initialize the child.61 if (!(initialize??true))62 return child;63 64 let bindToChild = function(cb) {65 if (typeof(child.callbacks[cb]) == 'function') {66 child.callbacks[cb] = child.callbacks[cb].bind(child);67 }68 },69 callbacks = _.keys(child.callbacks);70 for (let cb_val of callbacks) bindToChild(cb_val);71 72 // Registering the node by calling child.Init(...) function73 child.Init.apply(child);74 75 // Initialize the parent76 this.Init.apply(child);77 78 return child;79 };80 81 _.extend(pgAdmin.Browser.Node, {82 // Node type83 type: undefined,84 // Label85 label: '',86 // Help pages87 sqlAlterHelp: '',88 sqlCreateHelp: '',89 dialogHelp: '',90 epasHelp: false,91 92 title: function(d, action) {93 if(action == 'create') {94 return gettext('Create - %s', this.label);95 }96 return d._label??'';97 },98 copy: function(d) {99 // This function serves the purpose of facilitating modifications100 // during the copying process of any node.101 return d;102 },103 hasId: true,104 ///////105 // Initialization function106 // Generally - used to register the menus for this type of node.107 //108 // Also, look at pgAdmin.Browser.add_menus(...) function.109 //110 // NOTE: Override this for each node for initialization purpose111 Init: function() {112 let self = this;113 if (self.node_initialized)114 return;115 self.node_initialized = true;116 117 pgAdmin.Browser.add_menus([{118 name: 'refresh',119 node: self.type,120 module: self,121 applies: ['object', 'context'],122 callback: 'refresh',123 priority: 2,124 label: gettext('Refresh...'),125 enable: true,126 }]);127 128 if (self.canEdit) {129 pgAdmin.Browser.add_menus([{130 name: 'show_obj_properties',131 node: self.type,132 module: self,133 applies: ['object', 'context'],134 callback: 'show_obj_properties',135 priority: 999,136 label: gettext('Properties...'),137 data: {138 'action': 'edit',139 },140 enable: _.isFunction(self.canEdit) ?141 function() {142 return !!(self.canEdit(...arguments));143 } : (!!self.canEdit),144 }]);145 }146 147 if (self.canDrop) {148 pgAdmin.Browser.add_menus([{149 name: 'delete_object',150 node: self.type,151 module: self,152 applies: ['object', 'context'],153 callback: 'delete_obj',154 priority: self.dropPriority,155 label: (self.dropAsRemove) ? gettext('Remove %s', self.label) : gettext('Delete'),156 data: {157 'url': 'drop',158 data_disabled: gettext('The selected tree node does not support this option.'),159 },160 enable: _.isFunction(self.canDrop) ?161 function() {162 return !!(self.canDrop(...arguments));163 } : (!!self.canDrop),164 }]);165 166 if (self.canDropCascade) {167 pgAdmin.Browser.add_menus([{168 name: 'delete_object_cascade',169 node: self.type,170 module: self,171 applies: ['object', 'context'],172 callback: 'delete_obj',173 priority: 2,174 label: gettext('Delete (Cascade)'),175 data: {176 'url': 'delete',177 },178 enable: _.isFunction(self.canDropCascade) ?179 function() {180 return self.canDropCascade(...arguments);181 } : (!!self.canDropCascade),182 }]);183 }184 }185 186 // Show query tool only in context menu of supported nodes.187 if (_.indexOf(pgAdmin.unsupported_nodes, self.type) == -1) {188 let enable = function(itemData) {189 if (itemData?._type == 'database' && itemData?.allowConn)190 return true;191 else if (itemData?._type != 'database')192 return true;193 else194 return false;195 };196 pgAdmin.Browser.add_menus([{197 name: 'show_query_tool',198 node: self.type,199 module: self,200 applies: ['context'],201 callback: 'show_query_tool',202 priority: 998,203 label: gettext('Query Tool'),204 enable: enable,205 }]);206 207 // show search objects same as query tool208 pgAdmin.Browser.add_menus([{209 name: 'search_objects', node: self.type, module: pgAdmin.Tools.SearchObjects,210 applies: ['context'], callback: 'show_search_objects',211 priority: 997, label: gettext('Search Objects...'),212 icon: 'fa fa-search', enable: enable,213 }]);214 215 if(pgAdmin['enable_psql']) {216 // show psql tool same as query tool.217 pgAdmin.Browser.add_menus([{218 name: 'show_psql_tool', node: this.type, module: this,219 applies: ['context'], callback: 'show_psql_tool',220 priority: 998, label: gettext('PSQL Tool'),221 }]);222 }223 }224 225 // This will add options of scripts eg:'CREATE Script'226 if (self.hasScriptTypes && _.isArray(self.hasScriptTypes) &&227 self.hasScriptTypes.length > 0) {228 // For each script type create menu229 _.each(self.hasScriptTypes, function(stype) {230 231 let type_label = gettext('%s Script',stype.toUpperCase());232 233 stype = stype.toLowerCase();234 235 // Adding menu for each script type236 pgAdmin.Browser.add_menus([{237 name: 'show_script_' + stype,238 node: self.type,239 module: self,240 applies: ['object', 'context'],241 callback: 'show_script',242 priority: 4,243 label: type_label,244 category: gettext('Scripts'),245 data: {246 'script': stype,247 data_disabled: gettext('The selected tree node does not support this option.'),248 },249 enable: self.check_user_permission,250 }]);251 });252 }253 },254 ///////255 // Checks if Script Type is allowed to user256 // First check if role node & create role allowed257 // Otherwise test rest of database objects258 // if no permission matched then do not allow create script259 ///////260 check_user_permission: function(itemData, item, data) {261 // Do not display CREATE script on server group and server node262 if (itemData._type == 'server_group' || itemData._type == 'server') {263 return false;264 }265 266 // Do not display the menu if the database connection is not allowed267 if (itemData._type == 'database' && !itemData.allowConn)268 return false;269 270 let parentData = pgBrowser.tree.getTreeNodeHierarchy(item);271 if (_.indexOf(['create', 'insert', 'update', 'delete'], data.script) != -1) {272 if (itemData.type == 'role' &&273 parentData.server.user.can_create_role) {274 return true;275 } else if (276 ( parentData.server?.user.is_superuser || parentData.server?.user.can_create_db) ||277 (parentData.schema?.can_create)278 ) {279 return true;280 } else {281 return false;282 }283 } else {284 return true;285 }286 },287 /*288 * Default script type menu for node.289 *290 * Override this, to show more script type menus (e.g hasScriptTypes: ['create', 'select', 'insert', 'update', 'delete'])291 *292 * Or set it to empty array to disable script type menu on node (e.g hasScriptTypes: [])293 */294 hasScriptTypes: ['create'],295 /******************************************************************296 * This function determines the given item is editable or not.297 *298 * Override this, when a node is not editable.299 */300 canEdit: true,301 /******************************************************************302 * This function determines the given item is deletable or not.303 *304 * Override this, when a node is not deletable.305 */306 canDrop: false,307 /************************************************************************308 * This function determines the given item and children are deletable or309 * not.310 *311 * Override this, when a node is not deletable.312 */313 canDropCascade: false,314 /*********************************************************************************315 dropAsRemove should be true in case, Drop object label needs to be replaced by Remove316 */317 dropAsRemove: false,318 /******************************************************************************319 dropPriority is set to 2 by default, override it when change is required320 */321 dropPriority: 2,322 /******************************************************************************323 select collection node on deletion.324 */325 selectParentNodeOnDelete: false,326 // List of common callbacks - that can be used for different327 // operations!328 callbacks: {329 /******************************************************************330 * This function allows to create/edit/show properties of any331 * object depending on the arguments provided.332 *333 * args must be a object containing:334 * action - create/edit/properties335 * item - The properties of the item (tree node item)336 *337 * NOTE:338 * if item is not provided, the action will be done on the339 * currently selected tree item node.340 *341 **/342 show_obj_properties: function(args, item) {343 let t = pgBrowser.tree,344 nodeItem = args?.item || item || t.selected(),345 nodeData = nodeItem ? t.itemData(nodeItem) : undefined,346 panelTitle = this.title(nodeData, args.action),347 treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(nodeItem);348 349 if (!nodeData)350 return;351 352 const isParent = (_.isArray(this.parent_type) ?353 (_d)=>{354 return (_.indexOf(this.parent_type, _d._type) != -1);355 } : (_d)=>{356 return (this.parent_type == _d._type);357 });358 359 if (args.action == 'create') {360 // If we've parent, we will get the information of it for361 // proper object manipulation.362 if (this.parent_type && !isParent(nodeData)) {363 // actual parent of a table is schema, not Tables.364 while (nodeItem && t.hasParent(nodeItem)) {365 nodeItem = t.parent(nodeItem);366 let pd = t.itemData(nodeItem);367 368 if (isParent(pd)) {369 // Assign the data, this is my actual parent.370 nodeData = pd;371 break;372 }373 }374 }375 376 // The only node who does not have parent is the Server Group377 if (!nodeData || (this.parent_type != null && !isParent(nodeData))) {378 // It should never come here.379 // If it is here, that means - we do have some bug in code.380 return;381 }382 383 treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(nodeItem);384 const panelId = _.uniqueId(BROWSER_PANELS.EDIT_PROPERTIES);385 const onClose = (force=false)=>pgBrowser.docker.close(panelId, force);386 const onSave = (newNodeData)=>{387 // Clear the cache for this node now.388 setTimeout(()=>{389 this.clear_cache(item);390 }, 0);391 try {392 pgBrowser.Events.trigger(393 'pgadmin:browser:tree:add', _.clone(newNodeData.node),394 _.clone(treeNodeInfo)395 );396 } catch (e) {397 console.warn(e.stack || e);398 }399 onClose();400 };401 this.showPropertiesDialog(panelId, panelTitle, {402 treeNodeInfo: treeNodeInfo,403 item: nodeItem,404 nodeData: nodeData,405 actionType: 'create',406 onSave: onSave,407 onClose: onClose,408 });409 } else if (args.action == 'copy') {410 // This else-if block is used to copy the existing object and411 // open the respective dialog. Add the copied object into the object412 // browser tree upon the 'Save' button click.413 treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(nodeItem);414 const panelId = _.uniqueId(BROWSER_PANELS.EDIT_PROPERTIES);415 const onClose = (force=false)=>pgBrowser.docker.close(panelId, force);416 const onSave = (newNodeData)=>{417 // Clear the cache for this node now.418 setTimeout(()=>{419 this.clear_cache(item);420 }, 0);421 try {422 pgBrowser.Events.trigger(423 'pgadmin:browser:tree:add', _.clone(newNodeData.node),424 {'server_group': treeNodeInfo['server_group']}425 );426 } catch (e) {427 console.warn(e.stack || e);428 }429 onClose();430 };431 this.showPropertiesDialog(panelId, panelTitle, {432 treeNodeInfo: treeNodeInfo,433 item: nodeItem,434 nodeData: nodeData,435 actionType: 'copy',436 onSave: onSave,437 onClose: onClose,438 });439 } else {440 const panelId = BROWSER_PANELS.EDIT_PROPERTIES+nodeData.id;441 const onClose = (force=false)=>pgBrowser.docker.close(panelId, force);442 const onSave = (newNodeData)=>{443 let _old = nodeData,444 _new = newNodeData.node,445 info = treeNodeInfo;446 447 // Clear the cache for this node now.448 setTimeout(()=>{449 this.clear_cache(item);450 }, 0);451 452 pgBrowser.Events.trigger(453 'pgadmin:browser:tree:update',454 _old, _new, info, {455 success: function(_item, _newNodeData, _oldNodeData) {456 pgBrowser.Events.trigger(457 'pgadmin:browser:node:updated', _item, _newNodeData,458 _oldNodeData459 );460 pgBrowser.Events.trigger(461 'pgadmin:browser:node:' + _newNodeData._type + ':updated',462 _item, _newNodeData, _oldNodeData463 );464 },465 }466 );467 onClose();468 };469 if(pgBrowser.docker.find(panelId)) {470 let msg = gettext('Are you sure want to stop editing the properties of %s "%s"?');471 if (args.action == 'edit') {472 msg = gettext('Are you sure want to reset the current changes and re-open the panel for %s "%s"?');473 }474 475 pgAdmin.Browser.notifier.confirm(476 gettext('Edit in progress?'),477 commonUtils.sprintf(msg, this.label.toLowerCase(), nodeData.label),478 ()=>{479 this.showPropertiesDialog(panelId, panelTitle, {480 treeNodeInfo: treeNodeInfo,481 item: nodeItem,482 nodeData: nodeData,483 actionType: 'edit',484 onSave: onSave,485 onClose: onClose,486 }, true);487 },488 null489 );490 } else {491 this.showPropertiesDialog(panelId, panelTitle, {492 treeNodeInfo: treeNodeInfo,493 item: nodeItem,494 nodeData: nodeData,495 actionType: 'edit',496 onSave: onSave,497 onClose: onClose,498 });499 }500 }501 },502 // Delete the selected object503 delete_obj: function(args, item) {504 let input = args || {505 'url': 'drop',506 },507 obj = this,508 t = pgBrowser.tree,509 i = input.item || item || t.selected(),510 d = i ? t.itemData(i) : undefined;511 512 if (!d)513 return;514 515 /*516 * Make sure - we're using the correct version of node517 */518 obj = pgBrowser.Nodes[d._type];519 let objName = _.unescape(d.label);520 521 let msg, title;522 523 if (input.url == 'delete' && d._type === 'database') {524 msg = gettext('Delete database with the force option will attempt to terminate all existing connections to the "%s" database. Are you sure you want to proceed?', d.label);525 title = gettext('Delete FORCE %s?', obj.label);526 527 } else if (input.url == 'delete') {528 msg = gettext('Are you sure you want to delete %s "%s" and all the objects that depend on it?',529 obj.label.toLowerCase(), d.label);530 title = gettext('Delete CASCADE %s?', obj.label);531 532 if (!(_.isFunction(obj.canDropCascade) ?533 obj.canDropCascade(d, i) : obj.canDropCascade)) {534 pgAdmin.Browser.notifier.error(535 gettext('The %s "%s" cannot be dropped.', obj.label, d.label),536 10000537 );538 return;539 }540 } else {541 if (obj.dropAsRemove) {542 msg = gettext('Are you sure you want to remove %s "%s"?', obj.label.toLowerCase(), d.label);543 title = gettext('Remove %s?', obj.label);544 } else {545 msg = gettext('Are you sure you want to delete %s "%s"?', obj.label.toLowerCase(), d.label);546 title = gettext('Delete %s?', obj.label);547 }548 549 if (!(_.isFunction(obj.canDrop) ?550 obj.canDrop(d, i) : obj.canDrop)) {551 pgAdmin.Browser.notifier.error(552 gettext('The %s "%s" cannot be dropped/removed.', obj.label, d.label),553 10000554 );555 return;556 }557 }558 pgAdmin.Browser.notifier.confirm(title, msg,559 function() {560 getApiInstance().delete(561 obj.generate_url(i, input.url, d, true),562 ).then(({data: res})=> {563 if(res.success == 2){564 pgAdmin.Browser.notifier.error(res.info, null);565 return;566 }567 if (res.success == 0) {568 pgAdmin.Browser.notifier.alert(res.errormsg, res.info);569 } else {570 // Remove the node from tree and set collection node as selected.571 let selectNextNode = true;572 if(obj.selectParentNodeOnDelete) {573 let prv_i = t.parent(i);574 setTimeout(function() {575 t.select(prv_i);576 }, 10);577 selectNextNode = false;578 }579 pgBrowser.removeTreeNode(i, selectNextNode);580 }581 return true;582 }).catch(function(error) {583 let errmsg = error.request?.responseText;584 /* Error from the server */585 if (error.request?.status == 417 || error.request?.status == 410 || error.request?.status == 500) {586 try {587 let data = error.response.data;588 errmsg = data.info || data.errormsg;589 } catch (e) {590 console.warn(e.stack || e);591 }592 }593 pgAdmin.Browser.notifier.alert(gettext('Error dropping/removing %s: "%s"', obj.label, objName), errmsg);594 });595 }596 );597 },598 // Callback for creating script(s) & opening them in Query editor599 show_script: function(args, item) {600 let scriptType = args.script,601 obj,602 t = pgBrowser.tree,603 i = item || t.selected(),604 d = i ? t.itemData(i) : undefined;605 606 if (!d)607 return;608 609 /*610 * Make sure - we're using the correct version of node611 */612 obj = pgBrowser.Nodes[d._type];613 let sql_url;614 615 // URL for script type616 if (scriptType == 'insert') {617 sql_url = 'insert_sql';618 } else if (scriptType == 'update') {619 sql_url = 'update_sql';620 } else if (scriptType == 'delete') {621 sql_url = 'delete_sql';622 } else if (scriptType == 'select') {623 sql_url = 'select_sql';624 } else if (scriptType == 'exec') {625 sql_url = 'exec_sql';626 } else {627 // By Default get CREATE SQL628 sql_url = 'sql';629 }630 // Open data grid & pass the URL for fetching631 pgAdmin.Tools.SQLEditor.showQueryTool(632 obj.generate_url(i, sql_url, d, true),633 i, scriptType634 );635 },636 637 // Callback to render query editor638 show_query_tool: function(args, item) {639 let preference = usePreferences.getState().getPreferences('sqleditor', 'copy_sql_to_query_tool');640 let t = pgBrowser.tree,641 i = item || t.selected(),642 d = i ? t.itemData(i) : undefined;643 644 if (!d)645 return;646 647 // Here call data grid method to render query tool648 //Open query tool with create script if copy_sql_to_query_tool is true else open blank query tool649 if(preference.value && !d._type.includes('coll-')){650 let stype = d._type.toLowerCase();651 let data = {652 'script': stype,653 data_disabled: gettext('The selected tree node does not support this option.'),654 };655 pgBrowser.Node.callbacks.show_script(data);656 }else{657 pgAdmin.Tools.SQLEditor.showQueryTool('', i);658 }659 },660 661 // Callback to render psql tool.662 show_psql_tool: function(args) {663 let input = args || {},664 t = pgBrowser.tree,665 i = input.item || t.selected(),666 d = i ? t.itemData(i) : undefined;667 pgAdmin.Tools.Psql.openPsqlTool(d, i);668 },669 670 // Logic to change the server background colour671 // There is no way of applying CSS to parent element so we have to672 // do it via JS code only673 change_server_background: function(item, data) {674 if (!item || !data)675 return;676 const treeH = pgBrowser.tree.getTreeNodeHierarchy(item);677 const serverData = treeH['server'];678 if (!serverData) {679 return;680 }681 const index = item.path.indexOf(serverData.id);682 683 // Go further only if node type is a Server684 if (index !== -1) {685 // First element will be icon and second will be colour code686 let bgcolor = serverData.icon.split(' ')[1] || null,687 fgcolor = serverData.icon.split(' ')[2] || '';688 689 if (bgcolor) {690 let dynamic_class = 'pga_server_' + serverData._id + '_bgcolor';691 // Prepare dynamic style tag692 const styleTag = document.createElement('style');693 styleTag.setAttribute('id', dynamic_class);694 styleTag.setAttribute('type', 'text/css');695 styleTag.innerText = `696 .${dynamic_class} .file-label {697 border-radius: 3px;698 margin-bottom: 2px;699 background: ${bgcolor} !important;700 }701 ${fgcolor ? `702 .${dynamic_class} span.file-name, .${dynamic_class} span.file-name:hover, .${dynamic_class} span.file-name.pseudo-active {703 color: ${fgcolor} !important;704 }705 `:''}706 `;707 708 // Prepare dynamic style tag using template709 document.querySelector(`style[id="${dynamic_class}"]`)?.remove();710 document.head.appendChild(styleTag);711 // Add dynamic class to the tree node.712 pgBrowser.tree.addCssClass(item, dynamic_class);713 }714 }715 },716 added: function(item, data) {717 if (pgBrowser.tree.getData(item)._type.indexOf('coll-') !== -1){718 setTimeout(function() {719 let _item = pgAdmin.Browser.Nodes[pgBrowser.tree.getData(item).nodes[0]];720 _item.clear_cache.apply(_item);721 }, 0);722 }723 pgBrowser.Events.trigger('pgadmin:browser:tree:expand-from-previous-tree-state',724 item);725 pgBrowser.Node.callbacks.change_server_background(item, data);726 },727 // Callback called - when a node is selected in browser tree.728 selected: function(item, data) {729 // Show the information about the selected node in the below panels,730 // which are visible at this time:731 // + Properties732 // + Query (if applicable, otherwise empty)733 // + Dependents734 // + Dependencies735 // + Statistics736 // Update the menu items737 pgAdmin.Browser.enable_disable_menus.apply(pgBrowser, [item]);738 739 pgBrowser.Events.trigger('pgadmin-browser:node:selected', item, data);740 741 pgBrowser.Events.trigger('pgadmin:browser:tree:update-tree-state',742 item);743 return true;744 },745 removed: function(item) {746 let self = this;747 setTimeout(function() {748 self.clear_cache(item);749 }, 0);750 },751 refresh: function(cmd, _item) {752 let self = this,753 t = pgBrowser.tree,754 data = _item && t.itemData(_item);755 756 pgBrowser.Events.trigger(757 'pgadmin:browser:tree:refresh', _item || pgBrowser.tree.selected(), {758 success: function() {759 self.callbacks.selected.apply(self, [_item, data, pgBrowser]);760 },761 });762 },763 opened: function(item) {764 let tree = pgBrowser.tree,765 auto_expand = usePreferences.getState().getPreferences('browser', 'auto_expand_sole_children');766 767 if (auto_expand?.value && tree.children(item).length == 1) {768 // Automatically expand the child node, if a treeview node has only a single child.769 const first_child = tree.first(item);770 771 if (first_child._loaded) {772 tree.open(first_child);773 tree.select(first_child);774 } else {775 const openSoleItem = setInterval(() => {776 if (first_child._loaded) {777 tree.open(first_child);778 tree.select(first_child);779 clearSoleItemInterval();780 }781 }, 200);782 const clearSoleItemInterval = function() {783 clearInterval(openSoleItem);784 };785 }786 787 } else if(tree.children(item).length == 1) {788 const first_child = tree.first(item);789 tree.select(first_child);790 }791 792 pgBrowser.Events.trigger('pgadmin:browser:tree:update-tree-state', item);793 794 },795 closed: function(item) {796 pgBrowser.Events.trigger('pgadmin:browser:tree:remove-from-tree-state',797 item);798 },799 },800 showPropertiesDialog: function(panelId, panelTitle, dialogProps, update=false) {801 const panelData = {802 id: panelId,803 title: panelTitle,804 manualClose: true,805 icon: `dialog-node-icon ${evalFunc(this, this.node_image, dialogProps.nodeData) ?? ('icon-' + this.type)}`,806 content: (807 <ErrorBoundary>808 <ObjectNodeProperties809 panelId={panelId}810 node={this}811 formType="dialog"812 {...dialogProps}813 />814 </ErrorBoundary>815 )816 };817 818 let w = toPx(this.width || (pgBrowser.stdW.default + 'px'), 'width', true);819 let h = toPx(this.height || (pgBrowser.stdH.default + 'px'), 'height', true);820 821 /* Fit to standard sizes */822 if(w <= pgBrowser.stdW.sm) {823 w = pgBrowser.stdW.sm;824 } else if(w <= pgBrowser.stdW.md) {825 w = pgBrowser.stdW.md;826 } else {827 w = pgBrowser.stdW.lg;828 }829 830 if(h <= pgBrowser.stdH.sm) {831 h = pgBrowser.stdH.sm;832 } else if(h <= pgBrowser.stdH.md) {833 h = pgBrowser.stdH.md;834 } else {835 h = pgBrowser.stdH.lg;836 }837 838 if(update) {839 dialogProps.onClose(true);840 setTimeout(()=>{841 pgBrowser.docker.openDialog(panelData, w, h);842 }, 10);843 } else {844 pgBrowser.docker.openDialog(panelData, w, h);845 }846 },847 _find_parent_node: function(t, i, d) {848 if (this.parent_type) {849 d = d || t.itemData(i);850 851 if (_.isString(this.parent_type)) {852 if (this.parent_type == d._type) {853 return i;854 }855 while (t.hasParent(i)) {856 i = t.parent(i);857 d = t.itemData(i);858 859 if (this.parent_type == d._type)860 return i;861 }862 } else {863 if (_.indexOf(this.parent_type, d._type) >= 0) {864 return i;865 }866 while (t.hasParent(i)) {867 i = t.parent(i);868 d = t.itemData(i);869 870 if (_.indexOf(this.parent_type, d._type) >= 0)871 return i;872 }873 }874 }875 return null;876 },877 /**********************************************************************878 * Generate the URL for different operations879 *880 * arguments:881 * type: Create/drop/edit/properties/sql/depends/statistics882 * d: Provide the ItemData for the current item node883 * with_id: Required id information at the end?884 * jump_after_node: This will skip all the value between jump_after_node885 * to the last node, excluding jump_after_node and the last node. This is particularly886 * helpful in partition table where we need to skip parent table OID of a partitioned887 * table in URL formation. Partitioned table itself is a "table" and can be multilevel888 * Supports url generation for create, drop, edit, properties, sql,889 * depends, statistics890 */891 generate_url: function(item, type, d, with_id, info, jump_after_node) {892 let opURL = {893 'create': 'obj',894 'drop': 'obj',895 'edit': 'obj',896 'properties': 'obj',897 'statistics': 'stats',898 },899 self = this,900 priority = -Infinity;901 let treeInfo = (_.isUndefined(item) || _.isNull(item)) ?902 info || {} : pgBrowser.tree.getTreeNodeHierarchy(item);903 let actionType = type in opURL ? opURL[type] : type;904 let itemID = with_id && d._type == self.type ? encodeURIComponent(d._id) : '';905 906 if (self.parent_type) {907 if (_.isString(self.parent_type)) {908 let p = treeInfo[self.parent_type];909 if (p) {910 priority = p.priority;911 }912 } else {913 _.each(self.parent_type, function(o) {914 let p = treeInfo[o];915 if (p) {916 if (priority < p.priority) {917 priority = p.priority;918 }919 }920 });921 }922 }923 924 let jump_after_priority = priority;925 if(jump_after_node && treeInfo[jump_after_node]) {926 jump_after_priority = treeInfo[jump_after_node].priority;927 }928 929 let nodePickFunction = function(treeInfoValue) {930 return (treeInfoValue.priority <= jump_after_priority || treeInfoValue.priority == priority);931 };932 933 return generateUrl.generate_url(pgBrowser.URL, treeInfo, actionType, self.type, nodePickFunction, itemID);934 },935 cache: function(url, node_info, level, data) {936 let cached = this.cached = this.cached || {},937 hash = url,938 min_priority = (939 node_info?.[level] && node_info?.[level].priority940 ) || 0;941 942 if (node_info) {943 _.each(_.sortBy(_.values(_.pickBy(944 node_info,945 function(v) {946 return (v.priority <= min_priority);947 }948 )), function(o) {949 return o.priority;950 }), function(o) {951 hash = commonUtils.sprintf('%s_%s', hash, encodeURI(o._id));952 });953 }954 955 if (_.isUndefined(data)) {956 let res = cached[hash];957 958 if (!_.isUndefined(res) &&959 (res.at - Date.now() > 300000)) {960 res = undefined;961 }962 return res;963 }964 965 let res = cached[hash] = {966 data: data,967 at: Date.now(),968 level: level,969 };970 return res;971 },972 clear_cache: function(item) {973 /*974 * Reset the cache, when new node is created.975 *976 * FIXME:977 * At the moment, we will clear all the cache for this node. But - we978 * would like to clear the cache only this nodes parent, so that - it979 * fetches the new data.980 */981 this.cached = {};982 983 // Trigger Notify event about node's cache984 let self = this;985 pgBrowser.Events.trigger(986 'pgadmin:browser:node:' + self.type + ':cache_cleared',987 item, self988 );989 },990 cache_level: function(node_info, with_id) {991 if (node_info) {992 if (with_id && this.type in node_info) {993 return this.type;994 }995 if (_.isArray(this.parent_type)) {996 for (let parent in this.parent_type) {997 if (parent in node_info) {998 return parent;999 }1000 }1001 return this.type;1002 }1003 return this.parent_type;1004 }1005 },1006 });1007 1008 return pgAdmin.Browser.Node;1009});1010 