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 MainMenuFactory from './MainMenuFactory';11import _ from 'lodash';12import { checkMasterPassword, showQuickSearch } from '../../../static/js/Dialogs/index';13import { pgHandleItemError } from '../../../static/js/utils';14import { send_heartbeat, stop_heartbeat } from './heartbeat';15import getApiInstance from '../../../static/js/api_instance';16import usePreferences, { setupPreferenceBroadcast } from '../../../preferences/static/js/store';17import checkNodeVisibility from '../../../static/js/check_node_visibility';18 19define('pgadmin.browser', [20 'sources/gettext', 'sources/url_for', 'sources/pgadmin',21 'sources/csrf', 'pgadmin.authenticate.kerberos',22 'pgadmin.browser.utils', 'pgadmin.browser.messages',23 'pgadmin.browser.node', 'pgadmin.browser.collection', 'pgadmin.browser.activity',24 'pgadmin.browser.keyboard', 'sources/tree/pgadmin_tree_save_state',25], function(26 gettext, url_for, pgAdmin, csrfToken, Kerberos,27) {28 let pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};29 let select_object_msg = gettext('Please select an object in the tree view.');30 31 csrfToken.setPGCSRFToken(pgAdmin.csrf_token_header, pgAdmin.csrf_token);32 33 Kerberos.validate_kerberos_ticket();34 35 // Extend the browser class attributes36 _.extend(pgAdmin.Browser, {37 // The base url for browser38 URL: url_for('browser.index'),39 docker:null,40 // Reversed Engineer query for the selected database node object goes41 // here42 editor:null,43 // Left hand browser tree44 tree:null,45 // list of script to be loaded, when a certain type of node is loaded46 // It will be used to register extensions, tools, child node scripts,47 // etc.48 scripts: {},49 // Standard Widths and Height for dialogs in px50 stdW: {51 sm: 500,52 md: 700,53 lg: 900,54 default: 500,55 // If you change above values then make sure to update56 // calc method logic57 calc: (passed_width) => {58 let iw = window.innerWidth;59 if(iw > passed_width)60 return passed_width;61 else if (iw > pgAdmin.Browser.stdW.lg)62 return pgAdmin.Browser.stdW.lg;63 else if (iw > pgAdmin.Browser.stdW.md)64 return pgAdmin.Browser.stdW.md;65 else if (iw > pgAdmin.Browser.stdW.sm)66 return pgAdmin.Browser.stdW.sm;67 else68 // if avilable screen resolution is still69 // less then return the width value as it70 return iw;71 },72 },73 stdH: {74 sm: 200,75 md: 400,76 lg: 550,77 default: 550,78 // If you change above values then make sure to update79 // calc method logic80 calc: (passed_height) => {81 // We are excluding sm as it is too small for dialog82 let ih = window.innerHeight;83 if (ih > passed_height)84 return passed_height;85 else if (ih > pgAdmin.Browser.stdH.lg)86 return pgAdmin.Browser.stdH.lg;87 else if (ih > pgAdmin.Browser.stdH.md)88 return pgAdmin.Browser.stdH.md;89 else90 // if avilable screen resolution is still91 // less then return the height value as it92 return ih;93 },94 },95 // Default panels96 panels: {},97 // We also support showing dashboards, HTML file, external URL98 frames: {},99 /* Menus */100 // add_menus(...) will register all the menus101 // in this container102 all_menus_cache: {103 // All context menu goes here under certain menu types.104 // i.e. context: {'server': [...], 'server-group': [...]}105 context: {},106 // File menus107 file: {},108 // Edit menus109 edit: {},110 // Object menus111 object: {},112 // Management menus113 management: {},114 // Tools menus115 tools: {},116 // Help menus117 help: {},118 },119 MainMenus: [],120 BrowserContextMenu: [],121 122 menu_categories: {123 /* name, label (pair) */124 'register': {125 label: gettext('Register'),126 priority: 1,127 /* separator above this menu */128 above: false,129 below: true,130 /* icon: 'fa fa-magic', */131 single: true,132 },133 'create': {134 label: gettext('Create'),135 priority: 2,136 /* separator above this menu */137 above: false,138 below: true,139 /* icon: 'fa fa-magic', */140 single: true,141 },142 },143 // A callback to load/fetch a script when a certain node is loaded144 register_script: function(n, m, p) {145 let scripts = this.scripts;146 scripts[n] = _.isArray(scripts[n]) ? scripts[n] : [];147 scripts[n].push({'name': m, 'path': p, loaded: false});148 },149 masterpass_callback_queue: [],150 getMenuList: function(name, item, d, skipDisabled=false) {151 let obj = this;152 //This 'checkNoMenuOptionForNode' function will check if showMenu flag is present or not for selected node153 let {flag,showMenu}=MainMenuFactory.checkNoMenuOptionForNode(d);154 if(flag){155 if(showMenu===false){156 return [MainMenuFactory.createMenuItem({157 enable : false,158 label: gettext('No menu available for this object.'),159 name:'',160 priority: 1,161 category: 'create',162 })];163 }164 }else{165 let category = {166 'common': []167 };168 const nodeTypeMenus = obj.all_menus_cache[name][d._type];169 for(let key of Object.keys(nodeTypeMenus)) {170 let menuItem = nodeTypeMenus[key];171 let menuCategory = menuItem.category ?? 'common';172 category[menuCategory] = category[menuCategory] ?? [];173 category[menuCategory].push(menuItem);174 }175 let menuItemList = [];176 177 for(let c in category) {178 if((c in obj.menu_categories || category[c].length > 1) && c != 'common' ) {179 let allMenuItemsDisabled = true;180 category[c].forEach((mi)=> {181 mi.checkAndSetDisabled(d, item);182 if(allMenuItemsDisabled) {183 allMenuItemsDisabled = mi.isDisabled;184 }185 });186 187 const categoryMenuOptions = obj.menu_categories[c];188 let label = categoryMenuOptions?.label ?? c;189 let priority = categoryMenuOptions?.priority ?? 10;190 191 if(categoryMenuOptions?.above) {192 menuItemList.push(MainMenuFactory.getSeparator(label, priority));193 }194 if(!allMenuItemsDisabled && skipDisabled) {195 let _menuItem = MainMenuFactory.createMenuItem({196 name: c,197 label: label,198 module: c,199 category: c,200 menu_items: category[c],201 priority: priority202 });203 204 menuItemList.push(_menuItem);205 } else if(!skipDisabled){206 let _menuItem = MainMenuFactory.createMenuItem({207 name: c,208 label: label,209 module:c,210 category: c,211 menu_items: category[c],212 priority: priority213 });214 215 menuItemList.push(_menuItem);216 }217 if(categoryMenuOptions?.below) {218 menuItemList.push(MainMenuFactory.getSeparator(label, priority));219 }220 } else {221 category[c].forEach((c)=> {222 c.checkAndSetDisabled(d, item);223 });224 225 category[c].forEach((m)=> {226 if(!skipDisabled) {227 menuItemList.push(m);228 } else if(skipDisabled && !m.isDisabled){229 menuItemList.push(m);230 }231 });232 }233 }234 235 return menuItemList;236 }237 },238 // Enable/disable menu options239 enable_disable_menus: function(item) {240 let obj = this;241 let d = item ? obj.tree.itemData(item) : undefined;242 243 // All menus (except for the object menus) are already present.244 // They will just require to check, whether they are245 // enabled/disabled.246 pgBrowser.MainMenus.filter((m)=>m.name != 'object').forEach((menu) => {247 menu.menuItems.forEach((mitem) => {248 mitem.checkAndSetDisabled(d, item);249 });250 });251 252 // Create the object menu dynamically253 let objectMenu = pgBrowser.MainMenus.find((menu) => menu.name == 'object');254 if (item && obj.all_menus_cache['object']?.[d._type]) {255 let menuItemList = obj.getMenuList('object', item, d);256 objectMenu && MainMenuFactory.refreshMainMenuItems(objectMenu, menuItemList);257 let ctxMenuList = obj.getMenuList('context', item, d, true);258 obj.BrowserContextMenu = MainMenuFactory.getContextMenu(ctxMenuList);259 } else {260 objectMenu && MainMenuFactory.refreshMainMenuItems(objectMenu, [261 MainMenuFactory.createMenuItem({262 name: '',263 label: gettext('No object selected'),264 category: 'create',265 priority: 1,266 enable: false,267 })268 ]);269 }270 },271 init: function() {272 let obj=this;273 if (obj.initialized) {274 return;275 }276 obj.initialized = true;277 278 // Cache preferences279 usePreferences.getState().cache();280 setupPreferenceBroadcast();281 282 setTimeout(function() {283 obj?.editor?.setValue('-- ' + select_object_msg);284 obj?.editor?.refresh();285 }, 10);286 287 // Register scripts and add menus288 pgBrowser.utils.registerScripts(this);289 290 // Ping the server every 5 minutes291 setInterval(function() {292 getApiInstance().post(293 url_for('misc.cleanup')294 ).then(()=> {295 /*This is intentional (SonarQube)*/296 }).catch(function() {297 /*This is intentional (SonarQube)*/298 });299 }, 300000);300 301 obj.Events.on(302 'pgadmin:server:connected', send_heartbeat.bind(obj)303 );304 305 obj.Events.on(306 'pgadmin:server:disconnect', stop_heartbeat.bind(obj)307 );308 309 obj.check_corrupted_db_file();310 obj.Events.on('pgadmin:browser:tree:add', obj.onAddTreeNode.bind(obj));311 obj.Events.on('pgadmin:browser:tree:update', obj.onUpdateTreeNode.bind(obj));312 obj.Events.on('pgadmin:browser:tree:refresh', obj.onRefreshTreeNodeReact.bind(obj));313 obj.Events.on('pgadmin-browser:tree:loadfail', obj.onLoadFailNode.bind(obj));314 obj.bind_beforeunload();315 316 /* User UI activity */317 obj.log_activity(); /* The starting point */318 obj.register_to_activity_listener(document);319 obj.start_inactivity_timeout_daemon();320 },321 uiloaded: function() {322 this.set_master_password('');323 this.check_version_update();324 },325 check_corrupted_db_file: function() {326 getApiInstance().get(327 url_for('browser.check_corrupted_db_file')328 ).then(({data: res})=> {329 if(res.data.length > 0) {330 331 pgAdmin.Browser.notifier.alert(332 'Warning',333 'pgAdmin detected unrecoverable corruption in it\'s SQLite configuration database. ' +334 'The database has been backed up and recreated with default settings. '+335 'It may be possible to recover data such as query history manually from '+336 'the original/corrupt file using a tool such as DB Browser for SQLite if desired.'+337 '<br><br>Original file: ' + res.data + '<br>Replacement file: ' +338 res.data.substring(0, res.data.length - 14),339 );340 }341 }).catch(function(error) {342 pgAdmin.Browser.notifier.alert(error);343 });344 },345 check_master_password: function(on_resp_callback) {346 getApiInstance().get(347 url_for('browser.check_master_password')348 ).then(({data: res})=> {349 if(on_resp_callback) {350 if(res.data) {351 on_resp_callback(true);352 } else {353 on_resp_callback(false);354 }355 }356 }).catch(function(error) {357 pgAdmin.Browser.notifier.pgRespErrorNotify(error);358 });359 },360 361 reset_master_password: function() {362 let self = this;363 getApiInstance().delete(364 url_for('browser.set_master_password')365 ).then(({data: res})=> {366 if(!res.data) {367 self.set_master_password('');368 }369 }).catch(function(error) {370 pgAdmin.Browser.notifier.pgRespErrorNotify(error);371 });372 },373 374 set_master_password: function(password='',375 set_callback=()=>{/*This is intentional (SonarQube)*/},376 cancel_callback=()=>{/*This is intentional (SonarQube)*/}) {377 let data=null, self = this;378 379 data = {380 'password': password,381 };382 383 self.masterpass_callback_queue.push(set_callback);384 // Check master passowrd.385 checkMasterPassword(data, self.masterpass_callback_queue, cancel_callback);386 },387 388 check_version_update: function() {389 getApiInstance().get(390 url_for('misc.upgrade_check')391 ).then((res)=> {392 const data = res.data.data;393 if(data.outdated) {394 pgAdmin.Browser.notifier.warning(395 `396 ${gettext('You are currently running version %s of %s, <br/>however the current version is %s.', data.current_version, data.product_name, data.upgrade_version)}397 <br/><br/>398 ${gettext('Please click <a href="%s" target="_new" style="color:inherit">here</a> for more information.', data.download_url)}399 `,400 null401 );402 }403 404 }).catch(function() {405 // Suppress any errors406 });407 },408 409 bind_beforeunload: function() {410 window.addEventListener('beforeunload', function(e) {411 /* Can open you in new tab */412 const prefStore = usePreferences.getState();413 let tree_save_interval = prefStore.getPreferences('browser', 'browser_tree_state_save_interval'),414 confirm_on_refresh_close = prefStore.getPreferences('browser', 'confirm_on_refresh_close');415 416 if (!_.isUndefined(tree_save_interval) && tree_save_interval.value !== -1)417 pgAdmin.Browser.browserTreeState.save_state();418 419 if(!_.isUndefined(confirm_on_refresh_close) && confirm_on_refresh_close.value) {420 /* This message will not be displayed in Chrome, Firefox, Safari as they have disabled it*/421 let msg = gettext('Are you sure you want to close the %s browser?', pgBrowser.utils.app_name);422 if(e.originalEvent?.returnValue) {423 e.originalEvent.returnValue = msg;424 }425 return msg;426 }427 });428 },429 430 add_menu_category: function(431 id, label, priority, icon, above_separator, below_separator, single432 ) {433 this.menu_categories[id] = {434 label: label,435 priority: priority,436 icon: icon,437 above: (above_separator === true),438 below: (below_separator === true),439 single: single,440 };441 },442 443 // Add menus of module/extension at appropriate menu444 add_menus: function(menus) {445 let pgMenu = this.all_menus_cache;446 447 _.each(menus, function(m) {448 _.each(m.applies, function(a) {449 /* We do support menu type only from this list */450 if(['context', 'file', 'edit', 'object','management', 'tools', 'help'].indexOf(a) > -1){451 let _menus;452 453 // If current node is not visible in browser tree454 // then return from here455 if(!checkNodeVisibility(m.node)) {456 return;457 } else if(_.has(m, 'module') && !_.isUndefined(m.module)) {458 // If module to which this menu applies is not visible in459 // browser tree then also we do not display menu460 if(!checkNodeVisibility(m.module.type)) {461 return;462 }463 }464 465 pgMenu[a] = pgMenu[a] || {};466 if (_.isString(m.node)) {467 _menus = pgMenu[a][m.node] = pgMenu[a][m.node] || {};468 } else if (_.isString(m.category)) {469 _menus = pgMenu[a][m.category] = pgMenu[a][m.category] || {};470 }471 else {472 _menus = pgMenu[a];473 }474 475 let get_menuitem_obj = function(_m) {476 let enable = _m.enable;477 if(_m.enable == '') {478 enable = true;479 } else if(_.isString(_m.enable) && _m.enable.toLowerCase() == 'false') {480 enable = false;481 }482 483 // This is to handel quick search callback484 if(_m.name == 'mnu_quick_search_help') {485 _m.callback = () => {486 showQuickSearch();487 };488 }489 490 return MainMenuFactory.createMenuItem({491 name: _m.name,492 label: _m.label,493 module: _m.module,494 category: _m.category,495 callback: typeof _m.module == 'object' && _m.module[_m.callback] && _m.callback in _m.module[_m.callback] ? _m.module[_m.callback] : _m.callback,496 priority: _m.priority,497 data: _m.data,498 url: _m.url || '#',499 target: _m.target,500 icon: _m.icon,501 enable: enable || true,502 node: _m.node,503 checked: _m.checked,504 below: _m.below,505 applies: _m.applies,506 });507 };508 509 if (!_.has(_menus, m.name)) {510 _menus[m.name] = get_menuitem_obj(m);511 512 if(m.menu_items) {513 let sub_menu_items = [];514 515 for(let mnu_val of m.menu_items) {516 sub_menu_items.push(get_menuitem_obj(mnu_val));517 }518 _menus[m.name]['menu_items'] = sub_menu_items;519 }520 }521 } else {522 console.warn(523 'Developer warning: Category \'' +524 a +525 '\' is not supported!\nSupported categories are: context, file, edit, object, tools, management, help'526 );527 }528 });529 });530 },531 _findTreeChildNode: function(_i, _d, _o) {532 let loaded = _o.t.wasLoad(_i),533 onLoad = function() {534 let items = _o.t.children(_i),535 i, d, n, idx = 0, size = items.length;536 for (; idx < size; idx++) {537 i = items[idx];538 d = _o.t.itemData(i);539 if (d._type === _d._type) {540 if (!_o.hasId || d._id == _d._id) {541 _o.i = i;542 _o.d = _d;543 _o.pathOfTreeItems.push({coll: false, item: i, d: _d});544 545 _o.success();546 return;547 }548 } else {549 n = _o.b.Nodes[d._type];550 // Are we looking at the collection node for the given node?551 if (552 n?.collection_node && d.nodes &&553 _.indexOf(d.nodes, _d._type) != -1554 ) {555 _o.i = i;556 _o.d = null;557 _o.pathOfTreeItems.push({coll: true, item: i, d: d});558 559 // Set load to false when the current collection node's inode is false560 if (!_o.t.isInode(i)) {561 _o.load = false;562 }563 _o.b._findTreeChildNode(i, _d, _o);564 return;565 }566 }567 }568 _o.notFound && typeof(_o.notFound) == 'function' &&569 _o.notFound(_d);570 };571 572 if (!loaded && _o.load) {573 _o.t.open(_i).then(574 () => {575 onLoad();576 },577 () => {578 let fail = _o?.o?.fail;579 if (fail && typeof(fail) == 'function') {580 fail.apply(_o.t, []);581 }582 }583 );584 } else if (loaded) {585 onLoad();586 } else {587 _o.notFound && typeof(_o.notFound) == 'function' &&588 _o.notFound(_d);589 }590 },591 592 onAddTreeNode: function(_data, _hierarchy, _opts) {593 let ctx = {594 b: this, // Browser595 d: null, // current parent596 hasId: true,597 i: null, // current item598 p: _.toArray(_hierarchy || {}).sort(599 function(a, b) {600 if (a.priority === b.priority) {601 return 0;602 }603 return (a.priority < b.priority ? -1 : 1);604 }605 ), // path of the parent606 pathOfTreeItems: [], // path Item607 t: this.tree, // Tree Api608 o: _opts,609 },610 traversePath = function() {611 let _ctx = this, data;612 613 _ctx.success = traversePath;614 if (_ctx.p.length) {615 data = _ctx.p.shift();616 // This is the parent node.617 // Replace the parent-id of the data, which could be different618 // from the given hierarchy.619 if (!_ctx.p.length) {620 data._id = _data._pid;621 _ctx.success = addItemNode;622 }623 _ctx.b._findTreeChildNode(624 _ctx.i, data, _ctx625 );626 // if parent node is null627 if (!_data._pid) {628 addItemNode.apply(_ctx, arguments);629 }630 }631 return true;632 }.bind(ctx),633 addItemNode = function() {634 // Append the new data in the tree under the current item.635 // We may need to pass it to proper collection node.636 let _ctx = this,637 first = (_ctx.i || this.t.wasLoad(_ctx.i)) &&638 this.t.first(_ctx.i),639 findChildNode = function(success, notFound) {640 let __ctx = this;641 __ctx.success = success;642 __ctx.notFound = notFound;643 644 __ctx.b._findTreeChildNode(__ctx.i, _data, __ctx);645 }.bind(_ctx),646 selectNode = function() {647 this.t.openPath(this.i);648 this.t.select(this.i);649 if (typeof(_ctx?.o?.success) == 'function') {650 _ctx.o.success.apply(_ctx.t, [_ctx.i, _data]);651 }652 }.bind(_ctx),653 addNode = function() {654 let __ctx = this,655 items = __ctx.t.children(__ctx.i),656 s = 0, e = items.length - 1, i,657 linearSearch = function() {658 while (e >= s) {659 i = items[s];660 let d = __ctx.t.itemData(i);661 if (d._type === 'column') {662 if (pgAdmin.numeric_comparator(d._id, _data._id) == 1)663 return true;664 } else if (pgAdmin.natural_sort(d._label, _data._label) == 1)665 return true;666 s++;667 }668 //when the current element is greater than the end element669 if (e != items.length - 1) {670 i = items[e+1];671 return true;672 }673 i = null;674 return false;675 },676 binarySearch = function() {677 let d, m;678 // Binary search only outperforms Linear search for n > 44.679 // Reference:680 // https://en.wikipedia.org/wiki/Binary_search_algorithm#cite_note-30681 //682 // We will try until it's half.683 while (e - s > 22) {684 i = items[s];685 d = __ctx.t.itemData(i);686 if (d._type === 'column') {687 if (pgAdmin.numeric_comparator(d._id, _data._id) != -1)688 return true;689 } else if (pgAdmin.natural_sort(d._label, _data._label) != -1)690 return true;691 i = items[e];692 d = __ctx.t.itemData(i);693 let result;694 if (d._type === 'column') {695 result = pgAdmin.numeric_comparator(d._id, _data._id);696 } else {697 result = pgAdmin.natural_sort(d._label, _data._label);698 }699 if (result !=1) {700 if (e != items.length - 1) {701 i = items[e+1];702 return true;703 }704 i = null;705 return false;706 }707 m = s + Math.round((e - s) / 2);708 i = items[m];709 d = __ctx.t.itemData(i);710 if(d._type === 'column'){711 result = pgAdmin.numeric_comparator(d._id, _data._id);712 } else {713 result = pgAdmin.natural_sort(d._label, _data._label);714 }715 //result will never become 0 because of remove operation716 //which happens with updateTreeNode717 if (result == 0)718 return true;719 720 if (result == -1) {721 s = m + 1;722 e--;723 } else {724 s++;725 e = m - 1;726 }727 }728 return linearSearch();729 };730 731 if (binarySearch()) {732 __ctx.t.before(i, _data).then((_item) => {733 if (typeof(__ctx?.o?.success) == 'function') {734 __ctx.o.success.apply(__ctx.t, [i, _data]);735 } else {736 __ctx.t.select(_item);737 }738 }, () => {739 console.warn('Failed to add before...', arguments);740 if (typeof(__ctx.o?.fail) == 'function') {741 __ctx.o.fail.apply(__ctx.t, [i, _data]);742 }743 });744 } else {745 let _append = function() {746 let ___ctx = this,747 _parent_data = ___ctx.t.itemData(___ctx.i);748 749 ___ctx.t.append(___ctx.i, _data).then(750 (_i) => {751 // Open the item path only if its parent is loaded752 // or parent type is same as nodes753 if(754 ___ctx.t.wasLoad(___ctx.i) &&755 _parent_data && _parent_data._type.search(756 _data._type757 ) > -1758 ) {759 ___ctx.t.open(___ctx.i);760 ___ctx.t.select(_i);761 } else if (_parent_data) {762 // Unload the parent node so that we'll get763 // latest data when we try to expand it764 ___ctx.t.unload(___ctx.i).then(765 () => {766 ___ctx.t.open(___ctx.i);767 }768 );769 }770 if (typeof(___ctx?.o?.success) == 'function') {771 ___ctx.o.success.apply(___ctx.t, [_i, _data]);772 }773 },774 () => {775 console.warn('Failed to append...', arguments);776 if (typeof(___ctx?.o?.fail) == 'function') {777 ___ctx.o.fail.apply(___ctx.t, [___ctx.i, _data]);778 }779 }780 );781 }.bind(__ctx);782 783 if (__ctx.i && !__ctx.t.isInode(__ctx.i)) {784 __ctx.t.setInode(__ctx.i).then(785 () => {786 _append();787 }788 );789 } else {790 // Handle case for node without parent i.e. server-group791 // or if parent node's inode is true.792 _append();793 }794 }795 }.bind(_ctx);796 797 // Parent node do not have any children, let me unload it.798 if (!first && _ctx.t.wasLoad(_ctx.i)) {799 _ctx.t.unload(_ctx.i).then(() => {800 findChildNode(801 selectNode,802 function() {803 let o = this && this.o;804 if (typeof(o?.fail) == 'function') {805 o.fail.apply(this.t, [this.i, _data]);806 }807 }.bind(this)808 );809 },810 () => {811 let o = this && this.o;812 if (typeof(o?.fail) == 'function') {813 o.fail.apply(this.t, [this.i, _data]);814 }815 });816 return;817 }818 819 // We can find the collection node using _findTreeChildNode820 // indirectly.821 findChildNode(selectNode, addNode);822 }.bind(ctx);823 824 if (!ctx.t.wasInit() || !_data) {825 return;826 }827 828 traversePath();829 },830 831 onUpdateTreeNode: function(_old, _new, _hierarchy, _opts) {832 let ctx = {833 b: this, // Browser834 d: null, // current parent835 i: null, // current item836 hasId: true,837 p: _.toArray(_hierarchy || {}).sort(838 function(a, b) {839 if (a.priority === b.priority) {840 return 0;841 }842 return (a.priority < b.priority ? -1 : 1);843 }844 ), // path of the old object845 pathOfTreeItems: [], // path items846 t: this.tree, // Tree Api847 o: _opts,848 load: true,849 old: _old,850 new: _new,851 op: null,852 },853 errorOut = function() {854 let fail = this.o?.fail;855 if (fail && typeof(fail) == 'function') {856 fail.apply(this.t, [this.i, _new, _old]);857 }858 }.bind(ctx),859 deleteNode = function() {860 let self = this,861 pathOfTreeItems = this.pathOfTreeItems,862 findParent = function() {863 if (pathOfTreeItems.length) {864 pathOfTreeItems.pop();865 let length = pathOfTreeItems.length;866 this.i = (length && pathOfTreeItems[length - 1].item) || null;867 this.d = (length && pathOfTreeItems[length - 1].d) || null;868 869 // It is a collection item, let's find the node item870 if (length && pathOfTreeItems[length - 1].coll) {871 pathOfTreeItems.pop();872 length = pathOfTreeItems.length;873 this.i = (length && pathOfTreeItems[length - 1].item) || null;874 this.d = (length && pathOfTreeItems[length - 1].d) || null;875 }876 } else {877 this.i = null;878 this.d = null;879 }880 }.bind(this);881 882 let _item_parent = (this.i883 && this.t.hasParent(this.i)884 && this.t.parent(this.i)) || null,885 _item_grand_parent = _item_parent ?886 (this.t.hasParent(_item_parent)887 && this.t.parent(_item_parent) &&888 _item_parent.root != this.t.parent(_item_parent))889 : null;890 891 // Remove the current node first.892 if (893 this.i && this.d && this.old._id == this.d._id &&894 this.old._type == this.d._type895 ) {896 let _parent = this.t.parent(this.i) || null;897 898 // If there is no parent then just update the node899 if(this.t.isRootNode(_parent) ||900 (_parent && _parent.length == 0 && ctx.op == 'UPDATE')) {901 //Update node if browser has single child node.902 if(this.t.children().length === 1) {903 updateNode();904 } else {905 let that = this;906 this.t.remove(this.i).then(() => {907 that.t.before(that.i, that.new).then((new_item) => {908 that.t.select(new_item);909 }, () => {910 console.warn('Failed to add before..', arguments);911 });912 });913 }914 } else {915 let postRemove = function() {916 // If item has parent but no grand parent917 if (_item_parent.path !== '/browser' && !_item_grand_parent) {918 let parent = null;919 // We need to search in all parent siblings (eg: server groups)920 let parents = this.t.siblings(this.i) || [];921 parents.push(this.i);922 _.each(parents, function (p) {923 let d = self.t.itemData(p);924 // If new server group found then assign it parent925 if(d._id == self.new._pid) {926 parent = p;927 self.pathOfTreeItems.push({coll: true, item: parent, d: d});928 }929 });930 931 if (parent) {932 this.load = true;933 934 this.success = function() {935 addItemNode();936 }.bind(this);937 // We can refresh the collection node, but - let's not bother about938 // it right now.939 this.notFound = errorOut;940 941 let loaded = this.t.wasLoad(parent),942 onLoad = function() {943 self.i = parent;944 self.pathOfTreeItems.push({coll: false, item: parent, d: self.d});945 self.success();946 };947 948 if (!loaded && self.load) {949 self.t.open(parent).then(950 () => {951 onLoad();952 },953 () => {954 let fail = self?.o?.fail;955 if (956 fail && typeof(fail) == 'function'957 ) {958 fail.apply(self.t, []);959 }960 }961 );962 } else {963 onLoad();964 }965 }966 } else {967 // This is for rest of the nodes968 let _parentData = this.d;969 // Find the grand-parent, or the collection node of parent.970 findParent();971 972 if (this.i) {973 this.load = true;974 975 this.success = function() {976 addItemNode();977 }.bind(this);978 // We can refresh the collection node, but - let's not bother about979 // it right now.980 this.notFound = errorOut;981 982 // Find the new parent983 this.b._findTreeChildNode(984 this.i, {_id: this.new._pid, _type: _parentData._type}, this985 );986 } else {987 addItemNode();988 }989 }990 }.bind(this);991 992 this.t.remove(this.i).then(() => {993 findParent();994 if (_item_parent && !_item_grand_parent && _parent995 && self.t.children(_parent).length == 0) {996 self.t.unload(_parent).then( () => { setTimeout(postRemove); });997 }998 else {999 setTimeout(postRemove);1000 }1001 return true;1002 });1003 }1004 1005 }1006 errorOut();1007 1008 }.bind(ctx),1009 findNewParent = function(_d) {1010 let findParent = function() {1011 let pathOfTreeItems = this.pathOfTreeItems;1012 1013 if (pathOfTreeItems.length) {1014 pathOfTreeItems.pop();1015 let length = pathOfTreeItems.length;1016 this.i = (length && pathOfTreeItems[length - 1].item) || null;1017 this.d = (length && pathOfTreeItems[length - 1].d) || null;1018 1019 // It is a collection item, let's find the node item1020 if (length && pathOfTreeItems[length - 1].coll) {1021 pathOfTreeItems.pop();1022 length = pathOfTreeItems.length;1023 this.i = (length && pathOfTreeItems[length - 1].item) || null;1024 this.d = (length && pathOfTreeItems[length - 1].d) || null;1025 }1026 } else {1027 this.i = null;1028 this.d = null;1029 }1030 }.bind(this);1031 1032 // old parent was not found, can we find the new parent?1033 if (this.i) {1034 this.load = true;1035 this.success = function() {1036 addItemNode();1037 }.bind(this);1038 1039 if (_d._type == this.old._type) {1040 // We were already searching the old object under the parent.1041 findParent();1042 _d = this.d;1043 // Find the grand parent1044 findParent();1045 }1046 _d = this.new._pid;1047 1048 // We can refresh the collection node, but - let's not bother about1049 // it right now.1050 this.notFound = errorOut;1051 1052 // Find the new parent1053 this.b._findTreeChildNode(this.i, _d, this);1054 } else {1055 addItemNode();1056 }1057 }.bind(ctx),1058 updateNode = function() {1059 if (1060 this.i && this.d && this.new._type == this.d._type1061 ) {1062 let self = this,1063 _id = this.d._id;1064 if (this.new._id != this.d._id) {1065 // Found the new oid, update its node_id1066 let node_data = this.t.itemData(ctx.i);1067 node_data._id = _id = this.new._id;1068 }1069 if (this.new._id == _id) {1070 // Found the current1071 _.extend(this.d, this.new);1072 this.t.update(ctx.i, this.d);1073 this.t.setLabel(ctx.i, {label: this.new.label});1074 this.t.addIcon(ctx.i, {icon: this.new.icon});1075 this.t.setId(ctx.i, {id: this.new.id});1076 this.t.openPath(this.i);1077 this.t.deselect(this.i);1078 1079 // select tree item1080 self.t.select(self.i);1081 }1082 }1083 let success = this.o?.success;1084 if (success && typeof(success) == 'function') {1085 success.apply(this.t, [this.i, _old, _new]);1086 }1087 }.bind(ctx),1088 traversePath = function() {1089 let _ctx = this, data;1090 1091 _ctx.success = traversePath;1092 if (_ctx.p.length) {1093 data = _ctx.p.shift();1094 // This is the node, we can now do the required operations.1095 // We should first delete the existing node, if the parent-id is1096 // different.1097 if (!_ctx.p.length) {1098 if (_ctx.op == 'RECREATE') {1099 _ctx.load = false;1100 _ctx.success = deleteNode;1101 _ctx.notFound = findNewParent;1102 } else {1103 _ctx.success = updateNode;1104 _ctx.notFound = errorOut;1105 }1106 }1107 _ctx.b._findTreeChildNode(1108 _ctx.i, data, _ctx1109 );1110 } else if (_ctx.p.length == 1) {1111 _ctx.notFound = findNewParent;1112 }1113 return true;1114 }.bind(ctx),1115 addItemNode = function() {1116 let _ctx = this,1117 first = (_ctx.i || this.t.wasLoad(_ctx.i)) &&1118 this.t.first(_ctx.i),1119 findChildNode = function(success, notFound) {1120 let __ctx = this;1121 __ctx.success = success;1122 __ctx.notFound = notFound;1123 1124 __ctx.b._findTreeChildNode(__ctx.i, _new, __ctx);1125 }.bind(_ctx),1126 selectNode = function() {1127 this.t.openPath(this.i);1128 this.t.select(this.i);1129 if (1130 _ctx.o && _ctx.o.success && typeof(_ctx.o.success) == 'function'1131 ) {1132 _ctx.o.success.apply(_ctx.t, [_ctx.i, _new]);1133 }1134 }.bind(_ctx),1135 addNode = function() {1136 let __ctx = this,1137 items = __ctx.t.children(__ctx.i),1138 s = 0, e = items.length - 1, i,1139 linearSearch = function() {1140 while (e >= s) {1141 i = items[s];1142 let d = __ctx.t.itemData(i);1143 if (d._type === 'column') {1144 if (pgAdmin.numeric_comparator(d._id, _new._id) == 1)1145 return true;1146 } else if (pgAdmin.natural_sort(d._label, _new._label) == 1)1147 return true;1148 s++;1149 }1150 if (e != items.length - 1) {1151 i = items[e+1];1152 return true;1153 }1154 i = null;1155 return false;1156 },1157 binarySearch = function() {1158 while (e - s > 22) {1159 i = items[s];1160 let d = __ctx.t.itemData(i);1161 if (d._type === 'column') {1162 if (pgAdmin.numeric_comparator(d._id, _new._id) != -1)1163 return true;1164 } else if (pgAdmin.natural_sort(d._label, _new._label) != -1)1165 return true;1166 i = items[e];1167 d = __ctx.t.itemData(i);1168 let result;1169 if (d._type === 'column') {1170 result = pgAdmin.numeric_comparator(d._id, _new._id);1171 } else {1172 result = pgAdmin.natural_sort(d._label, _new._label);1173 }1174 if (result !=1) {1175 if (e != items.length - 1) {1176 i = items[e+1];1177 return true;1178 }1179 i = null;1180 return false;1181 }1182 let m = s + Math.round((e - s) / 2);1183 i = items[m];1184 d = __ctx.t.itemData(i);1185 if(d._type === 'column'){1186 result = pgAdmin.numeric_comparator(d._id, _new._id);1187 } else {1188 result = pgAdmin.natural_sort(d._label, _new._label);1189 }1190 //result will never become 0 because of remove operation1191 //which happens with updateTreeNode1192 if (result == 0)1193 return true;1194 1195 if (result == -1) {1196 s = m + 1;1197 e--;1198 } else {1199 s++;1200 e = m - 1;