mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
1) Fix network disconnect issue while establishing the connection via SSH Tunnel and it impossible to expand the Servers node. Fixes #4724.
2) Fix server connection drops out issue in query tool. Fixes #4818 3) Fix VPN network disconnect issue where pgAdmin4 hangs on expanding the Servers node. Fixes #4926. 4) Ensure that the Servers collection node should expand independently of server connections. Fixes #4933. Set the default connection timeout to 10 seconds instead of 0.
This commit is contained in:
parent
700e01708b
commit
4ed2d74d9c
BIN
docs/en_US/images/server_advanced.png
Executable file → Normal file
BIN
docs/en_US/images/server_advanced.png
Executable file → Normal file
Binary file not shown.
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 55 KiB |
@ -21,10 +21,14 @@ Bug fixes
|
||||
|
||||
| `Issue #3538 <https://redmine.postgresql.org/issues/3538>`_ - Fix issue where the Reset button does not get enabled till all the mandatory fields are provided in the dialog.
|
||||
| `Issue #4659 <https://redmine.postgresql.org/issues/4659>`_ - Updated documentation for default privileges to clarify more on the grantor.
|
||||
| `Issue #4724 <https://redmine.postgresql.org/issues/4724>`_ - Fix network disconnect issue while establishing the connection via SSH Tunnel and it impossible to expand the Servers node.
|
||||
| `Issue #4792 <https://redmine.postgresql.org/issues/4792>`_ - Ensure that the superuser should be able to create database, as the superuser overrides all the access restrictions.
|
||||
| `Issue #4818 <https://redmine.postgresql.org/issues/4818>`_ - Fix server connection drops out issue in query tool.
|
||||
| `Issue #4836 <https://redmine.postgresql.org/issues/4836>`_ - Updated the json file name from 'servers.json' to 'pgadmin4/servers.json' in the container deployment section of the documentation.
|
||||
| `Issue #4878 <https://redmine.postgresql.org/issues/4878>`_ - Ensure that the superuser should be able to create role, as the superuser overrides all the access restrictions.
|
||||
| `Issue #4925 <https://redmine.postgresql.org/issues/4925>`_ - Shown some text on process watcher till the initial logs are loaded.
|
||||
| `Issue #4926 <https://redmine.postgresql.org/issues/4926>`_ - Fix VPN network disconnect issue where pgAdmin4 hangs on expanding the Servers node.
|
||||
| `Issue #4930 <https://redmine.postgresql.org/issues/4930>`_ - Fix main window tab navigation accessibility issue.
|
||||
| `Issue #4933 <https://redmine.postgresql.org/issues/4933>`_ - Ensure that the Servers collection node should expand independently of server connections.
|
||||
| `Issue #4934 <https://redmine.postgresql.org/issues/4934>`_ - Fix the help button link on the User Management dialog.
|
||||
| `Issue #4935 <https://redmine.postgresql.org/issues/4935>`_ - Fix accessibility issues.
|
||||
|
@ -170,7 +170,8 @@ Use the fields in the *Advanced* tab to configure a connection:
|
||||
`Section 33.15 of the Postgres documentation <https://www.postgresql.org/docs/current/libpq-pgpass.html>`_.
|
||||
* Use the *Connection timeout* field to specify the maximum wait for connection,
|
||||
in seconds. Zero or not specified means wait indefinitely. It is not
|
||||
recommended to use a timeout of less than 2 seconds.
|
||||
recommended to use a timeout of less than 2 seconds. By default it is set to
|
||||
10 seconds.
|
||||
|
||||
.. note:: The password file option is only supported when pgAdmin is using libpq
|
||||
v10.0 or later to connect to the server.
|
||||
|
27
web/migrations/versions/aff1436e3c8c_.py
Normal file
27
web/migrations/versions/aff1436e3c8c_.py
Normal file
@ -0,0 +1,27 @@
|
||||
|
||||
""" Update the default timeout to 10 seconds instead on 0.
|
||||
0 indicates wait indefinitely which causes trouble when network connection
|
||||
to server is lost.
|
||||
|
||||
Revision ID: aff1436e3c8c
|
||||
Revises: a77a0932a568
|
||||
Create Date: 2019-10-28 12:47:36.828709
|
||||
|
||||
"""
|
||||
from pgadmin.model import db
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'aff1436e3c8c'
|
||||
down_revision = 'a77a0932a568'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
db.engine.execute(
|
||||
'UPDATE server SET connect_timeout=10 WHERE connect_timeout=0 OR connect_timeout IS NULL'
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
pass
|
@ -28,6 +28,7 @@ from pgadmin.model import db, Server, ServerGroup, User
|
||||
from pgadmin.utils.driver import get_driver
|
||||
from pgadmin.utils.master_password import get_crypt_key
|
||||
from pgadmin.utils.exception import CryptKeyMissing
|
||||
from psycopg2 import Error as psycopg2_Error, OperationalError
|
||||
|
||||
|
||||
def has_any(data, keys):
|
||||
@ -58,7 +59,7 @@ def recovery_state(connection, postgres_version):
|
||||
else:
|
||||
in_recovery = None
|
||||
wal_paused = None
|
||||
return in_recovery, wal_paused
|
||||
return status, result, in_recovery, wal_paused
|
||||
|
||||
|
||||
def server_icon_and_background(is_connected, manager, server):
|
||||
@ -121,19 +122,21 @@ class ServerModule(sg.ServerGroupPluginModule):
|
||||
for server in servers:
|
||||
connected = False
|
||||
manager = None
|
||||
errmsg = None
|
||||
was_connected = False
|
||||
in_recovery = None
|
||||
wal_paused = None
|
||||
try:
|
||||
manager = driver.connection_manager(server.id)
|
||||
conn = manager.connection()
|
||||
connected = conn.connected()
|
||||
was_connected = conn.wasConnected
|
||||
except CryptKeyMissing:
|
||||
# show the nodes at least even if not able to connect.
|
||||
pass
|
||||
except psycopg2_Error as e:
|
||||
current_app.logger.exception(e)
|
||||
errmsg = str(e)
|
||||
|
||||
in_recovery = None
|
||||
wal_paused = None
|
||||
|
||||
if connected:
|
||||
in_recovery, wal_paused = recovery_state(conn, manager.version)
|
||||
yield self.generate_browser_node(
|
||||
"%d" % (server.id),
|
||||
gid,
|
||||
@ -151,7 +154,9 @@ class ServerModule(sg.ServerGroupPluginModule):
|
||||
is_password_saved=True if server.password is not None
|
||||
else False,
|
||||
is_tunnel_password_saved=True
|
||||
if server.tunnel_password is not None else False
|
||||
if server.tunnel_password is not None else False,
|
||||
was_connected=was_connected,
|
||||
errmsg=errmsg
|
||||
)
|
||||
|
||||
@property
|
||||
@ -352,12 +357,16 @@ class ServerNode(PGChildNodeView):
|
||||
manager = driver.connection_manager(server.id)
|
||||
conn = manager.connection()
|
||||
connected = conn.connected()
|
||||
|
||||
errmsg = None
|
||||
in_recovery = None
|
||||
wal_paused = None
|
||||
if connected:
|
||||
in_recovery, wal_paused = recovery_state(conn, manager.version)
|
||||
else:
|
||||
in_recovery = None
|
||||
wal_paused = None
|
||||
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)
|
||||
|
||||
res.append(
|
||||
self.blueprint.generate_browser_node(
|
||||
@ -377,7 +386,8 @@ class ServerNode(PGChildNodeView):
|
||||
is_password_saved=True if server.password is not None
|
||||
else False,
|
||||
is_tunnel_password_saved=True
|
||||
if server.tunnel_password is not None else False
|
||||
if server.tunnel_password is not None else False,
|
||||
errmsg=errmsg
|
||||
)
|
||||
)
|
||||
|
||||
@ -409,12 +419,16 @@ class ServerNode(PGChildNodeView):
|
||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(server.id)
|
||||
conn = manager.connection()
|
||||
connected = conn.connected()
|
||||
|
||||
errmsg = None
|
||||
in_recovery = None
|
||||
wal_paused = None
|
||||
if connected:
|
||||
in_recovery, wal_paused = recovery_state(conn, manager.version)
|
||||
else:
|
||||
in_recovery = None
|
||||
wal_paused = None
|
||||
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)
|
||||
|
||||
return make_json_response(
|
||||
result=self.blueprint.generate_browser_node(
|
||||
@ -434,8 +448,9 @@ class ServerNode(PGChildNodeView):
|
||||
is_password_saved=True if server.password is not None
|
||||
else False,
|
||||
is_tunnel_password_saved=True
|
||||
if server.tunnel_password is not None else False
|
||||
)
|
||||
if server.tunnel_password is not None else False,
|
||||
errmsg=errmsg
|
||||
),
|
||||
)
|
||||
|
||||
@login_required
|
||||
@ -949,19 +964,33 @@ class ServerNode(PGChildNodeView):
|
||||
|
||||
def connect_status(self, gid, sid):
|
||||
"""Check and return the connection status."""
|
||||
server = Server.query.filter_by(id=sid).first()
|
||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
||||
conn = manager.connection()
|
||||
res = conn.connected()
|
||||
connected = conn.connected()
|
||||
in_recovery = None
|
||||
wal_paused = None
|
||||
errmsg = None
|
||||
if connected:
|
||||
status, result, in_recovery, wal_paused =\
|
||||
recovery_state(conn, manager.version)
|
||||
|
||||
if res:
|
||||
from pgadmin.utils.exception import ConnectionLost, \
|
||||
SSHTunnelConnectionLost
|
||||
try:
|
||||
conn.execute_scalar('SELECT 1')
|
||||
except (ConnectionLost, SSHTunnelConnectionLost):
|
||||
res = False
|
||||
if not status:
|
||||
connected = False
|
||||
manager.release()
|
||||
errmsg = "{0} : {1}".format(server.name, result)
|
||||
|
||||
return make_json_response(data={'connected': res})
|
||||
return make_json_response(
|
||||
data={
|
||||
'icon': server_icon_and_background(connected, manager, server),
|
||||
'connected': connected,
|
||||
'in_recovery': in_recovery,
|
||||
'wal_pause': wal_paused,
|
||||
'server_type': manager.server_type if connected else "pg",
|
||||
'user': manager.user_info if connected else None,
|
||||
'errmsg': errmsg
|
||||
}
|
||||
)
|
||||
|
||||
def connect(self, gid, sid):
|
||||
"""
|
||||
@ -1078,6 +1107,8 @@ class ServerNode(PGChildNodeView):
|
||||
tunnel_password=tunnel_password,
|
||||
server_types=ServerType.types()
|
||||
)
|
||||
except OperationalError as e:
|
||||
return internal_server_error(errormsg=str(e))
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
return self.get_response_for_password(
|
||||
@ -1088,7 +1119,7 @@ class ServerNode(PGChildNodeView):
|
||||
errmsg = errmsg.decode('utf-8')
|
||||
|
||||
current_app.logger.error(
|
||||
"Could not connected to server(#{0}) - '{1}'.\nError: {2}"
|
||||
"Could not connect to server(#{0}) - '{1}'.\nError: {2}"
|
||||
.format(server.id, server.name, errmsg)
|
||||
)
|
||||
return self.get_response_for_password(server, 401, True,
|
||||
@ -1125,7 +1156,8 @@ class ServerNode(PGChildNodeView):
|
||||
%s - %s' % (server.id, server.name))
|
||||
# Update the recovery and wal pause option for the server
|
||||
# if connected successfully
|
||||
in_recovery, wal_paused = recovery_state(conn, manager.version)
|
||||
_, _, in_recovery, wal_paused =\
|
||||
recovery_state(conn, manager.version)
|
||||
|
||||
return make_json_response(
|
||||
success=1,
|
||||
|
@ -292,6 +292,10 @@ define('pgadmin.node.server', [
|
||||
|
||||
// Call added method of node.js
|
||||
pgAdmin.Browser.Node.callbacks.added.apply(this, arguments);
|
||||
|
||||
if(data.was_connected) {
|
||||
fetch_connection_status(this, data, pgBrowser.tree, item);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
/* Reload configuration */
|
||||
@ -732,7 +736,7 @@ define('pgadmin.node.server', [
|
||||
tunnel_password: undefined,
|
||||
tunnel_authentication: 0,
|
||||
save_tunnel_password: false,
|
||||
connect_timeout: 0,
|
||||
connect_timeout: 10,
|
||||
},
|
||||
// Default values!
|
||||
initialize: function(attrs, args) {
|
||||
@ -1273,7 +1277,14 @@ define('pgadmin.node.server', [
|
||||
}
|
||||
};
|
||||
|
||||
/* Wait till the existing request completes */
|
||||
if(data.is_connecting) {
|
||||
return;
|
||||
}
|
||||
data.is_connecting = true;
|
||||
tree.setLeaf(item);
|
||||
tree.removeIcon(item);
|
||||
tree.addIcon(item, {icon: 'icon-server-connecting'});
|
||||
var url = obj.generate_url(item, 'connect', data, true);
|
||||
$.post(url)
|
||||
.done(function(res) {
|
||||
@ -1287,6 +1298,40 @@ define('pgadmin.node.server', [
|
||||
return onFailure(
|
||||
xhr, status, error, obj, data, tree, item, wasConnected
|
||||
);
|
||||
})
|
||||
.always(function(){
|
||||
data.is_connecting = false;
|
||||
});
|
||||
};
|
||||
var fetch_connection_status = function(obj, data, tree, item) {
|
||||
var url = obj.generate_url(item, 'connect', data, true);
|
||||
|
||||
tree.setLeaf(item);
|
||||
tree.removeIcon(item);
|
||||
tree.addIcon(item, {icon: 'icon-server-connecting'});
|
||||
$.get(url)
|
||||
.done(function(res) {
|
||||
tree.setInode(item);
|
||||
if (res && res.data) {
|
||||
if (typeof res.data.icon == 'string') {
|
||||
tree.removeIcon(item);
|
||||
data.icon = res.data.icon;
|
||||
tree.addIcon(item, {icon: data.icon});
|
||||
}
|
||||
_.extend(data, res.data);
|
||||
|
||||
var serverInfo = pgBrowser.serverInfo = pgBrowser.serverInfo || {};
|
||||
serverInfo[data._id] = _.extend({}, data);
|
||||
|
||||
if(data.errmsg) {
|
||||
Alertify.error(data.errmsg);
|
||||
}
|
||||
}
|
||||
})
|
||||
.fail(function(xhr, status, error) {
|
||||
tree.setInode(item);
|
||||
tree.addIcon(item, {icon: 'icon-server-not-connected'});
|
||||
Alertify.pgRespErrorNotify(xhr, error);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -17,7 +17,8 @@ from flask.views import View, MethodViewType, with_metaclass
|
||||
from flask_babelex import gettext
|
||||
|
||||
from config import PG_DEFAULT_DRIVER
|
||||
from pgadmin.utils.ajax import make_json_response, precondition_required
|
||||
from pgadmin.utils.ajax import make_json_response, precondition_required,\
|
||||
internal_server_error
|
||||
from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost,\
|
||||
CryptKeyMissing
|
||||
|
||||
@ -377,11 +378,7 @@ class PGChildNodeView(NodeView):
|
||||
if not conn.connected():
|
||||
status, msg = conn.connect()
|
||||
if not status:
|
||||
return precondition_required(
|
||||
gettext(
|
||||
"Connection to the server has been lost."
|
||||
)
|
||||
)
|
||||
return internal_server_error(errormsg=msg)
|
||||
except (ConnectionLost, SSHTunnelConnectionLost, CryptKeyMissing):
|
||||
raise
|
||||
except Exception as e:
|
||||
|
@ -543,6 +543,7 @@ define('pgadmin.dashboard', [
|
||||
);
|
||||
const WAIT_COUNTER = 3;
|
||||
let last_poll_wait_counter = 0;
|
||||
let resp_not_received_counter = 0;
|
||||
|
||||
/* Stop if running, only one poller lives */
|
||||
self.stopChartsPoller();
|
||||
@ -563,7 +564,7 @@ define('pgadmin.dashboard', [
|
||||
/* If none of the chart wants data, don't trouble
|
||||
* If response not received from prev poll, don't trouble !!
|
||||
*/
|
||||
if(chart_names_to_get.length == 0 || last_poll_wait_counter > 0) {
|
||||
if(chart_names_to_get.length == 0 || last_poll_wait_counter > 0 || resp_not_received_counter >= WAIT_COUNTER) {
|
||||
/* reduce the number of tries, request should be sent if last_poll_wait_counter
|
||||
* completes WAIT_COUNTER times.*/
|
||||
last_poll_wait_counter--;
|
||||
@ -571,12 +572,12 @@ define('pgadmin.dashboard', [
|
||||
}
|
||||
|
||||
var path = self.getStatsUrl(sid, did, chart_names_to_get);
|
||||
resp_not_received_counter++;
|
||||
$.ajax({
|
||||
url: path,
|
||||
type: 'GET',
|
||||
})
|
||||
.done(function(resp) {
|
||||
last_poll_wait_counter = 0;
|
||||
for(let chart_name in resp) {
|
||||
let chart_obj = chart_store[chart_name].chart_obj;
|
||||
$(chart_obj.getContainer()).removeClass('graph-error');
|
||||
@ -584,7 +585,6 @@ define('pgadmin.dashboard', [
|
||||
}
|
||||
})
|
||||
.fail(function(xhr) {
|
||||
last_poll_wait_counter = 0;
|
||||
let err = '';
|
||||
let msg = '';
|
||||
let cls = 'info';
|
||||
@ -613,6 +613,10 @@ define('pgadmin.dashboard', [
|
||||
'<div class="alert alert-' + cls + ' pg-panel-message" role="alert">' + msg + '</div>'
|
||||
);
|
||||
}
|
||||
})
|
||||
.always(function() {
|
||||
last_poll_wait_counter = 0;
|
||||
resp_not_received_counter--;
|
||||
});
|
||||
last_poll_wait_counter = WAIT_COUNTER;
|
||||
};
|
||||
|
@ -29,7 +29,7 @@ from flask_sqlalchemy import SQLAlchemy
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
SCHEMA_VERSION = 23
|
||||
SCHEMA_VERSION = 24
|
||||
|
||||
##########################################################################
|
||||
#
|
||||
|
@ -12,6 +12,7 @@ import $ from 'jquery';
|
||||
import url_for from '../url_for';
|
||||
import axios from 'axios';
|
||||
import * as httpErrorHandler from './query_tool_http_error_handler';
|
||||
import * as queryTxnStatus from 'sources/sqleditor/query_txn_status_constants';
|
||||
|
||||
class LoadingScreen {
|
||||
constructor(sqlEditor) {
|
||||
@ -83,7 +84,8 @@ class ExecuteQuery {
|
||||
self.loadingScreen.hide();
|
||||
self.enableSQLEditorButtons();
|
||||
// Enable/Disable commit and rollback button.
|
||||
if (result.data.data.transaction_status == 2 || result.data.data.transaction_status == 3) {
|
||||
if (result.data.data.transaction_status == queryTxnStatus.TRANSACTION_STATUS_INTRANS
|
||||
|| result.data.data.transaction_status == queryTxnStatus.TRANSACTION_STATUS_INERROR) {
|
||||
self.enableTransactionButtons();
|
||||
} else {
|
||||
self.disableTransactionButtons();
|
||||
@ -123,7 +125,8 @@ class ExecuteQuery {
|
||||
self.updateSqlEditorLastTransactionStatus(httpMessage.data.data.transaction_status);
|
||||
|
||||
// Enable/Disable commit and rollback button.
|
||||
if (httpMessage.data.data.transaction_status == 2 || httpMessage.data.data.transaction_status == 3) {
|
||||
if (httpMessage.data.data.transaction_status == queryTxnStatus.TRANSACTION_STATUS_INTRANS
|
||||
|| httpMessage.data.data.transaction_status == queryTxnStatus.TRANSACTION_STATUS_INERROR) {
|
||||
self.enableTransactionButtons();
|
||||
} else {
|
||||
self.disableTransactionButtons();
|
||||
@ -131,7 +134,7 @@ class ExecuteQuery {
|
||||
|
||||
if (ExecuteQuery.isQueryFinished(httpMessage)) {
|
||||
if (this.sqlServerObject.close_on_idle_transaction &&
|
||||
httpMessage.data.data.transaction_status == 0)
|
||||
httpMessage.data.data.transaction_status == queryTxnStatus.TRANSACTION_STATUS_IDLE)
|
||||
this.sqlServerObject.check_needed_confirmations_before_closing_panel();
|
||||
|
||||
self.loadingScreen.setMessage('Loading data from the database server and rendering...');
|
||||
|
@ -0,0 +1,11 @@
|
||||
/* psycopg2 transaction status constants
|
||||
* http://initd.org/psycopg/docs/extensions.html#transaction-status-constants
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
TRANSACTION_STATUS_IDLE: 0,
|
||||
TRANSACTION_STATUS_ACTIVE: 1,
|
||||
TRANSACTION_STATUS_INTRANS: 2,
|
||||
TRANSACTION_STATUS_INERROR: 3,
|
||||
TRANSACTION_STATUS_UNKNOWN: 5,
|
||||
};
|
@ -9,6 +9,7 @@
|
||||
|
||||
import {isValidData} from 'sources/utils';
|
||||
import $ from 'jquery';
|
||||
import Alertify from 'pgadmin.alertifyjs';
|
||||
|
||||
export class TreeNode {
|
||||
constructor(id, data, domNode, parent) {
|
||||
@ -261,6 +262,9 @@ export class Tree {
|
||||
|
||||
const parentId = this.translateTreeNodeIdFromACITree(api.parent(item));
|
||||
this.addNewNode(id, data, item, parentId);
|
||||
if(data.errmsg) {
|
||||
Alertify.error(data.errmsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
|
@ -36,6 +36,7 @@ define('tools.querytool', [
|
||||
'sources/sqleditor/calculate_query_run_time',
|
||||
'sources/sqleditor/call_render_after_poll',
|
||||
'sources/sqleditor/query_tool_preferences',
|
||||
'sources/sqleditor/query_txn_status_constants',
|
||||
'sources/csrf',
|
||||
'tools/datagrid/static/js/datagrid_panel_title',
|
||||
'sources/window',
|
||||
@ -52,7 +53,7 @@ define('tools.querytool', [
|
||||
XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler,
|
||||
GeometryViewer, historyColl, queryHist, querySources,
|
||||
keyboardShortcuts, queryToolActions, queryToolNotifications, Datagrid,
|
||||
modifyAnimation, calculateQueryRunTime, callRenderAfterPoll, queryToolPref, csrfToken, panelTitleFunc,
|
||||
modifyAnimation, calculateQueryRunTime, callRenderAfterPoll, queryToolPref, queryTxnStatus, csrfToken, panelTitleFunc,
|
||||
pgWindow) {
|
||||
/* Return back, this has been called more than once */
|
||||
if (pgAdmin.SqlEditor)
|
||||
@ -4182,8 +4183,9 @@ define('tools.querytool', [
|
||||
self.unsaved_changes_user_confirmation(msg, false);
|
||||
} // If a transaction is currently ongoing
|
||||
else if (self.preferences.prompt_commit_transaction
|
||||
&& self.last_transaction_status > 0) { // 0 -> idle (no transaction)
|
||||
var is_commit_disabled = self.last_transaction_status == 3; // 3 -> Failed transaction
|
||||
&& (self.last_transaction_status === queryTxnStatus.TRANSACTION_STATUS_INTRANS
|
||||
|| self.last_transaction_status === queryTxnStatus.TRANSACTION_STATUS_INERROR)) {
|
||||
var is_commit_disabled = self.last_transaction_status == queryTxnStatus.TRANSACTION_STATUS_INERROR;
|
||||
self.uncommitted_transaction_user_confirmation(is_commit_disabled);
|
||||
}
|
||||
else {
|
||||
|
@ -14,19 +14,21 @@ object.
|
||||
|
||||
"""
|
||||
import datetime
|
||||
from flask import session, request
|
||||
from flask import session
|
||||
from flask_login import current_user
|
||||
from flask_babelex import gettext
|
||||
import psycopg2
|
||||
from psycopg2.extensions import adapt
|
||||
from threading import Lock
|
||||
|
||||
import config
|
||||
from pgadmin.model import Server, User
|
||||
from pgadmin.model import Server
|
||||
from .keywords import ScanKeyword
|
||||
from ..abstract import BaseDriver
|
||||
from .connection import Connection
|
||||
from .server_manager import ServerManager
|
||||
|
||||
connection_restore_lock = Lock()
|
||||
|
||||
|
||||
class Driver(BaseDriver):
|
||||
"""
|
||||
@ -80,21 +82,30 @@ class Driver(BaseDriver):
|
||||
return None
|
||||
|
||||
if session.sid not in self.managers:
|
||||
self.managers[session.sid] = managers = dict()
|
||||
if '__pgsql_server_managers' in session:
|
||||
session_managers = session['__pgsql_server_managers'].copy()
|
||||
with connection_restore_lock:
|
||||
# The wait is over but the object might have been loaded
|
||||
# by some other thread check again
|
||||
if session.sid not in self.managers:
|
||||
self.managers[session.sid] = managers = dict()
|
||||
if '__pgsql_server_managers' in session:
|
||||
session_managers =\
|
||||
session['__pgsql_server_managers'].copy()
|
||||
for server in \
|
||||
Server.query.filter_by(
|
||||
user_id=current_user.id):
|
||||
manager = managers[str(server.id)] =\
|
||||
ServerManager(server)
|
||||
if server.id in session_managers:
|
||||
manager._restore(session_managers[server.id])
|
||||
manager.update_session()
|
||||
|
||||
for server in Server.query.filter_by(user_id=current_user.id):
|
||||
manager = managers[str(server.id)] = ServerManager(server)
|
||||
if server.id in session_managers:
|
||||
manager._restore(session_managers[server.id])
|
||||
manager.update_session()
|
||||
else:
|
||||
managers = self.managers[session.sid]
|
||||
if str(sid) in managers:
|
||||
manager = managers[str(sid)]
|
||||
manager._restore_connections()
|
||||
manager.update_session()
|
||||
with connection_restore_lock:
|
||||
manager._restore_connections()
|
||||
manager.update_session()
|
||||
|
||||
managers['pinged'] = datetime.datetime.now()
|
||||
if str(sid) not in managers:
|
||||
|
@ -25,13 +25,10 @@ from pgadmin.model import Server, User
|
||||
from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost,\
|
||||
CryptKeyMissing
|
||||
from pgadmin.utils.master_password import get_crypt_key
|
||||
from threading import Lock
|
||||
|
||||
if config.SUPPORT_SSH_TUNNEL:
|
||||
from sshtunnel import SSHTunnelForwarder, BaseSSHTunnelForwarderError
|
||||
|
||||
connection_restore_lock = Lock()
|
||||
|
||||
|
||||
class ServerManager(object):
|
||||
"""
|
||||
@ -292,80 +289,79 @@ WHERE db.oid = {0}""".format(did))
|
||||
|
||||
connections = data['connections']
|
||||
|
||||
with connection_restore_lock:
|
||||
for conn_id in connections:
|
||||
conn_info = connections[conn_id]
|
||||
if conn_info['conn_id'] in self.connections:
|
||||
conn = self.connections[conn_info['conn_id']]
|
||||
else:
|
||||
conn = self.connections[conn_info['conn_id']] = Connection(
|
||||
self, conn_info['conn_id'], conn_info['database'],
|
||||
conn_info['auto_reconnect'], conn_info['async_'],
|
||||
use_binary_placeholder=conn_info[
|
||||
'use_binary_placeholder'],
|
||||
array_to_string=conn_info['array_to_string']
|
||||
for conn_id in connections:
|
||||
conn_info = connections[conn_id]
|
||||
if conn_info['conn_id'] in self.connections:
|
||||
conn = self.connections[conn_info['conn_id']]
|
||||
else:
|
||||
conn = self.connections[conn_info['conn_id']] = Connection(
|
||||
self, conn_info['conn_id'], conn_info['database'],
|
||||
conn_info['auto_reconnect'], conn_info['async_'],
|
||||
use_binary_placeholder=conn_info[
|
||||
'use_binary_placeholder'],
|
||||
array_to_string=conn_info['array_to_string']
|
||||
)
|
||||
|
||||
# only try to reconnect if connection was connected previously
|
||||
# and auto_reconnect is true.
|
||||
if conn_info['wasConnected'] and conn_info['auto_reconnect']:
|
||||
try:
|
||||
# Check SSH Tunnel needs to be created
|
||||
if self.use_ssh_tunnel == 1 and \
|
||||
not self.tunnel_created:
|
||||
status, error = self.create_ssh_tunnel(
|
||||
data['tunnel_password'])
|
||||
|
||||
# Check SSH Tunnel is alive or not.
|
||||
self.check_ssh_tunnel_alive()
|
||||
|
||||
conn.connect(
|
||||
password=data['password'],
|
||||
server_types=ServerType.types()
|
||||
)
|
||||
|
||||
# only try to reconnect if connection was connected previously
|
||||
# and auto_reconnect is true.
|
||||
if conn_info['wasConnected'] and conn_info['auto_reconnect']:
|
||||
try:
|
||||
# Check SSH Tunnel needs to be created
|
||||
if self.use_ssh_tunnel == 1 and \
|
||||
not self.tunnel_created:
|
||||
status, error = self.create_ssh_tunnel(
|
||||
data['tunnel_password'])
|
||||
|
||||
# Check SSH Tunnel is alive or not.
|
||||
self.check_ssh_tunnel_alive()
|
||||
|
||||
conn.connect(
|
||||
password=data['password'],
|
||||
server_types=ServerType.types()
|
||||
)
|
||||
# This will also update wasConnected flag in
|
||||
# connection so no need to update the flag manually.
|
||||
except CryptKeyMissing:
|
||||
# maintain the status as this will help to restore once
|
||||
# the key is available
|
||||
conn.wasConnected = conn_info['wasConnected']
|
||||
conn.auto_reconnect = conn_info['auto_reconnect']
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
self.connections.pop(conn_info['conn_id'])
|
||||
raise
|
||||
# This will also update wasConnected flag in
|
||||
# connection so no need to update the flag manually.
|
||||
except CryptKeyMissing:
|
||||
# maintain the status as this will help to restore once
|
||||
# the key is available
|
||||
conn.wasConnected = conn_info['wasConnected']
|
||||
conn.auto_reconnect = conn_info['auto_reconnect']
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
self.connections.pop(conn_info['conn_id'])
|
||||
raise
|
||||
|
||||
def _restore_connections(self):
|
||||
with connection_restore_lock:
|
||||
for conn_id in self.connections:
|
||||
conn = self.connections[conn_id]
|
||||
# only try to reconnect if connection was connected previously
|
||||
# and auto_reconnect is true.
|
||||
wasConnected = conn.wasConnected
|
||||
auto_reconnect = conn.auto_reconnect
|
||||
if conn.wasConnected and conn.auto_reconnect:
|
||||
try:
|
||||
# Check SSH Tunnel needs to be created
|
||||
if self.use_ssh_tunnel == 1 and \
|
||||
not self.tunnel_created:
|
||||
status, error = self.create_ssh_tunnel(
|
||||
self.tunnel_password
|
||||
)
|
||||
for conn_id in self.connections:
|
||||
conn = self.connections[conn_id]
|
||||
# only try to reconnect if connection was connected previously
|
||||
# and auto_reconnect is true.
|
||||
wasConnected = conn.wasConnected
|
||||
auto_reconnect = conn.auto_reconnect
|
||||
if conn.wasConnected and conn.auto_reconnect:
|
||||
try:
|
||||
# Check SSH Tunnel needs to be created
|
||||
if self.use_ssh_tunnel == 1 and \
|
||||
not self.tunnel_created:
|
||||
status, error = self.create_ssh_tunnel(
|
||||
self.tunnel_password
|
||||
)
|
||||
|
||||
# Check SSH Tunnel is alive or not.
|
||||
self.check_ssh_tunnel_alive()
|
||||
# Check SSH Tunnel is alive or not.
|
||||
self.check_ssh_tunnel_alive()
|
||||
|
||||
conn.connect()
|
||||
# This will also update wasConnected flag in
|
||||
# connection so no need to update the flag manually.
|
||||
except CryptKeyMissing:
|
||||
# maintain the status as this will help to restore once
|
||||
# the key is available
|
||||
conn.wasConnected = wasConnected
|
||||
conn.auto_reconnect = auto_reconnect
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
raise
|
||||
conn.connect()
|
||||
# This will also update wasConnected flag in
|
||||
# connection so no need to update the flag manually.
|
||||
except CryptKeyMissing:
|
||||
# maintain the status as this will help to restore once
|
||||
# the key is available
|
||||
conn.wasConnected = wasConnected
|
||||
conn.auto_reconnect = auto_reconnect
|
||||
except Exception as e:
|
||||
self.connections.pop(conn_id)
|
||||
current_app.logger.exception(e)
|
||||
raise
|
||||
|
||||
def release(self, database=None, conn_id=None, did=None):
|
||||
# Stop the SSH tunnel if release() function calls without
|
||||
|
Loading…
Reference in New Issue
Block a user