Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
node.js1010 linesDownload Raw Back to js
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 
codekingpro/portable-devtools · Team Ai