public inbox for [email protected]
help / color / mirror / Atom feedFrom: Pradip Parkale <[email protected]>
To: pgadmin-hackers <[email protected]>
Subject: [pgAdmin][RM4979]: Configuration files for data sources or similar.
Date: Thu, 20 Aug 2020 15:58:37 +0530
Message-ID: <CAJ9T6SvOzSPGyRgVg+8XksV_4=mOSSMoy8RaGKm0O9qFTT_ZQQ@mail.gmail.com> (raw)
Hi Hackers,
Please find the attached patch for the shared server implementation.
Few key points:
1. The admin who is the owner of the server user can share the server
with other users.
2. This option will be available only for admin users.
3. If the user doesn't want to see the shared server then the option to
hide the shared server is available in preferences.
4. The user who is not the owner of the server, can't delete the shared
server and server group.
5. This option is only available in server mode.
--
Thanks & Regards,
Pradip Parkale
Software Engineer | EnterpriseDB Corporation
Attachments:
[application/octet-stream] RM4979.patch (125.3K, 3-RM4979.patch)
download | inline diff:
diff --git a/web/migrations/versions/a091c9611d20_.py b/web/migrations/versions/a091c9611d20_.py
new file mode 100644
index 000000000..d9da2b10f
--- /dev/null
+++ b/web/migrations/versions/a091c9611d20_.py
@@ -0,0 +1,72 @@
+
+"""empty message
+
+Revision ID: a091c9611d20
+Revises: 84700139beb0
+Create Date: 2020-07-14 17:20:22.705737
+
+"""
+from pgadmin.model import db
+
+
+# revision identifiers, used by Alembic.
+revision = 'a091c9611d20'
+down_revision = '84700139beb0'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ db.engine.execute(
+ 'ALTER TABLE server ADD COLUMN shared BOOLEAN'
+ )
+
+ db.engine.execute("""
+ CREATE TABLE sharedserver (
+ id INTEGER NOT NULL,
+ user_id INTEGER NOT NULL,
+ server_owner VARCHAR(64),
+ servergroup_id INTEGER NOT NULL,
+ name VARCHAR(128) NOT NULL,
+ host VARCHAR(128),
+ port INTEGER NOT NULL CHECK(port >= 1 AND port <= 65534),
+ maintenance_db VARCHAR(64),
+ username VARCHAR(64),
+ password VARCHAR(64),
+ role VARCHAR(64),
+ ssl_mode VARCHAR(16) NOT NULL CHECK(ssl_mode IN
+ ( 'allow' , 'prefer' , 'require' , 'disable' ,
+ 'verify-ca' , 'verify-full' )
+ ),
+ comment VARCHAR(1024),
+ discovery_id VARCHAR(128),
+ hostaddr TEXT(1024),
+ db_res TEXT,
+ passfile TEXT,
+ sslcert TEXT,
+ sslkey TEXT,
+ sslrootcert TEXT,
+ sslcrl TEXT,
+ sslcompression INTEGER DEFAULT 0,
+ bgcolor TEXT(10),
+ fgcolor TEXT(10),
+ service TEXT,
+ use_ssh_tunnel INTEGER DEFAULT 0,
+ tunnel_host TEXT,
+ tunnel_port TEXT,
+ tunnel_username TEXT,
+ tunnel_authentication INTEGER DEFAULT 0,
+ tunnel_identity_file TEXT,
+ shared BOOLEAN NOT NULL,
+ save_password BOOLEAN NOT NULL,
+ tunnel_password VARCHAR(64),
+ connect_timeout INTEGER ,
+ PRIMARY KEY(id),
+ FOREIGN KEY(user_id) REFERENCES user(id),
+ FOREIGN KEY(servergroup_id) REFERENCES servergroup(id)
+ );
+ """)
+
+
+def downgrade():
+ pass
diff --git a/web/pgadmin/browser/register_browser_preferences.py b/web/pgadmin/browser/register_browser_preferences.py
index 73cda77e2..122d086cb 100644
--- a/web/pgadmin/browser/register_browser_preferences.py
+++ b/web/pgadmin/browser/register_browser_preferences.py
@@ -8,6 +8,7 @@
##########################################################################
from flask_babelex import gettext
from pgadmin.utils.constants import KEYBOARD_SHORTCUTS
+import config
LOCK_LAYOUT_LEVEL = {
'PREVENT_DOCKING': 'docking',
@@ -22,6 +23,15 @@ def register_browser_preferences(self):
gettext("Show system objects?"), 'boolean', False,
category_label=gettext('Display')
)
+ if config.SERVER_MODE:
+ self.hide_shared_server = self.preference.register(
+ 'display', 'hide_shared_server',
+ gettext("Hide shared server?"), 'boolean', False,
+ category_label=gettext('Display'),
+ help_str=gettext(
+ 'If set to true, then all shared server will be hidden'
+ )
+ )
self.preference.register(
'display', 'enable_acitree_animation',
diff --git a/web/pgadmin/browser/server_groups/__init__.py b/web/pgadmin/browser/server_groups/__init__.py
index 3c4ec6831..bf5be6c84 100644
--- a/web/pgadmin/browser/server_groups/__init__.py
+++ b/web/pgadmin/browser/server_groups/__init__.py
@@ -13,7 +13,7 @@ import simplejson as json
from abc import ABCMeta, abstractmethod
import six
-from flask import request, jsonify
+from flask import request, jsonify, render_template
from flask_babelex import gettext
from flask_security import current_user, login_required
from pgadmin.browser import BrowserPluginModule
@@ -22,7 +22,27 @@ from pgadmin.utils.ajax import make_json_response, gone, \
make_response as ajax_response, bad_request
from pgadmin.utils.menu import MenuItem
from sqlalchemy import exc
-from pgadmin.model import db, ServerGroup
+from pgadmin.model import db, ServerGroup, Server
+import config
+from pgadmin.utils.preferences import Preferences
+
+
+def get_icon_css_class(group_id, group_user_id,
+ default_val='icon-server_group'):
+ """
+ Returns css value
+ :param group_id:
+ :param group_user_id:
+ :param default_val:
+ :return: default_val
+ """
+ if (config.SERVER_MODE and
+ group_user_id != current_user.id and
+ ServerGroupModule.has_shared_server(group_id)):
+ default_val = 'icon-server_group_shared'
+
+ return default_val
+
SG_NOT_FOUND_ERROR = 'The specified server group could not be found.'
@@ -31,19 +51,63 @@ class ServerGroupModule(BrowserPluginModule):
_NODE_TYPE = "server_group"
node_icon = "icon-%s" % _NODE_TYPE
+ @property
+ def csssnippets(self):
+ """
+ Returns a snippet of css to include in the page
+ """
+ snippets = [render_template("css/server_group.css")]
+
+ for submodule in self.submodules:
+ snippets.extend(submodule.csssnippets)
+
+ return snippets
+
+ @staticmethod
+ def has_shared_server(gid):
+ """
+ To check whether given server group contains shared server or not
+ :param gid:
+ :return: True if servergroup contains shared server else false
+ """
+ servers = Server.query.filter_by(servergroup_id=gid)
+ for s in servers:
+ if s.shared:
+ return True
+ return False
+
def get_nodes(self, *arg, **kwargs):
"""Return a JSON document listing the server groups for the user"""
- groups = ServerGroup.query.filter_by(
- user_id=current_user.id
- ).order_by("id")
+
+ hide_shared_server = None
+ if config.SERVER_MODE:
+ pref = Preferences.module('browser')
+ hide_shared_server = pref.preference('hide_shared_server').get()
+
+ if config.SERVER_MODE:
+ server_groups = ServerGroup.query.all()
+ groups = []
+ for group in server_groups:
+ if hide_shared_server and self.has_shared_server(
+ group.id) and group.user_id != current_user.id:
+ continue
+ if group.user_id == current_user.id or \
+ self.has_shared_server(group.id):
+ groups.append(group)
+ else:
+ groups = ServerGroup.query.filter_by(
+ user_id=current_user.id
+ ).order_by("id")
+
for idx, group in enumerate(groups):
yield self.generate_browser_node(
"%d" % (group.id), None,
group.name,
- self.node_icon,
+ get_icon_css_class(group.id, group.user_id),
True,
self.node_type,
- can_delete=True if idx > 0 else False
+ can_delete=True if idx > 0 else False,
+ user_id=group.user_id
)
@property
@@ -196,7 +260,7 @@ class ServerGroupView(NodeView):
gid,
None,
servergroup.name,
- self.node_icon,
+ get_icon_css_class(gid, servergroup.user_id),
True,
self.node_type,
can_delete=True # This is user created hence can deleted
@@ -207,10 +271,7 @@ class ServerGroupView(NodeView):
def properties(self, gid):
"""Update the server-group properties"""
- # There can be only one record at most
- sg = ServerGroup.query.filter_by(
- user_id=current_user.id,
- id=gid).first()
+ sg = ServerGroup.query.filter(ServerGroup.id == gid).first()
if sg is None:
return make_json_response(
@@ -220,7 +281,7 @@ class ServerGroupView(NodeView):
)
else:
return ajax_response(
- response={'id': sg.id, 'name': sg.name},
+ response={'id': sg.id, 'name': sg.name, 'user_id': sg.user_id},
status=200
)
@@ -246,7 +307,7 @@ class ServerGroupView(NodeView):
"%d" % sg.id,
None,
sg.name,
- self.node_icon,
+ get_icon_css_class(sg.id, sg.user_id),
True,
self.node_type,
# This is user created hence can deleted
@@ -306,14 +367,14 @@ class ServerGroupView(NodeView):
"%d" % group.id,
None,
group.name,
- self.node_icon,
+ get_icon_css_class(group.id, group.user_id),
True,
self.node_type
)
)
else:
- group = ServerGroup.query.filter_by(user_id=current_user.id,
- id=gid).first()
+ group = ServerGroup.query.filter(ServerGroup.id == gid).first()
+
if not group:
return gone(
errormsg=gettext("Could not find the server group.")
@@ -322,7 +383,7 @@ class ServerGroupView(NodeView):
nodes = self.blueprint.generate_browser_node(
"%d" % (group.id), None,
group.name,
- self.node_icon,
+ get_icon_css_class(group.id, group.user_id),
True,
self.node_type
)
diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py
index 1a295f515..f9c5cd4d4 100644
--- a/web/pgadmin/browser/server_groups/servers/__init__.py
+++ b/web/pgadmin/browser/server_groups/servers/__init__.py
@@ -23,7 +23,7 @@ from pgadmin.tools.sqleditor.utils.query_history import QueryHistory
import config
from config import PG_DEFAULT_DRIVER
-from pgadmin.model import db, Server, ServerGroup, User
+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
@@ -31,6 +31,8 @@ from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
from psycopg2 import Error as psycopg2_Error, OperationalError
from pgadmin.browser.server_groups.servers.utils import is_valid_ipaddress
from pgadmin.utils.constants import UNAUTH_REQ
+from sqlalchemy import or_
+from pgadmin.utils.preferences import Preferences
def has_any(data, keys):
@@ -91,6 +93,10 @@ def server_icon_and_background(is_connected, manager, server):
return 'icon-{0}{1}'.format(
manager.server_type, server_background_color
)
+ elif server.shared and config.SERVER_MODE:
+ return 'icon-shared-server-not-connected{0}'.format(
+ server_background_color
+ )
else:
return 'icon-server-not-connected{0}'.format(
server_background_color
@@ -113,15 +119,55 @@ class ServerModule(sg.ServerGroupPluginModule):
"""
return sg.ServerGroupModule.node_type
+ @staticmethod
+ def get_shared_server_properties(server, sharedserver):
+ """
+ Return shared server properties
+ :param server:
+ :param sharedserver:
+ :return:
+ """
+
+ server.bgcolor = sharedserver.bgcolor
+ server.fgcolor = sharedserver.fgcolor
+ server.name = sharedserver.name
+ server.role = sharedserver.role
+ server.tunnel_username = sharedserver.tunnel_username
+ server.tunnel_password = sharedserver.tunnel_password
+ server.save_password = sharedserver.save_password
+ server.passfile = sharedserver.passfile
+ server.servergroup_id = sharedserver.servergroup_id
+ server.sslcert = sharedserver.sslcert
+ server.username = sharedserver.username
+ server.server_owner = sharedserver.server_owner
+
+ return server
+
@login_required
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=gid)
+
+ hide_shared_server = None
+ if config.SERVER_MODE:
+ pref = Preferences.module('browser')
+ hide_shared_server = pref.preference('hide_shared_server').get()
+
+ servers = Server.query.filter(
+ or_(Server.user_id == current_user.id, Server.shared),
+ Server.servergroup_id == gid)
driver = get_driver(PG_DEFAULT_DRIVER)
for server in servers:
+ if server.shared and server.user_id != current_user.id:
+ # Don't include shared server if hide shared server is
+ # set to true
+ if hide_shared_server:
+ continue
+ shared_server = self.get_shared_server(server, gid)
+ server = self.get_shared_server_properties(server,
+ shared_server)
+
connected = False
manager = None
errmsg = None
@@ -157,7 +203,10 @@ class ServerModule(sg.ServerGroupPluginModule):
is_tunnel_password_saved=True
if server.tunnel_password is not None else False,
was_connected=was_connected,
- errmsg=errmsg
+ errmsg=errmsg,
+ user_id=server.user_id,
+ user_name=server.username,
+ shared=server.shared
)
@property
@@ -229,6 +278,81 @@ class ServerModule(sg.ServerGroupPluginModule):
def get_exposed_url_endpoints(self):
return ['NODE-server.connect_id']
+ @staticmethod
+ def create_shared_server(data, gid):
+ """
+ Create shared server
+ :param data:
+ :param gid:
+ :return: None
+ """
+
+ shared_server = None
+ try:
+ user = User.query.filter_by(id=data.user_id).first()
+ shared_server = SharedServer(
+ user_id=current_user.id,
+ server_owner=user.username,
+ servergroup_id=gid,
+ name=data.name,
+ host=data.host,
+ hostaddr=data.hostaddr,
+ port=data.port,
+ maintenance_db=None,
+ username=None,
+ save_password=0,
+ ssl_mode=data.ssl_mode,
+ comment=None,
+ role=data.role,
+ # db_res=','.join(data[u'db_res'])
+ # if u'db_res' in data else None,
+ sslcert=None,
+ sslkey=None,
+ sslrootcert=None,
+ sslcrl=None,
+ bgcolor=data.bgcolor if data.bgcolor else None,
+ fgcolor=data.fgcolor if data.fgcolor else None,
+ service=data.service if data.service else None,
+ connect_timeout=0,
+ use_ssh_tunnel=0,
+ tunnel_host=None,
+ tunnel_port=22,
+ tunnel_username=None,
+ tunnel_authentication=0,
+ tunnel_identity_file=None,
+ shared=data.shared if data.shared else None
+ )
+ db.session.add(shared_server)
+ db.session.commit()
+ except Exception as e:
+ if shared_server:
+ db.session.delete(shared_server)
+ db.session.commit()
+
+ current_app.logger.exception(e)
+ return internal_server_error(errormsg=str(e))
+
+ @staticmethod
+ def get_shared_server(server, gid):
+ """
+ return the shared server
+ :param server:
+ :param gid:
+ :return: shared_server
+ """
+ shared_server = SharedServer.query.filter_by(
+ name=server.name, user_id=current_user.id,
+ servergroup_id=gid).first()
+
+ if shared_server is None:
+ ServerModule.create_shared_server(server, gid)
+
+ shared_server = SharedServer.query.filter_by(
+ name=server.name, user_id=current_user.id,
+ servergroup_id=gid).first()
+
+ return shared_server
+
class ServerMenuItem(MenuItem):
def __init__(self, **kwargs):
@@ -326,12 +450,19 @@ class ServerNode(PGChildNodeView):
Return a JSON document listing the servers under this server group
for the user.
"""
- servers = Server.query.filter_by(user_id=current_user.id,
- servergroup_id=gid)
+ servers = Server.query.filter(
+ or_(Server.user_id == current_user.id,
+ Server.shared),
+ Server.servergroup_id == gid)
driver = get_driver(PG_DEFAULT_DRIVER)
for server in servers:
+ if server.shared and server.user_id != current_user.id:
+ shared_server = ServerModule.get_shared_server(server, gid)
+ server = \
+ ServerModule.get_shared_server_properties(server,
+ shared_server)
manager = driver.connection_manager(server.id)
conn = manager.connection()
connected = conn.connected()
@@ -364,7 +495,9 @@ class ServerNode(PGChildNodeView):
is_password_saved=bool(server.save_password),
is_tunnel_password_saved=True
if server.tunnel_password is not None else False,
- errmsg=errmsg
+ errmsg=errmsg,
+ user_name=server.username,
+ shared=server.shared
)
)
@@ -378,9 +511,7 @@ class ServerNode(PGChildNodeView):
@login_required
def node(self, gid, sid):
"""Return a JSON document listing the server groups for the user"""
- server = Server.query.filter_by(user_id=current_user.id,
- servergroup_id=gid,
- id=sid).first()
+ server = Server.query.filter_by(id=sid).first()
if server is None:
return make_json_response(
@@ -425,14 +556,36 @@ class ServerNode(PGChildNodeView):
is_password_saved=bool(server.save_password),
is_tunnel_password_saved=True
if server.tunnel_password is not None else False,
- errmsg=errmsg
+ errmsg=errmsg,
+ shared=server.shared
),
)
+ def delete_shared_server(self, server_name, gid):
+ """
+ Delete the shared server
+ :param server_name:
+ :return:
+ """
+ try:
+ shared_server = SharedServer.query.filter_by(name=server_name,
+ servergroup_id=gid)
+ for s in shared_server:
+ get_driver(PG_DEFAULT_DRIVER).delete_manager(s.id)
+ db.session.delete(s)
+ db.session.commit()
+
+ except Exception as e:
+ current_app.logger.exception(e)
+ return make_json_response(
+ success=0,
+ errormsg=e.message)
+
@login_required
def delete(self, gid, sid):
"""Delete a server node in the settings database."""
servers = Server.query.filter_by(user_id=current_user.id, id=sid)
+ server_name = None
# TODO:: A server, which is connected, cannot be deleted
if servers is None:
@@ -448,10 +601,11 @@ class ServerNode(PGChildNodeView):
else:
try:
for s in servers:
+ server_name = s.name
get_driver(PG_DEFAULT_DRIVER).delete_manager(s.id)
db.session.delete(s)
db.session.commit()
-
+ self.delete_shared_server(server_name, gid)
QueryHistory.clear_history(current_user.id, sid)
except Exception as e:
@@ -466,8 +620,8 @@ class ServerNode(PGChildNodeView):
@login_required
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(id=sid).first()
+ sharedserver = None
if server is None:
return make_json_response(
@@ -476,6 +630,9 @@ class ServerNode(PGChildNodeView):
errormsg=gettext("Could not find the required server.")
)
+ if config.SERVER_MODE and server.shared:
+ sharedserver = ServerModule.get_shared_server(server, gid)
+
# Not all parameters can be modified, while the server is connected
config_param_map = {
'name': 'name',
@@ -505,11 +662,12 @@ class ServerNode(PGChildNodeView):
'tunnel_username': 'tunnel_username',
'tunnel_authentication': 'tunnel_authentication',
'tunnel_identity_file': 'tunnel_identity_file',
+ 'shared': 'shared'
}
disp_lbl = {
'name': gettext('name'),
- 'host': gettext('Host name/address'),
+ 'hostaddr': gettext('Host name/address'),
'port': gettext('Port'),
'db': gettext('Maintenance database'),
'username': gettext('Username'),
@@ -541,6 +699,24 @@ class ServerNode(PGChildNodeView):
connected, data, disp_lbl)
idx = self._set_valid_attr_value(data, config_param_map, server)
+ for arg in config_param_map:
+ if arg in data:
+ value = data[arg]
+ # sqlite3 do not have boolean type so we need to convert
+ # it manually to integer
+ if 'shared' in data:
+ # Delete the shared server from DB if server
+ # owner uncheck shared property
+ if not data['shared']:
+ self.delete_shared_server(server.name, gid)
+ if arg == 'sslcompression':
+ value = 1 if value else 0
+
+ if server.shared and server.user_id != current_user.id:
+ setattr(sharedserver, config_param_map[arg], value)
+ else:
+ setattr(server, config_param_map[arg], value)
+ idx += 1
if idx == 0:
return make_json_response(
@@ -612,11 +788,11 @@ class ServerNode(PGChildNodeView):
"""
Return list of attributes of all servers.
"""
- servers = Server.query.filter_by(
- user_id=current_user.id,
- servergroup_id=gid).order_by(Server.name)
+ servers = Server.query.filter(
+ or_(Server.user_id == current_user.id,
+ Server.shared),
+ Server.servergroup_id == gid).order_by(Server.name)
sg = ServerGroup.query.filter_by(
- user_id=current_user.id,
id=gid
).first()
res = []
@@ -653,7 +829,6 @@ class ServerNode(PGChildNodeView):
def properties(self, gid, sid):
"""Return list of attributes of a server"""
server = Server.query.filter_by(
- user_id=current_user.id,
id=sid).first()
if server is None:
@@ -662,9 +837,7 @@ class ServerNode(PGChildNodeView):
success=0,
errormsg=self.not_found_error_msg
)
-
sg = ServerGroup.query.filter_by(
- user_id=current_user.id,
id=server.servergroup_id
).first()
@@ -676,50 +849,61 @@ class ServerNode(PGChildNodeView):
is_ssl = True if server.ssl_mode in self.SSL_MODES else False
- return ajax_response(
- response={
- 'id': server.id,
- 'name': server.name,
- 'host': server.host,
- 'hostaddr': server.hostaddr,
- 'port': server.port,
- 'db': server.maintenance_db,
- 'username': server.username,
- 'gid': str(server.servergroup_id),
- 'group-name': sg.name,
- 'comment': server.comment,
- 'role': server.role,
- 'connected': connected,
- 'version': manager.ver,
- 'sslmode': server.ssl_mode,
- 'server_type': manager.server_type if connected else 'pg',
- 'bgcolor': server.bgcolor,
- 'fgcolor': server.fgcolor,
- 'db_res': server.db_res.split(',') if server.db_res else None,
- 'passfile': server.passfile if server.passfile else None,
- 'sslcert': server.sslcert if is_ssl else None,
- 'sslkey': server.sslkey if is_ssl else None,
- 'sslrootcert': server.sslrootcert if is_ssl else None,
- 'sslcrl': server.sslcrl if is_ssl else None,
- 'sslcompression': True if is_ssl and server.sslcompression
- else False,
- 'service': server.service if server.service else None,
- 'connect_timeout':
- server.connect_timeout if server.connect_timeout else 0,
- 'use_ssh_tunnel': server.use_ssh_tunnel
- if server.use_ssh_tunnel else 0,
- 'tunnel_host': server.tunnel_host if server.tunnel_host
- else None,
- 'tunnel_port': server.tunnel_port if server.tunnel_port
- else 22,
- 'tunnel_username': server.tunnel_username
- if server.tunnel_username else None,
- 'tunnel_identity_file': server.tunnel_identity_file
- if server.tunnel_identity_file else None,
- 'tunnel_authentication': server.tunnel_authentication
- if server.tunnel_authentication else 0
- }
- )
+ # if server.shared and not current_user.has_role("Administrator"):
+ if server.shared and server.user_id != current_user.id:
+ shared_server = ServerModule.get_shared_server(server, gid)
+ server = ServerModule.get_shared_server_properties(server,
+ shared_server)
+
+ response = {
+ 'id': server.id,
+ 'name': server.name,
+ 'server_owner': server.server_owner if (
+ server.shared and not current_user.has_role(
+ "Administrator")) else None,
+ 'user_id': server.user_id,
+ 'host': server.host,
+ 'hostaddr': server.hostaddr,
+ 'port': server.port,
+ 'db': server.maintenance_db,
+ 'shared': server.shared if config.SERVER_MODE else None,
+ 'username': server.username,
+ 'gid': str(server.servergroup_id),
+ 'group-name': sg.name,
+ 'comment': server.comment,
+ 'role': server.role,
+ 'connected': connected,
+ 'version': manager.ver,
+ 'sslmode': server.ssl_mode,
+ 'server_type': manager.server_type if connected else 'pg',
+ 'bgcolor': server.bgcolor,
+ 'fgcolor': server.fgcolor,
+ 'db_res': server.db_res.split(',') if server.db_res else None,
+ 'passfile': server.passfile if server.passfile else None,
+ 'sslcert': server.sslcert if is_ssl else None,
+ 'sslkey': server.sslkey if is_ssl else None,
+ 'sslrootcert': server.sslrootcert if is_ssl else None,
+ 'sslcrl': server.sslcrl if is_ssl else None,
+ 'sslcompression': True if is_ssl and server.sslcompression
+ else False,
+ 'service': server.service if server.service else None,
+ 'connect_timeout':
+ server.connect_timeout if server.connect_timeout else 0,
+ 'use_ssh_tunnel': server.use_ssh_tunnel
+ if server.use_ssh_tunnel else 0,
+ 'tunnel_host': server.tunnel_host if server.tunnel_host
+ else None,
+ 'tunnel_port': server.tunnel_port if server.tunnel_port
+ else 22,
+ 'tunnel_username': server.tunnel_username
+ if server.tunnel_username else None,
+ 'tunnel_identity_file': server.tunnel_identity_file
+ if server.tunnel_identity_file else None,
+ 'tunnel_authentication': server.tunnel_authentication
+ if server.tunnel_authentication else 0
+ }
+
+ return ajax_response(response)
@login_required
def create(self, gid):
@@ -801,11 +985,11 @@ class ServerNode(PGChildNodeView):
tunnel_port=data.get('tunnel_port', 22),
tunnel_username=data.get('tunnel_username', None),
tunnel_authentication=data.get('tunnel_authentication', 0),
- tunnel_identity_file=data.get('tunnel_identity_file', None)
+ tunnel_identity_file=data.get('tunnel_identity_file', None),
+ shared=data.get('shared', None)
)
db.session.add(server)
db.session.commit()
-
connected = False
user = None
manager = None
@@ -1005,6 +1189,11 @@ class ServerNode(PGChildNodeView):
# Fetch Server Details
server = Server.query.filter_by(id=sid).first()
+ shared_server = None
+ if server.shared and server.user_id != current_user.id:
+ shared_server = ServerModule.get_shared_server(server, gid)
+ server = ServerModule.get_shared_server_properties(server,
+ shared_server)
if server is None:
return bad_request(self.not_found_error_msg)
@@ -1062,7 +1251,6 @@ class ServerNode(PGChildNodeView):
except Exception as e:
current_app.logger.exception(e)
return internal_server_error(errormsg=str(e))
-
if 'password' not in data:
conn_passwd = getattr(conn, 'password', None)
if conn_passwd is None and not server.save_password and \
@@ -1124,10 +1312,18 @@ class ServerNode(PGChildNodeView):
# every time user try to connect
# 1 is True in SQLite as no boolean type
setattr(server, 'save_password', 1)
+ if server.shared and server.user_id != current_user.id:
+ setattr(shared_server, 'save_password', 1)
+ else:
+ setattr(server, 'save_password', 1)
+
# Save the encrypted password using the user's login
# password key, if there is any password to save
if password:
- setattr(server, 'password', password)
+ if server.shared and server.user_id != current_user.id:
+ setattr(shared_server, 'password', password)
+ else:
+ setattr(server, 'password', password)
db.session.commit()
except Exception as e:
# Release Connection
@@ -1561,21 +1757,39 @@ class ServerNode(PGChildNodeView):
:return:
"""
try:
- server = Server.query.filter_by(
- user_id=current_user.id, id=sid
- ).first()
-
+ server = Server.query.filter_by(id=sid).first()
+ shared_server = None
if server is None:
return make_json_response(
success=0,
info=self.not_found_error_msg
)
- setattr(server, 'password', None)
+ if server.shared and server.user_id != current_user.id:
+ shared_server = SharedServer.query.filter_by(
+ name=server.name, user_id=current_user.id,
+ servergroup_id=gid).first()
+
+ if shared_server is None:
+ return make_json_response(
+ success=0,
+ info=gettext("Could not find the required server.")
+ )
+ server = ServerModule. \
+ get_shared_server_properties(server, shared_server)
+
+ if server.shared and server.user_id != current_user.id:
+ setattr(shared_server, 'save_password', None)
+ else:
+ setattr(server, 'save_password', None)
+
# If password was saved then clear the flag also
# 0 is False in SQLite db
if server.save_password:
- setattr(server, 'save_password', 0)
+ if server.shared and server.user_id != current_user.id:
+ setattr(shared_server, 'save_password', 0)
+ else:
+ setattr(server, 'save_password', 0)
db.session.commit()
except Exception as e:
current_app.logger.error(
diff --git a/web/pgadmin/browser/server_groups/servers/static/img/sharedserverbad.svg b/web/pgadmin/browser/server_groups/servers/static/img/sharedserverbad.svg
new file mode 100644
index 000000000..4455089ac
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/static/img/sharedserverbad.svg
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.2.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#F2F2F2;}
+ .st1{fill:#9BA5B0;}
+ .st2{fill:none;stroke:#9BA5B0;stroke-width:0.75;stroke-miterlimit:1;}
+ .st3{fill:#F7F7F7;}
+ .st4{fill:#354A5F;}
+ .st5{fill:none;stroke:#D0021B;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;}
+</style>
+<g>
+ <path class="st0" d="M6.7,2.1h4.6c0.6,0,1.1,0.5,1.1,1.1v0.8c0,0.6-0.5,1.1-1.1,1.1H6.7c-0.6,0-1.1-0.5-1.1-1.1V3.2
+ C5.6,2.6,6.1,2.1,6.7,2.1z"/>
+ <path class="st1" d="M11.3,2.5c0.4,0,0.8,0.3,0.8,0.8v0.8c0,0.4-0.3,0.8-0.8,0.8H6.7C6.3,4.8,6,4.5,6,4.1V3.2
+ c0-0.4,0.3-0.7,0.8-0.7H11.3 M11.3,1.7H6.7c-0.8,0-1.5,0.7-1.5,1.5v0.8c0,0.8,0.7,1.5,1.5,1.5h4.6c0.8,0,1.5-0.7,1.5-1.5V3.2
+ C12.8,2.4,12.1,1.7,11.3,1.7L11.3,1.7z"/>
+ <line class="st2" x1="8.9" y1="3.6" x2="6.8" y2="3.6"/>
+ <path class="st0" d="M6.7,5.2h4.6c0.6,0,1.1,0.5,1.1,1.1v0.8c0,0.6-0.5,1.1-1.1,1.1H6.7c-0.6,0-1.1-0.5-1.1-1.1V6.3
+ C5.6,5.7,6.1,5.2,6.7,5.2z"/>
+ <path class="st1" d="M11.3,5.6c0.4,0,0.8,0.3,0.8,0.8v0.8c0,0.4-0.3,0.8-0.8,0.8H6.7C6.3,7.9,6,7.6,6,7.1V6.3
+ c0-0.4,0.3-0.7,0.8-0.7H11.3 M11.3,4.8H6.7c-0.8,0-1.5,0.7-1.5,1.5v0.8c0,0.8,0.7,1.5,1.5,1.5h4.6c0.8,0,1.5-0.7,1.5-1.5V6.3
+ C12.8,5.5,12.1,4.8,11.3,4.8L11.3,4.8z"/>
+ <line class="st2" x1="8.9" y1="6.7" x2="6.8" y2="6.7"/>
+ <path class="st0" d="M6.7,8.3h4.6c0.6,0,1.1,0.5,1.1,1.1v0.9c0,0.6-0.5,1.1-1.1,1.1c0,0,0,0,0,0H6.7c-0.6,0-1.1-0.5-1.1-1.1l0,0
+ V9.4C5.6,8.8,6.1,8.3,6.7,8.3C6.7,8.3,6.7,8.3,6.7,8.3z"/>
+ <path class="st1" d="M11.3,8.7c0.4,0,0.8,0.3,0.8,0.8v0.8c0,0.4-0.3,0.8-0.8,0.8H6.7C6.3,11,6,10.7,6,10.3V9.4C6,9,6.3,8.7,6.7,8.7
+ H11.3 M11.3,7.9H6.7c-0.8,0-1.5,0.7-1.5,1.5v0.8c0,0.8,0.7,1.5,1.5,1.5h4.6c0.8,0,1.5-0.7,1.5-1.5V9.4C12.8,8.6,12.1,7.9,11.3,7.9z
+ "/>
+ <line class="st2" x1="8.9" y1="9.8" x2="6.8" y2="9.8"/>
+</g>
+<path class="st3" d="M11.6,10.5c-0.6,0.3-0.8,0.1-1.1-0.2c-0.1-0.2-0.3-0.4-0.6-0.5L7.1,8.5C6.8,8.4,6.4,8.3,6.1,8.3
+ C5.1,8.1,4.2,8.4,3.4,9l-1.4,1.1v2.7h0.9v-0.2l3.7,1.8c0.4,0.2,0.7,0.3,1.1,0.3c0.3,0,0.7-0.1,1-0.2l6.1-2.6
+ c0.3-0.1,0.6-0.4,0.7-0.7c0.1-0.3,0.1-0.7,0-1c-0.3-0.6-1.1-0.9-1.7-0.6l-0.4,0.2L11.6,10.5"/>
+<path class="st4" d="M15.5,10.2c-0.3-0.6-1.1-0.9-1.7-0.6l-0.4,0.2l-1.8,0.8l0.3,0.8l1.8-0.8l0.4-0.2c0.2-0.1,0.5,0,0.6,0.2
+ c0.1,0.1,0.1,0.2,0,0.3c0,0.1-0.1,0.2-0.2,0.2l-6.1,2.6c-0.5,0.2-1,0.2-1.4,0l-4.1-2v-1.2l1-0.8C4.5,9.2,5.2,9,6,9.1
+ c0.3,0,0.6,0.1,0.8,0.2l2.8,1.2c0.1,0,0.1,0.1,0.2,0.2c0.1,0.1,0.1,0.3,0,0.4c0,0,0,0.1-0.1,0.1c-0.1,0.1-0.3,0.2-0.5,0.1l-2.2-1
+ l-0.4,0.8l2.2,1c0.2,0.1,0.4,0.1,0.5,0.1c0.4,0,0.7-0.2,1-0.4c0.1-0.1,0.2-0.2,0.2-0.3c0.2-0.4,0.1-0.9-0.1-1.2
+ c-0.1-0.2-0.3-0.4-0.6-0.5L7.1,8.5C6.8,8.4,6.4,8.3,6.1,8.3C5.1,8.1,4.2,8.4,3.4,9L2.9,9.4V9.3c0-0.6-0.4-1-1-1H1.4
+ c-0.6,0-1,0.4-1,1v4.1c0,0.6,0.4,1,1,1h0.5c0.6,0,1-0.4,1-1v-0.6h0v-0.2l3.7,1.8c0.4,0.2,0.7,0.3,1.1,0.3c0.3,0,0.7-0.1,1-0.2
+ l6.1-2.6c0.3-0.1,0.6-0.4,0.7-0.7C15.7,10.9,15.7,10.5,15.5,10.2z M1.9,13.4H1.4V9.3h0.5L1.9,13.4z"/>
+<line class="st5" x1="13.8" y1="1.3" x2="10.8" y2="4.3"/>
+<line class="st5" x1="13.8" y1="4.3" x2="10.8" y2="1.3"/>
+</svg>
diff --git a/web/pgadmin/browser/server_groups/servers/static/js/server.js b/web/pgadmin/browser/server_groups/servers/static/js/server.js
index 7c1b58be8..a5ef532c6 100644
--- a/web/pgadmin/browser/server_groups/servers/static/js/server.js
+++ b/web/pgadmin/browser/server_groups/servers/static/js/server.js
@@ -56,7 +56,12 @@ define('pgadmin.node.server', [
type: 'server',
dialogHelp: url_for('help.static', {'filename': 'server_dialog.html'}),
label: gettext('Server'),
- canDrop: true,
+ canDrop: function(node){
+ var serverOwner = node.user_id;
+ if (serverOwner != current_user.id)
+ return false;
+ return true;
+ },
dropAsRemove: true,
dropPriority: 5,
hasStatistics: true,
@@ -75,12 +80,12 @@ define('pgadmin.node.server', [
name: 'create_server_on_sg', node: 'server_group', module: this,
applies: ['object', 'context'], callback: 'show_obj_properties',
category: 'create', priority: 1, label: gettext('Server...'),
- data: {action: 'create'}, icon: 'wcTabIcon icon-server',
+ data: {action: 'create'}, icon: 'wcTabIcon icon-server', enable: 'canCreate',
},{
name: 'create_server', node: 'server', module: this,
applies: ['object', 'context'], callback: 'show_obj_properties',
category: 'create', priority: 3, label: gettext('Server...'),
- data: {action: 'create'}, icon: 'wcTabIcon icon-server',
+ data: {action: 'create'}, icon: 'wcTabIcon icon-server', enable: 'canCreate',
},{
name: 'connect_server', node: 'server', module: this,
applies: ['object', 'context'], callback: 'connect_server',
@@ -150,6 +155,13 @@ define('pgadmin.node.server', [
is_not_connected: function(node) {
return (node && node.connected != true);
},
+ canCreate: function(node){
+ var serverOwner = node.user_id;
+ if (serverOwner == current_user.id || _.isUndefined(serverOwner))
+ return true;
+ return false;
+
+ },
is_connected: function(node) {
return (node && node.connected == true);
},
@@ -226,28 +238,25 @@ define('pgadmin.node.server', [
d = t.itemData(i);
t.removeIcon(i);
d.connected = false;
- d.icon = 'icon-server-not-connected';
+ if (d.shared){
+ d.icon = 'icon-shared-server-not-connected';
+ }else{
+ d.icon = 'icon-server-not-connected';
+ }
t.addIcon(i, {icon: d.icon});
obj.callbacks.refresh.apply(obj, [null, i]);
if (pgBrowser.serverInfo && d._id in pgBrowser.serverInfo) {
delete pgBrowser.serverInfo[d._id];
}
- pgBrowser.enable_disable_menus(i);
- // Trigger server disconnect event
- pgBrowser.Events.trigger(
- 'pgadmin:server:disconnect',
- {item: i, data: d}, false
- );
- }
- else {
- try {
- Alertify.error(res.errormsg);
- } catch (e) {
- console.warn(e.stack || e);
+ else {
+ try {
+ Alertify.error(res.errormsg);
+ } catch (e) {
+ console.warn(e.stack || e);
+ }
+ t.unload(i);
}
- t.unload(i);
- }
- })
+ }})
.fail(function(xhr, status, error) {
Alertify.pgRespErrorNotify(xhr, error);
t.unload(i);
@@ -745,12 +754,21 @@ define('pgadmin.node.server', [
id: 'id', label: gettext('ID'), type: 'int', mode: ['properties'],
},{
id: 'name', label: gettext('Name'), type: 'text',
- mode: ['properties', 'edit', 'create'],
- },{
+ mode: ['properties', 'edit', 'create'], disabled: function(model){
+ if (model.attributes.shared)
+ return true;
+ return false;
+ },
+ },
+ {
id: 'gid', label: gettext('Server group'), type: 'int',
control: 'node-list-by-id', node: 'server_group',
- mode: ['create', 'edit'], select2: {allowClear: false},
- },{
+ mode: ['create', 'edit'], select2: {allowClear: false}, visible: 'isVisible',
+ },
+ {
+ id: 'server_owner', label: gettext('Shared Server Owner'), type: 'text', mode: ['properties'],
+ },
+ {
id: 'server_type', label: gettext('Server type'), type: 'options',
mode: ['properties'], visible: 'isConnected',
'options': supported_servers,
@@ -773,6 +791,22 @@ define('pgadmin.node.server', [
id: 'connect_now', controlLabel: gettext('Connect now?'), type: 'checkbox',
group: null, mode: ['create'],
},{
+ id: 'shared', label: gettext('Shared with all?'), type: 'switch',
+ mode: ['properties', 'create', 'edit'], 'options': {'size': 'mini'},
+ readonly: function(model){
+ var serverOwner = model.attributes.user_id;
+ if (!model.isNew() && serverOwner != current_user.id){
+ return true;
+ }
+ return false;
+ },visible: function(){
+ if (current_user.is_admin && pgAdmin.server_mode == 'True')
+ return true;
+
+ return false;
+ },
+ },
+ {
id: 'comment', label: gettext('Comments'), type: 'multiline', group: null,
mode: ['properties', 'edit', 'create'],
},{
@@ -1057,7 +1091,23 @@ define('pgadmin.node.server', [
mode: ['properties', 'edit', 'create'], readonly: 'isConnected',
min: 0,
}],
+ isVisible: function(model){
+ var serverOwner = model.attributes.user_id;
+ if (!model.isNew() && serverOwner != current_user.id){
+ return false;
+ }
+ return true;
+
+ },
validate: function() {
+ var msg;
+ this.errorModel.clear();
+ if (!this.isNew() && this.attributes.shared && this.attributes.user_id != current_user.id &&(_.isNull(this.get('username')))) {
+ msg = gettext('Username is not set,Please set the username.');
+ this.errorModel.set('username', msg);
+ return msg;
+ }
+
const validateModel = new modelValidation.ModelValidation(this);
return validateModel.validate();
},
@@ -1153,7 +1203,18 @@ define('pgadmin.node.server', [
}
},
});
+
var connect_to_server = function(obj, data, tree, item, reconnect) {
+ // Open properties dialog in edit mode
+ const selectedTreeNode = tree.selected().length > 0 ? tree.selected() : tree.first();
+ const selectedTreeNodeData = selectedTreeNode && selectedTreeNode.length === 1 ? tree.itemData(selectedTreeNode) : undefined;
+ if (data.shared && _.isNull(data.user_name) && data.user_id != current_user.id){
+ if (selectedTreeNodeData._type == 'server')
+ pgAdmin.Browser.Node.callbacks.show_obj_properties.call(
+ pgAdmin.Browser.Nodes[tree.itemData(selectedTreeNode)._type], {action: 'edit'}
+ );
+ return;
+ }
var wasConnected = reconnect || data.connected,
onFailure = function(
xhr, status, error, _node, _data, _tree, _item, _wasConnected
@@ -1164,7 +1225,12 @@ define('pgadmin.node.server', [
// Let's not change the status of the tree node now.
if (!_wasConnected) {
tree.setInode(_item);
- tree.addIcon(_item, {icon: 'icon-server-not-connected'});
+ if (data.shared){
+ tree.addIcon(_item, {icon: 'icon-shared-server-not-connected'});
+ }else{
+ tree.addIcon(_item, {icon: 'icon-server-not-connected'});
+ }
+
}
Alertify.pgNotifier('error', xhr, error, function(msg) {
@@ -1312,7 +1378,11 @@ define('pgadmin.node.server', [
_tree.unload(_item);
_tree.setInode(_item);
_tree.removeIcon(_item);
- _tree.addIcon(_item, {icon: 'icon-server-not-connected'});
+ if (_data.shared){
+ _tree.addIcon(_item, {icon: 'icon-shared-server-not-connected'});
+ }else{
+ _tree.addIcon(_item, {icon: 'icon-server-not-connected'});
+ }
obj.trigger('connect:cancelled', data._id, data.db, obj, _item, _data);
pgBrowser.Events.trigger(
'pgadmin:server:connect:cancelled', data._id, _item, _data, obj
@@ -1378,7 +1448,11 @@ define('pgadmin.node.server', [
})
.fail(function(xhr, status, error) {
tree.setInode(item);
- tree.addIcon(item, {icon: 'icon-server-not-connected'});
+ if (data.shared){
+ tree.addIcon(item, {icon: 'icon-shared-server-not-connected'});
+ }else{
+ tree.addIcon(item, {icon: 'icon-server-not-connected'});
+ }
Alertify.pgRespErrorNotify(xhr, error);
});
};
diff --git a/web/pgadmin/browser/server_groups/servers/templates/css/servers.css b/web/pgadmin/browser/server_groups/servers/templates/css/servers.css
index 93c4536d4..a178f4fe5 100644
--- a/web/pgadmin/browser/server_groups/servers/templates/css/servers.css
+++ b/web/pgadmin/browser/server_groups/servers/templates/css/servers.css
@@ -15,3 +15,12 @@
vertical-align: middle;
height: 1.3em;
}
+
+.icon-shared-server-not-connected {
+ background-image: url('{{ url_for('NODE-server.static', filename='img/sharedserverbad.svg') }}') !important;
+ background-repeat: no-repeat;
+ background-size: 20px !important;
+ align-content: center;
+ vertical-align: middle;
+ height: 1.3em;
+}
diff --git a/web/pgadmin/browser/server_groups/servers/tests/servers_test_data.json b/web/pgadmin/browser/server_groups/servers/tests/servers_test_data.json
new file mode 100644
index 000000000..0189b91e5
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/servers_test_data.json
@@ -0,0 +1,865 @@
+{
+ "add_server": [
+ {
+ "name": "Add server with service id",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "owner_server": true,
+ "test_data": {
+ "service": "TestDB"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Test default server url",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "owner_server": true,
+ "test_data": {
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Add server with connect timeout",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "test_data": {
+ "connect_timeout": 5
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Add server using SSH tunnel with password",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "ssh_tunnel": true,
+ "with_password": true,
+ "save_password": false,
+ "test_data": {
+ "use_ssh_tunnel": 1,
+ "tunnel_host": "127.0.0.1",
+ "tunnel_port": 22,
+ "tunnel_username": "user",
+ "tunnel_authentication": 1,
+ "tunnel_identity_file": "pkey_rsa"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Add server using SSH tunnel with identity file",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "ssh_tunnel": true,
+ "with_password": false,
+ "save_password": false,
+ "test_data": {
+ "use_ssh_tunnel": 1,
+ "tunnel_host": "127.0.0.1",
+ "tunnel_port": 22,
+ "tunnel_username": "user",
+ "tunnel_authentication": 1,
+ "tunnel_identity_file": "pkey_rsa"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Add server using SSH tunnel with password and saved it",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "ssh_tunnel": true,
+ "with_password": true,
+ "save_password": true,
+ "test_data": {
+ "use_ssh_tunnel": 1,
+ "tunnel_host": "127.0.0.1",
+ "tunnel_port": 22,
+ "tunnel_username": "user",
+ "tunnel_authentication": 0,
+ "tunnel_password": "123456"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Add server using SSH tunnel with identity file and save the password",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "ssh_tunnel": true,
+ "with_password": false,
+ "save_password": true,
+ "test_data": {
+ "use_ssh_tunnel": 1,
+ "tunnel_host": "127.0.0.1",
+ "tunnel_port": 22,
+ "tunnel_username": "user",
+ "tunnel_authentication": 1,
+ "tunnel_identity_file": "pkey_rsa",
+ "tunnel_password": "123456"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Add server with password and save password to true",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "with_pwd": true,
+ "with_save": true,
+ "owner_server": true,
+ "test_data": {
+ "service": "TestDB"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Add server with password and save password to false",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "with_pwd": true,
+ "with_save": false,
+ "owner_server": true,
+ "test_data": {
+ "service": "TestDB"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Add server without password and save password to true",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "with_pwd": false,
+ "with_save": true,
+ "owner_server": true,
+ "test_data": {
+ "service": "TestDB"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ }
+ ],
+ "add_server_with_connect_now": [
+ {
+ "name": "Add server with connect now",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "owner_server": true,
+ "test_data": {
+ "service": "TestDB"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ }
+ ],
+ "is_password_saved": [
+ {
+ "name": "Connect server with 'save password",
+ "url": "/browser/server/connect/",
+ "is_positive_test": true,
+ "test_data": {
+ "is_password_saved": true
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200,
+ "message": "Server connected."
+ }
+ }
+ ],
+ "get_server": [
+ {
+ "name": "Get a server URL",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Reload a server configuration",
+ "url": "/browser/server/reload/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get a server URL using wrong server id",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "incorrect_server_id": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 410
+ }
+ },
+ {
+ "name": "Get a server Node dependants",
+ "url": "/browser/server/dependent/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get a server Node dependency",
+ "url": "/browser/server/dependency/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get a server Node sql",
+ "url": "/browser/server/sql/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get a server Node msql",
+ "url": "/browser/server/msql/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get a server Node statistics",
+ "url": "/browser/server/stats/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get a server pgpass details",
+ "url": "/browser/server/check_pgpass/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 410
+ }
+ }
+ ],
+ "get_shared_server": [
+ {
+ "name": "Get a shared server",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "shared": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get a all shared server",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "shared": true,
+ "no_server_id": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get the all available shared server",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "no_server_id": true,
+ "shared": true,
+ "server_list": true,
+ "mocking_required": false,
+ "mock_data": {
+ },
+ "expected_data": {
+ "status_code": 200
+ }
+ }
+ ],
+ "get_all_server": [
+ {
+ "name": "Get the all children of server",
+ "url": "/browser/server/children/",
+ "is_positive_test": true,
+ "children": true,
+ "mocking_required": false,
+ "mock_data": {
+ },
+ "expected_data": {
+ "status_code": 500
+ }
+ },
+ {
+ "name": "Get the all available servers",
+ "url": "/browser/server/nodes/",
+ "is_positive_test": true,
+ "invalid_server_group": true,
+ "mocking_required": false,
+ "mock_data": {
+ },
+ "expected_data": {
+ "status_code": 410
+ }
+ },
+ {
+ "name": "Get the all available server of server group",
+ "url": "/browser/server/nodes/",
+ "is_positive_test": true,
+ "server_list": true,
+ "mocking_required": false,
+ "mock_data": {
+ },
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get the all available server of server group",
+ "url": "/browser/server/nodes/",
+ "is_positive_test": true,
+ "server_list": true,
+ "servers": true,
+ "mocking_required": false,
+ "mock_data": {
+ },
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Get the all connected servers",
+ "url": "/browser/server/nodes/",
+ "is_positive_test": true,
+ "server_list": true,
+ "servers": true,
+ "connected": true,
+ "mocking_required": false,
+ "mock_data": {
+ },
+ "expected_data": {
+ "status_code": 200
+ }
+ }
+ ],
+ "connect_server": [
+ {
+ "name": "Get a server connection",
+ "url": "/browser/server/connect/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "connect to a server using password",
+ "url": "/browser/server/connect/",
+ "is_positive_test": true,
+ "connect": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Disconnect server test",
+ "url": "/browser/server/connect/",
+ "is_positive_test": true,
+ "disconnect": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Disconnect server when wrong server id passed",
+ "url": "/browser/server/connect/",
+ "is_positive_test": true,
+ "disconnect": true,
+ "wrong_server_id": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 400
+ }
+ },
+ {
+ "name": "Reload a server configuration",
+ "url": "/browser/server/reload/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Error while creating server restore point",
+ "url": "/browser/server/restore_point/",
+ "is_positive_test": true,
+ "restore_point": true,
+ "test_data": {
+ "Named restore point created": "PLACE_HOLDER"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 500
+ }
+ }
+ ],
+ "delete_server": [
+ {
+ "name": "Delete a server URL",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Disconnect server test",
+ "url": "/browser/server/connect/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Error while fetching a server to delete",
+ "url": "/browser/server/obj/",
+ "is_positive_test": false,
+ "mocking_required": true,
+ "mock_data": {
+ "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict",
+ "return_value": "(True, 'Mocked Internal Server Error')"
+ },
+ "expected_data": {
+ "status_code": 500
+ }
+ },
+ {
+ "name": "server not found while deleting a server",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "invalid_server_id": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 410
+ }
+ }
+ ],
+ "update_server": [
+ {
+ "name": "update a server name",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "sslcompression": 1
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "update a server details without data",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "test_data": {},
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Update server with wrong server id",
+ "url": "/browser/server/obj/",
+ "is_positive_test": false,
+ "clear_save_password": true,
+ "wrong_server_id": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 410
+ }
+ },
+ {
+ "name": "Update server with incorrect hostaddr",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "hostaddr": "PLACE_HOLDER",
+ "db_res": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 400
+ }
+ },
+ {
+ "name": "update a server , make server shared",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "owner_server": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "shared": true
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Clear saved password",
+ "url": "/browser/server/clear_saved_password/",
+ "is_positive_test": true,
+ "clear_save_password": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Clear saved password with wrong server id",
+ "url": "/browser/server/clear_saved_password/",
+ "is_positive_test": false,
+ "clear_save_password": true,
+ "wrong_server_id": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "wal replay",
+ "url": "/browser/server/wal_replay/",
+ "is_positive_test": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 410
+ }
+ },
+ {
+ "name": "Clear ssh tunnel password",
+ "url": "/browser/server/clear_sshtunnel_password/",
+ "is_positive_test": true,
+ "clear_save_password": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Clear ssh tunnel password with wrong server id",
+ "url": "/browser/server/clear_sshtunnel_password/",
+ "is_positive_test": false,
+ "clear_save_password": true,
+ "wrong_server_id": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "error while clearing a ssh password",
+ "url": "/browser/server/clear_sshtunnel_password/",
+ "is_positive_test": false,
+ "error_clearing_password": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ }
+ ],
+ "update_shared_server": [
+ {
+ "name": "update a server name",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "sslcompression": 1
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "update a server details without data",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "test_data": {},
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Update server with wrong server id",
+ "url": "/browser/server/obj/",
+ "is_positive_test": false,
+ "clear_save_password": true,
+ "wrong_server_id": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 410
+ }
+ },
+ {
+ "name": "Update server with incorrect hostaddr",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "hostaddr": "PLACE_HOLDER",
+ "db_res": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER"
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 400
+ }
+ },
+ {
+ "name": "update a server , make server shared",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "owner_server": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "shared": true
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Clear saved password when login user is not owner of server",
+ "url": "/browser/server/clear_saved_password/",
+ "is_positive_test": true,
+ "clear_save_password": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Clear saved password with wrong server id",
+ "url": "/browser/server/clear_saved_password/",
+ "is_positive_test": false,
+ "clear_save_password": true,
+ "wrong_server_id": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Clear ssh tunnel password",
+ "url": "/browser/server/clear_sshtunnel_password/",
+ "is_positive_test": true,
+ "clear_save_password": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "Clear ssh tunnel password with wrong server id",
+ "url": "/browser/server/clear_sshtunnel_password/",
+ "is_positive_test": false,
+ "clear_save_password": true,
+ "wrong_server_id": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ },
+ {
+ "name": "error while clearing a ssh password",
+ "url": "/browser/server/clear_sshtunnel_password/",
+ "is_positive_test": false,
+ "error_clearing_password": true,
+ "test_data": {
+ "comment": "PLACE_HOLDER",
+ "id": "PLACE_HOLDER",
+ "is_password_saved": false
+ },
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ }
+ ],
+ "delete_multiple_server": [
+ {
+ "name": "Delete multiple server",
+ "url": "/browser/server/obj/",
+ "is_positive_test": true,
+ "mocking_required": false,
+ "mock_data": {},
+ "expected_data": {
+ "status_code": 200
+ }
+ }
+ ]
+}
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_add_server.py b/web/pgadmin/browser/server_groups/servers/tests/test_add_server.py
new file mode 100644
index 000000000..8ce52b73f
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_add_server.py
@@ -0,0 +1,81 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+from . import utils as servers_utils
+
+
+class AddServerTest(BaseTestGenerator):
+ """ This class will add the servers under default server group. """
+
+ scenarios = utils.generate_scenarios('add_server',
+ servers_utils.test_cases)
+
+ def setUp(self):
+ pass
+
+ def create_server(self, url):
+ return self.tester.post(
+ url,
+ data=json.dumps(self.server),
+ content_type='html/json'
+ )
+
+ def runTest(self):
+ """ This function will add the server under default server group."""
+ url = "{0}{1}/".format(self.url, utils.SERVER_GROUP)
+
+ # Add service name in the config
+ if 'connect_timeout' in self.test_data:
+ self.server['connect_timeout'] = self.test_data['connect_timeout']
+ elif 'shared' in self.test_data:
+ self.server['shared'] = self.test_data['shared']
+ elif 'service' in self.test_data:
+ self.server['service'] = self.test_data['service']
+
+ if hasattr(self, 'ssh_tunnel'):
+ self.server['use_ssh_tunnel'] = self.test_data['use_ssh_tunnel']
+ self.server['tunnel_host'] = self.test_data['tunnel_host']
+ self.server['tunnel_port'] = self.test_data['tunnel_port']
+ self.server['tunnel_username'] = self.test_data['tunnel_username']
+
+ if self.with_password:
+ self.server['tunnel_authentication'] = self.test_data[
+ 'tunnel_authentication']
+ else:
+ self.server['tunnel_authentication'] = 1
+ self.server['tunnel_identity_file'] = 'pkey_rsa'
+
+ if self.save_password:
+ self.server['tunnel_password'] = self.test_data[
+ 'tunnel_password']
+
+ if self.is_positive_test:
+ if hasattr(self, 'with_save'):
+ self.server['save_password'] = self.with_save
+ if hasattr(self, 'with_pwd') and not self.with_pwd:
+ # Remove the password from server object
+ db_password = self.server['db_password']
+ del self.server['db_password']
+ response = self.create_server(url)
+ self.assertEquals(response.status_code,
+ self.expected_data["status_code"])
+ response_data = json.loads(response.data.decode('utf-8'))
+ self.server_id = response_data['node']['_id']
+
+ if hasattr(self, 'with_pwd') and not self.with_pwd:
+ # Remove the password from server object
+ self.server['db_password'] = db_password
+
+ def tearDown(self):
+ """This function delete the server from SQLite """
+ utils.delete_server_with_api(self.tester, self.server_id)
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_service_id.py b/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_connect_now.py
similarity index 65%
rename from web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_service_id.py
rename to web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_connect_now.py
index 9d8da94fd..b9bfea9d2 100644
--- a/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_service_id.py
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_connect_now.py
@@ -11,19 +11,23 @@ import json
from pgadmin.utils.route import BaseTestGenerator
from regression.python_test_utils import test_utils as utils
+from . import utils as servers_utils
-class ServersWithServiceIDAddTestCase(BaseTestGenerator):
+class ServersWithConnectNowAddTestCase(BaseTestGenerator):
""" This class will add the servers under default server group. """
- scenarios = [
- # Fetch the default url for server object
- (
- 'Default Server Node url', dict(
- url='/browser/server/obj/'
- )
- )
- ]
+ # scenarios = [
+ # # Fetch the default url for server object
+ # (
+ # 'Default Server Node url', dict(
+ # url='/browser/server/obj/'
+ # )
+ # )
+ # ]
+
+ scenarios = utils.generate_scenarios('add_server_with_connect_now',
+ servers_utils.test_cases)
def setUp(self):
pass
@@ -32,7 +36,10 @@ class ServersWithServiceIDAddTestCase(BaseTestGenerator):
""" This function will add the server under default server group."""
url = "{0}{1}/".format(self.url, utils.SERVER_GROUP)
# Add service name in the config
- self.server['service'] = "TestDB"
+ if 'connect_now' in self.test_data:
+ self.server['connect_now'] = self.test_data['connect_now']
+ self.server['password'] = self.server['db_password']
+
response = self.tester.post(
url,
data=json.dumps(self.server),
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_connect_timeout.py b/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_connect_timeout.py
deleted file mode 100644
index cce03041a..000000000
--- a/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_connect_timeout.py
+++ /dev/null
@@ -1,47 +0,0 @@
-##########################################################################
-#
-# pgAdmin 4 - PostgreSQL Tools
-#
-# Copyright (C) 2013 - 2020, The pgAdmin Development Team
-# This software is released under the PostgreSQL Licence
-#
-##########################################################################
-
-import json
-
-from pgadmin.utils.route import BaseTestGenerator
-from regression.python_test_utils import test_utils as utils
-
-
-class ServersWithConnectTimeoutAddTestCase(BaseTestGenerator):
- """ This class will add the servers under default server group. """
-
- scenarios = [
- # Fetch the default url for server object
- (
- 'Default Server Node url', dict(
- url='/browser/server/obj/'
- )
- )
- ]
-
- def setUp(self):
- pass
-
- def runTest(self):
- """ This function will add the server under default server group."""
- url = "{0}{1}/".format(self.url, utils.SERVER_GROUP)
- # Add service name in the config
- self.server['connect_timeout'] = 5
- response = self.tester.post(
- url,
- data=json.dumps(self.server),
- content_type='html/json'
- )
- self.assertEquals(response.status_code, 200)
- response_data = json.loads(response.data.decode('utf-8'))
- self.server_id = response_data['node']['_id']
-
- def tearDown(self):
- """This function delete the server from SQLite """
- utils.delete_server_with_api(self.tester, self.server_id)
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_ssh_tunnel.py b/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_ssh_tunnel.py
deleted file mode 100644
index e8b48c0fc..000000000
--- a/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_ssh_tunnel.py
+++ /dev/null
@@ -1,82 +0,0 @@
-##########################################################################
-#
-# pgAdmin 4 - PostgreSQL Tools
-#
-# Copyright (C) 2013 - 2020, The pgAdmin Development Team
-# This software is released under the PostgreSQL Licence
-#
-##########################################################################
-
-import json
-
-from pgadmin.utils.route import BaseTestGenerator
-from regression.python_test_utils import test_utils as utils
-
-
-class ServersWithSSHTunnelAddTestCase(BaseTestGenerator):
- """ This class will add the servers under default server group. """
-
- scenarios = [
- (
- 'Add server using SSH tunnel with password', dict(
- url='/browser/server/obj/',
- with_password=True,
- save_password=False,
- )
- ),
- (
- 'Add server using SSH tunnel with identity file', dict(
- url='/browser/server/obj/',
- with_password=False,
- save_password=False,
- )
- ),
- (
- 'Add server using SSH tunnel with password and saved it', dict(
- url='/browser/server/obj/',
- with_password=True,
- save_password=True,
- )
- ),
- (
- 'Add server using SSH tunnel with identity file and save the '
- 'password', dict(
- url='/browser/server/obj/',
- with_password=False,
- save_password=True,
- )
- ),
- ]
-
- def setUp(self):
- pass
-
- def runTest(self):
- """ This function will add the server under default server group."""
- url = "{0}{1}/".format(self.url, utils.SERVER_GROUP)
- # Add service name in the config
- self.server['use_ssh_tunnel'] = 1
- self.server['tunnel_host'] = '127.0.0.1'
- self.server['tunnel_port'] = 22
- self.server['tunnel_username'] = 'user'
- if self.with_password:
- self.server['tunnel_authentication'] = 0
- else:
- self.server['tunnel_authentication'] = 1
- self.server['tunnel_identity_file'] = 'pkey_rsa'
-
- if self.save_password:
- self.server['tunnel_password'] = '123456'
-
- response = self.tester.post(
- url,
- data=json.dumps(self.server),
- content_type='html/json'
- )
- self.assertEquals(response.status_code, 200)
- response_data = json.loads(response.data.decode('utf-8'))
- self.server_id = response_data['node']['_id']
-
- def tearDown(self):
- """This function delete the server from SQLite """
- utils.delete_server_with_api(self.tester, self.server_id)
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_all_server_get.py b/web/pgadmin/browser/server_groups/servers/tests/test_all_server_get.py
new file mode 100644
index 000000000..cd35a7a05
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_all_server_get.py
@@ -0,0 +1,78 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+import random
+
+from pgadmin.utils.route import BaseTestGenerator
+from regression import parent_node_dict
+from regression.python_test_utils import test_utils as utils
+from . import utils as servers_utils
+
+
+class AllServersGetTestCase(BaseTestGenerator):
+ """
+ This class will fetch added servers under default server group
+ by response code.
+ """
+
+ scenarios = utils.generate_scenarios('get_all_server',
+ servers_utils.test_cases)
+
+ def setUp(self):
+ """This function add the server to test the GET API"""
+ self.server['password'] = 'edb'
+ self.server_id = utils.create_server(self.server)
+ server_dict = {"server_id": self.server_id}
+ utils.write_node_info("sid", server_dict)
+
+ def get_server(self):
+ return self.tester.get(self.url, follow_redirects=True)
+
+ def connect_to_server(self, url):
+ return self.tester.post(
+ url,
+ data=self.server,
+ content_type='html/json'
+ )
+
+ def runTest(self):
+ """ This function will fetch the added servers to object browser. """
+ server_id = parent_node_dict["server"][-1]["server_id"]
+ if not server_id:
+ raise Exception("Server not found to test GET API")
+ response = None
+ if self.is_positive_test:
+ if hasattr(self, 'invalid_server_group'):
+ self.url = self.url + '{0}/{1}?_={1}'.format(
+ utils.SERVER_GROUP, random.randint(1, 9999999))
+ elif hasattr(self, 'children'):
+
+ self.url = self.url + '{0}/{1}'.format(
+ utils.SERVER_GROUP, server_id)
+ elif hasattr(self, 'server_list'):
+ if hasattr(self, 'servers'):
+ server_id = ''
+ self.url = self.url + '{0}/{1}'.format(
+ utils.SERVER_GROUP, server_id)
+ else:
+ if hasattr(self, "connected"):
+ url = '/browser/server/connect/' + '{0}/{1}'.format(
+ utils.SERVER_GROUP,
+ self.server_id)
+ self.server['password'] = 'edb'
+
+ self.connect_to_server(url)
+ self.url = self.url + '{0}/{1}?_={2}'.format(
+ utils.SERVER_GROUP, server_id, random.randint(1, 9999999))
+ response = self.get_server()
+ self.assertEquals(response.status_code,
+ self.expected_data["status_code"])
+
+ def tearDown(self):
+ """This function delete the server from SQLite """
+ utils.delete_server_with_api(self.tester, self.server_id)
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_check_connect.py b/web/pgadmin/browser/server_groups/servers/tests/test_check_connect.py
new file mode 100644
index 000000000..cca7ce57f
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_check_connect.py
@@ -0,0 +1,122 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+from pgadmin.utils.route import BaseTestGenerator
+from regression import parent_node_dict
+from regression.python_test_utils import test_utils as utils
+from . import utils as servers_utils
+import json
+from regression.test_setup import config_data
+
+test_user_details = config_data['pgAdmin4_test_non_admin_credentials']
+
+
+class ServersConnectTestCase(BaseTestGenerator):
+ """
+ This class will fetch added servers under default server group
+ by response code.
+ """
+
+ scenarios = utils.generate_scenarios('connect_server',
+ servers_utils.test_cases)
+
+ def get_ssh_tunnel(self):
+ print("in_get_ssh")
+ self.server['use_ssh_tunnel'] = 1
+ self.server['tunnel_host'] = '127.0.0.1'
+ self.server['tunnel_port'] = 22
+ self.server['tunnel_username'] = 'user'
+ if self.with_password:
+ self.server['tunnel_authentication'] = 0
+ else:
+ self.server['tunnel_authentication'] = 1
+ self.server['tunnel_identity_file'] = 'pkey_rsa'
+
+ if self.save_password:
+ self.server['tunnel_password'] = '123456'
+ # self.server['use_ssh_tunnel'] = self.test_data['use_ssh_tunnel']
+ # self.server['tunnel_host'] = self.test_data['tunnel_host']
+ # self.server['tunnel_port'] = self.test_data['tunnel_port']
+ # self.server['tunnel_username'] = self.test_data['tunnel_username']
+ #
+ # if self.with_password:
+ # self.server['tunnel_authentication'] = self.test_data[
+ # 'tunnel_authentication']
+ #
+ # if self.save_password:
+ # self.server['tunnel_password'] = self.test_data[
+ # 'tunnel_password']
+
+ def setUp(self):
+ """This function add the server to test the GET API"""
+
+ self.server_id = utils.create_server(self.server)
+ server_dict = {"server_id": self.server_id}
+ utils.write_node_info("sid", server_dict)
+
+ def get_server_connection(self, server_id):
+ return self.tester.get(self.url + str(utils.SERVER_GROUP) + '/' +
+ str(server_id),
+ follow_redirects=True)
+
+ def server_disonnect(self, server_id):
+ return self.tester.delete(self.url + str(utils.SERVER_GROUP) + '/' +
+ str(server_id))
+
+ def connect_to_server(self, url):
+ return self.tester.post(
+ url,
+ data=json.dumps(self.server),
+ content_type='html/json'
+ )
+
+ def add_server_details(self, url):
+ return self.tester.post(
+ url,
+ data=str(self.test_data),
+ content_type='html/json'
+ )
+
+ def runTest(self):
+ """ This function will fetch the added servers to object browser. """
+ server_id = parent_node_dict["server"][-1]["server_id"]
+ if not server_id:
+ raise Exception("Server not found to test GET API")
+ response = None
+ if self.is_positive_test:
+ if hasattr(self, 'disconnect'):
+ if hasattr(self, 'wrong_server_id'):
+ server_id = 99999
+ response = self.server_disonnect(server_id)
+ elif hasattr(self, "connect"):
+ url = self.url + '{0}/{1}'.format(
+ utils.SERVER_GROUP,
+ self.server_id)
+ self.server['password'] = self.server['db_password']
+ response = self.connect_to_server(url)
+ elif hasattr(self, 'restore_point') or hasattr(self,
+ 'change_password'):
+ connect_url = '/browser/server/connect/{0}/{1}'.format(
+ utils.SERVER_GROUP,
+ self.server_id)
+ url = self.url + '{0}/{1}'.format(
+ utils.SERVER_GROUP,
+ self.server_id)
+
+ self.connect_to_server(connect_url)
+ response = self.add_server_details(url)
+ else:
+ response = self.get_server_connection(server_id)
+
+ self.assertEquals(response.status_code,
+ self.expected_data["status_code"])
+
+ def tearDown(self):
+ """This function delete the server from SQLite """
+ utils.delete_server_with_api(self.tester, self.server_id)
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_is_password_saved.py b/web/pgadmin/browser/server_groups/servers/tests/test_is_password_saved.py
new file mode 100644
index 000000000..7d144d3d7
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_is_password_saved.py
@@ -0,0 +1,52 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+from . import utils as servers_utils
+
+
+class IsPasswordSaved(BaseTestGenerator):
+ """ This class will test the save password functionality. """
+
+ scenarios = utils.generate_scenarios('is_password_saved',
+ servers_utils.test_cases)
+
+ def setUp(self):
+ self.server_id = utils.create_server(self.server)
+ server_dict = {"server_id": self.server_id}
+ utils.write_node_info("sid", server_dict)
+
+ def runTest(self):
+ """This function will execute the connect server APIs"""
+ response = self.tester.post(
+ self.url + str(utils.SERVER_GROUP) + '/' + str(self.server_id),
+ data=dict(
+ password=self.server['db_password'],
+ save_password='on'),
+ follow_redirects=True)
+
+ expected_status_code = self.expected_data["status_code"]
+ actual_status_code = response.status_code
+ self.assertEquals(actual_status_code, expected_status_code)
+ response_data = json.loads(response.data.decode('utf-8'))
+
+ expected_message = self.expected_data["message"]
+ actual_message = response_data["info"]
+ self.assertEquals(actual_message, expected_message)
+
+ expected_is_password_saved = self.test_data["is_password_saved"]
+ actual_is_password_saved = response_data["data"]["is_password_saved"]
+ self.assertEquals(actual_is_password_saved, expected_is_password_saved)
+
+ def tearDown(self):
+ """This function delete the server from SQLite """
+ utils.delete_server_with_api(self.tester, self.server_id)
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_server_add.py b/web/pgadmin/browser/server_groups/servers/tests/test_server_add.py
deleted file mode 100644
index 7fe3cf6a8..000000000
--- a/web/pgadmin/browser/server_groups/servers/tests/test_server_add.py
+++ /dev/null
@@ -1,86 +0,0 @@
-##########################################################################
-#
-# pgAdmin 4 - PostgreSQL Tools
-#
-# Copyright (C) 2013 - 2020, The pgAdmin Development Team
-# This software is released under the PostgreSQL Licence
-#
-##########################################################################
-
-import json
-import copy
-from pgadmin.utils.route import BaseTestGenerator
-from regression.python_test_utils import test_utils as utils
-
-
-class ServersAddTestCase(BaseTestGenerator):
- """ This class will add the servers under default server group. """
-
- scenarios = [
- # Fetch the default url for server object
- ('Default Server Node url', dict(url='/browser/server/obj/'))
- ]
-
- def setUp(self):
- pass
-
- def runTest(self):
- """ This function will add the server under default server group."""
- url = "{0}{1}/".format(self.url, utils.SERVER_GROUP)
- response = self.tester.post(url, data=json.dumps(self.server),
- content_type='html/json')
- self.assertEquals(response.status_code, 200)
- response_data = json.loads(response.data.decode('utf-8'))
- self.server_id = response_data['node']['_id']
- server_dict = {"server_id": int(self.server_id)}
- utils.write_node_info("sid", server_dict)
-
- def tearDown(self):
- """This function delete the server from SQLite """
- utils.delete_server_with_api(self.tester, self.server_id)
-
-
-class AddServersWithSavePasswordTestCase(BaseTestGenerator):
- """ This class will add the servers under default server group. """
-
- scenarios = [
- # Fetch the default url for server object
- ('Add server with password and save password to true',
- dict(url='/browser/server/obj/', with_pwd=True, with_save=True)),
- ('Add server with password and save password to false',
- dict(url='/browser/server/obj/', with_pwd=True, with_save=False)),
- ('Add server without password and save password to true',
- dict(url='/browser/server/obj/', with_pwd=False, with_save=True)),
- ]
-
- def setUp(self):
- pass
-
- def runTest(self):
- """ This function will add the server under default server group."""
- url = "{0}{1}/".format(self.url, utils.SERVER_GROUP)
- _server = copy.deepcopy(self.server)
- # Update the flag as required
- _server['save_password'] = self.with_save
- if not self.with_pwd:
- # Remove the password from server object
- del _server['db_password']
-
- response = self.tester.post(url, data=json.dumps(_server),
- content_type='html/json')
- self.assertEquals(response.status_code, 200)
- response_data = json.loads(response.data.decode('utf-8'))
- self.server_id = response_data['node']['_id']
- server_dict = {"server_id": int(self.server_id)}
- # Fetch the node info to check if password was saved or not
- response = self.tester.get(self.url.replace('obj', 'nodes') +
- str(utils.SERVER_GROUP) + '/' +
- str(self.server_id),
- follow_redirects=True)
- self.assertEquals(response.status_code, 200)
- self.assertTrue('is_password_saved' in response.json['result'])
- utils.write_node_info("sid", server_dict)
-
- def tearDown(self):
- """This function delete the server from SQLite """
- utils.delete_server_with_api(self.tester, self.server_id)
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_server_delete.py b/web/pgadmin/browser/server_groups/servers/tests/test_server_delete.py
index c2b184295..43220eb2f 100644
--- a/web/pgadmin/browser/server_groups/servers/tests/test_server_delete.py
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_server_delete.py
@@ -9,15 +9,14 @@
from pgadmin.utils.route import BaseTestGenerator
from regression.python_test_utils import test_utils as utils
+from . import utils as servers_utils
class ServerDeleteTestCase(BaseTestGenerator):
""" This class will delete the last server present under tree node."""
- scenarios = [
- # Fetching the default url for server node
- ('Default Server Node url', dict(url='/browser/server/obj/'))
- ]
+ scenarios = utils.generate_scenarios('delete_server',
+ servers_utils.test_cases)
def setUp(self):
"""This function add the server to test the DELETE API"""
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_server_get.py b/web/pgadmin/browser/server_groups/servers/tests/test_server_get.py
index 9f92ffcce..5a83e2a08 100644
--- a/web/pgadmin/browser/server_groups/servers/tests/test_server_get.py
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_server_get.py
@@ -10,6 +10,8 @@
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils
+from . import utils as servers_utils
+import json
class ServersGetTestCase(BaseTestGenerator):
@@ -18,26 +20,50 @@ class ServersGetTestCase(BaseTestGenerator):
by response code.
"""
- scenarios = [
- # Fetch the default url for server node
- ('Default Server Node url', dict(url='/browser/server/obj/'))
- ]
+ scenarios = utils.generate_scenarios('get_server',
+ servers_utils.test_cases)
def setUp(self):
"""This function add the server to test the GET API"""
- self.server_id = utils.create_server(self.server)
+ if hasattr(self, 'shared'):
+
+ self.server['shared'] = True
+ url = "{0}{1}/".format(self.url, utils.SERVER_GROUP)
+ response = self.tester.post(
+ url,
+ data=json.dumps(self.server),
+ content_type='html/json'
+ )
+ response_data = json.loads(response.data.decode('utf-8'))
+ self.server_id = response_data['node']['_id']
+ else:
+ self.server_id = utils.create_server(self.server)
server_dict = {"server_id": self.server_id}
utils.write_node_info("sid", server_dict)
+ def get_server(self, server_id):
+ return self.tester.get(self.url + str(utils.SERVER_GROUP) + '/' +
+ str(server_id),
+ follow_redirects=True)
+
def runTest(self):
""" This function will fetch the added servers to object browser. """
server_id = parent_node_dict["server"][-1]["server_id"]
if not server_id:
raise Exception("Server not found to test GET API")
- response = self.tester.get(self.url + str(utils.SERVER_GROUP) + '/' +
- str(server_id),
- follow_redirects=True)
- self.assertEquals(response.status_code, 200)
+ response = None
+ if self.is_positive_test:
+ if hasattr(self, "incorrect_server_id"):
+ server_id = 9999
+ if hasattr(self, "server_list"):
+ server_id = ''
+ if hasattr(self, "server_node"):
+ server_id = ''
+ if hasattr(self, 'shared'):
+ server_id = self.server_id
+ response = self.get_server(server_id)
+ self.assertEquals(response.status_code,
+ self.expected_data["status_code"])
def tearDown(self):
"""This function delete the server from SQLite """
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_server_put.py b/web/pgadmin/browser/server_groups/servers/tests/test_server_put.py
index 8a69558e9..74674c7cb 100644
--- a/web/pgadmin/browser/server_groups/servers/tests/test_server_put.py
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_server_put.py
@@ -11,32 +11,61 @@ import json
from pgadmin.utils.route import BaseTestGenerator
from regression.python_test_utils import test_utils as utils
+from . import utils as servers_utils
class ServerUpdateTestCase(BaseTestGenerator):
""" This class will update server's comment field. """
- scenarios = [
- # Fetching the default url for server node
- ('Default Server Node url', dict(url='/browser/server/obj/'))
- ]
+ scenarios = utils.generate_scenarios('update_server',
+ servers_utils.test_cases)
def setUp(self):
"""This function add the server to test the PUT API"""
- self.server_id = utils.create_server(self.server)
+ if hasattr(self, 'clear_save_password'):
+ self.server['save_password'] = 1
+ create_server_url = "/browser/server/obj/{0}/".format(
+ utils.SERVER_GROUP)
+
+ self.server_id = \
+ servers_utils.create_server_with_api(self, create_server_url)
server_dict = {"server_id": self.server_id}
utils.write_node_info("sid", server_dict)
+ def update_server(self):
+ return self.tester.put(
+ self.url + str(utils.SERVER_GROUP) + '/' +
+ str(self.server_id), data=json.dumps(self.test_data),
+ content_type='html/json')
+
+ def connect_to_server(self, url):
+ return self.tester.post(
+ url,
+ data=json.dumps(self.server),
+ content_type='html/json'
+ )
+
def runTest(self):
"""This function update the server details"""
if not self.server_id:
raise Exception("No server to update.")
- data = {"comment": self.server['comment'], "id": self.server_id}
- put_response = self.tester.put(
- self.url + str(utils.SERVER_GROUP) + '/' +
- str(self.server_id), data=json.dumps(data),
- content_type='html/json')
- self.assertEquals(put_response.status_code, 200)
+ if 'comment' in self.test_data:
+ self.test_data["comment"] = self.server['comment']
+ self.test_data["id"] = self.server_id
+ if self.is_positive_test:
+ if hasattr(self, 'server_connected'):
+ url = '/browser/server/connect/{0}/{1}'.format(
+ utils.SERVER_GROUP,
+ self.server_id)
+ self.server['password'] = self.server['db_password']
+ self.connect_to_server(url)
+ put_response = self.update_server()
+ else:
+ if hasattr(self, 'wrong_server_id'):
+ self.server_id = 9999
+ put_response = self.update_server()
+ self.assertEquals(put_response.status_code,
+ self.expected_data["status_code"])
def tearDown(self):
"""This function delete the server from SQLite"""
diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_shared_server.py b/web/pgadmin/browser/server_groups/servers/tests/test_shared_server.py
new file mode 100644
index 000000000..bf34ad123
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_shared_server.py
@@ -0,0 +1,125 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+from pgadmin.utils.route import BaseTestGenerator
+from regression import parent_node_dict
+from regression.python_test_utils import test_utils as utils
+from . import utils as servers_utils
+import json
+from regression.test_setup import config_data
+from regression.python_test_utils.test_utils import \
+ create_user_wise_test_client
+
+test_user_details = config_data[
+ 'pgAdmin4_test_non_admin_credentials']
+
+
+class SharedServersGetTestCase(BaseTestGenerator):
+ """
+ This class will fetch added servers under default server group
+ by response code.
+ """
+
+ scenarios = utils.generate_scenarios('get_shared_server',
+ servers_utils.test_cases)
+
+ def setUp(self):
+ """This function add the server to test the GET API"""
+ self.server['shared'] = True
+ url = "{0}{1}/".format(self.url, utils.SERVER_GROUP)
+ response = self.tester.post(
+ url,
+ data=json.dumps(self.server),
+ content_type='html/json'
+ )
+ response_data = json.loads(response.data.decode('utf-8'))
+ self.server_id = response_data['node']['_id']
+
+ server_dict = {"server_id": self.server_id}
+ utils.write_node_info("sid", server_dict)
+
+ def get_server(self, server_id):
+ return self.tester.get(self.url + str(utils.SERVER_GROUP) + '/' +
+ str(server_id),
+ follow_redirects=True)
+
+ @create_user_wise_test_client(test_user_details)
+ def runTest(self):
+ """ This function will fetch the added servers to object browser. """
+ if not self.server_id:
+ raise Exception("Server not found to test GET API")
+ response = None
+ if self.is_positive_test:
+ if hasattr(self, 'no_server_id'):
+ if hasattr(self, 'server_list'):
+ self.url = '/browser/server/nodes/'
+ server_id = ''
+ response = self.get_server(server_id)
+ else:
+ response = self.get_server(self.server_id)
+ self.assertEquals(response.status_code,
+ self.expected_data["status_code"])
+
+ def tearDown(self):
+ """This function delete the server from SQLite """
+ utils.delete_server_with_api(self.tester, self.server_id)
+
+
+class SharedServerUpdateTestCase(BaseTestGenerator):
+ """ This class will update server's comment field. """
+
+ scenarios = utils.generate_scenarios('update_shared_server',
+ servers_utils.test_cases)
+
+ def setUp(self):
+ """This function add the server to test the PUT API"""
+ self.server['shared'] = True
+ if hasattr(self, 'clear_save_password'):
+ self.server['save_password'] = 1
+ create_server_url = "/browser/server/obj/{0}/".format(
+ utils.SERVER_GROUP)
+
+ self.server_id = \
+ servers_utils.create_server_with_api(self, create_server_url)
+ server_dict = {"server_id": self.server_id}
+ utils.write_node_info("sid", server_dict)
+
+ def update_server(self):
+ return self.tester.put(
+ self.url + str(utils.SERVER_GROUP) + '/' +
+ str(self.server_id), data=json.dumps(self.test_data),
+ content_type='html/json')
+
+ def connect_to_server(self, url):
+ return self.tester.post(
+ url,
+ data=json.dumps(self.server),
+ content_type='html/json'
+ )
+
+ @create_user_wise_test_client(test_user_details)
+ def runTest(self):
+ """This function update the server details"""
+ if not self.server_id:
+ raise Exception("No server to update.")
+ if 'comment' in self.test_data:
+ self.test_data["comment"] = self.server['comment']
+ self.test_data["id"] = self.server_id
+ if self.is_positive_test:
+ put_response = self.update_server()
+ else:
+ if hasattr(self, 'wrong_server_id'):
+ self.server_id = 9999
+ put_response = self.update_server()
+ self.assertEquals(put_response.status_code,
+ self.expected_data["status_code"])
+
+ def tearDown(self):
+ """This function delete the server from SQLite"""
+ utils.delete_server_with_api(self.tester, self.server_id)
diff --git a/web/pgadmin/browser/server_groups/servers/tests/utils.py b/web/pgadmin/browser/server_groups/servers/tests/utils.py
new file mode 100644
index 000000000..8680b29fb
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/utils.py
@@ -0,0 +1,52 @@
+import os
+import json
+import sqlite3
+import config
+from regression.python_test_utils import test_utils as utils
+
+
+CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
+with open(CURRENT_PATH + "/servers_test_data.json") as data_file:
+ test_cases = json.load(data_file)
+
+
+def create_server(server, SERVER_GROUP):
+ """This function is used to create server"""
+ try:
+ conn = sqlite3.connect(config.TEST_SQLITE_PATH)
+ # Create the server
+ cur = conn.cursor()
+ if 'shared' not in server:
+ server['shared'] = False
+ server_details = (1, SERVER_GROUP, server['name'], server['host'],
+ server['port'], server['db'], server['username'],
+ server['role'], server['sslmode'], server['comment'],
+ server['shared'])
+ cur.execute('INSERT INTO server (user_id, servergroup_id, name, host, '
+ 'port, maintenance_db, username, role, ssl_mode,'
+ ' comment, shared) VALUES (?,?,?,?,?,?,?,?,?,?,?)',
+ server_details)
+ server_id = cur.lastrowid
+ conn.commit()
+ conn.close()
+
+ type = utils.get_server_type(server)
+ server['type'] = type
+
+ return server_id
+ except Exception as exception:
+ raise Exception("Error while creating server. %s" % exception)
+
+
+def create_server_with_api(self, url):
+ try:
+ response = self.tester.post(
+ url,
+ data=json.dumps(self.server),
+ content_type='html/json'
+ )
+ response_data = json.loads(response.data.decode('utf-8'))
+ server_id = response_data['node']['_id']
+ return server_id
+ except Exception as exception:
+ raise Exception("Error while creating server. %s" % exception)
diff --git a/web/pgadmin/browser/server_groups/static/img/server_group_shared.svg b/web/pgadmin/browser/server_groups/static/img/server_group_shared.svg
new file mode 100644
index 000000000..6a4e22bf8
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/static/img/server_group_shared.svg
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#F2F2F2;}
+ .st1{fill:#9BA5B0;}
+ .st2{fill:none;stroke:#9BA5B0;stroke-width:0.75;stroke-miterlimit:1;}
+ .st3{fill:#F7F7F7;}
+ .st4{fill:#354A5F;}
+</style>
+<g>
+ <path class="st0" d="M6.7,2.1h4.6c0.6,0,1.1,0.5,1.1,1.1V4c0,0.6-0.5,1.1-1.1,1.1H6.7C6.1,5.1,5.6,4.6,5.6,4V3.2
+ C5.6,2.6,6.1,2.1,6.7,2.1z"/>
+ <path class="st1" d="M11.3,2.5c0.4,0,0.8,0.3,0.8,0.8v0.8c0,0.4-0.3,0.8-0.8,0.8H6.7C6.3,4.8,6,4.5,6,4.1V3.2
+ c0-0.4,0.3-0.7,0.8-0.7H11.3 M11.3,1.7H6.7c-0.8,0-1.5,0.7-1.5,1.5V4c0,0.8,0.7,1.5,1.5,1.5h4.6c0.8,0,1.5-0.7,1.5-1.5V3.2
+ C12.8,2.4,12.1,1.7,11.3,1.7L11.3,1.7z"/>
+ <line class="st2" x1="8.9" y1="3.6" x2="6.8" y2="3.6"/>
+ <path class="st0" d="M6.7,5.2h4.6c0.6,0,1.1,0.5,1.1,1.1v0.8c0,0.6-0.5,1.1-1.1,1.1H6.7c-0.6,0-1.1-0.5-1.1-1.1V6.3
+ C5.6,5.7,6.1,5.2,6.7,5.2z"/>
+ <path class="st1" d="M11.3,5.6c0.4,0,0.8,0.3,0.8,0.8v0.8c0,0.4-0.3,0.8-0.8,0.8H6.7C6.3,7.9,6,7.6,6,7.1V6.3
+ c0-0.4,0.3-0.7,0.8-0.7L11.3,5.6 M11.3,4.8H6.7c-0.8,0-1.5,0.7-1.5,1.5v0.8c0,0.8,0.7,1.5,1.5,1.5h4.6c0.8,0,1.5-0.7,1.5-1.5V6.3
+ C12.8,5.5,12.1,4.8,11.3,4.8L11.3,4.8z"/>
+ <line class="st2" x1="8.9" y1="6.7" x2="6.8" y2="6.7"/>
+ <path class="st0" d="M6.7,8.3h4.6c0.6,0,1.1,0.5,1.1,1.1v0.9c0,0.6-0.5,1.1-1.1,1.1l0,0H6.7c-0.6,0-1.1-0.5-1.1-1.1l0,0V9.4
+ C5.6,8.8,6.1,8.3,6.7,8.3L6.7,8.3z"/>
+ <path class="st1" d="M11.3,8.7c0.4,0,0.8,0.3,0.8,0.8v0.8c0,0.4-0.3,0.8-0.8,0.8H6.7C6.3,11,6,10.7,6,10.3V9.4C6,9,6.3,8.7,6.7,8.7
+ H11.3 M11.3,7.9H6.7c-0.8,0-1.5,0.7-1.5,1.5v0.8c0,0.8,0.7,1.5,1.5,1.5h4.6c0.8,0,1.5-0.7,1.5-1.5V9.4C12.8,8.6,12.1,7.9,11.3,7.9z
+ "/>
+ <line class="st2" x1="8.9" y1="9.8" x2="6.8" y2="9.8"/>
+</g>
+<path class="st3" d="M11.6,10.5c-0.6,0.3-0.8,0.1-1.1-0.2c-0.1-0.2-0.3-0.4-0.6-0.5L7.1,8.5c-0.3-0.1-0.7-0.2-1-0.2
+ C5.1,8.1,4.2,8.4,3.4,9L2,10.1v2.7h0.9v-0.2l3.7,1.8c0.4,0.2,0.7,0.3,1.1,0.3c0.3,0,0.7-0.1,1-0.2l6.1-2.6c0.3-0.1,0.6-0.4,0.7-0.7
+ c0.1-0.3,0.1-0.7,0-1c-0.3-0.6-1.1-0.9-1.7-0.6l-0.4,0.2L11.6,10.5"/>
+<path class="st4" d="M15.5,10.2c-0.3-0.6-1.1-0.9-1.7-0.6l-0.4,0.2l-1.8,0.8l0.3,0.8l1.8-0.8l0.4-0.2c0.2-0.1,0.5,0,0.6,0.2
+ c0.1,0.1,0.1,0.2,0,0.3c0,0.1-0.1,0.2-0.2,0.2l-6.1,2.6c-0.5,0.2-1,0.2-1.4,0l-4.1-2v-1.2l1-0.8C4.5,9.2,5.2,9,6,9.1
+ c0.3,0,0.6,0.1,0.8,0.2l2.8,1.2c0.1,0,0.1,0.1,0.2,0.2c0.1,0.1,0.1,0.3,0,0.4c0,0,0,0.1-0.1,0.1c-0.1,0.1-0.3,0.2-0.5,0.1l-2.2-1
+ l-0.4,0.8l2.2,1c0.2,0.1,0.4,0.1,0.5,0.1c0.4,0,0.7-0.2,1-0.4c0.1-0.1,0.2-0.2,0.2-0.3c0.2-0.4,0.1-0.9-0.1-1.2
+ c-0.1-0.2-0.3-0.4-0.6-0.5L7.1,8.5c-0.3-0.1-0.7-0.2-1-0.2C5.1,8.1,4.2,8.4,3.4,9L2.9,9.4V9.3c0-0.6-0.4-1-1-1H1.4c-0.6,0-1,0.4-1,1
+ v4.1c0,0.6,0.4,1,1,1h0.5c0.6,0,1-0.4,1-1v-0.6l0,0v-0.2l3.7,1.8c0.4,0.2,0.7,0.3,1.1,0.3c0.3,0,0.7-0.1,1-0.2l6.1-2.6
+ c0.3-0.1,0.6-0.4,0.7-0.7C15.7,10.9,15.7,10.5,15.5,10.2z M1.9,13.4H1.4V9.3h0.5V13.4z"/>
+</svg>
diff --git a/web/pgadmin/browser/server_groups/static/js/server_group.js b/web/pgadmin/browser/server_groups/static/js/server_group.js
index 9bd7df0f3..219b5954a 100644
--- a/web/pgadmin/browser/server_groups/static/js/server_group.js
+++ b/web/pgadmin/browser/server_groups/static/js/server_group.js
@@ -9,8 +9,8 @@
define('pgadmin.node.server_group', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
- 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.browser.node',
-], function(gettext, url_for, $, _, pgAdmin) {
+ 'sources/pgadmin', 'pgadmin.user_management.current_user', 'pgadmin.browser', 'pgadmin.browser.node',
+], function(gettext, url_for, $, _, pgAdmin, current_user) {
if (!pgAdmin.Browser.Nodes['server_group']) {
pgAdmin.Browser.Nodes['server_group'] = pgAdmin.Browser.Node.extend({
@@ -39,14 +39,27 @@ define('pgadmin.node.server_group', [
defaults: {
id: undefined,
name: null,
+ user_id: undefined,
},
schema: [
{
id: 'id', label: gettext('ID'), type: 'int', group: null,
mode: ['properties'],
+ visible: function(model){
+ if (model.attributes.user_id != current_user.id && !current_user.is_admin)
+ return false;
+
+ return true;
+ },
},{
id: 'name', label: gettext('Name'), type: 'text', group: null,
mode: ['properties', 'edit', 'create'],
+ disabled: function(model){
+ if (model.attributes.user_id != current_user.id && !_.isUndefined(model.attributes.user_id))
+ return true;
+
+ return false;
+ },
},
],
validate: function() {
@@ -69,7 +82,11 @@ define('pgadmin.node.server_group', [
return null;
},
}),
- canDrop: function(itemData) { return itemData.can_delete; },
+ canDrop: function(itemData) {
+ var serverOwner = itemData.user_id;
+ if (serverOwner != current_user.id)
+ return false;
+ return itemData.can_delete; },
dropAsRemove: true,
canDelete: function(i) {
var s = pgAdmin.Browser.tree.siblings(i, true);
diff --git a/web/pgadmin/browser/server_groups/templates/css/server_group.css b/web/pgadmin/browser/server_groups/templates/css/server_group.css
new file mode 100644
index 000000000..3b5b8e58a
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/templates/css/server_group.css
@@ -0,0 +1,17 @@
+.icon-server_group {
+ background-image: url('{{ url_for('NODE-server_group.static', filename='img/server_group.svg') }}') !important;
+ background-repeat: no-repeat;
+ background-size: 20px !important;
+ align-content: center;
+ vertical-align: middle;
+ height: 1.3em;
+}
+
+.icon-server_group_shared {
+ background-image: url('{{ url_for('NODE-server_group.static', filename='img/server_group_shared.svg') }}') !important;
+ background-repeat: no-repeat;
+ background-size: 20px !important;
+ align-content: center;
+ vertical-align: middle;
+ height: 1.3em;
+}
diff --git a/web/pgadmin/browser/templates/browser/js/utils.js b/web/pgadmin/browser/templates/browser/js/utils.js
index 33dd6809e..516b62697 100644
--- a/web/pgadmin/browser/templates/browser/js/utils.js
+++ b/web/pgadmin/browser/templates/browser/js/utils.js
@@ -44,6 +44,7 @@ define('pgadmin.browser.utils',
pgAdmin['csrf_token_header'] = '{{ current_app.config.get('WTF_CSRF_HEADERS')[0] }}';
pgAdmin['csrf_token'] = '{{ csrf_token() }}';
+ pgAdmin['server_mode'] = '{{ current_app.config.get('SERVER_MODE') }}';
/* Get the inactivity related config */
pgAdmin['user_inactivity_timeout'] = {{ current_app.config.get('USER_INACTIVITY_TIMEOUT') }};
diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py
index 03681e120..b33adc062 100644
--- a/web/pgadmin/model/__init__.py
+++ b/web/pgadmin/model/__init__.py
@@ -29,7 +29,7 @@ from flask_sqlalchemy import SQLAlchemy
#
##########################################################################
-SCHEMA_VERSION = 25
+SCHEMA_VERSION = 26
##########################################################################
#
@@ -173,6 +173,7 @@ class Server(db.Model):
)
tunnel_identity_file = db.Column(db.String(64), nullable=True)
tunnel_password = db.Column(db.String(64), nullable=True)
+ shared = db.Column(db.Boolean(), nullable=False)
class ModulePreference(db.Model):
@@ -305,3 +306,88 @@ class Database(db.Model):
nullable=False,
primary_key=True
)
+
+
+class SharedServer(db.Model):
+ """Define a shared Postgres server"""
+
+ __tablename__ = 'sharedserver'
+ id = db.Column(db.Integer, primary_key=True)
+ user_id = db.Column(
+ db.Integer,
+ db.ForeignKey('user.id')
+ )
+ server_owner = db.Column(
+ db.String(128),
+ db.ForeignKey('user.username')
+ )
+ servergroup_id = db.Column(
+ db.Integer,
+ db.ForeignKey('servergroup.id'),
+ nullable=False
+ )
+ name = db.Column(db.String(128), nullable=False)
+ host = db.Column(db.String(128), nullable=True)
+ hostaddr = db.Column(db.String(128), nullable=True)
+ port = db.Column(
+ db.Integer(),
+ db.CheckConstraint('port >= 1 AND port <= 65534'),
+ nullable=False)
+ maintenance_db = db.Column(db.String(64), nullable=True)
+ username = db.Column(db.String(64), nullable=False)
+ password = db.Column(db.String(64), nullable=True)
+ save_password = db.Column(
+ db.Integer(),
+ db.CheckConstraint('save_password >= 0 AND save_password <= 1'),
+ nullable=False
+ )
+ role = db.Column(db.String(64), nullable=True)
+ ssl_mode = db.Column(
+ db.String(16),
+ db.CheckConstraint(
+ "ssl_mode IN ('allow', 'prefer', 'require', 'disable', "
+ "'verify-ca', 'verify-full')"
+ ),
+ nullable=False)
+ comment = db.Column(db.String(1024), nullable=True)
+ discovery_id = db.Column(db.String(128), nullable=True)
+ servers = db.relationship(
+ 'ServerGroup',
+ backref=db.backref('sharedserver', cascade="all, delete-orphan"),
+ lazy='joined'
+ )
+ db_res = db.Column(db.Text(), nullable=True)
+ passfile = db.Column(db.Text(), nullable=True)
+ sslcert = db.Column(db.Text(), nullable=True)
+ sslkey = db.Column(db.Text(), nullable=True)
+ sslrootcert = db.Column(db.Text(), nullable=True)
+ sslcrl = db.Column(db.Text(), nullable=True)
+ sslcompression = db.Column(
+ db.Integer(),
+ db.CheckConstraint('sslcompression >= 0 AND sslcompression <= 1'),
+ nullable=False
+ )
+ bgcolor = db.Column(db.Text(10), nullable=True)
+ fgcolor = db.Column(db.Text(10), nullable=True)
+ service = db.Column(db.Text(), nullable=True)
+ connect_timeout = db.Column(db.Integer(), nullable=False)
+ use_ssh_tunnel = db.Column(
+ db.Integer(),
+ db.CheckConstraint('use_ssh_tunnel >= 0 AND use_ssh_tunnel <= 1'),
+ nullable=False
+ )
+ tunnel_host = db.Column(db.String(128), nullable=True)
+ tunnel_port = db.Column(
+ db.Integer(),
+ db.CheckConstraint('port <= 65534'),
+ nullable=True)
+ tunnel_username = db.Column(db.String(64), nullable=True)
+ tunnel_authentication = db.Column(
+ db.Integer(),
+ db.CheckConstraint('tunnel_authentication >= 0 AND '
+ 'tunnel_authentication <= 1'),
+ nullable=False
+ )
+ tunnel_identity_file = db.Column(db.String(64), nullable=True)
+ tunnel_password = db.Column(db.String(64), nullable=True)
+ shared = db.Column(db.Boolean(), nullable=False)
diff --git a/web/pgadmin/utils/driver/psycopg2/server_manager.py b/web/pgadmin/utils/driver/psycopg2/server_manager.py
index ea1d9efe1..77c4dec22 100644
--- a/web/pgadmin/utils/driver/psycopg2/server_manager.py
+++ b/web/pgadmin/utils/driver/psycopg2/server_manager.py
@@ -66,6 +66,7 @@ class ServerManager(object):
self.hostaddr = server.hostaddr
self.port = server.port
self.db = server.maintenance_db
+ self.shared = server.shared
self.did = None
self.user = server.username
self.password = server.password
diff --git a/web/regression/python_test_utils/test_utils.py b/web/regression/python_test_utils/test_utils.py
index 2f36b5e81..ae60e379f 100644
--- a/web/regression/python_test_utils/test_utils.py
+++ b/web/regression/python_test_utils/test_utils.py
@@ -36,6 +36,8 @@ from regression import test_setup
from pgadmin.utils.preferences import Preferences
+from functools import wraps
+
CURRENT_PATH = os.path.abspath(os.path.join(os.path.dirname(
os.path.realpath(__file__)), "../"))
@@ -1585,3 +1587,110 @@ def get_selenoid_browsers_list(arguments):
list_of_browsers = test_setup.config_data['selenoid_config'][
'browsers_list']
return list_of_browsers
+
+
+def login_using_user_account(tester):
+ """
+ This function login the test client username and password
+ :param tester: test client
+ :type tester: flask test client object
+ :return: None
+ """
+ username = tester.test_config_data['login_username']
+ password = tester.test_config_data['login_password']
+ response = tester.login(username, password)
+
+ if response.status_code != 302:
+ print("Unable to login test client, email and password not found.",
+ file=sys.stderr)
+ sys.exit(1)
+
+
+def logout_tester_account(tester):
+ """
+ This function logout the test account
+ :param tester: test client
+ :type tester: flask test client object
+ :return: None
+ """
+ tester.logout()
+
+
+def create_user(user_details):
+ try:
+ conn = sqlite3.connect(config.TEST_SQLITE_PATH)
+ # Create the server
+ cur = conn.cursor()
+ user_details = (
+ user_details['login_username'], user_details['login_username'],
+ user_details['login_password'], 1)
+
+ cur.execute(
+ 'select * from user where username = "%s"' % user_details[0])
+ user = cur.fetchone()
+ if user is None:
+ cur.execute('INSERT INTO user (username, email, password, active) '
+ 'VALUES (?,?,?,?)', user_details)
+ user_id = cur.lastrowid
+ conn.commit()
+ else:
+ user_id = user[0]
+ conn.close()
+
+ return user_id
+ except Exception as exception:
+ raise Exception("Error while creating server. %s" % exception)
+
+
+def get_test_user(self, user_details,
+ is_api=True, create_conn=True):
+ # assert id == 1 or id == 0
+ test_client = self.app.test_client()
+ if user_details is None:
+ return None, None
+
+ if is_api is True:
+
+ # Create test_client for this user, and login through it.
+ test_client = self.app.test_client()
+ user = create_user(user_details)
+ if user is not None:
+ test_client.test_config_data = dict({
+ "login_username": user_details['login_username'],
+ "login_password": user_details['login_password']
+ })
+ else:
+ return "User not created"
+ login_using_user_account(test_client)
+ user = test_client
+
+ return user
+
+
+def create_user_wise_test_client(user):
+ """
+ This function creates new test client and pem database connection as per
+ provided user and execute the test cases.
+ :return: None
+ """
+
+ def multi_user_decorator(func):
+ @wraps(func)
+ def wrapper(self, *args, **kwargs):
+ # self.user = user
+ main_tester = self.__class__.tester
+ try:
+ # Login with non-admin_user
+ test_user = get_test_user(self, user)
+ self.setTestClient(test_user)
+ # self.setTestClient(test_user['tester'])
+
+ # Call 'runTest' with new test client
+ func(self, *args, **kwargs)
+ finally:
+ # Restore the original user and driver
+ self.__class__.tester = main_tester
+
+ return wrapper
+
+ return multi_user_decorator
diff --git a/web/setup.py b/web/setup.py
index 14bd5f9cc..e8a71f812 100644
--- a/web/setup.py
+++ b/web/setup.py
@@ -110,6 +110,7 @@ def dump_servers(args):
add_value(attr_dict, "Role", server.role)
add_value(attr_dict, "SSLMode", server.ssl_mode)
add_value(attr_dict, "Comment", server.comment)
+ add_value(attr_dict, "Shared", server.shared)
add_value(attr_dict, "DBRestriction", server.db_res)
add_value(attr_dict, "PassFile", server.passfile)
add_value(attr_dict, "SSLCert", server.sslcert)
@@ -244,6 +245,14 @@ def load_servers(args):
print_summary()
sys.exit(1)
+ # Check if server is shared.Won't import if user is non-admin
+ if 'Shared' in obj:
+ if obj['Shared'] and \
+ not user.has_role("Administrator"):
+ print("Can't import the server '%s' as it is shared " %
+ obj["Name"])
+ continue
+
# Get the group. Create if necessary
group_id = -1
for g in groups:
view thread (13+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected]
Subject: Re: [pgAdmin][RM4979]: Configuration files for data sources or similar.
In-Reply-To: <CAJ9T6SvOzSPGyRgVg+8XksV_4=mOSSMoy8RaGKm0O9qFTT_ZQQ@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox