diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py index 15a4d7708..2f26b7b33 100644 --- a/web/pgadmin/browser/server_groups/servers/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/__init__.py @@ -31,7 +31,7 @@ from config import PG_DEFAULT_DRIVER from pgadmin.model import db, Server, ServerGroup, User, SharedServer from pgadmin.utils.driver import get_driver from pgadmin.utils.master_password import get_crypt_key -from pgadmin.utils.exception import CryptKeyMissing +from pgadmin.utils.exception import CryptKeyMissing, ConnectionLost from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry from pgadmin.browser.server_groups.servers.utils import \ (is_valid_ipaddress, get_replication_type, convert_connection_parameter, @@ -627,12 +627,18 @@ class ServerNode(PGChildNodeView): in_recovery = None wal_paused = None if connected: - status, result, in_recovery, wal_paused =\ - recovery_state(conn, manager.version) - if not status: + try: + status, result, in_recovery, wal_paused =\ + recovery_state(conn, manager.version) + + if not status: + connected = False + manager.release() + errmsg = "{0} : {1}".format(server.name, result) + + except ConnectionLost: connected = False manager.release() - errmsg = "{0} : {1}".format(server.name, result) return make_json_response( result=self.blueprint.generate_browser_node( diff --git a/web/pgadmin/browser/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js index 5f0d14c0c..cbe6eaf47 100644 --- a/web/pgadmin/browser/static/js/browser.js +++ b/web/pgadmin/browser/static/js/browser.js @@ -10,7 +10,6 @@ import MainMenuFactory from './MainMenuFactory'; import _ from 'lodash'; import { checkMasterPassword, showQuickSearch } from '../../../static/js/Dialogs/index'; -import { pgHandleItemError } from '../../../static/js/utils'; import { send_heartbeat, stop_heartbeat } from './heartbeat'; import getApiInstance from '../../../static/js/api_instance'; import usePreferences, { setupPreferenceBroadcast } from '../../../preferences/static/js/store'; @@ -195,7 +194,7 @@ define('pgadmin.browser', [ obj.check_corrupted_db_file(); obj.Events.on('pgadmin:browser:tree:add', obj.onAddTreeNode.bind(obj)); obj.Events.on('pgadmin:browser:tree:update', obj.onUpdateTreeNode.bind(obj)); - obj.Events.on('pgadmin:browser:tree:refresh', obj.onRefreshTreeNodeReact.bind(obj)); + obj.Events.on('pgadmin:browser:tree:refresh', obj.onRefreshTreeNode.bind(obj)); obj.Events.on('pgadmin-browser:tree:loadfail', obj.onLoadFailNode.bind(obj)); obj.bind_beforeunload(); @@ -1228,173 +1227,39 @@ define('pgadmin.browser', [ } }, - onRefreshTreeNodeReact: function(_i, _opts) { - this.tree.refresh(_i).then(() =>{ - if (_opts?.success) _opts.success(); - }); - }, + onRefreshTreeNode: async function(nodeItem, opts) { + this.tree.toggleItemLoader(nodeItem, true); - onRefreshTreeNode: function(_i, _opts) { - let _d = _i && this.tree.itemData(_i), - n = this.Nodes[_d?._type], - ctx = { - b: this, // Browser - d: _d, // current parent - i: _i, // current item - p: null, // path of the old object - pathOfTreeItems: [], // path items - t: this.tree, // Tree Api - o: _opts, - }, - isOpen, - idx = -1; + const itemNodeData = nodeItem && this.tree.itemData(nodeItem); + let nodeObj = this.Nodes[itemNodeData?._type]; - this.Events.trigger('pgadmin-browser:tree:refreshing', _i, _d, n); + // If the node is a collection node, we can directly refresh it. + if(!nodeObj?.collection_node) { + // If the node is not a collection node, we need to fetch its data + // from the server and update the tree node. + try { + const url = nodeObj.generate_url(nodeItem, 'nodes', itemNodeData, true); + const api = getApiInstance(); + const resp = await api.get(url); + // server response data comes in result + const respData = resp.data.data || resp.data.result; - if (!n) { - _i = null; - ctx.i = null; - ctx.d = null; - } else { - isOpen = (this.tree.isInode(_i) && this.tree.isOpen(_i)); - } - - ctx.branch = ctx.t.serialize( - _i, {}, function(i, el, d) { - idx++; - if (!idx || (d.inode && d.open)) { - return { - _id: d._id, _type: d._type, branch: d.branch, open: d.open, - }; - } - }); - - if (!n) { - ctx.t.destroy({ - success: function() { - ctx.t = ctx.b.tree; - ctx.i = null; - ctx.b._refreshNode(ctx, ctx.branch); - }, - error: function() { - let fail = _opts.o?.fail || _opts?.fail; - - if (typeof(fail) == 'function') { - fail(); - } - }, - }); - return; - } - - let api = getApiInstance(); - let fetchNodeInfo = function(__i, __d, __n) { - let info = __n.getTreeNodeHierarchy(__i), - url = __n.generate_url(__i, 'nodes', __d, true); - - api.get( - url - ).then(({data: res})=> { - // Node information can come as result/data - let newData = res.result || res.data; - - newData._label = newData.label; - newData.label = _.escape(newData.label); - - ctx.t.setLabel(ctx.i, {label: newData.label}); - ctx.t.addIcon(ctx.i, {icon: newData.icon}); - ctx.t.setId(ctx.i, {id: newData.id}); - if (newData.inode) - ctx.t.setInode(ctx.i, {inode: true}); - - // This will update the tree item data. - let itemData = ctx.t.itemData(ctx.i); - _.extend(itemData, newData); - - if ( - __n.can_expand && typeof(__n.can_expand) == 'function' - ) { - if (!__n.can_expand(itemData)) { - ctx.t.unload(ctx.i); - return; - } - } - ctx.b._refreshNode(ctx, ctx.branch); - let success = (ctx?.o?.success) || ctx.success; - if (success && typeof(success) == 'function') { - success(); - } - }).catch(function(error) { - if (!pgHandleItemError( - error, {item: __i, info: info} - )) { - if(error.response.headers['content-type'] == 'application/json') { - let jsonResp = error.response.data ?? {}; - if(error.response.status == 410 && jsonResp.success == 0) { - let parent = ctx.t.parent(ctx.i); - - ctx.t.remove(ctx.i, { - success: function() { - if (parent) { - // Try to refresh the parent on error - try { - pgBrowser.Events.trigger( - 'pgadmin:browser:tree:refresh', parent - ); - } catch (e) { console.warn(e.stack || e); } - } - }, - }); - } - } - - pgAdmin.Browser.notifier.pgNotifier('error', error, gettext('Error retrieving details for the node.'), function (msg) { - if (msg == 'CRYPTKEY_SET') { - fetchNodeInfo(__i, __d, __n); - } else { - console.warn(arguments); - } + if(respData) { + this.tree.update(nodeItem, { + ...itemNodeData, ...respData }); + this.tree.setLabel(nodeItem, {label: respData.label}); + this.tree.addIcon(nodeItem, {icon: respData.icon}); } - }); - }; - - if (n?.collection_node) { - let p = ctx.i = this.tree.parent(_i), - unloadNode = function() { - this.tree.unload(_i, { - success: function() { - _i = p; - _d = ctx.d = ctx.t.itemData(ctx.i); - n = ctx.b.Nodes[_d._type]; - _i = p; - fetchNodeInfo(_i, _d, n); - }, - fail: function() { console.warn(arguments); }, - }); - }.bind(this); - if (!this.tree.isInode(_i)) { - this.tree.setInode(_i, { success: unloadNode }); - } else { - unloadNode(); + } catch (error) { + console.error('Failed to refresh tree node:', error); + return; } - } else if (isOpen) { - this.tree.unload(_i, { - success: fetchNodeInfo.bind(this, _i, _d, n), - fail: function() { - console.warn(arguments); - }, - }); - } else if (!this.tree.isInode(_i) && _d.inode) { - this.tree.setInode(_i, { - success: fetchNodeInfo.bind(this, _i, _d, n), - fail: function() { - console.warn(arguments); - }, - }); - } else { - fetchNodeInfo(_i, _d, n); } + + await this.tree.refresh(nodeItem); + this.tree.toggleItemLoader(nodeItem, false); + opts?.success?.(); }, onLoadFailNode: function(_nodeData) { diff --git a/web/pgadmin/static/js/components/PgTree/FileTreeX/index.tsx b/web/pgadmin/static/js/components/PgTree/FileTreeX/index.tsx index 1599d0529..a81c18ab7 100644 --- a/web/pgadmin/static/js/components/PgTree/FileTreeX/index.tsx +++ b/web/pgadmin/static/js/components/PgTree/FileTreeX/index.tsx @@ -152,6 +152,7 @@ export class FileTreeX extends React.Component { resize: this.resize, showLoader: this.showLoader, hideLoader: this.hideLoader, + toggleItemLoader: this.toggleItemLoader, }; model.decorations.addDecoration(this.activeFileDec); @@ -556,6 +557,17 @@ export class FileTreeX extends React.Component { }; + private readonly toggleItemLoader = (item: FileOrDir, show=false) => { + const ref = FileTreeItem.itemIdToRefMap.get(item.id); + if (ref) { + if (show) { + this.showLoader(ref); + } else { + this.hideLoader(ref); + } + } + }; + private readonly showLoader = (ref: HTMLDivElement) => { // get label ref and add loading class ref.style.background = 'none'; diff --git a/web/pgadmin/static/js/tree/preference_nodes.ts b/web/pgadmin/static/js/tree/preference_nodes.ts deleted file mode 100644 index 7a44e6f37..000000000 --- a/web/pgadmin/static/js/tree/preference_nodes.ts +++ /dev/null @@ -1,249 +0,0 @@ -///////////////////////////////////////////////////////////// -// -// pgAdmin 4 - PostgreSQL Tools -// -// Copyright (C) 2013 - 2025, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -////////////////////////////////////////////////////////////// - -import * as BrowserFS from 'browserfs'; -import pgAdmin from 'sources/pgadmin'; -import _ from 'lodash'; -import { FileType } from 'react-aspen'; -import { findInTree } from './tree'; - -export class ManagePreferenceTreeNodes { - constructor(data) { - this.tree = {}; - this.tempTree = new TreeNode(undefined, {}); - this.treeData = data || []; - } - - public init = (_root: string) => new Promise((res) => { - const node = { parent: null, children: [], data: null }; - this.tree = {}; - this.tree[_root] = { name: 'root', type: FileType.Directory, metadata: node }; - res(); - }); - - public updateNode = (_path, _data) => new Promise((res) => { - const item = this.findNode(_path); - if (item) { - item.name = _data.label; - item.metadata.data = _data; - } - res(true); - }); - - public removeNode = async (_path) => { - const item = this.findNode(_path); - - if (item?.parentNode) { - item.children = []; - item.parentNode.children.splice(item.parentNode.children.indexOf(item), 1); - } - return true; - }; - - findNode(path) { - if (path === null || path === undefined || path.length === 0 || path == '/preferences') { - return this.tempTree; - } - - return findInTree(this.tempTree, path); - } - - public addNode = (_parent: string, _path: string, _data: []) => new Promise((res) => { - _data.type = _data.inode ? FileType.Directory : FileType.File; - _data._label = _data.label; - _data.label = _.escape(_data.label); - - _data.is_collection = isCollectionNode(_data._type); - const nodeData = { parent: _parent, children: _data?.children ? _data.children : [], data: _data }; - - const tmpParentNode = this.findNode(_parent); - const treeNode = new TreeNode(_data.id, _data, {}, tmpParentNode, nodeData, _data.type); - - if (tmpParentNode !== null && tmpParentNode !== undefined) tmpParentNode.children.push(treeNode); - - res(treeNode); - }); - - public readNode = (_path: string) => new Promise((res, rej) => { - const temp_tree_path = _path, - node = this.findNode(_path); - node.children = []; - - if (node && node.children.length > 0) { - if (!node.type === FileType.File) { - rej(new Error('It\'s a leaf node')); - } - else if (node?.children.length != 0) { - res(node.children); - } - } - - const self = this; - - async function loadData() { - const Path = BrowserFS.BFSRequire('path'); - const fill = async (tree) => { - for (const idx in tree) { - const _node = tree[idx]; - const _pathl = Path.join(_path, _node.id); - await self.addNode(temp_tree_path, _pathl, _node); - } - }; - - if (node && !_.isUndefined(node.id)) { - const _data = self.treeData.find((el) => el.id == node.id); - const subNodes = []; - - _data.childrenNodes.forEach(element => { - subNodes.push(element); - }); - - await fill(subNodes); - } else { - await fill(self.treeData); - } - - self.returnChildrens(node, res); - } - loadData(); - }); - - public returnChildrens = (node: any, res: any) =>{ - if (node?.children.length > 0) return res(node.children); - else return res(null); - }; -} - - - -export class TreeNode { - constructor(id, data, domNode, parent, metadata, type) { - this.id = id; - this.data = data; - this.setParent(parent); - this.children = []; - this.domNode = domNode; - this.metadata = metadata; - this.name = metadata ? metadata.data.label : ''; - this.type = type || undefined; - } - - hasParent() { - return this.parentNode !== null && this.parentNode !== undefined; - } - - parent() { - return this.parentNode; - } - - setParent(parent) { - this.parentNode = parent; - this.path = this.id; - if (this.id) - if (parent !== null && parent !== undefined && parent.path !== undefined) { - this.path = parent.path + '/' + this.id; - } else { - this.path = '/preferences/' + this.id; - } - } - - getData() { - if (this.data === undefined) { - return undefined; - } else if (this.data === null) { - return null; - } - return {...this.data}; - } - - getHtmlIdentifier() { - return this.domNode; - } - - /* - * Find the ancestor with matches this condition - */ - ancestorNode(condition) { - let node; - - while (this.hasParent()) { - node = this.parent(); - if (condition(node)) { - return node; - } - } - - return null; - } - - /** - * Given a condition returns true if the current node - * or any of the parent nodes condition result is true - */ - anyFamilyMember(condition) { - if (condition(this)) { - return true; - } - - return this.ancestorNode(condition) !== null; - } - anyParent(condition) { - return this.ancestorNode(condition) !== null; - } - - reload(tree) { - return new Promise((resolve) => { - this.unload(tree) - .then(() => { - tree.setInode(this.domNode); - tree.deselect(this.domNode); - setTimeout(() => { - tree.selectNode(this.domNode); - }, 0); - resolve(); - }); - }); - } - - unload(tree) { - return new Promise((resolve, reject) => { - this.children = []; - tree.unload(this.domNode) - .then( - () => { - resolve(true); - }, - () => { - reject(new Error()); - }); - }); - } - - - open(tree, suppressNoDom) { - return new Promise((resolve, reject) => { - if (suppressNoDom && (this.domNode == null || typeof (this.domNode) === 'undefined')) { - resolve(true); - } else if (tree.isOpen(this.domNode)) { - resolve(true); - } else { - tree.open(this.domNode).then(() => resolve(true), () => reject(new Error(true))); - } - }); - } - -} - -export function isCollectionNode(node) { - if (pgAdmin.Browser.Nodes && node in pgAdmin.Browser.Nodes) { - if (pgAdmin.Browser.Nodes[node].is_collection !== undefined) return pgAdmin.Browser.Nodes[node].is_collection; - else return false; - } - return false; -} diff --git a/web/pgadmin/static/js/tree/tree.js b/web/pgadmin/static/js/tree/tree.js index b18290268..becb5093d 100644 --- a/web/pgadmin/static/js/tree/tree.js +++ b/web/pgadmin/static/js/tree/tree.js @@ -583,6 +583,10 @@ export class Tree { onNodeCopy(copyCallback) { this.copyHandler = copyCallback; } + + toggleItemLoader(item, show) { + this.tree.toggleItemLoader(item, show); + } } function mapType(type, idx) {