diff --git a/frontend/src/pages/dashboard/log-user-selector.js b/frontend/src/pages/dashboard/log-user-selector.js index dc4f88a3e8..c17b4338f1 100644 --- a/frontend/src/pages/dashboard/log-user-selector.js +++ b/frontend/src/pages/dashboard/log-user-selector.js @@ -13,7 +13,7 @@ const propTypes = { onSelect: PropTypes.func.isRequired, isOpen: PropTypes.bool.isRequired, onToggle: PropTypes.func.isRequired, - searchUsersFunc: PropTypes.func.isRequired, + searchUsersFunc: PropTypes.func, searchGroupsFunc: PropTypes.func }; @@ -66,29 +66,39 @@ class LogUserSelector extends Component { this.setState({ isLoading: true }); - - this.props.searchUsersFunc(value).then((res) => { - const users = res.data.user_list || res.data.users || []; - this.setState({ - searchResults: users, - isLoading: false - }, () => { - if (this.props.searchGroupsFunc) { - this.props.searchGroupsFunc(value).then((res) => { - const groups = res.data.group_list || res.data.groups || []; - this.setState({ - searchResults: [...users, ...groups] + if (this.props.searchUsersFunc) { + this.props.searchUsersFunc(value).then((res) => { + const users = res.data.user_list || res.data.users || []; + this.setState({ + searchResults: users, + isLoading: false + }, () => { + if (this.props.searchGroupsFunc) { + this.props.searchGroupsFunc(value).then((res) => { + const groups = res.data.group_list || res.data.groups || []; + this.setState({ + searchResults: [...users, ...groups] + }); }); - }); - } + } + }); + }).catch((error) => { + this.setState({ + isLoading: false + }); + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); }); - }).catch((error) => { - this.setState({ - isLoading: false + } + if (this.props.searchGroupsFunc) { + this.props.searchGroupsFunc(value).then((res) => { + const groups = res.data.group_list || res.data.groups || []; + this.setState({ + searchResults: groups, + isLoading: false + }); }); - let errMessage = Utils.getErrorMsg(error); - toaster.danger(errMessage); - }); + } }; toggleSelectItem = (e, item) => { diff --git a/frontend/src/pages/sys-admin/logs-page/group-member-audit-logs.js b/frontend/src/pages/sys-admin/logs-page/group-member-audit-logs.js index bccb786ee8..2c283d7df5 100644 --- a/frontend/src/pages/sys-admin/logs-page/group-member-audit-logs.js +++ b/frontend/src/pages/sys-admin/logs-page/group-member-audit-logs.js @@ -3,15 +3,16 @@ import PropTypes from 'prop-types'; import { Link } from '@gatsbyjs/reach-router'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; -import { seafileAPI } from '../../../utils/seafile-api'; import { gettext, siteRoot } from '../../../utils/constants'; import { Utils } from '../../../utils/utils'; +import { systemAdminAPI } from '../../../utils/system-admin-api'; import EmptyTip from '../../../components/empty-tip'; import Loading from '../../../components/loading'; import Paginator from '../../../components/paginator'; import MainPanelTopbar from '../main-panel-topbar'; import UserLink from '../user-link'; import LogsNav from './logs-nav'; +import LogUserSelector from '../../dashboard/log-user-selector'; dayjs.extend(relativeTime); @@ -154,6 +155,11 @@ class GroupMemberAuditLogs extends Component { perPage: 100, currentPage: 1, hasNextPage: false, + availableUsers: [], + selectedUsers: [], + selectedOperators: [], + selectedGroups: [], + openSelector: null, }; this.initPage = 1; } @@ -170,8 +176,15 @@ class GroupMemberAuditLogs extends Component { } getLogsByPage = (page) => { - let { perPage } = this.state; - seafileAPI.sysAdminListGroupInviteLogs(page, perPage).then((res) => { + let { perPage, selectedUsers, selectedOperators, selectedGroups } = this.state; + + const emails = { + user_emails: selectedUsers.map(user => user.email), + operator_emails: selectedOperators.map(user => user.email), + group_ids: selectedGroups.map(group => group.id) + }; + + systemAdminAPI.sysAdminListGroupInviteLogs(page, perPage, emails).then((res) => { this.setState({ logList: res.data.group_invite_log_list, loading: false, @@ -186,6 +199,102 @@ class GroupMemberAuditLogs extends Component { }); }; + handleUserFilter = (user, shouldFetchData = true) => { + const { selectedUsers } = this.state; + let newSelectedUsers; + + if (user === null) { + newSelectedUsers = selectedUsers; + } else { + const isSelected = selectedUsers.find(item => item.email === user.email); + if (isSelected) { + newSelectedUsers = selectedUsers.filter(item => item.email !== user.email); + } else { + newSelectedUsers = [...selectedUsers, user]; + } + } + + this.setState({ + selectedUsers: newSelectedUsers, + currentPage: 1 + }, () => { + if (shouldFetchData) { + this.getLogsByPage(1); + } + }); + }; + + handleOperatorFilter = (user, shouldFetchData = true) => { + const { selectedOperators } = this.state; + let newSelectedUsers; + + if (user === null) { + newSelectedUsers = selectedOperators; + } else { + const isSelected = selectedOperators.find(item => item.email === user.email); + if (isSelected) { + newSelectedUsers = selectedOperators.filter(item => item.email !== user.email); + } else { + newSelectedUsers = [...selectedOperators, user]; + } + } + + this.setState({ + selectedOperators: newSelectedUsers, + currentPage: 1 + }, () => { + if (shouldFetchData) { + this.getLogsByPage(1); + } + }); + }; + + handleGroupFilter = (group, shouldFetchData = true) => { + const { selectedGroups } = this.state; + let newSelectedGroups; + + if (group === null) { + newSelectedGroups = selectedGroups; + } else { + const isSelected = selectedGroups.find(item => item.id === group.id); + if (isSelected) { + newSelectedGroups = selectedGroups.filter(item => item.id !== group.id); + } else { + newSelectedGroups = [...selectedGroups, group]; + } + } + + this.setState({ + selectedGroups: newSelectedGroups, + currentPage: 1 + }, () => { + if (shouldFetchData) { + this.getLogsByPage(1); + } + }); + }; + + handleSelectorToggle = (selectorType) => { + const { openSelector } = this.state; + const wasOpen = openSelector === selectorType; + + this.setState({ + openSelector: wasOpen ? null : selectorType + }, () => { + if (wasOpen) { + this.getLogsByPage(1); + } + }); + }; + + searchUsers = (value) => { + return systemAdminAPI.sysAdminSearchUsers(value); + }; + + searchGroups = (value) => { + return systemAdminAPI.sysAdminSearchGroups(value); + }; + resetPerPage = (newPerPage) => { this.setState({ perPage: newPerPage, @@ -193,7 +302,12 @@ class GroupMemberAuditLogs extends Component { }; render() { - let { logList, currentPage, perPage, hasNextPage } = this.state; + let { + logList, currentPage, perPage, hasNextPage, + availableUsers, selectedUsers, selectedOperators, selectedGroups, + openSelector + } = this.state; + return ( @@ -201,16 +315,47 @@ class GroupMemberAuditLogs extends Component {
- + +
+ this.handleSelectorToggle('user')} + searchUsersFunc={this.searchUsers} + /> + this.handleSelectorToggle('group')} + searchGroupsFunc={this.searchGroups} + /> + this.handleSelectorToggle('operator')} + searchUsersFunc={this.searchUsers} + /> +
+ +
diff --git a/frontend/src/utils/seafile-api.js b/frontend/src/utils/seafile-api.js index 3f273cbd58..ace33d8803 100644 --- a/frontend/src/utils/seafile-api.js +++ b/frontend/src/utils/seafile-api.js @@ -2235,14 +2235,6 @@ class SeafileAPI { return this.req.get(url, { params: params }); } - sysAdminListGroupInviteLogs(page, perPage) { - const url = this.server + '/api/v2.1/admin/logs/group-member-audit/'; - let params = { - page: page, - per_page: perPage - }; - return this.req.get(url, { params: params }); - } } diff --git a/frontend/src/utils/system-admin-api.js b/frontend/src/utils/system-admin-api.js index 8cb067f20a..a2c4fbc342 100644 --- a/frontend/src/utils/system-admin-api.js +++ b/frontend/src/utils/system-admin-api.js @@ -770,6 +770,24 @@ class SystemAdminAPI { return this.req.get(url, { params: params }); } + sysAdminListGroupInviteLogs(page, perPage, emails) { + const url = this.server + '/api/v2.1/admin/logs/group-member-audit/'; + let params = { + page: page, + per_page: perPage, + }; + if (emails.user_emails && emails.user_emails.length) { + params.user_emails = emails.user_emails.join(','); + } + if (emails.operator_emails && emails.operator_emails.length) { + params.operator_emails = emails.operator_emails.join(','); + } + if (emails.group_ids && emails.group_ids.length) { + params.group_ids = emails.group_ids.join(','); + } + return this.req.get(url, { params: params }); + } + sysAdminExportLogsExcel(start, end, logType) { const url = this.server + '/api/v2.1/admin/logs/export-excel/'; const params = { diff --git a/seahub/api2/endpoints/admin/logs.py b/seahub/api2/endpoints/admin/logs.py index 047fff2f25..2e8809905c 100644 --- a/seahub/api2/endpoints/admin/logs.py +++ b/seahub/api2/endpoints/admin/logs.py @@ -660,7 +660,30 @@ class AdminLogGroupMemberAuditLogs(APIView): error_msg = 'limit invalid' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) - events = GroupMemberAudit.objects.all().order_by('-timestamp')[start:start+limit+1] + user_emails = request.GET.get('user_emails') + user_emails = user_emails.split(',') if user_emails else [] + for user_selected in user_emails: + if not is_valid_email(user_selected): + error_msg = 'email %s invalid.' % user_selected + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + operator_emails = request.GET.get('operator_emails') + operator_emails = operator_emails.split(',') if operator_emails else [] + for user_selected in operator_emails: + if not is_valid_email(user_selected): + error_msg = 'email %s invalid.' % user_selected + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + group_ids = request.GET.get('group_ids') + group_ids = group_ids.split(',') if group_ids else [] + + queryset = GroupMemberAudit.objects.all() + if user_emails: + queryset = queryset.by_users(user_emails) + if operator_emails: + queryset = queryset.by_operators(operator_emails) + if group_ids: + queryset = queryset.by_group_ids(group_ids) + + events = queryset.order_by('-timestamp')[start:start+limit+1] if len(events) > limit: has_next_page = True events = events[:limit] diff --git a/seahub/base/models.py b/seahub/base/models.py index bc8860bb4e..daae0a381c 100644 --- a/seahub/base/models.py +++ b/seahub/base/models.py @@ -497,6 +497,16 @@ class RepoTransfer(models.Model): GROUP_MEMBER_ADD = 'group_member_add' GROUP_MEMBER_DELETE = 'group_member_delete' +class GroupMemberAuditQuerySet(models.QuerySet): + def by_users(self, users): + return self.filter(user__in=users) + + def by_operators(self, operators): + return self.filter(operator__in=operators) + + def by_group_ids(self, group_ids): + return self.filter(group_id__in=group_ids) + class GroupMemberAudit(models.Model): org_id = models.IntegerField(db_index=True) group_id = models.IntegerField(db_index=True) @@ -504,6 +514,7 @@ class GroupMemberAudit(models.Model): operator = models.CharField(max_length=255, db_index=True) operation = models.CharField(max_length=128) timestamp = models.DateTimeField(default=timezone.now, db_index=True) + objects = GroupMemberAuditQuerySet.as_manager() class Meta: db_table = 'group_member_audit'