Fixed an issue where pgAdmin failed to update the server connection status when the server was disconnected in the background and a refresh was performed on that server. #8149
parent
9eec4f5b8c
commit
b2ec3a5acc
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -152,6 +152,7 @@ export class FileTreeX extends React.Component<IFileTreeXProps> {
|
|||
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<IFileTreeXProps> {
|
|||
|
||||
};
|
||||
|
||||
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';
|
||||
|
|
|
|||
|
|
@ -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<string[]>((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;
|
||||
}
|
||||
|
|
@ -583,6 +583,10 @@ export class Tree {
|
|||
onNodeCopy(copyCallback) {
|
||||
this.copyHandler = copyCallback;
|
||||
}
|
||||
|
||||
toggleItemLoader(item, show) {
|
||||
this.tree.toggleItemLoader(item, show);
|
||||
}
|
||||
}
|
||||
|
||||
function mapType(type, idx) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue