public inbox for [email protected]
help / color / mirror / Atom feed[pgAdmin][RM4979]: Configuration files for data sources or similar.
13+ messages / 2 participants
[nested] [flat]
* [pgAdmin][RM4979]: Configuration files for data sources or similar.
@ 2020-08-20 10:28 Pradip Parkale <[email protected]>
2020-08-21 08:25 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Pradip Parkale @ 2020-08-20 10:28 UTC (permalink / raw)
To: pgadmin-hackers
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:
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin][RM4979]: Configuration files for data sources or similar.
2020-08-20 10:28 [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
@ 2020-08-21 08:25 ` Akshay Joshi <[email protected]>
2020-08-21 12:30 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Akshay Joshi @ 2020-08-21 08:25 UTC (permalink / raw)
To: Pradip Parkale <[email protected]>; +Cc: pgadmin-hackers
Hi Pradip
The patch is not applied, can you please rebase and send it again.
On Thu, Aug 20, 2020 at 3:58 PM Pradip Parkale <
[email protected]> wrote:
> 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
>
--
*Thanks & Regards*
*Akshay Joshi*
*pgAdmin Hacker | Sr. Software Architect*
*EDB Postgres <http://edbpostgres.com>*
*Mobile: +91 976-788-8246*
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin][RM4979]: Configuration files for data sources or similar.
2020-08-20 10:28 [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-21 08:25 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
@ 2020-08-21 12:30 ` Pradip Parkale <[email protected]>
2020-08-25 12:22 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Pradip Parkale @ 2020-08-21 12:30 UTC (permalink / raw)
To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers
Hi Akshay,
Please find the updated patch.
On Fri, Aug 21, 2020 at 1:55 PM Akshay Joshi <[email protected]>
wrote:
> Hi Pradip
>
> The patch is not applied, can you please rebase and send it again.
>
> On Thu, Aug 20, 2020 at 3:58 PM Pradip Parkale <
> [email protected]> wrote:
>
>> 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
>>
>
>
> --
> *Thanks & Regards*
> *Akshay Joshi*
> *pgAdmin Hacker | Sr. Software Architect*
> *EDB Postgres <http://edbpostgres.com>*
>
> *Mobile: +91 976-788-8246*
>
--
Thanks & Regards,
Pradip Parkale
Software Engineer | EnterpriseDB Corporation
Attachments:
[application/octet-stream] RM4979_v2.patch (125.9K, 3-RM4979_v2.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 948be41b8..215421cbf 100644
--- a/web/pgadmin/browser/register_browser_preferences.py
+++ b/web/pgadmin/browser/register_browser_preferences.py
@@ -9,6 +9,7 @@
from flask_babelex import gettext
from pgadmin.utils.constants import PREF_LABEL_DISPLAY,\
PREF_LABEL_KEYBOARD_SHORTCUTS
+import config
LOCK_LAYOUT_LEVEL = {
'PREVENT_DOCKING': 'docking',
@@ -23,6 +24,15 @@ def register_browser_preferences(self):
gettext("Show system objects?"), 'boolean', False,
category_label=PREF_LABEL_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 98bb1974d..714b4c9e0 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, MIMETYPE_APP_JS
+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,79 @@ 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,
+ 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 +448,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 +493,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 +509,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 +554,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 +599,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 +618,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 +628,10 @@ class ServerNode(PGChildNodeView):
errormsg=gettext("Could not find the required server.")
)
+ if config.SERVER_MODE and server.shared and \
+ server.user_id != current_user.id:
+ 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 +661,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'),
@@ -540,7 +697,8 @@ class ServerNode(PGChildNodeView):
self._server_modify_disallowed_when_connected(
connected, data, disp_lbl)
- idx = self._set_valid_attr_value(data, config_param_map, server)
+ idx = self._set_valid_attr_value(data, config_param_map, server,
+ sharedserver)
if idx == 0:
return make_json_response(
@@ -567,7 +725,7 @@ class ServerNode(PGChildNodeView):
node=self.blueprint.generate_browser_node(
"%d" % (server.id), server.servergroup_id,
server.name,
- server_icon_and_background(connected, manager, server),
+ server_icon_and_background(connected, manager, sharedserver) if server.shared and server.user_id != current_user.id else server_icon_and_background(connected, manager, server),
True,
self.node_type,
connected=connected,
@@ -576,7 +734,7 @@ class ServerNode(PGChildNodeView):
)
)
- def _set_valid_attr_value(self, data, config_param_map, server):
+ def _set_valid_attr_value(self, data, config_param_map, server, sharedserver):
idx = 0
for arg in config_param_map:
@@ -586,7 +744,11 @@ class ServerNode(PGChildNodeView):
# it manually to integer
if arg == 'sslcompression':
value = 1 if value else 0
- setattr(server, config_param_map[arg], value)
+ # setattr(server, config_param_map[arg], value)
+ 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
return idx
@@ -612,11 +774,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 +815,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 +823,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 +835,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 +971,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 +1175,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 +1237,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 +1298,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 +1743,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..e9eb73ce0
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/servers_test_data.json
@@ -0,0 +1,863 @@
+{
+ "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
+ }
+ },
+ {
+ "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..1b23fcdb8
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_add_server.py
@@ -0,0 +1,84 @@
+##########################################################################
+#
+# 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 'connect_now' in self.test_data:
+ self.server['connect_now'] = self.test_data['connect_now']
+ self.server['password'] = self.server['db_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_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_service_id.py b/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_service_id.py
deleted file mode 100644
index 9d8da94fd..000000000
--- a/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_service_id.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 ServersWithServiceIDAddTestCase(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['service'] = "TestDB"
- 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:
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin][RM4979]: Configuration files for data sources or similar.
2020-08-20 10:28 [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-21 08:25 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-08-21 12:30 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
@ 2020-08-25 12:22 ` Akshay Joshi <[email protected]>
2020-09-01 11:17 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Akshay Joshi @ 2020-08-25 12:22 UTC (permalink / raw)
To: Pradip Parkale <[email protected]>; pgadmin-hackers
Hi Pradip
Following are the GUI review comments:
*Testing Scenario*: I have three users U1 and U2 are Admin and U3 is a
normal user. Login from U1 first time and pgAdmin4 discover all the local
servers. Steps that I perform:
- Shared one server.
- Create a group 'Test' and move one server to that group.
- Share the server of the 'Test' group as well.
[image: Screenshot 2020-08-25 at 4.38.32 PM.png]
*Issues:*
- When login using U2 or U3 PostgreSQL 9.2 and 10 shown twice which
should not. Refer below screenshot
[image: Screenshot 2020-08-25 at 4.30.22 PM.png]
- When expanding the server or clicking on the 'Connect Server' menu of
the shared server, it opens the properties dialog.
- Try to update the IPAddress, Port it is not updating for the shared
server. Check other properties too.
- When we set "Hide shared server?" setting to true/false. A complete
browser tree should be refreshed. With the current implementation, each
group needs to be refreshed individually and the server group still visible
while refreshing the complete page Browser Tree looks perfect.
- When there is only one server in a group and we expanded the group
'Connect to server' dialog appears which should not. (*This may be an old
issue)
- Schema Diff shared servers are not visible inside the appropriate
group in the source and target server list.
- Documentation changes required (Create a new Redmine).
- SonarQube shows 1 new Bug and 4 new code smell. (Fixed the Bug, code
smell can be fixed later)
- 'pgAdmin4_test_non_admin_credentials' should be added in '
test_config.json.in' file.
- For Desktop mode shared server test cases should be skipped. It is
failing at the moment.
Code review still remains :)
On Fri, Aug 21, 2020 at 6:00 PM Pradip Parkale <
[email protected]> wrote:
> Hi Akshay,
> Please find the updated patch.
>
> On Fri, Aug 21, 2020 at 1:55 PM Akshay Joshi <
> [email protected]> wrote:
>
>> Hi Pradip
>>
>> The patch is not applied, can you please rebase and send it again.
>>
>> On Thu, Aug 20, 2020 at 3:58 PM Pradip Parkale <
>> [email protected]> wrote:
>>
>>> 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
>>>
>>
>>
>> --
>> *Thanks & Regards*
>> *Akshay Joshi*
>> *pgAdmin Hacker | Sr. Software Architect*
>> *EDB Postgres <http://edbpostgres.com>*
>>
>> *Mobile: +91 976-788-8246*
>>
>
>
> --
> Thanks & Regards,
> Pradip Parkale
> Software Engineer | EnterpriseDB Corporation
>
--
*Thanks & Regards*
*Akshay Joshi*
*pgAdmin Hacker | Sr. Software Architect*
*EDB Postgres <http://edbpostgres.com>*
*Mobile: +91 976-788-8246*
Attachments:
[image/png] Screenshot 2020-08-25 at 4.30.22 PM.png (70.2K, 3-Screenshot%202020-08-25%20at%204.30.22%20PM.png)
download | view image
[image/png] Screenshot 2020-08-25 at 4.38.32 PM.png (52.9K, 4-Screenshot%202020-08-25%20at%204.38.32%20PM.png)
download | view image
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin][RM4979]: Configuration files for data sources or similar.
2020-08-20 10:28 [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-21 08:25 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-08-21 12:30 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-25 12:22 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
@ 2020-09-01 11:17 ` Pradip Parkale <[email protected]>
2020-09-02 08:18 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Pradip Parkale @ 2020-09-01 11:17 UTC (permalink / raw)
To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers
Hi Akshay,
Please find the updated patch.
On Tue, Aug 25, 2020 at 5:53 PM Akshay Joshi <[email protected]>
wrote:
> Hi Pradip
>
> Following are the GUI review comments:
> *Testing Scenario*: I have three users U1 and U2 are Admin and U3 is a
> normal user. Login from U1 first time and pgAdmin4 discover all the local
> servers. Steps that I perform:
>
> - Shared one server.
> - Create a group 'Test' and move one server to that group.
> - Share the server of the 'Test' group as well.
>
> [image: Screenshot 2020-08-25 at 4.38.32 PM.png]
> *Issues:*
>
> - When login using U2 or U3 PostgreSQL 9.2 and 10 shown twice which
> should not. Refer below screenshot
> [image: Screenshot 2020-08-25 at 4.30.22 PM.png]
>
> Fixed. If the server is auto-discovered and U1 shared the auto-discovered
server(PostgreSQL 9.2 in the above example) and then log in using U2, in
this case, shared server(PostgreSQL 9.2) will not visible to U2 as the same
server will be auto-discovered for U2.
>
> -
> - When expanding the server or clicking on the 'Connect Server' menu
> of the shared server, it opens the properties dialog.
>
> This is expected. Properties dialogue will open the first time for a
shared server where the user has to enter server details. This will happen
for the user who is not the owner of the server.
>
> - Try to update the IPAddress, Port it is not updating for the shared
> server. Check other properties too.
>
> Fixed.
>
> -
> - When we set "Hide shared server?" setting to true/false. A complete
> browser tree should be refreshed. With the current implementation, each
> group needs to be refreshed individually and the server group still visible
> while refreshing the complete page Browser Tree looks perfect.
>
> Fixed.
>
> - When there is only one server in a group and we expanded the group
> 'Connect to server' dialog appears which should not. (*This may be an old
> issue)
>
> This is expected and its an old behavior.
>
> -
> - Schema Diff shared servers are not visible inside the appropriate
> group in the source and target server list.
>
> Fixed
>
> - Documentation changes required (Create a new Redmine).
>
> Done.
>
> -
> - SonarQube shows 1 new Bug and 4 new code smell. (Fixed the Bug, code
> smell can be fixed later)
>
> Fixed.
>
> - 'pgAdmin4_test_non_admin_credentials' should be added in '
> test_config.json.in' file.
>
> Fixed.
>
> - For Desktop mode shared server test cases should be skipped. It is
> failing at the moment.
>
> Fixed.
> Code review still remains :)
>
> On Fri, Aug 21, 2020 at 6:00 PM Pradip Parkale <
> [email protected]> wrote:
>
>> Hi Akshay,
>> Please find the updated patch.
>>
>> On Fri, Aug 21, 2020 at 1:55 PM Akshay Joshi <
>> [email protected]> wrote:
>>
>>> Hi Pradip
>>>
>>> The patch is not applied, can you please rebase and send it again.
>>>
>>> On Thu, Aug 20, 2020 at 3:58 PM Pradip Parkale <
>>> [email protected]> wrote:
>>>
>>>> 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
>>>>
>>>
>>>
>>> --
>>> *Thanks & Regards*
>>> *Akshay Joshi*
>>> *pgAdmin Hacker | Sr. Software Architect*
>>> *EDB Postgres <http://edbpostgres.com>*
>>>
>>> *Mobile: +91 976-788-8246*
>>>
>>
>>
>> --
>> Thanks & Regards,
>> Pradip Parkale
>> Software Engineer | EnterpriseDB Corporation
>>
>
>
> --
> *Thanks & Regards*
> *Akshay Joshi*
> *pgAdmin Hacker | Sr. Software Architect*
> *EDB Postgres <http://edbpostgres.com>*
>
> *Mobile: +91 976-788-8246*
>
--
Thanks & Regards,
Pradip Parkale
Software Engineer | EnterpriseDB Corporation
Attachments:
[image/png] Screenshot 2020-08-25 at 4.30.22 PM.png (70.2K, 3-Screenshot%202020-08-25%20at%204.30.22%20PM.png)
download | view image
[image/png] Screenshot 2020-08-25 at 4.38.32 PM.png (52.9K, 4-Screenshot%202020-08-25%20at%204.38.32%20PM.png)
download | view image
[application/octet-stream] RM4979_v3.patch (140.2K, 5-RM4979_v3.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/__init__.py b/web/pgadmin/__init__.py
index 516ba2c4d..1a712151a 100644
--- a/web/pgadmin/__init__.py
+++ b/web/pgadmin/__init__.py
@@ -28,7 +28,7 @@ from werkzeug.datastructures import ImmutableDict
from werkzeug.local import LocalProxy
from werkzeug.utils import find_modules
-from pgadmin.model import db, Role, Server, ServerGroup, \
+from pgadmin.model import db, Role, Server, SharedServer, ServerGroup, \
User, Keys, Version, SCHEMA_VERSION as CURRENT_SCHEMA_VERSION
from pgadmin.utils import PgAdminModule, driver, KeyManager
from pgadmin.utils.preferences import Preferences
diff --git a/web/pgadmin/browser/register_browser_preferences.py b/web/pgadmin/browser/register_browser_preferences.py
index 948be41b8..215421cbf 100644
--- a/web/pgadmin/browser/register_browser_preferences.py
+++ b/web/pgadmin/browser/register_browser_preferences.py
@@ -9,6 +9,7 @@
from flask_babelex import gettext
from pgadmin.utils.constants import PREF_LABEL_DISPLAY,\
PREF_LABEL_KEYBOARD_SHORTCUTS
+import config
LOCK_LAYOUT_LEVEL = {
'PREVENT_DOCKING': 'docking',
@@ -23,6 +24,15 @@ def register_browser_preferences(self):
gettext("Show system objects?"), 'boolean', False,
category_label=PREF_LABEL_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 717795bde..9f0b15ff3 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
@@ -296,9 +357,25 @@ class ServerGroupView(NodeView):
def nodes(self, gid=None):
"""Return a JSON document listing the server groups for the user"""
nodes = []
+ hide_shared_server = None
+ if config.SERVER_MODE:
+ pref = Preferences.module('browser')
+ hide_shared_server = pref.preference('hide_shared_server').get()
if gid is None:
- groups = ServerGroup.query.filter_by(user_id=current_user.id)
+ if config.SERVER_MODE:
+ server_groups = ServerGroup.query.all()
+ groups = []
+ for group in server_groups:
+ if hide_shared_server and \
+ ServerGroupModule.has_shared_server(group.id) and \
+ group.user_id != current_user.id:
+ continue
+ if group.user_id == current_user.id or \
+ ServerGroupModule.has_shared_server(group.id):
+ groups.append(group)
+ else:
+ groups = ServerGroup.query.filter_by(user_id=current_user.id)
for group in groups:
nodes.append(
@@ -306,14 +383,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 +399,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 2eb058a40..8b1486cac 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
@@ -32,6 +32,8 @@ 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, MIMETYPE_APP_JS, \
SERVER_CONNECTION_CLOSED
+from sqlalchemy import or_
+from pgadmin.utils.preferences import Preferences
def has_any(data, keys):
@@ -92,6 +94,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
@@ -114,15 +120,61 @@ 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
+ auto_detected_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.discovery_id:
+ auto_detected_server = server.name
+ 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)
+
+ if shared_server.name == auto_detected_server:
+ continue
+ server = self.get_shared_server_properties(server,
+ shared_server)
+
connected = False
manager = None
errmsg = None
@@ -158,7 +210,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
@@ -230,6 +285,79 @@ 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,
+ 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):
@@ -327,12 +455,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()
@@ -365,7 +500,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
)
)
@@ -379,9 +516,12 @@ 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.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 make_json_response(
@@ -426,14 +566,37 @@ 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,
+ user_name=server.username
),
)
+ 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:
@@ -449,10 +612,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:
@@ -467,8 +631,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(
@@ -477,6 +641,10 @@ class ServerNode(PGChildNodeView):
errormsg=gettext("Could not find the required server.")
)
+ if config.SERVER_MODE and server.shared and \
+ server.user_id != current_user.id:
+ sharedserver = ServerModule.get_shared_server(server, gid)
+
# Not all parameters can be modified, while the server is connected
config_param_map = {
'name': 'name',
@@ -506,11 +674,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,7 +710,8 @@ class ServerNode(PGChildNodeView):
self._server_modify_disallowed_when_connected(
connected, data, disp_lbl)
- idx = self._set_valid_attr_value(data, config_param_map, server)
+ idx = self._set_valid_attr_value(gid, data, config_param_map, server,
+ sharedserver)
if idx == 0:
return make_json_response(
@@ -568,7 +738,11 @@ class ServerNode(PGChildNodeView):
node=self.blueprint.generate_browser_node(
"%d" % (server.id), server.servergroup_id,
server.name,
- server_icon_and_background(connected, manager, server),
+ server_icon_and_background(
+ connected, manager, sharedserver)
+ if server.shared and server.user_id != current_user.id
+ else server_icon_and_background(
+ connected, manager, server),
True,
self.node_type,
connected=connected,
@@ -577,7 +751,8 @@ class ServerNode(PGChildNodeView):
)
)
- def _set_valid_attr_value(self, data, config_param_map, server):
+ def _set_valid_attr_value(self, gid, data, config_param_map, server,
+ sharedserver):
idx = 0
for arg in config_param_map:
@@ -585,9 +760,17 @@ class ServerNode(PGChildNodeView):
value = data[arg]
# sqlite3 do not have boolean type so we need to convert
# it manually to integer
+ if 'shared' in data and not data['shared']:
+ # Delete the shared server from DB if server
+ # owner uncheck shared property
+ self.delete_shared_server(server.name, gid)
if arg == 'sslcompression':
value = 1 if value else 0
- setattr(server, config_param_map[arg], value)
+ # setattr(server, config_param_map[arg], value)
+ 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
return idx
@@ -613,11 +796,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 = []
@@ -625,6 +808,11 @@ class ServerNode(PGChildNodeView):
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()
@@ -654,7 +842,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:
@@ -663,9 +850,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()
@@ -677,50 +862,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):
@@ -802,11 +998,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
@@ -1006,9 +1202,22 @@ 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())
+ # Return if username is blank
+ if server.username is None:
+ return make_json_response(
+ status=200,
+ success=0,
+ errormsg=gettext(
+ u"Please enter the server details to connect")
+ )
if current_user and hasattr(current_user, 'id'):
# Fetch User Details.
user = User.query.filter_by(id=current_user.id).first()
@@ -1063,7 +1272,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 \
@@ -1125,10 +1333,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
@@ -1560,21 +1776,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(
@@ -1599,10 +1833,7 @@ 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()
if server is None:
return make_json_response(
success=0,
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 8c43a55f7..f6197ede2 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,17 @@ 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: 'isShared',
+ },
+ {
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,11 +787,27 @@ 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'],
},{
id: 'host', label: gettext('Host name/address'), type: 'text', group: gettext('Connection'),
- mode: ['properties', 'edit', 'create'],
+ mode: ['properties', 'edit', 'create'],disabled: 'isShared',
control: Backform.InputControl.extend({
onChange: function() {
Backform.InputControl.prototype.onChange.apply(this, arguments);
@@ -798,7 +828,7 @@ define('pgadmin.node.server', [
}),
},{
id: 'port', label: gettext('Port'), type: 'int', group: gettext('Connection'),
- mode: ['properties', 'edit', 'create'], min: 1, max: 65535,
+ mode: ['properties', 'edit', 'create'], min: 1, max: 65535, disabled: 'isShared',
control: Backform.InputControl.extend({
onChange: function() {
Backform.InputControl.prototype.onChange.apply(this, arguments);
@@ -819,7 +849,7 @@ define('pgadmin.node.server', [
}),
},{
id: 'db', label: gettext('Maintenance database'), type: 'text', group: gettext('Connection'),
- mode: ['properties', 'edit', 'create'], readonly: 'isConnected',
+ mode: ['properties', 'edit', 'create'], readonly: 'isConnected',disabled: 'isShared',
},{
id: 'username', label: gettext('Username'), type: 'text', group: gettext('Connection'),
mode: ['properties', 'edit', 'create'],
@@ -1057,6 +1087,21 @@ 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;
+
+ },
+ isShared: function(model){
+ var serverOwner = model.attributes.user_id;
+ if (!model.isNew() && serverOwner != current_user.id && model.attributes.shared){
+ return true;
+ }
+ return false;
+ },
validate: function() {
const validateModel = new modelValidation.ModelValidation(this);
return validateModel.validate();
@@ -1153,7 +1198,36 @@ 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;
+ var server_url = obj.generate_url(item, 'obj', data, true);
+ // Fetch the updated data
+ $.get(server_url)
+ .done(function(res) {
+ if (res.shared && _.isNull(res.username) && data.user_id != current_user.id){
+ if (selectedTreeNodeData._type == 'server'){
+ pgAdmin.Browser.Node.callbacks.show_obj_properties.call(
+ pgAdmin.Browser.Nodes[tree.itemData(item)._type], {action: 'edit'}
+ );
+ data.is_connecting = false;
+ tree.unload(item);
+ tree.setInode(item);
+ tree.addIcon(item, {icon: 'icon-shared-server-not-connected'});
+ }else{
+ data.is_connecting = false;
+ tree.unload(item);
+ tree.setInode(item);
+ tree.addIcon(item, {icon: 'icon-shared-server-not-connected'});
+ }
+ }
+ return;
+ }).always(function(){
+ data.is_connecting = false;
+ });
+
var wasConnected = reconnect || data.connected,
onFailure = function(
xhr, status, error, _node, _data, _tree, _item, _wasConnected
@@ -1164,7 +1238,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) {
@@ -1321,7 +1400,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
@@ -1387,7 +1470,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..829c273aa 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,21 @@
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;
+}
+
+.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..117a2982b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/servers_test_data.json
@@ -0,0 +1,862 @@
+{
+ "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
+ }
+ },
+ {
+ "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,
+ "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,
+ "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..1b23fcdb8
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_add_server.py
@@ -0,0 +1,84 @@
+##########################################################################
+#
+# 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 'connect_now' in self.test_data:
+ self.server['connect_now'] = self.test_data['connect_now']
+ self.server['password'] = self.server['db_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_connect_timeout.py b/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_connect_timeout.py
deleted file mode 100644
index 94d555617..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.assertEqual(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_service_id.py b/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_service_id.py
deleted file mode 100644
index 9b4d4d377..000000000
--- a/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_service_id.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 ServersWithServiceIDAddTestCase(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['service'] = "TestDB"
- response = self.tester.post(
- url,
- data=json.dumps(self.server),
- content_type='html/json'
- )
- self.assertEqual(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 7c0b005d9..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.assertEqual(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..6d49cea7b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_check_connect.py
@@ -0,0 +1,107 @@
+##########################################################################
+#
+# 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
+
+
+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'
+
+ 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 8444e1d72..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.assertEqual(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.assertEqual(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.assertEqual(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 30917bd3c..78911f5a8 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 0b4dc183b..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.assertEqual(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 31c7a476e..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.assertEqual(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..b3f8215a4
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_shared_server.py
@@ -0,0 +1,134 @@
+##########################################################################
+#
+# 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
+import config
+
+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"""
+
+ if config.SERVER_MODE is False:
+ self.skipTest(
+ "Can not run shared servers test cases in the SERVER mode."
+ )
+ 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"""
+ if config.SERVER_MODE is False:
+ self.skipTest(
+ "Can not run shared servers test cases in the Desktop mode."
+ )
+ 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'
+ )
+
+ 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..ac1a0c821 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,25 @@ 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 +80,12 @@ 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 true; },
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/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js
index f5ab09399..0eb7840b0 100644
--- a/web/pgadmin/browser/static/js/browser.js
+++ b/web/pgadmin/browser/static/js/browser.js
@@ -19,7 +19,7 @@ define('pgadmin.browser', [
'pgadmin.browser.error', 'pgadmin.browser.frame',
'pgadmin.browser.node', 'pgadmin.browser.collection', 'pgadmin.browser.activity',
'sources/codemirror/addon/fold/pgadmin-sqlfoldcode',
- 'pgadmin.browser.keyboard', 'sources/tree/pgadmin_tree_save_state',
+ 'pgadmin.browser.keyboard', 'sources/tree/pgadmin_tree_save_state','jquery.acisortable', 'jquery.acifragment',
], function(
tree,
gettext, url_for, require, $, _,
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/preferences/__init__.py b/web/pgadmin/preferences/__init__.py
index 6212f63f8..db11d1d09 100644
--- a/web/pgadmin/preferences/__init__.py
+++ b/web/pgadmin/preferences/__init__.py
@@ -23,6 +23,7 @@ from pgadmin.utils.ajax import success_return, \
from pgadmin.utils.menu import MenuItem
from pgadmin.utils.preferences import Preferences
from pgadmin.utils.constants import MIMETYPE_APP_JS
+from pgadmin.browser.server_groups import ServerGroupModule as sgm
MODULE_NAME = 'preferences'
@@ -203,6 +204,7 @@ def save(pid):
res, msg = Preferences.save(
data['mid'], data['category_id'], data['id'], data['value'])
+ sgm.get_nodes(sgm)
if not res:
return internal_server_error(errormsg=msg)
diff --git a/web/pgadmin/preferences/static/js/preferences.js b/web/pgadmin/preferences/static/js/preferences.js
index 62eda7aa6..9087c9589 100644
--- a/web/pgadmin/preferences/static/js/preferences.js
+++ b/web/pgadmin/preferences/static/js/preferences.js
@@ -477,6 +477,28 @@ define('pgadmin.preferences', [
if(pref.name == 'theme') {
requires_refresh = true;
}
+
+ if(pref.name == 'hide_shared_server') {
+ Alertify.confirm(
+ gettext('Browser tree refresh required'),
+ gettext('A browser tree refresh is required. Do you wish to refresh the tree?'),
+ function() {
+ pgAdmin.Browser.tree.destroy({
+ success: function() {
+ pgAdmin.Browser.initializeBrowserTree(pgAdmin.Browser);
+ return true;
+ },
+ });
+ },
+ function() {
+ return true;
+ }
+ ).set('labels', {
+ ok: gettext('Refresh'),
+ cancel: gettext('Later'),
+ });
+ }
+
});
if(requires_refresh) {
diff --git a/web/pgadmin/tools/schema_diff/__init__.py b/web/pgadmin/tools/schema_diff/__init__.py
index a325e9a01..03d3478b6 100644
--- a/web/pgadmin/tools/schema_diff/__init__.py
+++ b/web/pgadmin/tools/schema_diff/__init__.py
@@ -27,6 +27,7 @@ from config import PG_DEFAULT_DRIVER
from pgadmin.utils.driver import get_driver
from pgadmin.utils.preferences import Preferences
from pgadmin.utils.constants import PREF_LABEL_DISPLAY, MIMETYPE_APP_JS
+from sqlalchemy import or_
MODULE_NAME = 'schema_diff'
@@ -276,8 +277,11 @@ def servers():
from pgadmin.browser.server_groups.servers import\
server_icon_and_background
+ Server.query.filter(
+ or_(Server.user_id == current_user.id, Server.shared))
- for server in Server.query.filter_by(user_id=current_user.id):
+ for server in Server.query.filter(
+ or_(Server.user_id == current_user.id, Server.shared)):
manager = driver.connection_manager(server.id)
conn = manager.connection()
connected = conn.connected()
diff --git a/web/pgadmin/tools/schema_diff/static/js/schema_diff.backform.js b/web/pgadmin/tools/schema_diff/static/js/schema_diff.backform.js
index 55ef1e5f4..1b095db98 100644
--- a/web/pgadmin/tools/schema_diff/static/js/schema_diff.backform.js
+++ b/web/pgadmin/tools/schema_diff/static/js/schema_diff.backform.js
@@ -175,7 +175,7 @@ let SchemaDiffSelect2Control =
let span = this.$el.find('.select2-selection .select2-selection__rendered span.wcTabIcon'),
selSpan = this.$el.find('option:selected');
- if (span.hasClass('icon-server-not-connected')) {
+ if (span.hasClass('icon-server-not-connected') || span.hasClass('icon-shared-server-not-connected')) {
let icon = (data.icon) ? data.icon : 'icon-pg';
span.removeClass('icon-server-not-connected');
span.addClass(icon);
diff --git a/web/pgadmin/utils/driver/psycopg2/server_manager.py b/web/pgadmin/utils/driver/psycopg2/server_manager.py
index c35a3726f..6d3ce25ff 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 6d66af15a..491bddbd1 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__)), "../"))
@@ -1598,3 +1600,217 @@ 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
+
+
+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/regression/test_config.json.in b/web/regression/test_config.json.in
index 690f46c1c..393be6a84 100644
--- a/web/regression/test_config.json.in
+++ b/web/regression/test_config.json.in
@@ -11,6 +11,11 @@
"login_password": "PASSWORD",
"login_username": "[email protected]"
},
+ "pgAdmin4_test_non_admin_credentials": {
+ "new_password": "NEWPASSWORD",
+ "login_password": "PASSWORD",
+ "login_username": "[email protected]"
+ },
"pgAdmin4_ldap_credentials": {
"login_password": "PASSWORD",
"login_username": "USERNAME"
diff --git a/web/setup.py b/web/setup.py
index c88c4e1fa..93ae25264 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)
@@ -248,6 +249,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:
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin][RM4979]: Configuration files for data sources or similar.
2020-08-20 10:28 [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-21 08:25 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-08-21 12:30 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-25 12:22 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-01 11:17 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
@ 2020-09-02 08:18 ` Akshay Joshi <[email protected]>
2020-09-03 06:55 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Akshay Joshi @ 2020-09-02 08:18 UTC (permalink / raw)
To: Pradip Parkale <[email protected]>; +Cc: pgadmin-hackers
Hi Pradip
Please fix SonarQube issues that introduce with this patch.
On Tue, Sep 1, 2020 at 4:47 PM Pradip Parkale <
[email protected]> wrote:
> Hi Akshay,
>
> Please find the updated patch.
>
> On Tue, Aug 25, 2020 at 5:53 PM Akshay Joshi <
> [email protected]> wrote:
>
>> Hi Pradip
>>
>> Following are the GUI review comments:
>> *Testing Scenario*: I have three users U1 and U2 are Admin and U3 is a
>> normal user. Login from U1 first time and pgAdmin4 discover all the local
>> servers. Steps that I perform:
>>
>> - Shared one server.
>> - Create a group 'Test' and move one server to that group.
>> - Share the server of the 'Test' group as well.
>>
>> [image: Screenshot 2020-08-25 at 4.38.32 PM.png]
>> *Issues:*
>>
>> - When login using U2 or U3 PostgreSQL 9.2 and 10 shown twice which
>> should not. Refer below screenshot
>> [image: Screenshot 2020-08-25 at 4.30.22 PM.png]
>>
>> Fixed. If the server is auto-discovered and U1 shared the auto-discovered
> server(PostgreSQL 9.2 in the above example) and then log in using U2, in
> this case, shared server(PostgreSQL 9.2) will not visible to U2 as the same
> server will be auto-discovered for U2.
>
>>
>> -
>> - When expanding the server or clicking on the 'Connect Server' menu
>> of the shared server, it opens the properties dialog.
>>
>> This is expected. Properties dialogue will open the first time for a
> shared server where the user has to enter server details. This will happen
> for the user who is not the owner of the server.
>
>>
>> - Try to update the IPAddress, Port it is not updating for the shared
>> server. Check other properties too.
>>
>> Fixed.
>
>>
>> -
>> - When we set "Hide shared server?" setting to true/false. A complete
>> browser tree should be refreshed. With the current implementation, each
>> group needs to be refreshed individually and the server group still visible
>> while refreshing the complete page Browser Tree looks perfect.
>>
>> Fixed.
>
>>
>> - When there is only one server in a group and we expanded the group
>> 'Connect to server' dialog appears which should not. (*This may be an old
>> issue)
>>
>> This is expected and its an old behavior.
>
>>
>> -
>> - Schema Diff shared servers are not visible inside the appropriate
>> group in the source and target server list.
>>
>> Fixed
>
>>
>> - Documentation changes required (Create a new Redmine).
>>
>> Done.
>
>>
>> -
>> - SonarQube shows 1 new Bug and 4 new code smell. (Fixed the Bug,
>> code smell can be fixed later)
>>
>> Fixed.
>
>>
>> - 'pgAdmin4_test_non_admin_credentials' should be added in '
>> test_config.json.in' file.
>>
>> Fixed.
>
>>
>> - For Desktop mode shared server test cases should be skipped. It is
>> failing at the moment.
>>
>> Fixed.
>
>> Code review still remains :)
>>
>> On Fri, Aug 21, 2020 at 6:00 PM Pradip Parkale <
>> [email protected]> wrote:
>>
>>> Hi Akshay,
>>> Please find the updated patch.
>>>
>>> On Fri, Aug 21, 2020 at 1:55 PM Akshay Joshi <
>>> [email protected]> wrote:
>>>
>>>> Hi Pradip
>>>>
>>>> The patch is not applied, can you please rebase and send it again.
>>>>
>>>> On Thu, Aug 20, 2020 at 3:58 PM Pradip Parkale <
>>>> [email protected]> wrote:
>>>>
>>>>> 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
>>>>>
>>>>
>>>>
>>>> --
>>>> *Thanks & Regards*
>>>> *Akshay Joshi*
>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>
>>>> *Mobile: +91 976-788-8246*
>>>>
>>>
>>>
>>> --
>>> Thanks & Regards,
>>> Pradip Parkale
>>> Software Engineer | EnterpriseDB Corporation
>>>
>>
>>
>> --
>> *Thanks & Regards*
>> *Akshay Joshi*
>> *pgAdmin Hacker | Sr. Software Architect*
>> *EDB Postgres <http://edbpostgres.com>*
>>
>> *Mobile: +91 976-788-8246*
>>
>
>
> --
> Thanks & Regards,
> Pradip Parkale
> Software Engineer | EnterpriseDB Corporation
>
--
*Thanks & Regards*
*Akshay Joshi*
*pgAdmin Hacker | Sr. Software Architect*
*EDB Postgres <http://edbpostgres.com>*
*Mobile: +91 976-788-8246*
Attachments:
[image/png] Screenshot 2020-08-25 at 4.30.22 PM.png (70.2K, 3-Screenshot%202020-08-25%20at%204.30.22%20PM.png)
download | view image
[image/png] Screenshot 2020-08-25 at 4.38.32 PM.png (52.9K, 4-Screenshot%202020-08-25%20at%204.38.32%20PM.png)
download | view image
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin][RM4979]: Configuration files for data sources or similar.
2020-08-20 10:28 [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-21 08:25 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-08-21 12:30 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-25 12:22 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-01 11:17 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-09-02 08:18 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
@ 2020-09-03 06:55 ` Pradip Parkale <[email protected]>
2020-09-03 07:34 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Pradip Parkale @ 2020-09-03 06:55 UTC (permalink / raw)
To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers
Hi Akshay,
Please find the updated patch.
On Wed, Sep 2, 2020 at 1:48 PM Akshay Joshi <[email protected]>
wrote:
> Hi Pradip
>
> Please fix SonarQube issues that introduce with this patch.
>
> On Tue, Sep 1, 2020 at 4:47 PM Pradip Parkale <
> [email protected]> wrote:
>
>> Hi Akshay,
>>
>> Please find the updated patch.
>>
>> On Tue, Aug 25, 2020 at 5:53 PM Akshay Joshi <
>> [email protected]> wrote:
>>
>>> Hi Pradip
>>>
>>> Following are the GUI review comments:
>>> *Testing Scenario*: I have three users U1 and U2 are Admin and U3 is a
>>> normal user. Login from U1 first time and pgAdmin4 discover all the local
>>> servers. Steps that I perform:
>>>
>>> - Shared one server.
>>> - Create a group 'Test' and move one server to that group.
>>> - Share the server of the 'Test' group as well.
>>>
>>> [image: Screenshot 2020-08-25 at 4.38.32 PM.png]
>>> *Issues:*
>>>
>>> - When login using U2 or U3 PostgreSQL 9.2 and 10 shown twice which
>>> should not. Refer below screenshot
>>> [image: Screenshot 2020-08-25 at 4.30.22 PM.png]
>>>
>>> Fixed. If the server is auto-discovered and U1 shared the
>> auto-discovered server(PostgreSQL 9.2 in the above example) and then log in
>> using U2, in this case, shared server(PostgreSQL 9.2) will not visible to
>> U2 as the same server will be auto-discovered for U2.
>>
>>>
>>> -
>>> - When expanding the server or clicking on the 'Connect Server' menu
>>> of the shared server, it opens the properties dialog.
>>>
>>> This is expected. Properties dialogue will open the first time for a
>> shared server where the user has to enter server details. This will happen
>> for the user who is not the owner of the server.
>>
>>>
>>> - Try to update the IPAddress, Port it is not updating for the
>>> shared server. Check other properties too.
>>>
>>> Fixed.
>>
>>>
>>> -
>>> - When we set "Hide shared server?" setting to true/false. A
>>> complete browser tree should be refreshed. With the current implementation,
>>> each group needs to be refreshed individually and the server group still
>>> visible while refreshing the complete page Browser Tree looks perfect.
>>>
>>> Fixed.
>>
>>>
>>> - When there is only one server in a group and we expanded the group
>>> 'Connect to server' dialog appears which should not. (*This may be an old
>>> issue)
>>>
>>> This is expected and its an old behavior.
>>
>>>
>>> -
>>> - Schema Diff shared servers are not visible inside the appropriate
>>> group in the source and target server list.
>>>
>>> Fixed
>>
>>>
>>> - Documentation changes required (Create a new Redmine).
>>>
>>> Done.
>>
>>>
>>> -
>>> - SonarQube shows 1 new Bug and 4 new code smell. (Fixed the Bug,
>>> code smell can be fixed later)
>>>
>>> Fixed.
>>
>>>
>>> - 'pgAdmin4_test_non_admin_credentials' should be added in '
>>> test_config.json.in' file.
>>>
>>> Fixed.
>>
>>>
>>> - For Desktop mode shared server test cases should be skipped. It is
>>> failing at the moment.
>>>
>>> Fixed.
>>
>>> Code review still remains :)
>>>
>>> On Fri, Aug 21, 2020 at 6:00 PM Pradip Parkale <
>>> [email protected]> wrote:
>>>
>>>> Hi Akshay,
>>>> Please find the updated patch.
>>>>
>>>> On Fri, Aug 21, 2020 at 1:55 PM Akshay Joshi <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Pradip
>>>>>
>>>>> The patch is not applied, can you please rebase and send it again.
>>>>>
>>>>> On Thu, Aug 20, 2020 at 3:58 PM Pradip Parkale <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> 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
>>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> *Thanks & Regards*
>>>>> *Akshay Joshi*
>>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>
>>>>> *Mobile: +91 976-788-8246*
>>>>>
>>>>
>>>>
>>>> --
>>>> Thanks & Regards,
>>>> Pradip Parkale
>>>> Software Engineer | EnterpriseDB Corporation
>>>>
>>>
>>>
>>> --
>>> *Thanks & Regards*
>>> *Akshay Joshi*
>>> *pgAdmin Hacker | Sr. Software Architect*
>>> *EDB Postgres <http://edbpostgres.com>*
>>>
>>> *Mobile: +91 976-788-8246*
>>>
>>
>>
>> --
>> Thanks & Regards,
>> Pradip Parkale
>> Software Engineer | EnterpriseDB Corporation
>>
>
>
> --
> *Thanks & Regards*
> *Akshay Joshi*
> *pgAdmin Hacker | Sr. Software Architect*
> *EDB Postgres <http://edbpostgres.com>*
>
> *Mobile: +91 976-788-8246*
>
--
Thanks & Regards,
Pradip Parkale
Software Engineer | EnterpriseDB Corporation
Attachments:
[image/png] Screenshot 2020-08-25 at 4.30.22 PM.png (70.2K, 3-Screenshot%202020-08-25%20at%204.30.22%20PM.png)
download | view image
[image/png] Screenshot 2020-08-25 at 4.38.32 PM.png (52.9K, 4-Screenshot%202020-08-25%20at%204.38.32%20PM.png)
download | view image
[application/octet-stream] RM4979_v4.patch (142.1K, 5-RM4979_v4.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/__init__.py b/web/pgadmin/__init__.py
index 516ba2c4d..1a712151a 100644
--- a/web/pgadmin/__init__.py
+++ b/web/pgadmin/__init__.py
@@ -28,7 +28,7 @@ from werkzeug.datastructures import ImmutableDict
from werkzeug.local import LocalProxy
from werkzeug.utils import find_modules
-from pgadmin.model import db, Role, Server, ServerGroup, \
+from pgadmin.model import db, Role, Server, SharedServer, ServerGroup, \
User, Keys, Version, SCHEMA_VERSION as CURRENT_SCHEMA_VERSION
from pgadmin.utils import PgAdminModule, driver, KeyManager
from pgadmin.utils.preferences import Preferences
diff --git a/web/pgadmin/browser/register_browser_preferences.py b/web/pgadmin/browser/register_browser_preferences.py
index 948be41b8..215421cbf 100644
--- a/web/pgadmin/browser/register_browser_preferences.py
+++ b/web/pgadmin/browser/register_browser_preferences.py
@@ -9,6 +9,7 @@
from flask_babelex import gettext
from pgadmin.utils.constants import PREF_LABEL_DISPLAY,\
PREF_LABEL_KEYBOARD_SHORTCUTS
+import config
LOCK_LAYOUT_LEVEL = {
'PREVENT_DOCKING': 'docking',
@@ -23,6 +24,15 @@ def register_browser_preferences(self):
gettext("Show system objects?"), 'boolean', False,
category_label=PREF_LABEL_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 717795bde..cbb3fde6a 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,50 @@ 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")
+
+ if config.SERVER_MODE:
+ groups = ServerGroupView.get_all_server_groups()
+ 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 +247,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 +258,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 +268,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 +294,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
@@ -292,13 +340,41 @@ class ServerGroupView(NodeView):
def dependents(self, gid):
return make_json_response(status=422)
+ @staticmethod
+ def get_all_server_groups():
+ """
+ Returns the list of server groups to show in server mode and
+ if there is any shared server in the group.
+ :return: server groups
+ """
+
+ # Don't display shared server if user has
+ # selected 'Hide shared server'
+ pref = Preferences.module('browser')
+ hide_shared_server = pref.preference('hide_shared_server').get()
+
+ server_groups = ServerGroup.query.all()
+ groups = []
+ for group in server_groups:
+ if hide_shared_server and \
+ ServerGroupModule.has_shared_server(group.id) and \
+ group.user_id != current_user.id:
+ continue
+ if group.user_id == current_user.id or \
+ ServerGroupModule.has_shared_server(group.id):
+ groups.append(group)
+ return groups
+
@login_required
def nodes(self, gid=None):
"""Return a JSON document listing the server groups for the user"""
nodes = []
-
if gid is None:
- groups = ServerGroup.query.filter_by(user_id=current_user.id)
+ if config.SERVER_MODE:
+
+ groups = self.get_all_server_groups()
+ else:
+ groups = ServerGroup.query.filter_by(user_id=current_user.id)
for group in groups:
nodes.append(
@@ -306,14 +382,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 +398,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 2eb058a40..ed8fae0f0 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
@@ -32,6 +32,8 @@ 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, MIMETYPE_APP_JS, \
SERVER_CONNECTION_CLOSED
+from sqlalchemy import or_
+from pgadmin.utils.preferences import Preferences
def has_any(data, keys):
@@ -65,6 +67,19 @@ def recovery_state(connection, postgres_version):
return status, result, in_recovery, wal_paused
+def get_preferences():
+ """
+ Get preferences setting
+ :return: whether to hide shared server or not.
+ """
+ hide_shared_server = None
+ if config.SERVER_MODE:
+ pref = Preferences.module('browser')
+ hide_shared_server = pref.preference('hide_shared_server').get()
+
+ return hide_shared_server
+
+
def server_icon_and_background(is_connected, manager, server):
"""
@@ -92,6 +107,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
@@ -114,25 +133,93 @@ 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
+
+ @staticmethod
+ def check_to_hide_shared_server(hide_shared_server, shared_server,
+ auto_detected_server):
+
+ hide_server = False
+ if hide_shared_server or \
+ shared_server.name == auto_detected_server:
+ hide_server = True
+
+ return hide_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 = get_preferences()
+
+ 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, auto_detected_server = \
+ self.get_shared_server(server, gid)
+
+ if self.check_to_hide_shared_server(hide_shared_server,
+ shared_server,
+ auto_detected_server):
+ # Don't include shared server if hide shared server is
+ # set to true
+ continue
+
+ # if hide_shared_server or \
+ # shared_server.name == auto_detected_server:
+ # # Don't include shared server if hide shared server is
+ # # set to true
+ # continue
+
+ # if shared_server.name == auto_detected_server:
+ # continue
+ server = self.get_shared_server_properties(server,
+ shared_server)
connected = False
manager = None
errmsg = None
was_connected = False
in_recovery = None
wal_paused = None
+ server_type = 'pg'
+ user_info = None
try:
manager = driver.connection_manager(server.id)
conn = manager.connection()
was_connected = conn.wasConnected
+ connected = conn.connected()
+ if connected:
+ server_type = manager.server_type
+ user_info = manager.user_info
except CryptKeyMissing:
# show the nodes at least even if not able to connect.
pass
@@ -148,17 +235,20 @@ class ServerModule(sg.ServerGroupPluginModule):
True,
self.node_type,
connected=connected,
- server_type=manager.server_type if connected else "pg",
+ server_type=server_type,
version=manager.version,
db=manager.db,
- user=manager.user_info if connected else None,
+ user=user_info,
in_recovery=in_recovery,
wal_pause=wal_paused,
is_password_saved=bool(server.save_password),
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
@@ -230,6 +320,82 @@ 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,
+ 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
+ """
+ auto_detected_server = None
+ shared_server = SharedServer.query.filter_by(
+ name=server.name, user_id=current_user.id,
+ servergroup_id=gid).first()
+ if server.discovery_id:
+ auto_detected_server = server.name
+
+ 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, auto_detected_server
+
class ServerMenuItem(MenuItem):
def __init__(self, **kwargs):
@@ -327,19 +493,29 @@ 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, auto_detected_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()
errmsg = None
in_recovery = None
wal_paused = None
+ server_type = 'pg'
if connected:
+ server_type = manager.server_type
status, result, in_recovery, wal_paused =\
recovery_state(conn, manager.version)
if not status:
@@ -356,7 +532,7 @@ class ServerNode(PGChildNodeView):
True,
self.node_type,
connected=connected,
- server_type=manager.server_type if connected else 'pg',
+ server_type=server_type,
version=manager.version,
db=manager.db,
user=manager.user_info if connected else None,
@@ -365,7 +541,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
)
)
@@ -379,9 +557,13 @@ 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.shared and server.user_id != current_user.id:
+ shared_server, auto_detected_server = \
+ ServerModule.get_shared_server(server, gid)
+ server = ServerModule.get_shared_server_properties(server,
+ shared_server)
if server is None:
return make_json_response(
@@ -426,14 +608,37 @@ 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,
+ user_name=server.username
),
)
+ 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:
@@ -449,10 +654,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:
@@ -467,8 +673,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(
@@ -477,6 +683,11 @@ class ServerNode(PGChildNodeView):
errormsg=gettext("Could not find the required server.")
)
+ if config.SERVER_MODE and server.shared and \
+ server.user_id != current_user.id:
+ sharedserver, auto_detected_server = \
+ ServerModule.get_shared_server(server, gid)
+
# Not all parameters can be modified, while the server is connected
config_param_map = {
'name': 'name',
@@ -506,11 +717,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,7 +753,8 @@ class ServerNode(PGChildNodeView):
self._server_modify_disallowed_when_connected(
connected, data, disp_lbl)
- idx = self._set_valid_attr_value(data, config_param_map, server)
+ idx = self._set_valid_attr_value(gid, data, config_param_map, server,
+ sharedserver)
if idx == 0:
return make_json_response(
@@ -568,7 +781,11 @@ class ServerNode(PGChildNodeView):
node=self.blueprint.generate_browser_node(
"%d" % (server.id), server.servergroup_id,
server.name,
- server_icon_and_background(connected, manager, server),
+ server_icon_and_background(
+ connected, manager, sharedserver)
+ if server.shared and server.user_id != current_user.id
+ else server_icon_and_background(
+ connected, manager, server),
True,
self.node_type,
connected=connected,
@@ -577,7 +794,16 @@ class ServerNode(PGChildNodeView):
)
)
- def _set_valid_attr_value(self, data, config_param_map, server):
+ @staticmethod
+ def _update_server_details(server, sharedserver,
+ config_param_map, arg, value):
+ 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)
+
+ def _set_valid_attr_value(self, gid, data, config_param_map, server,
+ sharedserver):
idx = 0
for arg in config_param_map:
@@ -585,9 +811,14 @@ class ServerNode(PGChildNodeView):
value = data[arg]
# sqlite3 do not have boolean type so we need to convert
# it manually to integer
+ if 'shared' in data and not data['shared']:
+ # Delete the shared server from DB if server
+ # owner uncheck shared property
+ self.delete_shared_server(server.name, gid)
if arg == 'sslcompression':
value = 1 if value else 0
- setattr(server, config_param_map[arg], value)
+ self._update_server_details(server, sharedserver,
+ config_param_map, arg, value)
idx += 1
return idx
@@ -613,11 +844,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 = []
@@ -625,6 +856,12 @@ class ServerNode(PGChildNodeView):
driver = get_driver(PG_DEFAULT_DRIVER)
for server in servers:
+ if server.shared and server.user_id != current_user.id:
+ shared_server, auto_detected_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()
@@ -653,8 +890,12 @@ class ServerNode(PGChildNodeView):
@login_required
def properties(self, gid, sid):
"""Return list of attributes of a server"""
+
+ sslcert = None
+ sslkey = None
+ sslrootcert = None
+ sslcrl = None
server = Server.query.filter_by(
- user_id=current_user.id,
id=sid).first()
if server is None:
@@ -663,9 +904,8 @@ class ServerNode(PGChildNodeView):
success=0,
errormsg=self.not_found_error_msg()
)
-
+ server_owner = None
sg = ServerGroup.query.filter_by(
- user_id=current_user.id,
id=server.servergroup_id
).first()
@@ -675,52 +915,77 @@ class ServerNode(PGChildNodeView):
conn = manager.connection()
connected = conn.connected()
+ # if server.shared and not current_user.has_role("Administrator"):
+ if server.shared and server.user_id != current_user.id:
+ shared_server, auto_detected_server = \
+ ServerModule.get_shared_server(server, gid)
+ server = ServerModule.get_shared_server_properties(server,
+ shared_server)
+ server_owner = server.server_owner
+
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 is_ssl:
+ sslcert = server.sslcert
+ sslkey = server.sslkey
+ sslrootcert = server.sslrootcert
+ sslcrl = server.sslcrl
+
+ use_ssh_tunnel = 0
+ tunnel_host = None
+ tunnel_port = 22
+ tunnel_username = None
+ tunnel_authentication = 0
+
+ if server.use_ssh_tunnel:
+ use_ssh_tunnel = server.use_ssh_tunnel
+ tunnel_host = server.tunnel_host
+ tunnel_port = server.tunnel_port
+ tunnel_username = server.tunnel_username
+ tunnel_authentication = server.tunnel_authentication
+
+ response = {
+ 'id': server.id,
+ 'name': server.name,
+ 'server_owner': server_owner,
+ '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': sslcert,
+ 'sslkey': sslkey,
+ 'sslrootcert': sslrootcert,
+ 'sslcrl': sslcrl,
+ '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': use_ssh_tunnel,
+ 'tunnel_host': tunnel_host,
+ 'tunnel_port': tunnel_port,
+ 'tunnel_username': tunnel_username,
+ 'tunnel_identity_file': server.tunnel_identity_file
+ if server.tunnel_identity_file else None,
+ 'tunnel_authentication': tunnel_authentication
+ }
+
+ return ajax_response(response)
@login_required
def create(self, gid):
@@ -802,11 +1067,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
@@ -1006,9 +1271,23 @@ 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, auto_detected_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())
+ # Return if username is blank
+ if server.username is None:
+ return make_json_response(
+ status=200,
+ success=0,
+ errormsg=gettext(
+ u"Please enter the server details to connect")
+ )
if current_user and hasattr(current_user, 'id'):
# Fetch User Details.
user = User.query.filter_by(id=current_user.id).first()
@@ -1063,7 +1342,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 \
@@ -1125,10 +1403,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
@@ -1560,21 +1846,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(
@@ -1599,10 +1903,7 @@ 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()
if server is None:
return make_json_response(
success=0,
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 8c43a55f7..9170e0d44 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);
@@ -735,6 +744,7 @@ define('pgadmin.node.server', [
// Default values!
initialize: function(attrs, args) {
var isNew = (_.size(attrs) === 0);
+ console.warn('warn');
if (isNew) {
this.set({'gid': args.node_info['server_group']._id});
@@ -745,12 +755,17 @@ 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: 'isShared',
+ },
+ {
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,11 +788,27 @@ 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'],
},{
id: 'host', label: gettext('Host name/address'), type: 'text', group: gettext('Connection'),
- mode: ['properties', 'edit', 'create'],
+ mode: ['properties', 'edit', 'create'],disabled: 'isShared',
control: Backform.InputControl.extend({
onChange: function() {
Backform.InputControl.prototype.onChange.apply(this, arguments);
@@ -798,7 +829,7 @@ define('pgadmin.node.server', [
}),
},{
id: 'port', label: gettext('Port'), type: 'int', group: gettext('Connection'),
- mode: ['properties', 'edit', 'create'], min: 1, max: 65535,
+ mode: ['properties', 'edit', 'create'], min: 1, max: 65535, disabled: 'isShared',
control: Backform.InputControl.extend({
onChange: function() {
Backform.InputControl.prototype.onChange.apply(this, arguments);
@@ -819,7 +850,7 @@ define('pgadmin.node.server', [
}),
},{
id: 'db', label: gettext('Maintenance database'), type: 'text', group: gettext('Connection'),
- mode: ['properties', 'edit', 'create'], readonly: 'isConnected',
+ mode: ['properties', 'edit', 'create'], readonly: 'isConnected',disabled: 'isShared',
},{
id: 'username', label: gettext('Username'), type: 'text', group: gettext('Connection'),
mode: ['properties', 'edit', 'create'],
@@ -1057,6 +1088,21 @@ 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;
+
+ },
+ isShared: function(model){
+ var serverOwner = model.attributes.user_id;
+ if (!model.isNew() && serverOwner != current_user.id && model.attributes.shared){
+ return true;
+ }
+ return false;
+ },
validate: function() {
const validateModel = new modelValidation.ModelValidation(this);
return validateModel.validate();
@@ -1153,7 +1199,36 @@ 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;
+ var server_url = obj.generate_url(item, 'obj', data, true);
+ // Fetch the updated data
+ $.get(server_url)
+ .done(function(res) {
+ if (res.shared && _.isNull(res.username) && data.user_id != current_user.id){
+ if (selectedTreeNodeData._type == 'server'){
+ pgAdmin.Browser.Node.callbacks.show_obj_properties.call(
+ pgAdmin.Browser.Nodes[tree.itemData(item)._type], {action: 'edit'}
+ );
+ data.is_connecting = false;
+ tree.unload(item);
+ tree.setInode(item);
+ tree.addIcon(item, {icon: 'icon-shared-server-not-connected'});
+ }else{
+ data.is_connecting = false;
+ tree.unload(item);
+ tree.setInode(item);
+ tree.addIcon(item, {icon: 'icon-shared-server-not-connected'});
+ }
+ }
+ return;
+ }).always(function(){
+ data.is_connecting = false;
+ });
+
var wasConnected = reconnect || data.connected,
onFailure = function(
xhr, status, error, _node, _data, _tree, _item, _wasConnected
@@ -1164,7 +1239,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) {
@@ -1321,7 +1401,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
@@ -1387,7 +1471,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..829c273aa 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,21 @@
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;
+}
+
+.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..117a2982b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/servers_test_data.json
@@ -0,0 +1,862 @@
+{
+ "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
+ }
+ },
+ {
+ "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,
+ "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,
+ "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..1b23fcdb8
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_add_server.py
@@ -0,0 +1,84 @@
+##########################################################################
+#
+# 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 'connect_now' in self.test_data:
+ self.server['connect_now'] = self.test_data['connect_now']
+ self.server['password'] = self.server['db_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_connect_timeout.py b/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_connect_timeout.py
deleted file mode 100644
index 94d555617..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.assertEqual(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_service_id.py b/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_service_id.py
deleted file mode 100644
index 9b4d4d377..000000000
--- a/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_service_id.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 ServersWithServiceIDAddTestCase(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['service'] = "TestDB"
- response = self.tester.post(
- url,
- data=json.dumps(self.server),
- content_type='html/json'
- )
- self.assertEqual(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 7c0b005d9..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.assertEqual(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..6d49cea7b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_check_connect.py
@@ -0,0 +1,107 @@
+##########################################################################
+#
+# 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
+
+
+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'
+
+ 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 8444e1d72..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.assertEqual(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.assertEqual(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.assertEqual(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 30917bd3c..78911f5a8 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 0b4dc183b..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.assertEqual(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 31c7a476e..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.assertEqual(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..b3f8215a4
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_shared_server.py
@@ -0,0 +1,134 @@
+##########################################################################
+#
+# 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
+import config
+
+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"""
+
+ if config.SERVER_MODE is False:
+ self.skipTest(
+ "Can not run shared servers test cases in the SERVER mode."
+ )
+ 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"""
+ if config.SERVER_MODE is False:
+ self.skipTest(
+ "Can not run shared servers test cases in the Desktop mode."
+ )
+ 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'
+ )
+
+ 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..ac1a0c821 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,25 @@ 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 +80,12 @@ 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 true; },
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/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js
index f5ab09399..0eb7840b0 100644
--- a/web/pgadmin/browser/static/js/browser.js
+++ b/web/pgadmin/browser/static/js/browser.js
@@ -19,7 +19,7 @@ define('pgadmin.browser', [
'pgadmin.browser.error', 'pgadmin.browser.frame',
'pgadmin.browser.node', 'pgadmin.browser.collection', 'pgadmin.browser.activity',
'sources/codemirror/addon/fold/pgadmin-sqlfoldcode',
- 'pgadmin.browser.keyboard', 'sources/tree/pgadmin_tree_save_state',
+ 'pgadmin.browser.keyboard', 'sources/tree/pgadmin_tree_save_state','jquery.acisortable', 'jquery.acifragment',
], function(
tree,
gettext, url_for, require, $, _,
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/preferences/__init__.py b/web/pgadmin/preferences/__init__.py
index 6212f63f8..db11d1d09 100644
--- a/web/pgadmin/preferences/__init__.py
+++ b/web/pgadmin/preferences/__init__.py
@@ -23,6 +23,7 @@ from pgadmin.utils.ajax import success_return, \
from pgadmin.utils.menu import MenuItem
from pgadmin.utils.preferences import Preferences
from pgadmin.utils.constants import MIMETYPE_APP_JS
+from pgadmin.browser.server_groups import ServerGroupModule as sgm
MODULE_NAME = 'preferences'
@@ -203,6 +204,7 @@ def save(pid):
res, msg = Preferences.save(
data['mid'], data['category_id'], data['id'], data['value'])
+ sgm.get_nodes(sgm)
if not res:
return internal_server_error(errormsg=msg)
diff --git a/web/pgadmin/preferences/static/js/preferences.js b/web/pgadmin/preferences/static/js/preferences.js
index 62eda7aa6..6c35cf0d6 100644
--- a/web/pgadmin/preferences/static/js/preferences.js
+++ b/web/pgadmin/preferences/static/js/preferences.js
@@ -463,6 +463,7 @@ define('pgadmin.preferences', [
}
if (e.button.text == gettext('Save')) {
+ debugger;
let requires_refresh = false;
preferences.updateAll();
@@ -477,6 +478,29 @@ define('pgadmin.preferences', [
if(pref.name == 'theme') {
requires_refresh = true;
}
+
+ if(pref.name == 'hide_shared_server') {
+ Alertify.confirm(
+ gettext('Browser tree refresh required'),
+ gettext('A browser tree refresh is required. Do you wish to refresh the tree?'),
+ function() {
+ pgAdmin.Browser.tree.destroy({
+ success: function() {
+ pgAdmin.Browser.initializeBrowserTree(pgAdmin.Browser);
+ return true;
+ },
+ });
+ },
+ function() {
+ preferences.reset();
+ changed = {};
+ return true;
+ }
+ ).set('labels', {
+ ok: gettext('Refresh'),
+ cancel: gettext('Later'),
+ });
+ }
});
if(requires_refresh) {
diff --git a/web/pgadmin/tools/schema_diff/__init__.py b/web/pgadmin/tools/schema_diff/__init__.py
index a325e9a01..babb05888 100644
--- a/web/pgadmin/tools/schema_diff/__init__.py
+++ b/web/pgadmin/tools/schema_diff/__init__.py
@@ -20,13 +20,14 @@ from flask_babelex import gettext
from pgadmin.utils import PgAdminModule
from pgadmin.utils.ajax import make_json_response, bad_request, \
make_response as ajax_response, internal_server_error
-from pgadmin.model import Server
+from pgadmin.model import Server, SharedServer
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
from pgadmin.tools.schema_diff.model import SchemaDiffModel
from config import PG_DEFAULT_DRIVER
from pgadmin.utils.driver import get_driver
from pgadmin.utils.preferences import Preferences
from pgadmin.utils.constants import PREF_LABEL_DISPLAY, MIMETYPE_APP_JS
+from sqlalchemy import or_
MODULE_NAME = 'schema_diff'
@@ -270,6 +271,7 @@ def servers():
server id.
"""
res = {}
+ auto_detected_server = None
try:
"""Return a JSON document listing the server groups for the user"""
driver = get_driver(PG_DEFAULT_DRIVER)
@@ -277,7 +279,19 @@ def servers():
from pgadmin.browser.server_groups.servers import\
server_icon_and_background
- for server in Server.query.filter_by(user_id=current_user.id):
+ for server in Server.query.filter(
+ or_(Server.user_id == current_user.id, Server.shared)):
+
+ shared_server = SharedServer.query.filter_by(
+ name=server.name, user_id=current_user.id,
+ servergroup_id=server.servergroup_id).first()
+
+ if server.discovery_id:
+ auto_detected_server = server.name
+
+ if shared_server and shared_server.name == auto_detected_server:
+ continue
+
manager = driver.connection_manager(server.id)
conn = manager.connection()
connected = conn.connected()
diff --git a/web/pgadmin/tools/schema_diff/static/js/schema_diff.backform.js b/web/pgadmin/tools/schema_diff/static/js/schema_diff.backform.js
index 55ef1e5f4..1b095db98 100644
--- a/web/pgadmin/tools/schema_diff/static/js/schema_diff.backform.js
+++ b/web/pgadmin/tools/schema_diff/static/js/schema_diff.backform.js
@@ -175,7 +175,7 @@ let SchemaDiffSelect2Control =
let span = this.$el.find('.select2-selection .select2-selection__rendered span.wcTabIcon'),
selSpan = this.$el.find('option:selected');
- if (span.hasClass('icon-server-not-connected')) {
+ if (span.hasClass('icon-server-not-connected') || span.hasClass('icon-shared-server-not-connected')) {
let icon = (data.icon) ? data.icon : 'icon-pg';
span.removeClass('icon-server-not-connected');
span.addClass(icon);
diff --git a/web/pgadmin/utils/driver/psycopg2/server_manager.py b/web/pgadmin/utils/driver/psycopg2/server_manager.py
index c35a3726f..6d3ce25ff 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 6d66af15a..022856c0c 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__)), "../"))
@@ -1598,3 +1600,107 @@ 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:
+ traceback.print_exc(file=sys.stderr)
+ raise ("Error while creating server. %s" % exception)
+
+
+def get_test_user(self, user_details,
+ is_api=True, create_conn=True):
+ 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):
+ main_tester = self.__class__.tester
+ try:
+ # Login with non-admin_user
+ test_user = get_test_user(self, user)
+ self.setTestClient(test_user)
+
+ # 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/regression/test_config.json.in b/web/regression/test_config.json.in
index 690f46c1c..393be6a84 100644
--- a/web/regression/test_config.json.in
+++ b/web/regression/test_config.json.in
@@ -11,6 +11,11 @@
"login_password": "PASSWORD",
"login_username": "[email protected]"
},
+ "pgAdmin4_test_non_admin_credentials": {
+ "new_password": "NEWPASSWORD",
+ "login_password": "PASSWORD",
+ "login_username": "[email protected]"
+ },
"pgAdmin4_ldap_credentials": {
"login_password": "PASSWORD",
"login_username": "USERNAME"
diff --git a/web/setup.py b/web/setup.py
index e9c42decd..cfdf2045a 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)
@@ -258,6 +259,14 @@ def load_servers(args):
for server in data["Servers"]:
obj = data["Servers"][server]
+ # Check if server is shared.Won't import if user is non-admin
+ if 'Shared' in obj \
+ and 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 = next(
(g.id for g in groups if g.name == obj["Group"]), -1)
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin][RM4979]: Configuration files for data sources or similar.
2020-08-20 10:28 [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-21 08:25 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-08-21 12:30 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-25 12:22 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-01 11:17 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-09-02 08:18 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-03 06:55 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
@ 2020-09-03 07:34 ` Akshay Joshi <[email protected]>
2020-09-03 08:37 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Akshay Joshi @ 2020-09-03 07:34 UTC (permalink / raw)
To: Pradip Parkale <[email protected]>; +Cc: pgadmin-hackers
Thanks, patch applied.
On Thu, Sep 3, 2020 at 12:25 PM Pradip Parkale <
[email protected]> wrote:
> Hi Akshay,
> Please find the updated patch.
>
> On Wed, Sep 2, 2020 at 1:48 PM Akshay Joshi <[email protected]>
> wrote:
>
>> Hi Pradip
>>
>> Please fix SonarQube issues that introduce with this patch.
>>
>> On Tue, Sep 1, 2020 at 4:47 PM Pradip Parkale <
>> [email protected]> wrote:
>>
>>> Hi Akshay,
>>>
>>> Please find the updated patch.
>>>
>>> On Tue, Aug 25, 2020 at 5:53 PM Akshay Joshi <
>>> [email protected]> wrote:
>>>
>>>> Hi Pradip
>>>>
>>>> Following are the GUI review comments:
>>>> *Testing Scenario*: I have three users U1 and U2 are Admin and U3 is a
>>>> normal user. Login from U1 first time and pgAdmin4 discover all the local
>>>> servers. Steps that I perform:
>>>>
>>>> - Shared one server.
>>>> - Create a group 'Test' and move one server to that group.
>>>> - Share the server of the 'Test' group as well.
>>>>
>>>> [image: Screenshot 2020-08-25 at 4.38.32 PM.png]
>>>> *Issues:*
>>>>
>>>> - When login using U2 or U3 PostgreSQL 9.2 and 10 shown twice which
>>>> should not. Refer below screenshot
>>>> [image: Screenshot 2020-08-25 at 4.30.22 PM.png]
>>>>
>>>> Fixed. If the server is auto-discovered and U1 shared the
>>> auto-discovered server(PostgreSQL 9.2 in the above example) and then log in
>>> using U2, in this case, shared server(PostgreSQL 9.2) will not visible to
>>> U2 as the same server will be auto-discovered for U2.
>>>
>>>>
>>>> -
>>>> - When expanding the server or clicking on the 'Connect Server'
>>>> menu of the shared server, it opens the properties dialog.
>>>>
>>>> This is expected. Properties dialogue will open the first time for a
>>> shared server where the user has to enter server details. This will happen
>>> for the user who is not the owner of the server.
>>>
>>>>
>>>> - Try to update the IPAddress, Port it is not updating for the
>>>> shared server. Check other properties too.
>>>>
>>>> Fixed.
>>>
>>>>
>>>> -
>>>> - When we set "Hide shared server?" setting to true/false. A
>>>> complete browser tree should be refreshed. With the current implementation,
>>>> each group needs to be refreshed individually and the server group still
>>>> visible while refreshing the complete page Browser Tree looks perfect.
>>>>
>>>> Fixed.
>>>
>>>>
>>>> - When there is only one server in a group and we expanded the
>>>> group 'Connect to server' dialog appears which should not. (*This may be an
>>>> old issue)
>>>>
>>>> This is expected and its an old behavior.
>>>
>>>>
>>>> -
>>>> - Schema Diff shared servers are not visible inside the appropriate
>>>> group in the source and target server list.
>>>>
>>>> Fixed
>>>
>>>>
>>>> - Documentation changes required (Create a new Redmine).
>>>>
>>>> Done.
>>>
>>>>
>>>> -
>>>> - SonarQube shows 1 new Bug and 4 new code smell. (Fixed the Bug,
>>>> code smell can be fixed later)
>>>>
>>>> Fixed.
>>>
>>>>
>>>> - 'pgAdmin4_test_non_admin_credentials' should be added in '
>>>> test_config.json.in' file.
>>>>
>>>> Fixed.
>>>
>>>>
>>>> - For Desktop mode shared server test cases should be skipped. It
>>>> is failing at the moment.
>>>>
>>>> Fixed.
>>>
>>>> Code review still remains :)
>>>>
>>>> On Fri, Aug 21, 2020 at 6:00 PM Pradip Parkale <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Akshay,
>>>>> Please find the updated patch.
>>>>>
>>>>> On Fri, Aug 21, 2020 at 1:55 PM Akshay Joshi <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Pradip
>>>>>>
>>>>>> The patch is not applied, can you please rebase and send it again.
>>>>>>
>>>>>> On Thu, Aug 20, 2020 at 3:58 PM Pradip Parkale <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> 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
>>>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>> *Thanks & Regards*
>>>>>> *Akshay Joshi*
>>>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>>
>>>>>> *Mobile: +91 976-788-8246*
>>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> Thanks & Regards,
>>>>> Pradip Parkale
>>>>> Software Engineer | EnterpriseDB Corporation
>>>>>
>>>>
>>>>
>>>> --
>>>> *Thanks & Regards*
>>>> *Akshay Joshi*
>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>
>>>> *Mobile: +91 976-788-8246*
>>>>
>>>
>>>
>>> --
>>> Thanks & Regards,
>>> Pradip Parkale
>>> Software Engineer | EnterpriseDB Corporation
>>>
>>
>>
>> --
>> *Thanks & Regards*
>> *Akshay Joshi*
>> *pgAdmin Hacker | Sr. Software Architect*
>> *EDB Postgres <http://edbpostgres.com>*
>>
>> *Mobile: +91 976-788-8246*
>>
>
>
> --
> Thanks & Regards,
> Pradip Parkale
> Software Engineer | EnterpriseDB Corporation
>
--
*Thanks & Regards*
*Akshay Joshi*
*pgAdmin Hacker | Sr. Software Architect*
*EDB Postgres <http://edbpostgres.com>*
*Mobile: +91 976-788-8246*
Attachments:
[image/png] Screenshot 2020-08-25 at 4.30.22 PM.png (70.2K, 3-Screenshot%202020-08-25%20at%204.30.22%20PM.png)
download | view image
[image/png] Screenshot 2020-08-25 at 4.38.32 PM.png (52.9K, 4-Screenshot%202020-08-25%20at%204.38.32%20PM.png)
download | view image
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin][RM4979]: Configuration files for data sources or similar.
2020-08-20 10:28 [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-21 08:25 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-08-21 12:30 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-25 12:22 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-01 11:17 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-09-02 08:18 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-03 06:55 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-09-03 07:34 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
@ 2020-09-03 08:37 ` Pradip Parkale <[email protected]>
2020-09-03 08:53 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Pradip Parkale @ 2020-09-03 08:37 UTC (permalink / raw)
To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers
Hi Akshay,
Please find the patch.
On Thu, Sep 3, 2020 at 1:05 PM Akshay Joshi <[email protected]>
wrote:
> Thanks, patch applied.
>
> On Thu, Sep 3, 2020 at 12:25 PM Pradip Parkale <
> [email protected]> wrote:
>
>> Hi Akshay,
>> Please find the updated patch.
>>
>> On Wed, Sep 2, 2020 at 1:48 PM Akshay Joshi <
>> [email protected]> wrote:
>>
>>> Hi Pradip
>>>
>>> Please fix SonarQube issues that introduce with this patch.
>>>
>>> On Tue, Sep 1, 2020 at 4:47 PM Pradip Parkale <
>>> [email protected]> wrote:
>>>
>>>> Hi Akshay,
>>>>
>>>> Please find the updated patch.
>>>>
>>>> On Tue, Aug 25, 2020 at 5:53 PM Akshay Joshi <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Pradip
>>>>>
>>>>> Following are the GUI review comments:
>>>>> *Testing Scenario*: I have three users U1 and U2 are Admin and U3 is
>>>>> a normal user. Login from U1 first time and pgAdmin4 discover all the local
>>>>> servers. Steps that I perform:
>>>>>
>>>>> - Shared one server.
>>>>> - Create a group 'Test' and move one server to that group.
>>>>> - Share the server of the 'Test' group as well.
>>>>>
>>>>> [image: Screenshot 2020-08-25 at 4.38.32 PM.png]
>>>>> *Issues:*
>>>>>
>>>>> - When login using U2 or U3 PostgreSQL 9.2 and 10 shown twice
>>>>> which should not. Refer below screenshot
>>>>> [image: Screenshot 2020-08-25 at 4.30.22 PM.png]
>>>>>
>>>>> Fixed. If the server is auto-discovered and U1 shared the
>>>> auto-discovered server(PostgreSQL 9.2 in the above example) and then log in
>>>> using U2, in this case, shared server(PostgreSQL 9.2) will not visible to
>>>> U2 as the same server will be auto-discovered for U2.
>>>>
>>>>>
>>>>> -
>>>>> - When expanding the server or clicking on the 'Connect Server'
>>>>> menu of the shared server, it opens the properties dialog.
>>>>>
>>>>> This is expected. Properties dialogue will open the first time for a
>>>> shared server where the user has to enter server details. This will happen
>>>> for the user who is not the owner of the server.
>>>>
>>>>>
>>>>> - Try to update the IPAddress, Port it is not updating for the
>>>>> shared server. Check other properties too.
>>>>>
>>>>> Fixed.
>>>>
>>>>>
>>>>> -
>>>>> - When we set "Hide shared server?" setting to true/false. A
>>>>> complete browser tree should be refreshed. With the current implementation,
>>>>> each group needs to be refreshed individually and the server group still
>>>>> visible while refreshing the complete page Browser Tree looks perfect.
>>>>>
>>>>> Fixed.
>>>>
>>>>>
>>>>> - When there is only one server in a group and we expanded the
>>>>> group 'Connect to server' dialog appears which should not. (*This may be an
>>>>> old issue)
>>>>>
>>>>> This is expected and its an old behavior.
>>>>
>>>>>
>>>>> -
>>>>> - Schema Diff shared servers are not visible inside the
>>>>> appropriate group in the source and target server list.
>>>>>
>>>>> Fixed
>>>>
>>>>>
>>>>> - Documentation changes required (Create a new Redmine).
>>>>>
>>>>> Done.
>>>>
>>>>>
>>>>> -
>>>>> - SonarQube shows 1 new Bug and 4 new code smell. (Fixed the Bug,
>>>>> code smell can be fixed later)
>>>>>
>>>>> Fixed.
>>>>
>>>>>
>>>>> - 'pgAdmin4_test_non_admin_credentials' should be added in '
>>>>> test_config.json.in' file.
>>>>>
>>>>> Fixed.
>>>>
>>>>>
>>>>> - For Desktop mode shared server test cases should be skipped. It
>>>>> is failing at the moment.
>>>>>
>>>>> Fixed.
>>>>
>>>>> Code review still remains :)
>>>>>
>>>>> On Fri, Aug 21, 2020 at 6:00 PM Pradip Parkale <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Akshay,
>>>>>> Please find the updated patch.
>>>>>>
>>>>>> On Fri, Aug 21, 2020 at 1:55 PM Akshay Joshi <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi Pradip
>>>>>>>
>>>>>>> The patch is not applied, can you please rebase and send it again.
>>>>>>>
>>>>>>> On Thu, Aug 20, 2020 at 3:58 PM Pradip Parkale <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> 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
>>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> *Thanks & Regards*
>>>>>>> *Akshay Joshi*
>>>>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>>>
>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>> Thanks & Regards,
>>>>>> Pradip Parkale
>>>>>> Software Engineer | EnterpriseDB Corporation
>>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> *Thanks & Regards*
>>>>> *Akshay Joshi*
>>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>
>>>>> *Mobile: +91 976-788-8246*
>>>>>
>>>>
>>>>
>>>> --
>>>> Thanks & Regards,
>>>> Pradip Parkale
>>>> Software Engineer | EnterpriseDB Corporation
>>>>
>>>
>>>
>>> --
>>> *Thanks & Regards*
>>> *Akshay Joshi*
>>> *pgAdmin Hacker | Sr. Software Architect*
>>> *EDB Postgres <http://edbpostgres.com>*
>>>
>>> *Mobile: +91 976-788-8246*
>>>
>>
>>
>> --
>> Thanks & Regards,
>> Pradip Parkale
>> Software Engineer | EnterpriseDB Corporation
>>
>
>
> --
> *Thanks & Regards*
> *Akshay Joshi*
> *pgAdmin Hacker | Sr. Software Architect*
> *EDB Postgres <http://edbpostgres.com>*
>
> *Mobile: +91 976-788-8246*
>
--
Thanks & Regards,
Pradip Parkale
Software Engineer | EnterpriseDB Corporation
Attachments:
[image/png] Screenshot 2020-08-25 at 4.30.22 PM.png (70.2K, 3-Screenshot%202020-08-25%20at%204.30.22%20PM.png)
download | view image
[image/png] Screenshot 2020-08-25 at 4.38.32 PM.png (52.9K, 4-Screenshot%202020-08-25%20at%204.38.32%20PM.png)
download | view image
[application/octet-stream] jenkins_fail_issue.patch (735B, 5-jenkins_fail_issue.patch)
download | inline diff:
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
index b3f8215a4..3ed6e4353 100644
--- a/web/pgadmin/browser/server_groups/servers/tests/test_shared_server.py
+++ b/web/pgadmin/browser/server_groups/servers/tests/test_shared_server.py
@@ -17,8 +17,10 @@ from regression.python_test_utils.test_utils import \
create_user_wise_test_client
import config
-test_user_details = config_data[
- 'pgAdmin4_test_non_admin_credentials']
+test_user_details = None
+if config.SERVER_MODE:
+ test_user_details = config_data[
+ 'pgAdmin4_test_non_admin_credentials']
class SharedServersGetTestCase(BaseTestGenerator):
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin][RM4979]: Configuration files for data sources or similar.
2020-08-20 10:28 [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-21 08:25 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-08-21 12:30 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-25 12:22 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-01 11:17 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-09-02 08:18 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-03 06:55 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-09-03 07:34 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-03 08:37 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
@ 2020-09-03 08:53 ` Akshay Joshi <[email protected]>
2020-09-07 17:16 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Akshay Joshi @ 2020-09-03 08:53 UTC (permalink / raw)
To: Pradip Parkale <[email protected]>; +Cc: pgadmin-hackers
Thanks, patch applied.
On Thu, Sep 3, 2020 at 2:07 PM Pradip Parkale <
[email protected]> wrote:
> Hi Akshay,
>
> Please find the patch.
>
> On Thu, Sep 3, 2020 at 1:05 PM Akshay Joshi <[email protected]>
> wrote:
>
>> Thanks, patch applied.
>>
>> On Thu, Sep 3, 2020 at 12:25 PM Pradip Parkale <
>> [email protected]> wrote:
>>
>>> Hi Akshay,
>>> Please find the updated patch.
>>>
>>> On Wed, Sep 2, 2020 at 1:48 PM Akshay Joshi <
>>> [email protected]> wrote:
>>>
>>>> Hi Pradip
>>>>
>>>> Please fix SonarQube issues that introduce with this patch.
>>>>
>>>> On Tue, Sep 1, 2020 at 4:47 PM Pradip Parkale <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Akshay,
>>>>>
>>>>> Please find the updated patch.
>>>>>
>>>>> On Tue, Aug 25, 2020 at 5:53 PM Akshay Joshi <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Pradip
>>>>>>
>>>>>> Following are the GUI review comments:
>>>>>> *Testing Scenario*: I have three users U1 and U2 are Admin and U3 is
>>>>>> a normal user. Login from U1 first time and pgAdmin4 discover all the local
>>>>>> servers. Steps that I perform:
>>>>>>
>>>>>> - Shared one server.
>>>>>> - Create a group 'Test' and move one server to that group.
>>>>>> - Share the server of the 'Test' group as well.
>>>>>>
>>>>>> [image: Screenshot 2020-08-25 at 4.38.32 PM.png]
>>>>>> *Issues:*
>>>>>>
>>>>>> - When login using U2 or U3 PostgreSQL 9.2 and 10 shown twice
>>>>>> which should not. Refer below screenshot
>>>>>> [image: Screenshot 2020-08-25 at 4.30.22 PM.png]
>>>>>>
>>>>>> Fixed. If the server is auto-discovered and U1 shared the
>>>>> auto-discovered server(PostgreSQL 9.2 in the above example) and then log in
>>>>> using U2, in this case, shared server(PostgreSQL 9.2) will not visible to
>>>>> U2 as the same server will be auto-discovered for U2.
>>>>>
>>>>>>
>>>>>> -
>>>>>> - When expanding the server or clicking on the 'Connect Server'
>>>>>> menu of the shared server, it opens the properties dialog.
>>>>>>
>>>>>> This is expected. Properties dialogue will open the first time for a
>>>>> shared server where the user has to enter server details. This will happen
>>>>> for the user who is not the owner of the server.
>>>>>
>>>>>>
>>>>>> - Try to update the IPAddress, Port it is not updating for the
>>>>>> shared server. Check other properties too.
>>>>>>
>>>>>> Fixed.
>>>>>
>>>>>>
>>>>>> -
>>>>>> - When we set "Hide shared server?" setting to true/false. A
>>>>>> complete browser tree should be refreshed. With the current implementation,
>>>>>> each group needs to be refreshed individually and the server group still
>>>>>> visible while refreshing the complete page Browser Tree looks perfect.
>>>>>>
>>>>>> Fixed.
>>>>>
>>>>>>
>>>>>> - When there is only one server in a group and we expanded the
>>>>>> group 'Connect to server' dialog appears which should not. (*This may be an
>>>>>> old issue)
>>>>>>
>>>>>> This is expected and its an old behavior.
>>>>>
>>>>>>
>>>>>> -
>>>>>> - Schema Diff shared servers are not visible inside the
>>>>>> appropriate group in the source and target server list.
>>>>>>
>>>>>> Fixed
>>>>>
>>>>>>
>>>>>> - Documentation changes required (Create a new Redmine).
>>>>>>
>>>>>> Done.
>>>>>
>>>>>>
>>>>>> -
>>>>>> - SonarQube shows 1 new Bug and 4 new code smell. (Fixed the Bug,
>>>>>> code smell can be fixed later)
>>>>>>
>>>>>> Fixed.
>>>>>
>>>>>>
>>>>>> - 'pgAdmin4_test_non_admin_credentials' should be added in '
>>>>>> test_config.json.in' file.
>>>>>>
>>>>>> Fixed.
>>>>>
>>>>>>
>>>>>> - For Desktop mode shared server test cases should be skipped. It
>>>>>> is failing at the moment.
>>>>>>
>>>>>> Fixed.
>>>>>
>>>>>> Code review still remains :)
>>>>>>
>>>>>> On Fri, Aug 21, 2020 at 6:00 PM Pradip Parkale <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi Akshay,
>>>>>>> Please find the updated patch.
>>>>>>>
>>>>>>> On Fri, Aug 21, 2020 at 1:55 PM Akshay Joshi <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Hi Pradip
>>>>>>>>
>>>>>>>> The patch is not applied, can you please rebase and send it again.
>>>>>>>>
>>>>>>>> On Thu, Aug 20, 2020 at 3:58 PM Pradip Parkale <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> 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
>>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> --
>>>>>>>> *Thanks & Regards*
>>>>>>>> *Akshay Joshi*
>>>>>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>>>>
>>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> Thanks & Regards,
>>>>>>> Pradip Parkale
>>>>>>> Software Engineer | EnterpriseDB Corporation
>>>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>> *Thanks & Regards*
>>>>>> *Akshay Joshi*
>>>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>>
>>>>>> *Mobile: +91 976-788-8246*
>>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> Thanks & Regards,
>>>>> Pradip Parkale
>>>>> Software Engineer | EnterpriseDB Corporation
>>>>>
>>>>
>>>>
>>>> --
>>>> *Thanks & Regards*
>>>> *Akshay Joshi*
>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>
>>>> *Mobile: +91 976-788-8246*
>>>>
>>>
>>>
>>> --
>>> Thanks & Regards,
>>> Pradip Parkale
>>> Software Engineer | EnterpriseDB Corporation
>>>
>>
>>
>> --
>> *Thanks & Regards*
>> *Akshay Joshi*
>> *pgAdmin Hacker | Sr. Software Architect*
>> *EDB Postgres <http://edbpostgres.com>*
>>
>> *Mobile: +91 976-788-8246*
>>
>
>
> --
> Thanks & Regards,
> Pradip Parkale
> Software Engineer | EnterpriseDB Corporation
>
--
*Thanks & Regards*
*Akshay Joshi*
*pgAdmin Hacker | Sr. Software Architect*
*EDB Postgres <http://edbpostgres.com>*
*Mobile: +91 976-788-8246*
Attachments:
[image/png] Screenshot 2020-08-25 at 4.30.22 PM.png (70.2K, 3-Screenshot%202020-08-25%20at%204.30.22%20PM.png)
download | view image
[image/png] Screenshot 2020-08-25 at 4.38.32 PM.png (52.9K, 4-Screenshot%202020-08-25%20at%204.38.32%20PM.png)
download | view image
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin][RM4979]: Configuration files for data sources or similar.
2020-08-20 10:28 [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-21 08:25 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-08-21 12:30 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-25 12:22 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-01 11:17 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-09-02 08:18 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-03 06:55 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-09-03 07:34 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-03 08:37 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-09-03 08:53 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
@ 2020-09-07 17:16 ` Pradip Parkale <[email protected]>
2020-09-07 17:45 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Pradip Parkale @ 2020-09-07 17:16 UTC (permalink / raw)
To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers
Hi Akshay,
Please find attached for the shared server. The server icon was getting
change to the shared server icon in Desktop mode.
On Thu, Sep 3, 2020 at 2:23 PM Akshay Joshi <[email protected]>
wrote:
> Thanks, patch applied.
>
> On Thu, Sep 3, 2020 at 2:07 PM Pradip Parkale <
> [email protected]> wrote:
>
>> Hi Akshay,
>>
>> Please find the patch.
>>
>> On Thu, Sep 3, 2020 at 1:05 PM Akshay Joshi <
>> [email protected]> wrote:
>>
>>> Thanks, patch applied.
>>>
>>> On Thu, Sep 3, 2020 at 12:25 PM Pradip Parkale <
>>> [email protected]> wrote:
>>>
>>>> Hi Akshay,
>>>> Please find the updated patch.
>>>>
>>>> On Wed, Sep 2, 2020 at 1:48 PM Akshay Joshi <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Pradip
>>>>>
>>>>> Please fix SonarQube issues that introduce with this patch.
>>>>>
>>>>> On Tue, Sep 1, 2020 at 4:47 PM Pradip Parkale <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Akshay,
>>>>>>
>>>>>> Please find the updated patch.
>>>>>>
>>>>>> On Tue, Aug 25, 2020 at 5:53 PM Akshay Joshi <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi Pradip
>>>>>>>
>>>>>>> Following are the GUI review comments:
>>>>>>> *Testing Scenario*: I have three users U1 and U2 are Admin and U3
>>>>>>> is a normal user. Login from U1 first time and pgAdmin4 discover all the
>>>>>>> local servers. Steps that I perform:
>>>>>>>
>>>>>>> - Shared one server.
>>>>>>> - Create a group 'Test' and move one server to that group.
>>>>>>> - Share the server of the 'Test' group as well.
>>>>>>>
>>>>>>> [image: Screenshot 2020-08-25 at 4.38.32 PM.png]
>>>>>>> *Issues:*
>>>>>>>
>>>>>>> - When login using U2 or U3 PostgreSQL 9.2 and 10 shown twice
>>>>>>> which should not. Refer below screenshot
>>>>>>> [image: Screenshot 2020-08-25 at 4.30.22 PM.png]
>>>>>>>
>>>>>>> Fixed. If the server is auto-discovered and U1 shared the
>>>>>> auto-discovered server(PostgreSQL 9.2 in the above example) and then log in
>>>>>> using U2, in this case, shared server(PostgreSQL 9.2) will not visible to
>>>>>> U2 as the same server will be auto-discovered for U2.
>>>>>>
>>>>>>>
>>>>>>> -
>>>>>>> - When expanding the server or clicking on the 'Connect Server'
>>>>>>> menu of the shared server, it opens the properties dialog.
>>>>>>>
>>>>>>> This is expected. Properties dialogue will open the first time for a
>>>>>> shared server where the user has to enter server details. This will happen
>>>>>> for the user who is not the owner of the server.
>>>>>>
>>>>>>>
>>>>>>> - Try to update the IPAddress, Port it is not updating for the
>>>>>>> shared server. Check other properties too.
>>>>>>>
>>>>>>> Fixed.
>>>>>>
>>>>>>>
>>>>>>> -
>>>>>>> - When we set "Hide shared server?" setting to true/false. A
>>>>>>> complete browser tree should be refreshed. With the current implementation,
>>>>>>> each group needs to be refreshed individually and the server group still
>>>>>>> visible while refreshing the complete page Browser Tree looks perfect.
>>>>>>>
>>>>>>> Fixed.
>>>>>>
>>>>>>>
>>>>>>> - When there is only one server in a group and we expanded the
>>>>>>> group 'Connect to server' dialog appears which should not. (*This may be an
>>>>>>> old issue)
>>>>>>>
>>>>>>> This is expected and its an old behavior.
>>>>>>
>>>>>>>
>>>>>>> -
>>>>>>> - Schema Diff shared servers are not visible inside the
>>>>>>> appropriate group in the source and target server list.
>>>>>>>
>>>>>>> Fixed
>>>>>>
>>>>>>>
>>>>>>> - Documentation changes required (Create a new Redmine).
>>>>>>>
>>>>>>> Done.
>>>>>>
>>>>>>>
>>>>>>> -
>>>>>>> - SonarQube shows 1 new Bug and 4 new code smell. (Fixed the
>>>>>>> Bug, code smell can be fixed later)
>>>>>>>
>>>>>>> Fixed.
>>>>>>
>>>>>>>
>>>>>>> - 'pgAdmin4_test_non_admin_credentials' should be added in '
>>>>>>> test_config.json.in' file.
>>>>>>>
>>>>>>> Fixed.
>>>>>>
>>>>>>>
>>>>>>> - For Desktop mode shared server test cases should be skipped.
>>>>>>> It is failing at the moment.
>>>>>>>
>>>>>>> Fixed.
>>>>>>
>>>>>>> Code review still remains :)
>>>>>>>
>>>>>>> On Fri, Aug 21, 2020 at 6:00 PM Pradip Parkale <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Hi Akshay,
>>>>>>>> Please find the updated patch.
>>>>>>>>
>>>>>>>> On Fri, Aug 21, 2020 at 1:55 PM Akshay Joshi <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> Hi Pradip
>>>>>>>>>
>>>>>>>>> The patch is not applied, can you please rebase and send it again.
>>>>>>>>>
>>>>>>>>> On Thu, Aug 20, 2020 at 3:58 PM Pradip Parkale <
>>>>>>>>> [email protected]> wrote:
>>>>>>>>>
>>>>>>>>>> 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
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> --
>>>>>>>>> *Thanks & Regards*
>>>>>>>>> *Akshay Joshi*
>>>>>>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>>>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>>>>>
>>>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> --
>>>>>>>> Thanks & Regards,
>>>>>>>> Pradip Parkale
>>>>>>>> Software Engineer | EnterpriseDB Corporation
>>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> *Thanks & Regards*
>>>>>>> *Akshay Joshi*
>>>>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>>>
>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>> Thanks & Regards,
>>>>>> Pradip Parkale
>>>>>> Software Engineer | EnterpriseDB Corporation
>>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> *Thanks & Regards*
>>>>> *Akshay Joshi*
>>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>
>>>>> *Mobile: +91 976-788-8246*
>>>>>
>>>>
>>>>
>>>> --
>>>> Thanks & Regards,
>>>> Pradip Parkale
>>>> Software Engineer | EnterpriseDB Corporation
>>>>
>>>
>>>
>>> --
>>> *Thanks & Regards*
>>> *Akshay Joshi*
>>> *pgAdmin Hacker | Sr. Software Architect*
>>> *EDB Postgres <http://edbpostgres.com>*
>>>
>>> *Mobile: +91 976-788-8246*
>>>
>>
>>
>> --
>> Thanks & Regards,
>> Pradip Parkale
>> Software Engineer | EnterpriseDB Corporation
>>
>
>
> --
> *Thanks & Regards*
> *Akshay Joshi*
> *pgAdmin Hacker | Sr. Software Architect*
> *EDB Postgres <http://edbpostgres.com>*
>
> *Mobile: +91 976-788-8246*
>
--
Thanks & Regards,
Pradip Parkale
Software Engineer | EnterpriseDB Corporation
Attachments:
[image/png] Screenshot 2020-08-25 at 4.30.22 PM.png (70.2K, 3-Screenshot%202020-08-25%20at%204.30.22%20PM.png)
download | view image
[image/png] Screenshot 2020-08-25 at 4.38.32 PM.png (52.9K, 4-Screenshot%202020-08-25%20at%204.38.32%20PM.png)
download | view image
[application/octet-stream] shared-server-icon-issue.patch (2.0K, 5-shared-server-icon-issue.patch)
download | inline diff:
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 e48524924..e361c3839 100644
--- a/web/pgadmin/browser/server_groups/servers/static/js/server.js
+++ b/web/pgadmin/browser/server_groups/servers/static/js/server.js
@@ -238,7 +238,7 @@ define('pgadmin.node.server', [
d = t.itemData(i);
t.removeIcon(i);
d.connected = false;
- if (d.shared){
+ if (d.shared && pgAdmin.server_mode == 'True'){
d.icon = 'icon-shared-server-not-connected';
}else{
d.icon = 'icon-server-not-connected';
@@ -1238,7 +1238,7 @@ define('pgadmin.node.server', [
// Let's not change the status of the tree node now.
if (!_wasConnected) {
tree.setInode(_item);
- if (data.shared){
+ if (data.shared && pgAdmin.server_mode == 'True'){
tree.addIcon(_item, {icon: 'icon-shared-server-not-connected'});
}else{
tree.addIcon(_item, {icon: 'icon-server-not-connected'});
@@ -1400,7 +1400,7 @@ define('pgadmin.node.server', [
_tree.unload(_item);
_tree.setInode(_item);
_tree.removeIcon(_item);
- if (_data.shared){
+ if (_data.shared && pgAdmin.server_mode == 'True'){
_tree.addIcon(_item, {icon: 'icon-shared-server-not-connected'});
}else{
_tree.addIcon(_item, {icon: 'icon-server-not-connected'});
@@ -1470,7 +1470,7 @@ define('pgadmin.node.server', [
})
.fail(function(xhr, status, error) {
tree.setInode(item);
- if (data.shared){
+ if (data.shared && pgAdmin.server_mode == 'True'){
tree.addIcon(item, {icon: 'icon-shared-server-not-connected'});
}else{
tree.addIcon(item, {icon: 'icon-server-not-connected'});
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin][RM4979]: Configuration files for data sources or similar.
2020-08-20 10:28 [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-21 08:25 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-08-21 12:30 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-25 12:22 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-01 11:17 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-09-02 08:18 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-03 06:55 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-09-03 07:34 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-03 08:37 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-09-03 08:53 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-07 17:16 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
@ 2020-09-07 17:45 ` Pradip Parkale <[email protected]>
2020-09-08 06:35 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
0 siblings, 1 reply; 13+ messages in thread
From: Pradip Parkale @ 2020-09-07 17:45 UTC (permalink / raw)
To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers
Please ignore previous patch. Find the updated patch.
On Mon, Sep 7, 2020 at 10:46 PM Pradip Parkale <
[email protected]> wrote:
> Hi Akshay,
>
> Please find attached for the shared server. The server icon was getting
> change to the shared server icon in Desktop mode.
>
> On Thu, Sep 3, 2020 at 2:23 PM Akshay Joshi <[email protected]>
> wrote:
>
>> Thanks, patch applied.
>>
>> On Thu, Sep 3, 2020 at 2:07 PM Pradip Parkale <
>> [email protected]> wrote:
>>
>>> Hi Akshay,
>>>
>>> Please find the patch.
>>>
>>> On Thu, Sep 3, 2020 at 1:05 PM Akshay Joshi <
>>> [email protected]> wrote:
>>>
>>>> Thanks, patch applied.
>>>>
>>>> On Thu, Sep 3, 2020 at 12:25 PM Pradip Parkale <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Akshay,
>>>>> Please find the updated patch.
>>>>>
>>>>> On Wed, Sep 2, 2020 at 1:48 PM Akshay Joshi <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Pradip
>>>>>>
>>>>>> Please fix SonarQube issues that introduce with this patch.
>>>>>>
>>>>>> On Tue, Sep 1, 2020 at 4:47 PM Pradip Parkale <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi Akshay,
>>>>>>>
>>>>>>> Please find the updated patch.
>>>>>>>
>>>>>>> On Tue, Aug 25, 2020 at 5:53 PM Akshay Joshi <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Hi Pradip
>>>>>>>>
>>>>>>>> Following are the GUI review comments:
>>>>>>>> *Testing Scenario*: I have three users U1 and U2 are Admin and U3
>>>>>>>> is a normal user. Login from U1 first time and pgAdmin4 discover all the
>>>>>>>> local servers. Steps that I perform:
>>>>>>>>
>>>>>>>> - Shared one server.
>>>>>>>> - Create a group 'Test' and move one server to that group.
>>>>>>>> - Share the server of the 'Test' group as well.
>>>>>>>>
>>>>>>>> [image: Screenshot 2020-08-25 at 4.38.32 PM.png]
>>>>>>>> *Issues:*
>>>>>>>>
>>>>>>>> - When login using U2 or U3 PostgreSQL 9.2 and 10 shown twice
>>>>>>>> which should not. Refer below screenshot
>>>>>>>> [image: Screenshot 2020-08-25 at 4.30.22 PM.png]
>>>>>>>>
>>>>>>>> Fixed. If the server is auto-discovered and U1 shared the
>>>>>>> auto-discovered server(PostgreSQL 9.2 in the above example) and then log in
>>>>>>> using U2, in this case, shared server(PostgreSQL 9.2) will not visible to
>>>>>>> U2 as the same server will be auto-discovered for U2.
>>>>>>>
>>>>>>>>
>>>>>>>> -
>>>>>>>> - When expanding the server or clicking on the 'Connect Server'
>>>>>>>> menu of the shared server, it opens the properties dialog.
>>>>>>>>
>>>>>>>> This is expected. Properties dialogue will open the first time for
>>>>>>> a shared server where the user has to enter server details. This will
>>>>>>> happen for the user who is not the owner of the server.
>>>>>>>
>>>>>>>>
>>>>>>>> - Try to update the IPAddress, Port it is not updating for the
>>>>>>>> shared server. Check other properties too.
>>>>>>>>
>>>>>>>> Fixed.
>>>>>>>
>>>>>>>>
>>>>>>>> -
>>>>>>>> - When we set "Hide shared server?" setting to true/false. A
>>>>>>>> complete browser tree should be refreshed. With the current implementation,
>>>>>>>> each group needs to be refreshed individually and the server group still
>>>>>>>> visible while refreshing the complete page Browser Tree looks perfect.
>>>>>>>>
>>>>>>>> Fixed.
>>>>>>>
>>>>>>>>
>>>>>>>> - When there is only one server in a group and we expanded the
>>>>>>>> group 'Connect to server' dialog appears which should not. (*This may be an
>>>>>>>> old issue)
>>>>>>>>
>>>>>>>> This is expected and its an old behavior.
>>>>>>>
>>>>>>>>
>>>>>>>> -
>>>>>>>> - Schema Diff shared servers are not visible inside the
>>>>>>>> appropriate group in the source and target server list.
>>>>>>>>
>>>>>>>> Fixed
>>>>>>>
>>>>>>>>
>>>>>>>> - Documentation changes required (Create a new Redmine).
>>>>>>>>
>>>>>>>> Done.
>>>>>>>
>>>>>>>>
>>>>>>>> -
>>>>>>>> - SonarQube shows 1 new Bug and 4 new code smell. (Fixed the
>>>>>>>> Bug, code smell can be fixed later)
>>>>>>>>
>>>>>>>> Fixed.
>>>>>>>
>>>>>>>>
>>>>>>>> - 'pgAdmin4_test_non_admin_credentials' should be added in '
>>>>>>>> test_config.json.in' file.
>>>>>>>>
>>>>>>>> Fixed.
>>>>>>>
>>>>>>>>
>>>>>>>> - For Desktop mode shared server test cases should be skipped.
>>>>>>>> It is failing at the moment.
>>>>>>>>
>>>>>>>> Fixed.
>>>>>>>
>>>>>>>> Code review still remains :)
>>>>>>>>
>>>>>>>> On Fri, Aug 21, 2020 at 6:00 PM Pradip Parkale <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> Hi Akshay,
>>>>>>>>> Please find the updated patch.
>>>>>>>>>
>>>>>>>>> On Fri, Aug 21, 2020 at 1:55 PM Akshay Joshi <
>>>>>>>>> [email protected]> wrote:
>>>>>>>>>
>>>>>>>>>> Hi Pradip
>>>>>>>>>>
>>>>>>>>>> The patch is not applied, can you please rebase and send it again.
>>>>>>>>>>
>>>>>>>>>> On Thu, Aug 20, 2020 at 3:58 PM Pradip Parkale <
>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>
>>>>>>>>>>> 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
>>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> --
>>>>>>>>>> *Thanks & Regards*
>>>>>>>>>> *Akshay Joshi*
>>>>>>>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>>>>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>>>>>>
>>>>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> --
>>>>>>>>> Thanks & Regards,
>>>>>>>>> Pradip Parkale
>>>>>>>>> Software Engineer | EnterpriseDB Corporation
>>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> --
>>>>>>>> *Thanks & Regards*
>>>>>>>> *Akshay Joshi*
>>>>>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>>>>
>>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> Thanks & Regards,
>>>>>>> Pradip Parkale
>>>>>>> Software Engineer | EnterpriseDB Corporation
>>>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>> *Thanks & Regards*
>>>>>> *Akshay Joshi*
>>>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>>
>>>>>> *Mobile: +91 976-788-8246*
>>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> Thanks & Regards,
>>>>> Pradip Parkale
>>>>> Software Engineer | EnterpriseDB Corporation
>>>>>
>>>>
>>>>
>>>> --
>>>> *Thanks & Regards*
>>>> *Akshay Joshi*
>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>
>>>> *Mobile: +91 976-788-8246*
>>>>
>>>
>>>
>>> --
>>> Thanks & Regards,
>>> Pradip Parkale
>>> Software Engineer | EnterpriseDB Corporation
>>>
>>
>>
>> --
>> *Thanks & Regards*
>> *Akshay Joshi*
>> *pgAdmin Hacker | Sr. Software Architect*
>> *EDB Postgres <http://edbpostgres.com>*
>>
>> *Mobile: +91 976-788-8246*
>>
>
>
> --
> Thanks & Regards,
> Pradip Parkale
> Software Engineer | EnterpriseDB Corporation
>
--
Thanks & Regards,
Pradip Parkale
Software Engineer | EnterpriseDB Corporation
Attachments:
[image/png] Screenshot 2020-08-25 at 4.30.22 PM.png (70.2K, 3-Screenshot%202020-08-25%20at%204.30.22%20PM.png)
download | view image
[image/png] Screenshot 2020-08-25 at 4.38.32 PM.png (52.9K, 4-Screenshot%202020-08-25%20at%204.38.32%20PM.png)
download | view image
[application/octet-stream] shared-server-icon-issue_v2.patch (2.4K, 5-shared-server-icon-issue_v2.patch)
download | inline diff:
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 e48524924..4db122473 100644
--- a/web/pgadmin/browser/server_groups/servers/static/js/server.js
+++ b/web/pgadmin/browser/server_groups/servers/static/js/server.js
@@ -238,7 +238,7 @@ define('pgadmin.node.server', [
d = t.itemData(i);
t.removeIcon(i);
d.connected = false;
- if (d.shared){
+ if (d.shared && pgAdmin.server_mode == 'True'){
d.icon = 'icon-shared-server-not-connected';
}else{
d.icon = 'icon-server-not-connected';
@@ -1216,6 +1216,7 @@ define('pgadmin.node.server', [
tree.unload(item);
tree.setInode(item);
tree.addIcon(item, {icon: 'icon-shared-server-not-connected'});
+ Alertify.info('Please enter the server details to connect to server.The server is shared server.');
}else{
data.is_connecting = false;
tree.unload(item);
@@ -1238,7 +1239,7 @@ define('pgadmin.node.server', [
// Let's not change the status of the tree node now.
if (!_wasConnected) {
tree.setInode(_item);
- if (data.shared){
+ if (data.shared && pgAdmin.server_mode == 'True'){
tree.addIcon(_item, {icon: 'icon-shared-server-not-connected'});
}else{
tree.addIcon(_item, {icon: 'icon-server-not-connected'});
@@ -1400,7 +1401,7 @@ define('pgadmin.node.server', [
_tree.unload(_item);
_tree.setInode(_item);
_tree.removeIcon(_item);
- if (_data.shared){
+ if (_data.shared && pgAdmin.server_mode == 'True'){
_tree.addIcon(_item, {icon: 'icon-shared-server-not-connected'});
}else{
_tree.addIcon(_item, {icon: 'icon-server-not-connected'});
@@ -1470,7 +1471,7 @@ define('pgadmin.node.server', [
})
.fail(function(xhr, status, error) {
tree.setInode(item);
- if (data.shared){
+ if (data.shared && pgAdmin.server_mode == 'True'){
tree.addIcon(item, {icon: 'icon-shared-server-not-connected'});
}else{
tree.addIcon(item, {icon: 'icon-server-not-connected'});
^ permalink raw reply [nested|flat] 13+ messages in thread
* Re: [pgAdmin][RM4979]: Configuration files for data sources or similar.
2020-08-20 10:28 [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-21 08:25 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-08-21 12:30 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-25 12:22 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-01 11:17 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-09-02 08:18 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-03 06:55 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-09-03 07:34 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-03 08:37 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-09-03 08:53 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Akshay Joshi <[email protected]>
2020-09-07 17:16 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-09-07 17:45 ` Re: [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
@ 2020-09-08 06:35 ` Akshay Joshi <[email protected]>
0 siblings, 0 replies; 13+ messages in thread
From: Akshay Joshi @ 2020-09-08 06:35 UTC (permalink / raw)
To: Pradip Parkale <[email protected]>; +Cc: pgadmin-hackers
Thanks, patch applied.
On Mon, Sep 7, 2020 at 11:15 PM Pradip Parkale <
[email protected]> wrote:
> Please ignore previous patch. Find the updated patch.
>
> On Mon, Sep 7, 2020 at 10:46 PM Pradip Parkale <
> [email protected]> wrote:
>
>> Hi Akshay,
>>
>> Please find attached for the shared server. The server icon was getting
>> change to the shared server icon in Desktop mode.
>>
>> On Thu, Sep 3, 2020 at 2:23 PM Akshay Joshi <
>> [email protected]> wrote:
>>
>>> Thanks, patch applied.
>>>
>>> On Thu, Sep 3, 2020 at 2:07 PM Pradip Parkale <
>>> [email protected]> wrote:
>>>
>>>> Hi Akshay,
>>>>
>>>> Please find the patch.
>>>>
>>>> On Thu, Sep 3, 2020 at 1:05 PM Akshay Joshi <
>>>> [email protected]> wrote:
>>>>
>>>>> Thanks, patch applied.
>>>>>
>>>>> On Thu, Sep 3, 2020 at 12:25 PM Pradip Parkale <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Akshay,
>>>>>> Please find the updated patch.
>>>>>>
>>>>>> On Wed, Sep 2, 2020 at 1:48 PM Akshay Joshi <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi Pradip
>>>>>>>
>>>>>>> Please fix SonarQube issues that introduce with this patch.
>>>>>>>
>>>>>>> On Tue, Sep 1, 2020 at 4:47 PM Pradip Parkale <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Hi Akshay,
>>>>>>>>
>>>>>>>> Please find the updated patch.
>>>>>>>>
>>>>>>>> On Tue, Aug 25, 2020 at 5:53 PM Akshay Joshi <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> Hi Pradip
>>>>>>>>>
>>>>>>>>> Following are the GUI review comments:
>>>>>>>>> *Testing Scenario*: I have three users U1 and U2 are Admin and U3
>>>>>>>>> is a normal user. Login from U1 first time and pgAdmin4 discover all the
>>>>>>>>> local servers. Steps that I perform:
>>>>>>>>>
>>>>>>>>> - Shared one server.
>>>>>>>>> - Create a group 'Test' and move one server to that group.
>>>>>>>>> - Share the server of the 'Test' group as well.
>>>>>>>>>
>>>>>>>>> [image: Screenshot 2020-08-25 at 4.38.32 PM.png]
>>>>>>>>> *Issues:*
>>>>>>>>>
>>>>>>>>> - When login using U2 or U3 PostgreSQL 9.2 and 10 shown twice
>>>>>>>>> which should not. Refer below screenshot
>>>>>>>>> [image: Screenshot 2020-08-25 at 4.30.22 PM.png]
>>>>>>>>>
>>>>>>>>> Fixed. If the server is auto-discovered and U1 shared the
>>>>>>>> auto-discovered server(PostgreSQL 9.2 in the above example) and then log in
>>>>>>>> using U2, in this case, shared server(PostgreSQL 9.2) will not visible to
>>>>>>>> U2 as the same server will be auto-discovered for U2.
>>>>>>>>
>>>>>>>>>
>>>>>>>>> -
>>>>>>>>> - When expanding the server or clicking on the 'Connect
>>>>>>>>> Server' menu of the shared server, it opens the properties dialog.
>>>>>>>>>
>>>>>>>>> This is expected. Properties dialogue will open the first time for
>>>>>>>> a shared server where the user has to enter server details. This will
>>>>>>>> happen for the user who is not the owner of the server.
>>>>>>>>
>>>>>>>>>
>>>>>>>>> - Try to update the IPAddress, Port it is not updating for the
>>>>>>>>> shared server. Check other properties too.
>>>>>>>>>
>>>>>>>>> Fixed.
>>>>>>>>
>>>>>>>>>
>>>>>>>>> -
>>>>>>>>> - When we set "Hide shared server?" setting to true/false. A
>>>>>>>>> complete browser tree should be refreshed. With the current implementation,
>>>>>>>>> each group needs to be refreshed individually and the server group still
>>>>>>>>> visible while refreshing the complete page Browser Tree looks perfect.
>>>>>>>>>
>>>>>>>>> Fixed.
>>>>>>>>
>>>>>>>>>
>>>>>>>>> - When there is only one server in a group and we expanded the
>>>>>>>>> group 'Connect to server' dialog appears which should not. (*This may be an
>>>>>>>>> old issue)
>>>>>>>>>
>>>>>>>>> This is expected and its an old behavior.
>>>>>>>>
>>>>>>>>>
>>>>>>>>> -
>>>>>>>>> - Schema Diff shared servers are not visible inside the
>>>>>>>>> appropriate group in the source and target server list.
>>>>>>>>>
>>>>>>>>> Fixed
>>>>>>>>
>>>>>>>>>
>>>>>>>>> - Documentation changes required (Create a new Redmine).
>>>>>>>>>
>>>>>>>>> Done.
>>>>>>>>
>>>>>>>>>
>>>>>>>>> -
>>>>>>>>> - SonarQube shows 1 new Bug and 4 new code smell. (Fixed the
>>>>>>>>> Bug, code smell can be fixed later)
>>>>>>>>>
>>>>>>>>> Fixed.
>>>>>>>>
>>>>>>>>>
>>>>>>>>> - 'pgAdmin4_test_non_admin_credentials' should be added in '
>>>>>>>>> test_config.json.in' file.
>>>>>>>>>
>>>>>>>>> Fixed.
>>>>>>>>
>>>>>>>>>
>>>>>>>>> - For Desktop mode shared server test cases should be skipped.
>>>>>>>>> It is failing at the moment.
>>>>>>>>>
>>>>>>>>> Fixed.
>>>>>>>>
>>>>>>>>> Code review still remains :)
>>>>>>>>>
>>>>>>>>> On Fri, Aug 21, 2020 at 6:00 PM Pradip Parkale <
>>>>>>>>> [email protected]> wrote:
>>>>>>>>>
>>>>>>>>>> Hi Akshay,
>>>>>>>>>> Please find the updated patch.
>>>>>>>>>>
>>>>>>>>>> On Fri, Aug 21, 2020 at 1:55 PM Akshay Joshi <
>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>
>>>>>>>>>>> Hi Pradip
>>>>>>>>>>>
>>>>>>>>>>> The patch is not applied, can you please rebase and send it
>>>>>>>>>>> again.
>>>>>>>>>>>
>>>>>>>>>>> On Thu, Aug 20, 2020 at 3:58 PM Pradip Parkale <
>>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>>
>>>>>>>>>>>> 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
>>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> --
>>>>>>>>>>> *Thanks & Regards*
>>>>>>>>>>> *Akshay Joshi*
>>>>>>>>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>>>>>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>>>>>>>
>>>>>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> --
>>>>>>>>>> Thanks & Regards,
>>>>>>>>>> Pradip Parkale
>>>>>>>>>> Software Engineer | EnterpriseDB Corporation
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> --
>>>>>>>>> *Thanks & Regards*
>>>>>>>>> *Akshay Joshi*
>>>>>>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>>>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>>>>>
>>>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> --
>>>>>>>> Thanks & Regards,
>>>>>>>> Pradip Parkale
>>>>>>>> Software Engineer | EnterpriseDB Corporation
>>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> *Thanks & Regards*
>>>>>>> *Akshay Joshi*
>>>>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>>>
>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>> Thanks & Regards,
>>>>>> Pradip Parkale
>>>>>> Software Engineer | EnterpriseDB Corporation
>>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> *Thanks & Regards*
>>>>> *Akshay Joshi*
>>>>> *pgAdmin Hacker | Sr. Software Architect*
>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>
>>>>> *Mobile: +91 976-788-8246*
>>>>>
>>>>
>>>>
>>>> --
>>>> Thanks & Regards,
>>>> Pradip Parkale
>>>> Software Engineer | EnterpriseDB Corporation
>>>>
>>>
>>>
>>> --
>>> *Thanks & Regards*
>>> *Akshay Joshi*
>>> *pgAdmin Hacker | Sr. Software Architect*
>>> *EDB Postgres <http://edbpostgres.com>*
>>>
>>> *Mobile: +91 976-788-8246*
>>>
>>
>>
>> --
>> Thanks & Regards,
>> Pradip Parkale
>> Software Engineer | EnterpriseDB Corporation
>>
>
>
> --
> Thanks & Regards,
> Pradip Parkale
> Software Engineer | EnterpriseDB Corporation
>
--
*Thanks & Regards*
*Akshay Joshi*
*pgAdmin Hacker | Sr. Software Architect*
*EDB Postgres <http://edbpostgres.com>*
*Mobile: +91 976-788-8246*
Attachments:
[image/png] Screenshot 2020-08-25 at 4.30.22 PM.png (70.2K, 3-Screenshot%202020-08-25%20at%204.30.22%20PM.png)
download | view image
[image/png] Screenshot 2020-08-25 at 4.38.32 PM.png (52.9K, 4-Screenshot%202020-08-25%20at%204.38.32%20PM.png)
download | view image
^ permalink raw reply [nested|flat] 13+ messages in thread
end of thread, other threads:[~2020-09-08 06:35 UTC | newest]
Thread overview: 13+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-08-20 10:28 [pgAdmin][RM4979]: Configuration files for data sources or similar. Pradip Parkale <[email protected]>
2020-08-21 08:25 ` Akshay Joshi <[email protected]>
2020-08-21 12:30 ` Pradip Parkale <[email protected]>
2020-08-25 12:22 ` Akshay Joshi <[email protected]>
2020-09-01 11:17 ` Pradip Parkale <[email protected]>
2020-09-02 08:18 ` Akshay Joshi <[email protected]>
2020-09-03 06:55 ` Pradip Parkale <[email protected]>
2020-09-03 07:34 ` Akshay Joshi <[email protected]>
2020-09-03 08:37 ` Pradip Parkale <[email protected]>
2020-09-03 08:53 ` Akshay Joshi <[email protected]>
2020-09-07 17:16 ` Pradip Parkale <[email protected]>
2020-09-07 17:45 ` Pradip Parkale <[email protected]>
2020-09-08 06:35 ` Akshay Joshi <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox