Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8108ed6
fix: get_all_server_groups returning all server for admin user in ser…
Jun 2, 2026
cdb686b
fix: class ServerGroupView get_all_server_groups
Jun 2, 2026
634c7d8
feat: Configuration for ADMIN see or not servers of all users
Jun 3, 2026
a57892d
fix: use get_all_server_groups to retrive groups in getnodes
Jun 3, 2026
96f1ed5
refactor: Refactoring get_servers_from_group as sugested
Jun 3, 2026
e79034c
fix: Adjust relationship between ServerGroup and Server
Jun 3, 2026
e226406
fix: Fix N+1 shared-server lookup
Jun 3, 2026
0181a09
Adjust get_nodes() to grant to not allow delete for the first owned …
Jun 3, 2026
d00a8ce
chore: Enforce comment that admin can see all servers and groups
Jun 3, 2026
5c494ec
Fix (#9917)
sbraaa Jun 5, 2026
27406b5
fix: fixed invalid '| None' syntax for python 3.9 (#9993)
jasparm Jun 5, 2026
2dc9d71
fix(webpack): alias react-checkbox-tree to its ESM bundle (#9989)
dpage Jun 5, 2026
679c39f
fix: resolve E501 pycodestyle line-too-long in llm tools database mod…
hiteshjambhale Jun 5, 2026
44a1a2c
fix: get_all_server_groups returning all server for admin user in ser…
Jun 2, 2026
802abc4
fix: class ServerGroupView get_all_server_groups
Jun 2, 2026
5ac036c
feat: Configuration for ADMIN see or not servers of all users
Jun 3, 2026
7f81ccb
fix: use get_all_server_groups to retrive groups in getnodes
Jun 3, 2026
2ad8768
refactor: Refactoring get_servers_from_group as sugested
Jun 3, 2026
4560cad
fix: Adjust relationship between ServerGroup and Server
Jun 3, 2026
3c9cf33
fix: Fix N+1 shared-server lookup
Jun 3, 2026
19eb8a3
Adjust get_nodes() to grant to not allow delete for the first owned …
Jun 3, 2026
297d07c
chore: Enforce comment that admin can see all servers and groups
Jun 3, 2026
0ca99cf
feat: Enforce order im get_all_server_groups
Jun 3, 2026
5a2b314
Merge branch 'fix-get_all_server_groups' of https://github.com/lkmats…
Jun 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions web/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,12 @@
# "Unauthorised use is strictly forbidden."
LOGIN_BANNER = ""

##########################################################################
# Admin options
##########################################################################
ADMIN_CAN_SEE_ALL_SERVERS = False


##########################################################################
# Log settings
##########################################################################
Expand Down
118 changes: 87 additions & 31 deletions web/pgadmin/browser/server_groups/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,37 @@
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 sqlalchemy import exc, case
from pgadmin.model import db, ServerGroup, Server
import config
from pgadmin.utils.preferences import Preferences
from pgadmin.utils.server_access import get_server_group, \
get_server_groups_for_user
get_server_groups_for_user, \
get_servers_from_group


def get_icon_css_class(group_id, group_user_id,
default_val='icon-server_group'):
default_val='icon-server_group',
shared_group_ids=None):
"""
Returns css value
:param group_id:
:param group_user_id:
:param default_val:
:param shared_group_ids: Optional set of group IDs containing shared servers
: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, True
if config.SERVER_MODE and group_user_id != current_user.id:
if shared_group_ids is None:
# Fallback to per-group check if not provided
has_shared = ServerGroupModule.has_shared_server(group_id)
else:
# Use pre-fetched set for O(1) membership check
has_shared = group_id in shared_group_ids

if has_shared:
default_val = 'icon-server_group_shared'
return default_val, True

return default_val, False

Expand All @@ -69,35 +78,68 @@ def csssnippets(self):
@staticmethod
def has_shared_server(gid):
"""
To check whether given server group contains shared server or not
To check whether given server group contains shared servers
:param gid:
:return: True if servergroup contains shared server else false
:return: True if servergroup contains shared servers else false
"""
servers = Server.query.filter_by(servergroup_id=gid)
pref = Preferences.module('browser')
hide_shared_server = pref.preference('hide_shared_server').get()

servers = get_servers_from_group(gid, hide_shared_server)
for s in servers:
if s.shared:
return True

return False

@staticmethod
def has_not_shared_server(gid):
"""
To check whether given server group contains NOT shared servers
:param gid:
:return: True if servergroup contains NOT shared servers else false
"""
pref = Preferences.module('browser')
hide_shared_server = pref.preference('hide_shared_server').get()

servers = get_servers_from_group(gid, hide_shared_server)
for s in servers:
if not s.shared:
return True

return False

def get_nodes(self, *arg, **kwargs):
"""Return a JSON document listing the server groups for the user"""

if config.SERVER_MODE:
groups = ServerGroupView.get_all_server_groups()
else:
groups = ServerGroup.query.filter_by(
user_id=current_user.id
).order_by("id")
pref = Preferences.module('browser')
hide_shared_server = pref.preference('hide_shared_server').get()

groups = list(ServerGroupView.get_all_server_groups())

# Fetch all shared server group IDs in one query to avoid N+1 queries
shared_group_ids = ServerGroupView.get_shared_server_group_ids(hide_shared_server)

first_owned_group_id = next(
(group.id for group in groups if group.user_id == current_user.id),
None
)

for idx, group in enumerate(groups):
icon_class, is_shared = get_icon_css_class(group.id, group.user_id)
for group in groups:
icon_class, is_shared = get_icon_css_class(
group.id, group.user_id, shared_group_ids=shared_group_ids
)
can_delete = (
group.user_id == current_user.id and
group.id != first_owned_group_id
)
yield self.generate_browser_node(
"%d" % (group.id), None,
group.name,
icon_class,
True,
self.node_type,
can_delete=True if idx > 0 else False,
can_delete=can_delete,
user_id=group.user_id,
is_shared=is_shared
)
Expand Down Expand Up @@ -387,27 +429,41 @@ def get_all_server_groups():
pref = Preferences.module('browser')
hide_shared_server = pref.preference('hide_shared_server').get()

server_groups = get_server_groups_for_user()

if hide_shared_server:
groups = []
for group in server_groups:
if group.user_id != current_user.id and \
ServerGroupModule.has_shared_server(group.id):
continue
groups.append(group)
return groups
server_groups = ( get_server_groups_for_user(only_owned=hide_shared_server)
.order_by(
case((ServerGroup.user_id == current_user.id, 0), else_=1),
ServerGroup.id
)
)

return server_groups

@staticmethod
def get_shared_server_group_ids(hide_shared_server=False):
"""
Fetch all server group IDs that contain shared servers in one query.
Eliminates N+1 queries when checking multiple groups.
:param hide_shared_server: If True, filter by user ownership
:return: Set of server group IDs containing shared servers
"""

query = get_server_groups_for_user(only_owned=hide_shared_server).filter(
ServerGroup.servers.any(Server.shared)
)

group_ids = {row.id for row in query}

return group_ids


@pga_login_required
def nodes(self, gid=None):
"""Return a JSON document listing the server groups for the user"""
nodes = []
if gid is None:
if config.SERVER_MODE:

groups = self.get_all_server_groups()

else:
groups = ServerGroup.query.filter_by(user_id=current_user.id)

Expand Down
4 changes: 3 additions & 1 deletion web/pgadmin/llm/tools/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ def _get_connection(sid: int, did: int, conn_id: str):
)


def _connect_readonly(_manager, conn, conn_id: str) -> tuple[bool, str | None]:
def _connect_readonly(
_manager, conn, conn_id: str
) -> tuple[bool, Optional[str]]:
"""
Establish a read-only connection.
Expand Down
11 changes: 9 additions & 2 deletions web/pgadmin/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,13 @@ class ServerGroup(db.Model, UserScopedMixin):
name = db.Column(db.String(128), nullable=False)
__table_args__ = (db.UniqueConstraint('user_id', 'name'),)

servers = db.relationship(
'Server',
back_populates='servergroup',
lazy='select',
cascade=CASCADE_STR
)

@property
def serialize(self):
"""Return object data in easily serializable format"""
Expand Down Expand Up @@ -293,9 +300,9 @@ class Server(db.Model, UserScopedMixin):
role = db.Column(db.String(64), nullable=True)
comment = db.Column(db.String(1024), nullable=True)
discovery_id = db.Column(db.String(128), nullable=True)
servers = db.relationship(
servergroup = db.relationship(
'ServerGroup',
backref=db.backref('server', cascade=CASCADE_STR),
back_populates='servers',
lazy='joined'
)
db_res = db.Column(db.Text(), nullable=True)
Expand Down
6 changes: 3 additions & 3 deletions web/pgadmin/tools/schema_diff/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,10 +307,10 @@ def servers():
"connected": connected
}

if server.servers.name in res:
res[server.servers.name].append(server_info)
if server.servergroup.name in res:
res[server.servergroup.name].append(server_info)
else:
res[server.servers.name] = [server_info]
res[server.servergroup.name] = [server_info]

except Exception as e:
app.logger.exception(e)
Expand Down
2 changes: 1 addition & 1 deletion web/pgadmin/tools/sqleditor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2420,7 +2420,7 @@ def get_new_connection_data(sgid=None, sid=None):
manager = driver.connection_manager(server.id)
conn = manager.connection()
connected = conn.connected()
server_group_data[server.servers.name].append({
server_group_data[server.servergroup.name].append({
'label': server.name,
"value": server.id,
'image': server_icon_and_background(connected, manager,
Expand Down
Binary file modified web/pgadmin/translations/it/LC_MESSAGES/messages.mo
Binary file not shown.
Loading