From d8971b9598c9b14db3c5901cf4396ed9a787f699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E6=B0=B8=E5=BC=BA?= <11704063+s-yongqiang@user.noreply.gitee.com> Date: Sun, 28 Apr 2024 18:22:33 +0800 Subject: [PATCH] admin add ilter api for users --- .../src/components/dialog/dropdown-dialog.js | 93 ++++++++++++++++++ .../pages/sys-admin/users/users-content.js | 79 ++++++++++----- frontend/src/pages/sys-admin/users/users.js | 68 +++++++++++-- seahub/api2/endpoints/admin/users.py | 26 ++++- seahub/utils/ccnet_db.py | 95 ++++++++++++++++++- 5 files changed, 321 insertions(+), 40 deletions(-) create mode 100644 frontend/src/components/dialog/dropdown-dialog.js diff --git a/frontend/src/components/dialog/dropdown-dialog.js b/frontend/src/components/dialog/dropdown-dialog.js new file mode 100644 index 00000000000..b5d963fe025 --- /dev/null +++ b/frontend/src/components/dialog/dropdown-dialog.js @@ -0,0 +1,93 @@ +import React, { Component } from 'react'; +import { gettext } from '../../utils/constants'; +import PropTypes from 'prop-types'; +const { availableRoles } = window.sysadmin.pageOptions; + +class DropdownComponent extends Component { + constructor(props) { + super(props); + } + + + handleStatusChange = (event) => { + this.props.handleFilterActive(event.target.value); + }; + + handleRoleChange = (event) => { + this.props.handleFilterRole(event.target.value); + }; + + translateRole = (role) => { + switch (role) { + case 'default': + return gettext('Default'); + case 'guest': + return gettext('Guest'); + default: + return role; + } + }; + + render() { + const styles = { + filterBar: { + marginTop: '16px', + marginBottom: '8px', + display: 'flex', + alignItems: 'center' + }, + filterContainer: { + marginRight: '20px', + }, + filterLabel: { + marginRight: '8px', + fontSize: '14px', + color: '#000' + }, + filterSelect: { + height: '30px', + backgroundColor: '#F5F5F5', + border: '1px solid #ddd', + color: '#333', + fontSize: '14px' + } + }; + return ( +
+
+ + +
+
+ + +
+
+ ); + } +} + +DropdownComponent.propTypes = { + loading: PropTypes.bool, + curPerPage: PropTypes.number, + sortBy: PropTypes.string, + currentPage: PropTypes.number, + sortOrder: PropTypes.string, + handleFilterActive: PropTypes.func, + handleFilterRole: PropTypes.func, + role: PropTypes.string, + isActive: PropTypes.string, + availableRoles: PropTypes.array, +}; +export default DropdownComponent; diff --git a/frontend/src/pages/sys-admin/users/users-content.js b/frontend/src/pages/sys-admin/users/users-content.js index 47b352c317c..67e9f4c7bdc 100644 --- a/frontend/src/pages/sys-admin/users/users-content.js +++ b/frontend/src/pages/sys-admin/users/users-content.js @@ -14,18 +14,21 @@ import OpMenu from '../../../components/dialog/op-menu'; import SysAdminUserSetQuotaDialog from '../../../components/dialog/sysadmin-dialog/set-quota'; import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog'; import UserLink from '../user-link'; - +import DropdownComponent from '../../../components/dialog/dropdown-dialog'; const { availableRoles, availableAdminRoles, institutions } = window.sysadmin.pageOptions; + + class Content extends Component { constructor(props) { super(props); this.state = { - isItemFreezed: false + isItemFreezed: false, }; } + toggleItemFreezed = (isFreezed) => { this.setState({ isItemFreezed: isFreezed }); }; @@ -39,33 +42,47 @@ class Content extends Component { }; getPreviousPage = () => { - this.props.getListByPage(this.props.currentPage - 1); + this.props.getListByPage(this.props.currentPage - 1, this.props.is_active, this.props.role); }; getNextPage = () => { - this.props.getListByPage(this.props.currentPage + 1); + this.props.getListByPage(this.props.currentPage + 1, this.props.is_active, this.props.role); }; sortByQuotaUsage = (e) => { e.preventDefault(); this.props.sortByQuotaUsage(); }; - render() { const { isAdmin, loading, errorMsg, items, isAllUsersSelected, curPerPage, hasNextPage, currentPage, sortBy, sortOrder } = this.props; + if (loading) { return ; } else if (errorMsg) { return

{errorMsg}

; } else { const emptyTip = ( - -

{gettext('No users')}

-
+
+ {this.props.currentItem == 'database' && + + + } + + +

{gettext('No users')}

+
+
+ ); @@ -116,6 +133,14 @@ class Content extends Component { const table = ( + {this.props.currentItem=='database' && + + } + @@ -129,21 +154,24 @@ class Content extends Component { {items.map((item, index) => { - return (); + if (index < this.props.curPerPage) { + return (); + } + return null; })}
@@ -188,6 +216,11 @@ Content.propTypes = { curPerPage: PropTypes.number, hasNextPage: PropTypes.bool, sortOrder: PropTypes.string, + is_active: PropTypes.string, + role: PropTypes.string, + currentItem: PropTypes.string, + handleFilterActive: PropTypes.func, + handleFilterRole: PropTypes.func }; class Item extends Component { diff --git a/frontend/src/pages/sys-admin/users/users.js b/frontend/src/pages/sys-admin/users/users.js index 598ef5a380f..37e53d9ae26 100644 --- a/frontend/src/pages/sys-admin/users/users.js +++ b/frontend/src/pages/sys-admin/users/users.js @@ -43,7 +43,9 @@ class Users extends Component { isAddUserDialogOpen: false, isBatchSetQuotaDialogOpen: false, isBatchDeleteUserDialogOpen: false, - isBatchAddAdminDialogOpen: false + isBatchAddAdminDialogOpen: false, + is_active: null, + role: null, }; } @@ -55,15 +57,16 @@ class Users extends Component { const { currentPage, perPage, sortBy = '', - sortOrder = 'asc' + sortOrder = 'asc', + is_active, role } = this.state; this.setState({ perPage: parseInt(urlParams.get('per_page') || perPage), currentPage: parseInt(urlParams.get('page') || currentPage), sortBy: urlParams.get('order_by') || sortBy, - sortOrder: urlParams.get('direction') || sortOrder + sortOrder: urlParams.get('direction') || sortOrder, }, () => { - this.getUsersListByPage(this.state.currentPage); + this.getUsersListByPage(this.state.currentPage, is_active, role); }); } } @@ -160,10 +163,10 @@ class Users extends Component { }); }; - getUsersListByPage = (page) => { + getUsersListByPage = (page, is_active, role) => { const { perPage, sortBy, sortOrder } = this.state; const { isLDAPImported } = this.props; - seafileAPI.sysAdminListUsers(page, perPage, isLDAPImported, sortBy, sortOrder).then(res => { + seafileAPI.sysAdminListUsers(page, perPage, isLDAPImported, sortBy, sortOrder, is_active, role).then(res => { let users = res.data.data.map(user => {return new SysAdminUser(user);}); this.setState({ userList: users, @@ -179,6 +182,47 @@ class Users extends Component { }); }; + handleUserListSelect = (users, page, hasNextPage) => { + this.setState({ + userList: users, + loading: false, + hasNextPage: hasNextPage, + currentPage: page + }); + }; + + + updateURL = (page, perPage) => { + let url = new URL(location.href); + let searchParams = new URLSearchParams(url.search); + searchParams.set('page', page); + searchParams.set('per_page', perPage); + url.search = searchParams.toString(); + navigate(url.toString()); + }; + + handleFilterActive = (is_active) => { + this.setState({ + is_active: is_active, + currentPage: 1 + }, ()=>{ + const { currentPage, perPage, is_active, role } = this.state; + this.updateURL(currentPage, perPage); + this.getUsersListByPage(currentPage, is_active, role); + }); + }; + + handleFilterRole = (role) => { + this.setState({ + role: role, + currentPage: 1 + }, ()=>{ + const { currentPage,perPage, is_active, role } = this.state; + this.updateURL(currentPage, perPage); + this.getUsersListByPage(currentPage, is_active, role); + }); + }; + sortByQuotaUsage = () => { this.setState({ sortBy: 'quota_usage', @@ -187,13 +231,13 @@ class Users extends Component { }, () => { let url = new URL(location.href); let searchParams = new URLSearchParams(url.search); - const { currentPage, sortBy, sortOrder } = this.state; + const { currentPage, sortBy, sortOrder, is_active, role } = this.state; searchParams.set('page', currentPage); searchParams.set('order_by', sortBy); searchParams.set('direction', sortOrder); url.search = searchParams.toString(); navigate(url.toString()); - this.getUsersListByPage(currentPage); + this.getUsersListByPage(currentPage, is_active, role); }); }; @@ -309,7 +353,7 @@ class Users extends Component { this.setState({ perPage: perPage }, () => { - this.getUsersListByPage(1); + this.getUsersListByPage(1, this.state.is_active, this.state.role); }); }; @@ -468,6 +512,8 @@ class Users extends Component { currentPage={this.state.currentPage} hasNextPage={this.state.hasNextPage} curPerPage={this.state.perPage} + is_active={this.state.is_active} + role={this.state.role} resetPerPage={this.resetPerPage} getListByPage={this.getUsersListByPage} updateUser={this.updateUser} @@ -477,6 +523,10 @@ class Users extends Component { onUserSelected={this.onUserSelected} isAllUsersSelected={this.isAllUsersSelected} toggleSelectAllUsers={this.toggleSelectAllUsers} + handleUserListSelect={this.handleUserListSelect} + handleFilterRole={this.handleFilterRole} + handleFilterActive={this.handleFilterActive} + currentItem={this.getCurrentNavItem()} /> diff --git a/seahub/api2/endpoints/admin/users.py b/seahub/api2/endpoints/admin/users.py index d4b8f861888..a0fc009f900 100644 --- a/seahub/api2/endpoints/admin/users.py +++ b/seahub/api2/endpoints/admin/users.py @@ -46,6 +46,7 @@ datetime_to_isoformat_timestr from seahub.utils.user_permissions import get_user_role from seahub.utils.repo import normalize_repo_status_code +from seahub.utils.ccnet_db import CcnetDB from seahub.constants import DEFAULT_ADMIN from seahub.role_permissions.models import AdminRole from seahub.role_permissions.utils import get_available_roles @@ -549,7 +550,7 @@ class AdminUsers(APIView): throttle_classes = (UserRateThrottle, ) def get_info_of_users_order_by_quota_usage(self, source, direction, - page, per_page): + page, per_page, is_active=None, role=None): # get user's quota usage info user_usage_dict = {} @@ -581,6 +582,13 @@ def get_info_of_users_order_by_quota_usage(self, source, direction, # sort users.sort(key=lambda item: item.quota_usage, reverse=direction == 'desc') + if is_active == '1': + users = [u for u in users if u.is_active] + elif is_active == '0': + users = [u for u in users if not u.is_active] + + if role: + users = [u for u in users if get_user_role(u) == role] data = [] MULTI_INSTITUTION = getattr(settings, 'MULTI_INSTITUTION', False) @@ -633,12 +641,15 @@ def get(self, request): try: page = int(request.GET.get('page', '1')) per_page = int(request.GET.get('per_page', '25')) + is_active = request.GET.get('is_active', None) + role = request.GET.get('role', None) except ValueError: page = 1 per_page = 25 + is_active, role = None, None start = (page - 1) * per_page - + limit = per_page + 1 source = request.GET.get('source', 'DB').lower().strip() if source not in ['db', 'ldapimport']: # source: 'DB' or 'LDAPImport', default is 'DB' @@ -671,7 +682,10 @@ def get(self, request): data = self.get_info_of_users_order_by_quota_usage(source, direction, page, - per_page) + per_page, + is_active, + role, + ) except Exception as e: logger.error(e) error_msg = 'Internal Server Error' @@ -680,7 +694,11 @@ def get(self, request): result = {'data': data, 'total_count': total_count} return Response(result) else: - users = ccnet_api.get_emailusers('DB', start, per_page) + try: + ccnet_db = CcnetDB() + users, total_count = ccnet_db.list_eligible_users(start, limit, is_active, role) + except Exception: + users = ccnet_api.get_emailusers('DB', start, per_page) elif source == 'ldapimport': ldap_users_count = multi_ldap_users_count = 0 diff --git a/seahub/utils/ccnet_db.py b/seahub/utils/ccnet_db.py index e38ef5067a4..c0626986ab8 100644 --- a/seahub/utils/ccnet_db.py +++ b/seahub/utils/ccnet_db.py @@ -28,8 +28,9 @@ def get_ccnet_db_name(): import configparser from django.db import connection + class CcnetGroup(object): - + def __init__(self, **kwargs): self.id = kwargs.get('group_id') self.group_name = kwargs.get('group_name') @@ -37,13 +38,25 @@ def __init__(self, **kwargs): self.timestamp = kwargs.get('timestamp') self.parent_group_id = kwargs.get('parent_group_id') + +class CcnetUsers(object): + + def __init__(self, **kwargs): + self.user_id = kwargs.get('user_id') + self.email = kwargs.get('email') + self.is_staff = kwargs.get('is_staff') + self.is_active = kwargs.get('is_active') + self.ctime = kwargs.get('ctime') + self.role = kwargs.get('role') + self.passwd = kwargs.get('passwd') + + class CcnetDB: def __init__(self): self.db_name = get_ccnet_db_name()[0] - def list_org_departments(self, org_id): sql = f""" SELECT @@ -63,10 +76,10 @@ def list_org_departments(self, org_id): group_id = item[0] group_name = item[1] creator_name = item[2] - timestamp=item[3] + timestamp = item[3] parent_group_id = item[5] params = { - 'group_id':group_id, + 'group_id': group_id, 'group_name': group_name, 'creator_name': creator_name, 'timestamp': timestamp, @@ -75,3 +88,77 @@ def list_org_departments(self, org_id): group_obj = CcnetGroup(**params) groups.append(group_obj) return groups + + def list_eligible_users(self, start, limit, is_active=None, role=None): + + def status(is_active): + return 'AND t1.is_active=%s ' % is_active + + def is_role(role): + if role == 'default': + return 'AND (t2.role is null or t2.role = "default")' + else: + return 'AND t2.role = "%s"' % role + + search_clause = '' + if is_active: + search_clause += status(is_active) + if role: + search_clause += is_role(role) + + count_sql = f""" + SELECT count(1) + FROM + `{self.db_name}`.`EmailUser` t1 + LEFT JOIN + `{self.db_name}`.`UserRole` t2 + ON + t1.email = t2.email + WHERE + t1.email NOT LIKE '%%@seafile_group' %s + ORDER BY t1.id + """ % search_clause + + sql = f""" + SELECT t1.id, t1.email, t1.is_staff, t1.is_active, t1.ctime, t2.role, t1.passwd + FROM + `{self.db_name}`.`EmailUser` t1 + LEFT JOIN + `{self.db_name}`.`UserRole` t2 + ON + t1.email = t2.email + WHERE + t1.email NOT LIKE '%%@seafile_group' %s + ORDER BY t1.id + LIMIT {limit} OFFSET {start} + """ % search_clause + + users = [] + total = 0 + with connection.cursor() as cursor: + cursor.execute(count_sql) + cursor.execute(count_sql) + total_count = int(cursor.fetchone()[0]) + + cursor.execute(sql) + for item in cursor.fetchall(): + user_id = item[0] + email = item[1] + is_staff = item[2] + is_active = item[3] + ctime = item[4] + role = item[5] + passwd = item[6] + params = { + 'user_id': user_id, + 'email': email, + 'is_staff': is_staff, + 'is_active': is_active, + 'ctime': ctime, + 'role': role, + 'passwd': passwd + } + users_obj = CcnetUsers(**params) + users.append(users_obj) + + return users, total_count