Skip to content

Commit 52e6620

Browse files
Open user management in a tab instead of dialog for better UI/UX. #8574
1 parent f635df6 commit 52e6620

File tree

12 files changed

+747
-49
lines changed

12 files changed

+747
-49
lines changed

web/pgadmin/browser/templates/browser/js/utils.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,10 @@ define('pgadmin.browser.utils',
156156
{% endif %}
157157
{% if is_admin %}
158158
{
159-
label: '{{ _('Users') }}',
159+
label: '{{ _('User Management') }}',
160160
type: 'normal',
161161
callback: ()=>{
162-
pgAdmin.UserManagement.show_users()
162+
pgAdmin.UserManagement.launchUserManagement()
163163
}
164164
},
165165
{

web/pgadmin/static/js/components/PgReactTableStyled.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,11 +441,11 @@ export function getCheckboxHeaderCell({title}) {
441441
return Cell;
442442
}
443443

444-
export function getEditCell({isDisabled, title}) {
444+
export function getEditCell({isDisabled, title, onClick}) {
445445
const Cell = ({ row }) => {
446446
return <PgIconButton data-test="expand-row" title={title} icon={<EditRoundedIcon fontSize="small" />} className='pgrt-cell-button'
447447
onClick={()=>{
448-
row.toggleExpanded();
448+
onClick ? onClick(row) : row.toggleExpanded();
449449
}} disabled={isDisabled?.(row)}
450450
/>;
451451
};

web/pgadmin/tools/user_management/__init__.py

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def get_exposed_url_endpoints(self):
6767
current_app.login_manager.login_view,
6868
'user_management.auth_sources', 'user_management.change_owner',
6969
'user_management.shared_servers', 'user_management.admin_users',
70-
'user_management.save'
70+
'user_management.save', 'user_management.save_id'
7171
]
7272

7373

@@ -168,7 +168,8 @@ def user(uid):
168168
'active': u.active,
169169
'role': u.roles[0].id,
170170
'auth_source': u.auth_source,
171-
'locked': u.locked
171+
'locked': u.locked,
172+
'canDrop': u.id != current_user.id
172173
})
173174

174175
res = users_data
@@ -337,7 +338,6 @@ def admin_users(uid=None):
337338

338339
return make_json_response(
339340
success=1,
340-
info=_("No shared servers found"),
341341
data={
342342
'status': 'success',
343343
'msg': 'Admin user list',
@@ -398,36 +398,32 @@ def auth_sources():
398398

399399

400400
@blueprint.route('/save', methods=['POST'], endpoint='save')
401+
@blueprint.route('/save/<int:id>', methods=['DELETE'], endpoint='save_id')
401402
@roles_required('Administrator')
402-
def save():
403+
def save(id=None):
403404
"""
404405
This function is used to add/update/delete users.
405406
"""
407+
if request.method == 'DELETE':
408+
status, res = delete_user(id)
409+
if not status:
410+
return internal_server_error(errormsg=res)
411+
412+
return ajax_response(
413+
status=200
414+
)
415+
406416
data = request.form if request.form else json.loads(
407417
request.data
408418
)
409419

410-
try:
411-
# Delete Users
412-
if 'deleted' in data:
413-
for item in data['deleted']:
414-
status, res = delete_user(item['id'])
415-
if not status:
416-
return internal_server_error(errormsg=res)
417-
# Create Users
418-
if 'added' in data:
419-
for item in data['added']:
420-
status, res = create_user(item)
421-
if not status:
422-
return internal_server_error(errormsg=res)
423-
# Modify Users
424-
if 'changed' in data:
425-
for item in data['changed']:
426-
status, res = update_user(item['id'], item)
427-
if not status:
428-
return internal_server_error(errormsg=res)
429-
except Exception as e:
430-
return internal_server_error(errormsg=str(e))
420+
if 'id' not in data:
421+
status, res = create_user(data)
422+
else:
423+
status, res = update_user(data['id'], data)
424+
425+
if not status:
426+
return internal_server_error(errormsg=res)
431427

432428
return ajax_response(
433429
status=200
@@ -468,9 +464,25 @@ def validate_password(data, new_data):
468464
raise InternalServerError(_("Passwords do not match."))
469465

470466

467+
def validate_unique_user(data):
468+
if 'username' not in data:
469+
return
470+
471+
exist_users = User.query.filter_by(
472+
username=data['username'],
473+
auth_source=data['auth_source']
474+
).count()
475+
476+
if exist_users != 0:
477+
raise InternalServerError(_("User email/username must be unique "
478+
"for an authentication source."))
479+
480+
471481
def validate_user(data):
472482
new_data = dict()
473483

484+
validate_unique_user(data)
485+
474486
validate_password(data, new_data)
475487

476488
if 'email' in data and data['email'] and data['email'] != "":
@@ -508,20 +520,12 @@ def _create_new_user(new_data):
508520
:param new_data: Data from user creation.
509521
:return: Return new created user.
510522
"""
511-
auth_source = new_data['auth_source'] if 'auth_source' in new_data \
512-
else INTERNAL
513-
username = new_data['username'] if \
514-
'username' in new_data and auth_source != \
515-
INTERNAL else new_data['email']
516-
email = new_data['email'] if 'email' in new_data else None
517-
password = new_data['password'] if 'password' in new_data else None
518-
519-
usr = User(username=username,
520-
email=email,
523+
usr = User(username=new_data['username'],
524+
email=new_data['email'],
521525
roles=new_data['roles'],
522526
active=new_data['active'],
523-
password=password,
524-
auth_source=auth_source)
527+
password=new_data['password'],
528+
auth_source=new_data['auth_source'])
525529
db.session.add(usr)
526530
db.session.commit()
527531
# Add default server group for new user.
@@ -544,6 +548,14 @@ def create_user(data):
544548
else:
545549
return False, _("Missing field: '{0}'").format(f)
546550

551+
data['auth_source'] = data['auth_source'] if 'auth_source' in data \
552+
else INTERNAL
553+
data['username'] = data['username'] if \
554+
'username' in data and data['auth_source'] != \
555+
INTERNAL else data['email']
556+
data['email'] = data['email'] if 'email' in data else None
557+
data['password'] = data['password'] if 'password' in data else None
558+
547559
try:
548560
new_data = validate_user(data)
549561

@@ -588,7 +600,7 @@ def update_user(uid, data):
588600
if 'roles' in new_data:
589601
new_data['roles'] = [Role.query.get(new_data['roles'])]
590602
except Exception as e:
591-
return False, str(e.description)
603+
return False, str(e)
592604

593605
try:
594606
for k, v in new_data.items():
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React from 'react';
2+
import { Box, styled, Tab, Tabs } from '@mui/material';
3+
import TabPanel from '../../../../static/js/components/TabPanel';
4+
import Users from './Users';
5+
6+
const Root = styled('div')(({theme}) => ({
7+
height: '100%',
8+
background: theme.palette.grey[400],
9+
display: 'flex',
10+
flexDirection: 'column',
11+
padding: '8px',
12+
13+
'& .Component-panel': {
14+
flexGrow: 1,
15+
display: 'flex',
16+
flexDirection: 'column',
17+
...theme.mixins.panelBorder.all,
18+
}
19+
}));
20+
21+
export default function Component() {
22+
const [tabValue, setTabValue] = React.useState(0);
23+
24+
return (
25+
<Root>
26+
<Box className='Component-panel'>
27+
<Box>
28+
<Tabs
29+
value={tabValue}
30+
onChange={(_e, selTabValue) => {
31+
setTabValue(selTabValue);
32+
}}
33+
variant="scrollable"
34+
scrollButtons="auto"
35+
action={(ref)=>ref?.updateIndicator()}
36+
>
37+
<Tab label="Users" />
38+
</Tabs>
39+
</Box>
40+
<TabPanel value={tabValue} index={0}>
41+
<Users />
42+
</TabPanel>
43+
</Box>
44+
</Root>
45+
);
46+
}

0 commit comments

Comments
 (0)