Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions web/pgadmin/browser/templates/browser/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,10 @@ define('pgadmin.browser.utils',
{% endif %}
{% if is_admin %}
{
label: '{{ _('Users') }}',
label: '{{ _('User Management') }}',
type: 'normal',
callback: ()=>{
pgAdmin.UserManagement.show_users()
pgAdmin.UserManagement.launchUserManagement()
}
},
{
Expand Down
8 changes: 7 additions & 1 deletion web/pgadmin/static/js/Theme/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,12 @@ basicSettings = createTheme(basicSettings, {
height: '100%',
boxSizing: 'border-box',
},
adornedStart: {
paddingLeft: basicSettings.spacing(0.75),
},
inputAdornedStart: {
paddingLeft: '2px',
},
adornedEnd: {
paddingRight: basicSettings.spacing(0.75),
},
Expand Down Expand Up @@ -523,7 +529,7 @@ function getFinalTheme(baseTheme) {
},
inputSizeSmall: {
height: '16px', // + 12px of padding = 28px;
}
},
}
},
MuiSelect: {
Expand Down
4 changes: 2 additions & 2 deletions web/pgadmin/static/js/components/PgReactTableStyled.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -441,11 +441,11 @@ export function getCheckboxHeaderCell({title}) {
return Cell;
}

export function getEditCell({isDisabled, title}) {
export function getEditCell({isDisabled, title, onClick}) {
const Cell = ({ row }) => {
return <PgIconButton data-test="expand-row" title={title} icon={<EditRoundedIcon fontSize="small" />} className='pgrt-cell-button'
onClick={()=>{
row.toggleExpanded();
onClick ? onClick(row) : row.toggleExpanded();
}} disabled={isDisabled?.(row)}
/>;
};
Expand Down
2 changes: 2 additions & 0 deletions web/pgadmin/static/js/components/PgTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import gettext from 'sources/gettext';
import EmptyPanelMessage from './EmptyPanelMessage';
import { InputText } from './FormComponents';
import { PgReactTable, PgReactTableBody, PgReactTableCell, PgReactTableHeader, PgReactTableRow, PgReactTableRowContent, PgReactTableRowExpandContent, getCheckboxCell, getCheckboxHeaderCell } from './PgReactTableStyled';
import SearchRoundedIcon from '@mui/icons-material/SearchRounded';


const ROW_HEIGHT = 30;
Expand Down Expand Up @@ -334,6 +335,7 @@ export default function PgTable({ caveTable = true, tableNoBorder = true, tableN
onChange={(val) => {
setSearchVal(val);
}}
startAdornment={<SearchRoundedIcon />}
/>
</Box>
</Box>}
Expand Down
90 changes: 52 additions & 38 deletions web/pgadmin/tools/user_management/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def get_exposed_url_endpoints(self):
current_app.login_manager.login_view,
'user_management.auth_sources', 'user_management.change_owner',
'user_management.shared_servers', 'user_management.admin_users',
'user_management.save'
'user_management.save', 'user_management.save_id'
]


Expand Down Expand Up @@ -168,7 +168,8 @@ def user(uid):
'active': u.active,
'role': u.roles[0].id,
'auth_source': u.auth_source,
'locked': u.locked
'locked': u.locked,
'canDrop': u.id != current_user.id
})

res = users_data
Expand Down Expand Up @@ -337,7 +338,6 @@ def admin_users(uid=None):

return make_json_response(
success=1,
info=_("No shared servers found"),
data={
'status': 'success',
'msg': 'Admin user list',
Expand Down Expand Up @@ -398,36 +398,32 @@ def auth_sources():


@blueprint.route('/save', methods=['POST'], endpoint='save')
@blueprint.route('/save/<int:id>', methods=['DELETE'], endpoint='save_id')
@roles_required('Administrator')
def save():
def save(id=None):
"""
This function is used to add/update/delete users.
"""
if request.method == 'DELETE':
status, res = delete_user(id)
if not status:
return internal_server_error(errormsg=res)

return ajax_response(
status=200
)

data = request.form if request.form else json.loads(
request.data
)

try:
# Delete Users
if 'deleted' in data:
for item in data['deleted']:
status, res = delete_user(item['id'])
if not status:
return internal_server_error(errormsg=res)
# Create Users
if 'added' in data:
for item in data['added']:
status, res = create_user(item)
if not status:
return internal_server_error(errormsg=res)
# Modify Users
if 'changed' in data:
for item in data['changed']:
status, res = update_user(item['id'], item)
if not status:
return internal_server_error(errormsg=res)
except Exception as e:
return internal_server_error(errormsg=str(e))
if 'id' not in data:
status, res = create_user(data)
else:
status, res = update_user(data['id'], data)

if not status:
return internal_server_error(errormsg=res)

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


def validate_unique_user(data):
if 'username' not in data:
return

exist_users = User.query.filter_by(
username=data['username'],
auth_source=data['auth_source']
).count()

if exist_users != 0:
raise InternalServerError(_("User email/username must be unique "
"for an authentication source."))


def validate_user(data):
new_data = dict()

validate_unique_user(data)

validate_password(data, new_data)

if 'email' in data and data['email'] and data['email'] != "":
Expand Down Expand Up @@ -508,20 +520,12 @@ def _create_new_user(new_data):
:param new_data: Data from user creation.
:return: Return new created user.
"""
auth_source = new_data['auth_source'] if 'auth_source' in new_data \
else INTERNAL
username = new_data['username'] if \
'username' in new_data and auth_source != \
INTERNAL else new_data['email']
email = new_data['email'] if 'email' in new_data else None
password = new_data['password'] if 'password' in new_data else None

usr = User(username=username,
email=email,
usr = User(username=new_data['username'],
email=new_data['email'],
roles=new_data['roles'],
active=new_data['active'],
password=password,
auth_source=auth_source)
password=new_data['password'],
auth_source=new_data['auth_source'])
db.session.add(usr)
db.session.commit()
# Add default server group for new user.
Expand All @@ -544,8 +548,18 @@ def create_user(data):
else:
return False, _("Missing field: '{0}'").format(f)

data['auth_source'] = data['auth_source'] if 'auth_source' in data \
else INTERNAL
data['username'] = data['username'] if \
'username' in data and data['auth_source'] != \
INTERNAL else data['email']
data['email'] = data['email'] if 'email' in data else None
data['password'] = data['password'] if 'password' in data else None

try:
new_data = validate_user(data)
new_data['password'] = new_data['password']\
if 'password' in new_data else None

if 'roles' in new_data:
new_data['roles'] = [Role.query.get(new_data['roles'])]
Expand Down Expand Up @@ -588,7 +602,7 @@ def update_user(uid, data):
if 'roles' in new_data:
new_data['roles'] = [Role.query.get(new_data['roles'])]
except Exception as e:
return False, str(e.description)
return False, str(e)

try:
for k, v in new_data.items():
Expand Down
55 changes: 55 additions & 0 deletions web/pgadmin/tools/user_management/static/js/Component.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2025, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////

import React from 'react';
import { Box, styled, Tab, Tabs } from '@mui/material';
import TabPanel from '../../../../static/js/components/TabPanel';
import Users from './Users';

const Root = styled('div')(({theme}) => ({
height: '100%',
background: theme.palette.grey[400],
display: 'flex',
flexDirection: 'column',
padding: '8px',

'& .Component-panel': {
flexGrow: 1,
display: 'flex',
flexDirection: 'column',
...theme.mixins.panelBorder.all,
}
}));

export default function Component() {
const [tabValue, setTabValue] = React.useState(0);

return (
<Root>
<Box className='Component-panel'>
<Box>
<Tabs
value={tabValue}
onChange={(_e, selTabValue) => {
setTabValue(selTabValue);
}}
variant="scrollable"
scrollButtons="auto"
action={(ref)=>ref?.updateIndicator()}
>
<Tab label="Users" />
</Tabs>
</Box>
<TabPanel value={tabValue} index={0}>
<Users />
</TabPanel>
</Box>
</Root>
);
}
Loading
Loading