1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-05-13 10:25:46 +00:00

add filter group audit

This commit is contained in:
孙永强 2025-04-01 17:54:13 +08:00
parent 8d44d72d7d
commit 0fd229bfbe
6 changed files with 243 additions and 44 deletions
frontend/src
seahub
api2/endpoints/admin
base

View File

@ -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) => {

View File

@ -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 (
<Fragment>
<MainPanelTopbar {...this.props} />
@ -201,16 +315,47 @@ class GroupMemberAuditLogs extends Component {
<div className="cur-view-container">
<LogsNav currentItem="groupMember" />
<div className="cur-view-content">
<Content
loading={this.state.loading}
errorMsg={this.state.errorMsg}
items={logList}
currentPage={currentPage}
perPage={perPage}
hasNextPage={hasNextPage}
getLogsByPage={this.getLogsByPage}
resetPerPage={this.resetPerPage}
/>
<Fragment>
<div className="d-flex align-items-center mb-2">
<LogUserSelector
componentName="Member"
items={availableUsers}
selectedItems={selectedUsers}
onSelect={this.handleUserFilter}
isOpen={openSelector === 'user'}
onToggle={() => this.handleSelectorToggle('user')}
searchUsersFunc={this.searchUsers}
/>
<LogUserSelector
componentName="Group"
items={availableUsers}
selectedItems={selectedGroups}
onSelect={this.handleGroupFilter}
isOpen={openSelector === 'group'}
onToggle={() => this.handleSelectorToggle('group')}
searchGroupsFunc={this.searchGroups}
/>
<LogUserSelector
componentName="Operator"
items={availableUsers}
selectedItems={selectedOperators}
onSelect={this.handleOperatorFilter}
isOpen={openSelector === 'operator'}
onToggle={() => this.handleSelectorToggle('operator')}
searchUsersFunc={this.searchUsers}
/>
</div>
<Content
loading={this.state.loading}
errorMsg={this.state.errorMsg}
items={logList}
currentPage={currentPage}
perPage={perPage}
hasNextPage={hasNextPage}
getLogsByPage={this.getLogsByPage}
resetPerPage={this.resetPerPage}
/>
</Fragment>
</div>
</div>
</div>

View File

@ -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 });
}
}

View File

@ -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 = {

View File

@ -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]

View File

@ -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'