Added support for the infrastructure for on demand access/create the

server connection.

The BaseDriver and BaseConnection are two abstract classes, which allows
us to replace the existing driver with the currently used. The current
implementation supports to connect the PostgreSQL and Postgres Plus
Advanced Server using the psycopg2 driver.
This commit is contained in:
Ashesh Vashi
2015-10-20 12:33:18 +05:30
parent b52d72f176
commit e27e39a8f3
34 changed files with 2625 additions and 417 deletions

View File

@@ -20,6 +20,7 @@ from pgadmin.utils.ajax import make_json_response, \
from pgadmin.browser import BrowserPluginModule
from pgadmin.utils.menu import MenuItem
from pgadmin.settings.settings_model import db, ServerGroup
from pgadmin.browser.utils import NodeView
class ServerGroupModule(BrowserPluginModule):
@@ -30,13 +31,14 @@ class ServerGroupModule(BrowserPluginModule):
"""Return a JSON document listing the server groups for the user"""
groups = ServerGroup.query.filter_by(user_id=current_user.id)
for group in groups:
group = self.generate_browser_node(
yield self.generate_browser_node(
"%d" % (group.id),
None,
group.name,
"icon-%s" % self.node_type,
True)
yield group
True,
self.node_type
)
@property
def node_type(self):
@@ -46,10 +48,6 @@ class ServerGroupModule(BrowserPluginModule):
def script_load(self):
return None
@property
def node_path(self):
return BrowserPluginModule.browser_url_prefix + self.node_type
class ServerGroupMenuItem(MenuItem):
@@ -65,29 +63,19 @@ class ServerGroupPluginModule(BrowserPluginModule):
__metaclass__ = ABCMeta
@abstractmethod
def get_nodes(self, *arg, **kwargs):
pass
@property
def node_path(self):
return BrowserPluginModule.browser_url_prefix + self.node_type
blueprint = ServerGroupModule( __name__, static_url_path='')
# Initialise the module
from pgadmin.browser.utils import NodeView
blueprint = ServerGroupModule(__name__, static_url_path='')
class ServerGroupView(NodeView):
node_type = ServerGroupModule.NODE_TYPE
parent_ids = []
ids = [{'type':'int', 'id':'gid'}]
ids = [{'type': 'int', 'id': 'gid'}]
def list(self):
res = []
@@ -95,7 +83,6 @@ class ServerGroupView(NodeView):
res.append(g)
return make_json_response(result=res)
def delete(self, gid):
"""Delete a server group node in the settings database"""
@@ -108,18 +95,22 @@ class ServerGroupView(NodeView):
return make_json_response(
status=417,
success=0,
errormsg=gettext('The specified server group could not be found.'))
errormsg=gettext(
'The specified server group could not be found.'
)
)
else:
try:
for sg in servergroup:
db.session.delete(sg)
db.session.commit()
except Exception as e:
return make_json_response(status=410, success=0, errormsg=e.message)
return make_json_response(
status=410, success=0, errormsg=e.message
)
return make_json_response(result=request.form)
def update(self, gid):
"""Update the server-group properties"""
@@ -134,18 +125,22 @@ class ServerGroupView(NodeView):
return make_json_response(
status=417,
success=0,
errormsg=gettext('The specified server group could not be found.'))
errormsg=gettext(
'The specified server group could not be found.'
)
)
else:
try:
if u'name' in data:
servergroup.name = data[u'name']
db.session.commit()
except Exception as e:
return make_json_response(status=410, success=0, errormsg=e.message)
return make_json_response(
status=410, success=0, errormsg=e.message
)
return make_json_response(result=request.form)
def properties(self, gid):
"""Update the server-group properties"""
@@ -159,11 +154,15 @@ class ServerGroupView(NodeView):
return make_json_response(
status=417,
success=0,
errormsg=gettext('The specified server group could not be found.'))
errormsg=gettext(
'The specified server group could not be found.'
)
)
else:
return ajax_response(response={'id': sg.id, 'name': sg.name},
status=200)
return ajax_response(
response={'id': sg.id, 'name': sg.name},
status=200
)
def create(self):
data = request.form if request.form else json.loads(request.data)
@@ -179,14 +178,17 @@ class ServerGroupView(NodeView):
data[u'id'] = sg.id
data[u'name'] = sg.name
return jsonify(node=blueprint.generate_browser_node(
"%d" % (sg.id),
None,
sg.name,
"icon-%s" % self.node_type,
True))
return jsonify(
node=self.blueprint.generate_browser_node(
"%d" % (sg.id),
None,
sg.name,
"icon-%s" % self.node_type,
True,
self.node_type
)
)
except Exception as e:
print 'except'
return make_json_response(
status=410,
success=0,
@@ -198,31 +200,18 @@ class ServerGroupView(NodeView):
success=0,
errormsg=gettext('No server group name was specified'))
def nodes(self, gid):
"""Build a list of treeview nodes from the child nodes."""
nodes = []
for module in blueprint.submodules:
nodes.extend(module.get_nodes(server_group=gid))
return make_json_response(data=nodes)
def sql(self, gid):
return make_json_response(status=422)
def modified_sql(self, gid):
return make_json_response(status=422)
def statistics(self, gid):
return make_json_response(status=422)
def dependencies(self, gid):
return make_json_response(status=422)
def dependents(self, gid):
return make_json_response(status=422)

View File

@@ -0,0 +1,44 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2015, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
from pgadmin.browser.server_groups.servers import ServerTypeModule
from pgadmin.browser.utils import PGChildModule
from flask.ext.babel import gettext
class PPASServer(ServerTypeModule, PGChildModule):
NODE_TYPE = "ppas"
@property
def node_type(self):
return self.NODE_TYPE
@property
def jssnippets(self):
return []
@property
def type(self):
return "PPAS"
@property
def description(self):
return "Postgres Plus Advanced Server"
@property
def driver(self):
return "psycopg2"
@property
def priority(self):
return 1
def instanceOf(self, ver):
return ver.startswith("EnterpriseDB")
blueprint = PPASServer(__name__, static_url_path='/static')

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

View File

@@ -0,0 +1,43 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2015, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
from pgadmin.browser.server_groups.servers import ServerTypeModule
from pgadmin.browser.utils import PGChildModule
class PGServer(ServerTypeModule, PGChildModule):
NODE_TYPE = "pg"
@property
def node_type(self):
return self.NODE_TYPE
@property
def jssnippets(self):
return []
@property
def type(self):
return "PG"
@property
def description(self):
return "PostgreSQL"
@property
def driver(self):
return "psycopg2"
@property
def priority(self):
return 10
def instanceOf(self, ver):
return True
blueprint = PGServer(__name__, static_url_path='/static')

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 B

View File

@@ -7,17 +7,21 @@
#
##########################################################################
import json
from flask import render_template, request, make_response, jsonify
from abc import ABCMeta, abstractmethod, abstractproperty
from flask import render_template, request, make_response, jsonify, current_app
from flask.ext.security import login_required, current_user
from pgadmin.settings.settings_model import db, Server, ServerGroup
from pgadmin.settings.settings_model import db, Server, ServerGroup, User
from pgadmin.utils.menu import MenuItem
from pgadmin.utils.ajax import make_json_response, \
make_response as ajax_response
from pgadmin.browser.utils import NodeView, generate_browser_node
make_response as ajax_response, internal_server_error, success_return, \
unauthorized, bad_request, precondition_required, forbidden
from pgadmin.browser.utils import NodeView
import traceback
from flask.ext.babel import gettext
import pgadmin.browser.server_groups as sg
from pgadmin.utils.crypto import encrypt, decrypt
from pgadmin.browser import BrowserPluginModule
from config import PG_DEFAULT_DRIVER
class ServerModule(sg.ServerGroupPluginModule):
@@ -35,27 +39,48 @@ class ServerModule(sg.ServerGroupPluginModule):
"""
return sg.ServerGroupModule.NODE_TYPE
def get_nodes(self, server_group):
def get_nodes(self, gid):
"""Return a JSON document listing the server groups for the user"""
servers = Server.query.filter_by(user_id=current_user.id,
servergroup_id=server_group)
servergroup_id=gid)
from pgadmin.utils.driver import get_driver
driver = get_driver(PG_DEFAULT_DRIVER)
# TODO: Move this JSON generation to a Server method
for server in servers:
node = generate_browser_node(
"%d" % (server.id),
"%d" % server_group,
server.name,
"icon-%s-not-connected" % self.NODE_TYPE,
True,
self.NODE_TYPE)
manager = driver.connection_manager(server.id)
conn = manager.connection()
module = getattr(manager, "module", None)
connected = conn.connected()
yield node
yield self.generate_browser_node(
"%d" % (server.id),
"%d" % gid,
server.name,
"icon-server-not-connected" if not connected else
"icon-{0}".format(module.NODE_TYPE),
True,
self.NODE_TYPE,
connected=connected,
server_type=module.type if module is not None else "PG"
)
@property
def jssnippets(self):
return []
@property
def csssnippets(self):
"""
Returns a snippet of css to include in the page
"""
snippets = [render_template("css/servers.css")]
for submodule in self.submodules:
snippets.extend(submodule.csssnippets)
return snippets
class ServerMenuItem(MenuItem):
def __init__(self, **kwargs):
@@ -66,8 +91,83 @@ class ServerMenuItem(MenuItem):
blueprint = ServerModule(__name__)
class ServerTypeModule(BrowserPluginModule):
"""
Base class for different server types.
"""
__metaclass__ = ABCMeta
@abstractproperty
def type(self):
pass
@abstractproperty
def description(self):
pass
@abstractproperty
def priority(self):
pass
def get_nodes(self, manager=None, sid=None):
assert(sid is not None)
nodes = []
for module in self.submodules:
if isinstance(module, PGChildModule):
if manager and module.BackendSupported(manager):
nodes.extend(module.get_nodes(sid=sid, manager=manager))
else:
nodes.extend(module.get_nodes(sid=sid))
return nodes
@abstractmethod
def instanceOf(self, version):
pass
def __str__(self):
return "Type: {0},Description:{1}".format(self.type, self.description)
@property
def csssnippets(self):
"""
Returns a snippet of css to include in the page
"""
snippets = [
render_template(
"css/node.css",
node_type=self.node_type
)
]
for submodule in self.submodules:
snippets.extend(submodule.csssnippets)
return snippets
def get_own_javascripts(self):
scripts = []
for module in self.submodules:
scripts.extend(module.get_own_javascripts())
return scripts
@property
def script_load(self):
"""
Load the module script for all server types, when a server node is
initialized.
"""
return ServerTypeModule.NODE_TYPE
class ServerNode(NodeView):
node_type = ServerModule.NODE_TYPE
parent_ids = [{'type': 'int', 'id': 'gid'}]
ids = [{'type': 'int', 'id': 'sid'}]
operations = dict({
@@ -80,7 +180,9 @@ class ServerNode(NodeView):
'stats': [{'get': 'statistics'}],
'deps': [{'get': 'dependencies', 'post': 'dependents'}],
'module.js': [{}, {}, {'get': 'module_js'}],
'connect': [{'get': 'connect_status', 'post': 'connect', 'delete': 'disconnect'}]
'connect': [{
'get': 'connect_status', 'post': 'connect', 'delete': 'disconnect'
}]
})
def list(self, gid):
@@ -89,20 +191,32 @@ class ServerNode(NodeView):
servers = Server.query.filter_by(user_id=current_user.id,
servergroup_id=gid)
from pgadmin.utils.driver import get_driver
driver = get_driver(PG_DEFAULT_DRIVER)
for server in servers:
manager = driver.connection_manager(server.id)
conn = manager.connection()
module = getattr(manager, "module", None)
connected = conn.connected()
res.append(
generate_browser_node(
"%s" % server.id,
"%s" % gid,
self.blueprint.generate_browser_node(
"%d" % (server.id),
"%d" % gid,
server.name,
"icon-%s-not-connected" % ServerModule.NODE_TYPE,
"icon-server-not-connected" if not connected else
"icon-{0}".format(module.NODE_TYPE),
True,
ServerModule.NODE_TYPE)
)
self.node_type,
connected=connected,
server_type=module.type if module is not None else 'PG'
)
)
return make_json_response(result=res)
def delete(self, gid, sid):
"""Delete a server node in the settings database"""
"""Delete a server node in the settings database."""
servers = Server.query.filter_by(user_id=current_user.id, id=sid)
# TODO:: A server, which is connected, can not be deleted
@@ -130,7 +244,8 @@ class ServerNode(NodeView):
def update(self, gid, sid):
"""Update the server settings"""
server = Server.query.filter_by(user_id=current_user.id, id=sid).first()
server = Server.query.filter_by(
user_id=current_user.id, id=sid).first()
if server is None:
return make_json_response(
@@ -138,9 +253,8 @@ class ServerNode(NodeView):
errormsg=gettext("Couldn't find the given server.")
)
# TODO::
# Not all parameters can be modified, while the server is connected
possible_args = {
# Not all parameters can be modified, while the server is connected
config_param_map = {
'name': 'name',
'host': 'host',
'port': 'port',
@@ -148,21 +262,49 @@ class ServerNode(NodeView):
'username': 'username',
'sslmode': 'sslmode',
'gid': 'servergroup_id',
'comment': 'comment'
'comment': 'comment',
'role': 'role'
}
disp_lbl = {
'name': gettext('name'),
'host': gettext('Host name/address'),
'port': gettext('Port'),
'db': gettext('Maintenance database'),
'username': gettext('Username'),
'sslmode': gettext('SSL Mode'),
'comment': gettext('Comments'),
'role': gettext('Role')
}
idx = 0
data = request.form if request.form else json.loads(request.data)
for arg in possible_args:
from pgadmin.utils.driver import get_driver
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
conn = manager.connection()
not_allowed = {}
if conn.connected():
for arg in {
'host', 'port', 'db', 'username', 'sslmode', 'role'
}:
if arg in data:
return forbidden(
errormsg=gettext(
"'{0}' is not allowed to modify, when server is connected."
).format(disp_lbl[arg])
)
for arg in config_param_map:
if arg in data:
setattr(server, possible_args[arg], data[arg])
setattr(server, config_param_map[arg], data[arg])
idx += 1
if idx == 0:
return make_json_response(
success=0,
errormsg=gettext('No parameters were chagned!')
errormsg=gettext('No parameters were changed!')
)
try:
@@ -173,11 +315,14 @@ class ServerNode(NodeView):
errormsg=e.message
)
manager.update(server)
return make_json_response(
success=1,
data={
'id': server.id,
'gid': server.servergroup_id
'gid': server.servergroup_id,
'icon': 'icon-server-not-connected'
}
)
@@ -198,6 +343,14 @@ class ServerNode(NodeView):
id=server.servergroup_id
).first()
from pgadmin.utils.driver import get_driver
driver = get_driver(PG_DEFAULT_DRIVER)
manager = driver.connection_manager(sid)
conn = manager.connection()
connected = conn.connected()
module = getattr(manager, 'module', None)
return ajax_response(
response={
'id': server.id,
@@ -209,9 +362,10 @@ class ServerNode(NodeView):
'gid': server.servergroup_id,
'group-name': sg.name,
'comment': server.comment,
# TODO:: Make sure - we do have correct values here
'connected': True,
'version': 'PostgreSQL 9.3 (linux-x64)'
'role': server.role,
'connected': connected,
'version': manager.ver,
'server_type': module.type if module is not None else 'PG'
}
)
@@ -223,7 +377,8 @@ class ServerNode(NodeView):
u'port',
u'db',
u'username',
u'sslmode'
u'sslmode',
u'role'
]
data = request.form if request.form else json.loads(request.data)
@@ -247,19 +402,25 @@ class ServerNode(NodeView):
port=data[u'port'],
maintenance_db=data[u'db'],
username=data[u'username'],
ssl_mode=data['sslmode'],
comment=data['comment'] if 'comment' in data else None
ssl_mode=data[u'sslmode'],
comment=data[u'comment'] if u'comment' in data else None,
role=data[u'role'] if u'role' in data else None
)
db.session.add(server)
db.session.commit()
return jsonify(node=generate_browser_node(
'%s' % server.id,
'%s' % gid,
'%s' % server.name,
"icon-{0}-not-connected".format(ServerModule.NODE_TYPE),
True,
ServerModule.NODE_TYPE))
return jsonify(
node=self.blueprint.generate_browser_node(
"%d" % (server.id),
"%d" % gid,
server.name,
"icon-server-not-connected",
True,
self.node_type,
connected=False,
server_type='PG' # Default server type
)
)
except Exception as e:
return make_json_response(
@@ -270,12 +431,27 @@ class ServerNode(NodeView):
def nodes(self, gid, sid):
"""Build a list of treeview nodes from the child nodes."""
from pgadmin.utils.driver import get_driver
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
conn = manager.connection()
if not conn.connected():
return precondition_required(
gettext(
"Please make a connection to the server first!"
)
)
nodes = []
# TODO::
# We can have nodes for the server object, only when
# the server is connected at the moment.
for module in blueprint.submodules:
nodes.extend(module.get_nodes(server=sid))
# We will rely on individual server type modules to generate nodes for
# them selves.
module = getattr(manager, 'module', None)
if module:
nodes.extend(module.get_nodes(sid=sid, manager=manager))
return make_json_response(data=nodes)
def sql(self, gid, sid):
@@ -299,9 +475,180 @@ class ServerNode(NodeView):
Override this property for your own logic.
"""
return make_response(
render_template("servers/servers.js"),
render_template(
"servers/servers.js",
server_types=sorted(
[
m for m in self.blueprint.submodules
if isinstance(m, ServerTypeModule)
],
key=lambda x: x.priority
),
_=gettext
),
200, {'Content-Type': 'application/x-javascript'}
)
def connect_status(self, gid, sid):
"""Check and return the connection status."""
from pgadmin.utils.driver import get_driver
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
conn = manager.get_connection()
return make_json_response(data={'connected': conn.connected()})
def connect(self, gid, sid):
"""
Connect the Server and return the connection object.
Verification Process before Connection:
Verify requested server.
Check the server password is already been stored in the
database or not.
If Yes, connect the server and return connection.
If No, Raise HTTP error and ask for the password.
In case of 'Save Password' request from user, excrypted Pasword
will be stored in the respected server database and
establish the connection OR just connect the server and do not
store the password.
"""
current_app.logger.info(
'Connection Request for server#{0}'.format(sid)
)
# Fetch Server Details
server = Server.query.filter_by(id=sid).first()
if server is None:
return bad_request(gettext("Server Not Found."))
# Fetch User Details.
user = User.query.filter_by(id=current_user.id).first()
if user is None:
return unauthorized(gettext("Unauthorized Request."))
data = request.form if request.form else json.loads(request.data) if \
request.data else {}
password = None
save_password = False
if 'password' not in data:
if server.password is None:
# Return the password template in case password is not
# provided, or password has not been saved earlier.
return make_json_response(
success=0,
status=428,
result=render_template(
'servers/password.html',
server_label=server.name,
username=server.username,
_=gettext
)
)
else:
password = data['password'] if 'password' in data else None
save_password = \
data['save_password'] if password and \
'save_password' in data else False
# Encrypt the password before saving with user's login password key.
try:
password = encrypt(password, user.password) \
if password is not None else server.password
except Exception as e:
return internal_server_error(errormsg=e.message)
# Connect the Server
from pgadmin.utils.driver import get_driver
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
conn = manager.connection()
try:
status, errmsg = conn.connect(
password=password,
modules=[
m for m in self.blueprint.submodules
if isinstance(m, ServerTypeModule)
]
)
except Exception as e:
# TODO::
# Ask the password again (if existing password couldn't be
# descrypted)
return internal_server_error(errormsg=e.message)
if not status:
current_app.logger.error(
"Could not connected to server(#{0}) - '{1}'.\nError: {2}".format(
server.id, server.name, errmsg
)
)
return make_json_response(
success=0,
status=401,
result=render_template(
'servers/password.html',
server_label=server.name,
username=server.username,
errmsg=errmsg,
_=gettext
)
)
else:
if save_password:
try:
# Save the encrypted password using the user's login
# password key.
setattr(server, 'password', password)
db.session.commit()
except Exception as e:
# Release Connection
manager.release(database=server.maintenance_db)
conn = None
return internal_server_error(errormsg=e.message)
current_app.logger.info('Connection Established for server: \
%s - %s' % (server.id, server.name))
return make_json_response(
success=1,
info=gettext("Server Connected."),
data={
'icon': 'icon-{0}'.format(
manager.module.NODE_TYPE
),
'connected': True
}
)
def disconnect(self, gid, sid):
"""Disconnect the Server."""
server = Server.query.filter_by(id=sid).first()
if server is None:
return bad_request(gettext("Server Not Found."))
# Release Connection
from pgadmin.utils.driver import get_driver
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
status = manager.release()
if not status:
return unauthorized(gettext("Server Could Not Disconnect."))
else:
return make_json_response(
success=1,
info=gettext("Server Disconnected."),
data={
'icon': 'icon-server-not-connected',
'connected': False
}
)
ServerNode.register_node_view(blueprint)

View File

@@ -0,0 +1,7 @@
.icon-{{node_type}} {
background-image: url('{{ url_for('NODE-%s.static' % node_type, filename='img/%s.png' % node_type )}}') !important;
background-repeat: no-repeat;
align-content: center;
vertical-align: middle;
height: 1.3em;
}

View File

@@ -0,0 +1,17 @@
.icon-server {
background-image: url('{{ url_for('NODE-server.static', filename='img/server.png') }}') !important;
border-radius: 10px
}
.icon-server-not-connected {
background-image: url('{{ url_for('NODE-server.static', filename='img/serverbad.png') }}') !important;
border-radius: 10px
}
.icon-server-connecting {
background-image: url('{{ url_for('browser.static', filename='css/aciTree/image/load-node.gif')}}') !important;
background-repeat: no-repeat;
align-content: center;
vertical-align: middle;
height: 1.3em;
}

View File

@@ -0,0 +1,19 @@
<form name="frmPassword" id="frmPassword" style="height: 100%; width: 100%" onsubmit="return false;">
<div>{% if errmsg %}
<div class="highlight has-error">
<div class='control-label'>{{ errmsg }}</div>
</div>{% endif %}
<div><b>{{ _('Please enter the password for the user \'{0}\' to connect the server - "{1}"').format(username, server_label) }}</b></div>
<div style="padding: 5px; height: 1px;"></div>
<div style="width: 100%">
<span style="width: 25%;display: inline-table;">Password</span>
<span style="width: 73%;display: inline-block;">
<input style="width:100%" id="password" class="form-control" name="password" type="password">
</span>
<span style="margin-left: 25%; padding-top: 15px;width: 45%;display: inline-block;">
<input id="save_password" name="save_password" type="checkbox">&nbsp;&nbsp;Save Password
</span>
</div>
<div style="padding: 5px; height: 1px;"></div>
</div>
</form>

View File

@@ -1,6 +1,6 @@
define(
['jquery', 'underscore', 'pgadmin', 'pgadmin.browser', 'alertify'],
function($, _, pgAdmin, pgBrowser, alertify) {
['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', 'alertify'],
function($, _, S, pgAdmin, pgBrowser, alertify) {
if (!pgBrowser.Nodes['server']) {
pgAdmin.Browser.Nodes['server'] = pgAdmin.Browser.Node.extend({
@@ -29,76 +29,99 @@ function($, _, pgAdmin, pgBrowser, alertify) {
name: 'drop_server', node: 'server', module: this,
applies: ['object', 'context'], callback: 'delete_obj',
category: 'drop', priority: 3, label: '{{ _('Drop Server...') }}',
icon: 'fa fa-trash'
icon: 'fa fa-trash', enable: 'is_not_connected'
},{
name: 'connect_server', node: 'server', module: this,
applies: ['object', 'context'], callback: 'connect_server',
category: 'connect', priority: 4, label: '{{ _('Connect Server...') }}',
icon: 'fa fa-link', enable : 'is_not_connected'
},
{
name: 'disconnect_server', node: 'server', module: this,
applies: ['object', 'context'], callback: 'disconnect_server',
category: 'drop', priority: 5, label: '{{ _('Disconnect Server...') }}',
icon: 'fa fa-chain-broken', enable : 'is_connected'
}]);
},
is_not_connected: function(node) {
return (node && node.connected != true);
},
is_connected: function(node) {
return (node && node.connected == true);
},
callbacks: {
// Add a server
create_server: function (item) {
var alert = alertify.prompt(
'{{ _('Create a server') }}',
'{{ _('Enter a name for the new server') }}',
'',
function(evt, value) {
var d = tree.itemData(item);
if (d._type != 'server-group') {
d = tree.itemData(tree.parent(item));
}
$.post(
"{{ url_for('browser.index') }}server/obj/" + d.refid + '/' + d.id + '/',
{ name: value }
)
.done(function(data) {
if (data.success == 0) {
report_error(data.errormsg, data.info);
} else {
var item = {
id: data.data.id,
label: data.data.name,
inode: true,
open: false,
icon: 'icon-server-not-connected'
}
tree.append(null, {
itemData: item
});
/* Connect the server */
connect_server: function(args){
var input = args || {};
obj = this,
t = pgBrowser.tree,
i = input.item || t.selected(),
d = i && i.length == 1 ? t.itemData(i) : undefined;
if (!d)
return false;
connect_to_server(obj, d, t, i);
return false;
},
/* Disconnect the server */
disconnect_server: function(args) {
var input = args || {};
obj = this,
t = pgBrowser.tree,
i = input.item || t.selected(),
d = i && i.length == 1 ? t.itemData(i) : undefined;
if (!d)
return false;
alertify.confirm(
'{{ _('Disconnect the server') }}',
S('{{ _('Are you sure you want to disconnect the server - %%s ?') }}').sprintf(d.label).value(),
function(evt) {
$.ajax({
url: obj.generate_url('connect', d, true),
type:'DELETE',
success: function(res) {
if (res.success == 1) {
alertify.success("{{ _('" + res.info + "') }}");
t.removeIcon(i);
d.connected = false;
d.icon = 'icon-server-not-connected';
t.addIcon(i, {icon: d.icon});
t.unload(i);
t.setInode(i);
}
});
},
null
);
alert.show();
},
error: function(xhr, status, error) {
try {
var err = $.parseJSON(xhr.responseText);
if (err.success == 0) {
msg = S('{{ _(' + err.errormsg + ')}}').value();
alertify.error("{{ _('" + err.errormsg + "') }}");
}
} catch (e) {}
t.unload(i);
}
});
},
function(evt) {
return true;
});
return false;
},
/* Connect the server (if not connected), before opening this node */
beforeopen: function(o) {
o.browser.tree.removeIcon(o.item);
if (o.data.connected) {
o.browser.tree.addIcon(o.item, {icon: 'icon-server-connected'});
} else {
o.browser.tree.addIcon(o.item, {icon: 'icon-server-not-connected'});
}
var data = o.data;
if(!data || data._type != 'server') {
return false;
}
o.browser.tree.addIcon(o.item, {icon: data.icon});
if (!data.connected) {
alertify.confirm(
'{{ _('Connect to server') }}',
'{{ _('Do you want to connect the server?') }}',
function(evt) {
$.post(
"{{ url_for('browser.index') }}server/connect/" + data.refid + '/'
).done(function(data) {
if (data.success == 0) {
report_error(data.errormsg, data.info);
}
}).fail(function() {});
return true;
},
function(evt) {
return true;
}
);
connect_to_server(this, data, o.browser.tree, o.item);
return false;
}
return true;
@@ -107,40 +130,61 @@ function($, _, pgAdmin, pgBrowser, alertify) {
model: pgAdmin.Browser.Node.Model.extend({
defaults: {
id: undefined,
name: undefined,
sslmode: 'prefer'
name: null,
sslmode: 'prefer',
host: null,
port: 5432,
db: null,
username: null,
role: null
},
schema: [{
id: 'id', label: 'ID', type: 'int', group: null,
id: 'id', label: '{{ _('ID') }}', type: 'int', group: null,
mode: ['properties']
},{
id: 'name', label:'Name', type: 'text', group: null,
id: 'name', label:'{{ _('Name') }}', type: 'text', group: null,
mode: ['properties', 'edit', 'create']
},{
id: 'connected', label:'Connected', type: 'text', group: null,
id: 'connected', label:'{{ _('Connected') }}', type: 'text', group: null,
mode: ['properties']
},{
id: 'version', label:'Version', type: 'text', group: null,
id: 'version', label:'{{ _('Version') }}', type: 'text', group: null,
mode: ['properties'], show: 'isConnected'
},{
id: 'comment', label:'Comments:', type: 'multiline', group: null,
mode: ['properties', 'edit', 'create'], disable: 'notEditMode'
id: 'comment', label:'{{ _('Comments:') }}', type: 'multiline', group: null,
mode: ['properties', 'edit', 'create'], disabled: 'notEditMode'
},{
id: 'host', label:'Host Name/Address', type: 'text', group: "Connection",
mode: ['properties', 'edit', 'create']
id: 'host', label:'{{ _('Host Name/Address') }}', type: 'text', group: "Connection",
mode: ['properties', 'edit', 'create'], disabled: 'isConnected'
},{
id: 'port', label:'Port', type: 'int', group: "Connection",
mode: ['properties', 'edit', 'create']
id: 'port', label:'{{ _('Port') }}', type: 'int', group: "Connection",
mode: ['properties', 'edit', 'create'], disabled: 'isConnected'
},{
id: 'db', label:'Maintenance Database', type: 'text', group: "Connection",
mode: ['properties', 'edit', 'create']
id: 'db', label:'{{ _('Maintenance Database') }}', type: 'text', group: "Connection",
mode: ['properties', 'edit', 'create'], disabled: 'isConnected'
},{
id: 'username', label:'User Name', type: 'text', group: "Connection",
mode: ['properties', 'edit', 'create']
id: 'username', label:'{{ _('User Name') }}', type: 'text', group: "Connection",
mode: ['properties', 'edit', 'create'], disabled: 'isConnected'
},{
id: 'sslmode', label:'SSL Mode', type: 'options', group: "Connection",
mode: ['properties', 'edit', 'create'],
'options': [{label:'Allow', value:'allow'}, {label: 'Prefer', value:'prefer'}, {label: 'Require', value: 'require'}, {label: 'Disable', value:'disable'}, {label:'Verify-CA', value: 'verify-ca'}, {label:'Verify-Full', value:'verify-full'}]
id: 'role', label:'{{ _('Role') }}', type: 'text', group: "Connection",
mode: ['properties', 'edit', 'create'], disabled: 'isConnected'
},{
id: 'sslmode', label:'{{ _('SSL Mode') }}', type: 'options', group: "Connection",
mode: ['properties', 'edit', 'create'], disabled: 'isConnected',
'options': [
{label: 'Allow', value: 'allow'},
{label: 'Prefer', value: 'prefer'},
{label: 'Require', value: 'require'},
{label: 'Disable', value: 'disable'},
{label: 'Verify-CA', value: 'verify-ca'},
{label: 'Verify-Full', value: 'verify-full'}
]
},{
id: 'server_type', label: '{{ _('Server Type') }}', type: 'options',
mode: ['properties'], show: 'isConnected',
'options': [{% set cnt = 1 %}{% for server_type in server_types %}{% if cnt != 1 %},{% endif %}
{label: '{{ server_type.description }}', value: '{{ server_type.type}}'}{% set cnt = cnt + 1 %}{% endfor %}
]
}],
validate: function(attrs, options) {
if (!this.isNew() && 'id' in this.changed) {
@@ -151,13 +195,145 @@ function($, _, pgAdmin, pgBrowser, alertify) {
}
return null;
},
isConnected: function(mode) {
return mode == 'properties' && this.get('connected');
isConnected: function(model) {
return model.get('connected');
}
})
});
function connect_to_server(obj, data, tree, item) {
var onFailure = function(xhr, status, error, _model, _data, _tree, _item) {
tree.setInode(_item);
tree.addIcon(_item, {icon: 'icon-server-not-connected'});
alertify.pgNotifier('error', xhr, error, function(msg) {
setTimeout(function() {
alertify.dlgServerPass(
'{{ _('Connect to Server') }}',
msg, _model, _data, _tree, _item
).resizeTo();
}, 100);
});
},
onSuccess = function(res, model, data, tree, item) {
tree.deselect(item);
tree.setInode(item);
if (res && res.data) {
if(typeof res.data.connected == 'boolean') {
data.connected = res.data.connected;
}
if (typeof res.data.icon == 'string') {
tree.removeIcon(item);
data.icon = res.data.icon;
tree.addIcon(item, {icon: data.icon});
}
alertify.success(res.info);
setTimeout(function() {tree.select(item);}, 10);
setTimeout(function() {tree.open(item);}, 100);
}
};
// Ask Password and send it back to the connect server
if (!alertify.dlgServerPass) {
alertify.dialog('dlgServerPass', function factory() {
return {
main: function(title, message, model, data, tree, item) {
this.set('title', title);
this.message = message;
this.tree = tree;
this.nodeData = data;
this.nodeItem = item;
this.nodeModel = model;
},
setup:function() {
return {
buttons:[
{
text: "{{ _('OK') }}", key: 13, className: "btn btn-primary"
},
{
text: "{{ _('Cancel') }}", className: "btn btn-danger"
}
],
focus: { element: '#password', select: true },
options: {
modal: 0, resizable: false, maximizable: false, pinnable: false
}
};
},
build:function() {},
prepare:function() {
this.setContent(this.message);
},
callback: function(closeEvent) {
var _sdata = this.nodeData,
_tree = this.tree,
_item = this.nodeItem,
_model = this.nodeModel;
if (closeEvent.button.text == "{{ _('OK') }}") {
var _url = _model.generate_url('connect', _sdata, true);
_tree.setLeaf(_item);
_tree.removeIcon(_item);
_tree.addIcon(_item, {icon: 'icon-server-connecting'});
$.ajax({
type: 'POST',
timeout: 30000,
url: _url,
data: $('#frmPassword').serialize(),
success: function(res) {
return onSuccess(
res, _model, _sdata, _tree, _item
);
},
error: function(xhr, status, error) {
return onFailure(
xhr, status, error, _model, _sdata, _tree, _item
);
}
});
} else {
_tree.setInode(_item);
_tree.removeIcon(_item);
_tree.addIcon(_item, {icon: 'icon-server-not-connected'});
}
}
};
});
}
alertify.confirm(
'{{ _('Connect to server') }}',
'{{ _('Do you want to connect the server?') }}',
function(evt) {
url = obj.generate_url("connect", data, true);
$.post(url)
.done(
function(res) {
if (res.success == 1) {
return onSuccess(res, obj, data, tree, item);
}
})
.fail(
function(xhr, status, error) {
return onFailure(xhr, status, error, obj, data, tree, item);
});
},
notEditMode: function(mode) {
return mode != 'edit';
}})
});
function() {});
}
/* Send PING to indicate that session is alive */
function server_status(server_id)
{
url = "/ping";
$.post(url)
.done(function(data) { return true})
.fail(function(xhr, status, error) { return false})
}
}
return pgBrowser.Nodes['server'];

View File

@@ -29,7 +29,7 @@ function($, _, pgAdmin, Backbone) {
model: pgAdmin.Browser.Node.Model.extend({
defaults: {
id: undefined,
name: undefined
name: null
},
schema: [
{id: 'id', label: 'ID', type: 'int', group: null, mode: ['properties']},