mirror of
https://github.com/haiwen/seahub.git
synced 2025-05-12 01:45:04 +00:00
Add group audit log 13 (#7661)
* add group audit log * Update mysql.sql * optimize parameters * update * update * update * optimize * code optimize * update * Update operation-logs.js * update * Update models.py * update --------- Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com> Co-authored-by: r350178982 <32759763+r350178982@users.noreply.github.com>
This commit is contained in:
parent
29c8c12fa8
commit
ebe1c54153
frontend/src
models
pages
org-admin
sys-admin
utils
seahub
sql
21
frontend/src/models/org-logs-group-member-audit.js
Normal file
21
frontend/src/models/org-logs-group-member-audit.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { lang } from '../utils/constants';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
dayjs.locale(lang);
|
||||
|
||||
class OrgGroupMemberAuditLog {
|
||||
constructor(object) {
|
||||
this.group_id = object.group_id;
|
||||
this.group_name = object.group_name;
|
||||
this.user_contact_email = object.user_contact_email;
|
||||
this.user_email = object.user_email;
|
||||
this.user_name = object.user_name;
|
||||
this.operator_email = object.operator_email;
|
||||
this.operator_name = object.operator_name;
|
||||
this.operator_contact_email = object.operator_contact_email;
|
||||
this.operation = object.operation;
|
||||
this.time = dayjs(object.date).format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
}
|
||||
|
||||
export default OrgGroupMemberAuditLog;
|
@ -38,6 +38,7 @@ import OrgLogsFileAudit from './org-logs-file-audit';
|
||||
import OrgLogsFileUpdate from './org-logs-file-update';
|
||||
import OrgLogsPermAudit from './org-logs-perm-audit';
|
||||
import OrgLogsFileTransfer from './org-logs-file-transfer';
|
||||
import OrgLogsGroupMemberAudit from './org-logs-group-member-audit';
|
||||
import OrgSAMLConfig from './org-saml-config';
|
||||
import OrgSubscription from './org-subscription';
|
||||
|
||||
@ -128,6 +129,7 @@ class Org extends React.Component {
|
||||
<OrgLogsFileUpdate path='file-update' />
|
||||
<OrgLogsPermAudit path='perm-audit' />
|
||||
<OrgLogsFileTransfer path='repo-transfer' />
|
||||
<OrgLogsGroupMemberAudit path='group-member-audit' />
|
||||
</OrgLogs>
|
||||
{enableMultiADFS &&
|
||||
<OrgSAMLConfig path={siteRoot + 'org/samlconfig/'}/>
|
||||
|
150
frontend/src/pages/org-admin/org-logs-group-member-audit.js
Normal file
150
frontend/src/pages/org-admin/org-logs-group-member-audit.js
Normal file
@ -0,0 +1,150 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import dayjs from 'dayjs';
|
||||
import { orgAdminAPI } from '../../utils/org-admin-api';
|
||||
import { siteRoot, gettext, lang } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import toaster from '../../components/toast';
|
||||
import OrgGroupMemberAuditLog from '../../models/org-logs-group-member-audit';
|
||||
import '../../css/org-logs.css';
|
||||
import UserLink from './user-link';
|
||||
import { Link } from '@gatsbyjs/reach-router';
|
||||
|
||||
dayjs.locale(lang);
|
||||
|
||||
class OrgLogsGroupMemberAudit extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
page: 1,
|
||||
perPage: 25,
|
||||
pageNext: false,
|
||||
eventList: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let page = this.state.page;
|
||||
let perPage = this.state.perPage;
|
||||
this.initData(page, perPage);
|
||||
}
|
||||
|
||||
initData = (page, perPage) => {
|
||||
orgAdminAPI.orgAdminListGroupInvite(page, perPage).then(res => {
|
||||
let eventList = res.data.log_list.map(item => {
|
||||
return new OrgGroupMemberAuditLog(item);
|
||||
});
|
||||
|
||||
this.setState({
|
||||
eventList: eventList,
|
||||
pageNext: res.data.page_next,
|
||||
page: res.data.page,
|
||||
});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
onChangePageNum = (e, num) => {
|
||||
e.preventDefault();
|
||||
let page = this.state.page;
|
||||
let perPage = this.state.perPage;
|
||||
if (num == 1) {
|
||||
page = page + 1;
|
||||
} else {
|
||||
page = page - 1;
|
||||
}
|
||||
this.initData(page, perPage);
|
||||
};
|
||||
|
||||
render() {
|
||||
let eventList = this.state.eventList;
|
||||
return (
|
||||
<div className="cur-view-content">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="20%">{gettext('User')}</th>
|
||||
<th width="20%">{gettext('Group')}</th>
|
||||
<th width="20%">{gettext('Operator')}</th>
|
||||
<th width="20%">{gettext('Action')}</th>
|
||||
<th width="20%">{gettext('Date')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{eventList.map((item, index) => {
|
||||
return (
|
||||
<GroupInviteItem
|
||||
key={index}
|
||||
groupEvent={item}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="paginator">
|
||||
{this.state.page != 1 && <a href="#" onClick={(e) => this.onChangePageNum(e, -1)}>{gettext('Previous')}</a>}
|
||||
{(this.state.page != 1 && this.state.pageNext) && <span> | </span>}
|
||||
{this.state.pageNext && <a href="#" onClick={(e) => this.onChangePageNum(e, 1)}>{gettext('Next')}</a>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const propTypes = {
|
||||
groupEvent: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
class GroupInviteItem extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
highlight: false,
|
||||
};
|
||||
}
|
||||
|
||||
handleMouseOver = () => {
|
||||
this.setState({
|
||||
highlight: true
|
||||
});
|
||||
};
|
||||
|
||||
handleMouseOut = () => {
|
||||
this.setState({
|
||||
highlight: false
|
||||
});
|
||||
};
|
||||
|
||||
getActionTextByEType = (operation) => {
|
||||
if (operation.indexOf('group_member_add') != -1) {
|
||||
return gettext('Add member');
|
||||
} else if (operation.indexOf('group_member_delete') != -1) {
|
||||
return gettext('Delete member');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
let { groupEvent } = this.props;
|
||||
return (
|
||||
<tr className={this.state.highlight ? 'tr-highlight' : ''}
|
||||
onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
||||
<td>{<UserLink email={groupEvent.user_email} name={groupEvent.user_name} />}</td>
|
||||
<td>{<Link to={`${siteRoot}org/groupadmin/${groupEvent.group_id}/`}>{groupEvent.group_name}</Link>}</td>
|
||||
<td>{<UserLink email={groupEvent.operator_email} name={groupEvent.operator_name} />}</td>
|
||||
<td>{this.getActionTextByEType(groupEvent.operation)}</td>
|
||||
<td>{dayjs(groupEvent.time).fromNow()}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
GroupInviteItem.propTypes = propTypes;
|
||||
|
||||
export default OrgLogsGroupMemberAudit;
|
@ -40,7 +40,7 @@ class OrgLogs extends Component {
|
||||
const { isExportExcelDialogOpen, logType } = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
{this.props.currentTab === 'repo-transfer' ?
|
||||
{this.props.currentTab === 'repo-transfer' || this.props.currentTab === 'group-member-audit' ?
|
||||
<MainPanelTopbar />
|
||||
:
|
||||
<MainPanelTopbar>
|
||||
@ -75,6 +75,12 @@ class OrgLogs extends Component {
|
||||
to={siteRoot + 'org/logadmin/repo-transfer/'} title={gettext('Repo Transfer')}>{gettext('Repo Transfer')}
|
||||
</Link>
|
||||
</li>
|
||||
<li className="nav-item" onClick={() => this.tabItemClick('group-member-audit')}>
|
||||
<Link
|
||||
className={`nav-link ${this.props.currentTab === 'group-member-audit' ? 'active' : ''}`}
|
||||
to={siteRoot + 'org/logadmin/group-member-audit/'} title={gettext('Group Invite')}>{gettext('Group Invite')}
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="h-100 o-auto">
|
||||
|
@ -108,6 +108,8 @@ class Item extends Component {
|
||||
case 'user_add': return gettext('Add User');
|
||||
case 'user_delete': return gettext('Delete User');
|
||||
case 'user_migrate': return gettext('Migrate User');
|
||||
case 'group_member_add': return gettext('Add User to Group');
|
||||
case 'group_member_delete': return gettext('Delete User from Group');
|
||||
default: return '';
|
||||
}
|
||||
};
|
||||
@ -193,6 +195,18 @@ class Item extends Component {
|
||||
.replace('{user_to}', '<span class="font-weight-bold">' + detail.to + '</span>');
|
||||
return detailText;
|
||||
|
||||
case 'group_member_add':
|
||||
detailText = gettext('Added user {user} to group {group}')
|
||||
.replace('{user}', '<span class="font-weight-bold">' + detail.user + '</span>')
|
||||
.replace('{group}', '<span class="font-weight-bold">' + detail.name + '</span>');
|
||||
return detailText;
|
||||
|
||||
case 'group_member_delete':
|
||||
detailText = gettext('Deleted user {user} from group {group}')
|
||||
.replace('{user}', '<span class="font-weight-bold">' + detail.user + '</span>')
|
||||
.replace('{group}', '<span class="font-weight-bold">' + detail.name + '</span>');
|
||||
return detailText;
|
||||
|
||||
default: return '';
|
||||
}
|
||||
};
|
||||
|
@ -68,7 +68,7 @@ import FileAccessLogs from './logs-page/file-access-logs';
|
||||
import FileUpdateLogs from './logs-page/file-update-logs';
|
||||
import SharePermissionLogs from './logs-page/share-permission-logs';
|
||||
import FIleTransferLogs from './logs-page/file-transfer-log';
|
||||
|
||||
import GroupMemberAuditLogs from './logs-page/group-member-audit-logs';
|
||||
import WebSettings from './web-settings/web-settings';
|
||||
import Notifications from './notifications/notifications';
|
||||
import FileScanRecords from './file-scan-records';
|
||||
@ -253,6 +253,7 @@ class SysAdmin extends React.Component {
|
||||
<LoginLogs path={siteRoot + 'sys/logs/login'} {...commonProps} />
|
||||
<FileAccessLogs path={siteRoot + 'sys/logs/file-access'} {...commonProps} />
|
||||
<FIleTransferLogs path={siteRoot + 'sys/logs/repo-transfer'} {...commonProps} />
|
||||
<GroupMemberAuditLogs path={siteRoot + 'sys/logs/group-member-audit'} {...commonProps} />
|
||||
<FileUpdateLogs path={siteRoot + 'sys/logs/file-update'} {...commonProps} />
|
||||
<SharePermissionLogs path={siteRoot + 'sys/logs/share-permission'} {...commonProps} />
|
||||
<AdminOperationLogs path={siteRoot + 'sys/admin-logs/operation'} {...commonProps} />
|
||||
|
@ -0,0 +1,222 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
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 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';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
class Content extends Component {
|
||||
|
||||
getPreviousPage = () => {
|
||||
this.props.getLogsByPage(this.props.currentPage - 1);
|
||||
};
|
||||
|
||||
getNextPage = () => {
|
||||
this.props.getLogsByPage(this.props.currentPage + 1);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { loading, errorMsg, items, perPage, currentPage, hasNextPage } = this.props;
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
} else if (errorMsg) {
|
||||
return <p className="error text-center">{errorMsg}</p>;
|
||||
} else {
|
||||
const emptyTip = (
|
||||
<EmptyTip text={gettext('No group invite logs')}>
|
||||
</EmptyTip>
|
||||
);
|
||||
const table = (
|
||||
<Fragment>
|
||||
<table className="table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="20%">{gettext('User')}</th>
|
||||
<th width="20%">{gettext('Group')}</th>
|
||||
<th width="20%">{gettext('Operator')}</th>
|
||||
<th width="25%">{gettext('Action')}</th>
|
||||
<th width="15%">{gettext('Date')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{items &&
|
||||
<tbody>
|
||||
{items.map((item, index) => {
|
||||
return (<Item
|
||||
key={index}
|
||||
item={item}
|
||||
/>);
|
||||
})}
|
||||
</tbody>
|
||||
}
|
||||
</table>
|
||||
<Paginator
|
||||
gotoPreviousPage={this.getPreviousPage}
|
||||
gotoNextPage={this.getNextPage}
|
||||
currentPage={currentPage}
|
||||
hasNextPage={hasNextPage}
|
||||
curPerPage={perPage}
|
||||
resetPerPage={this.props.resetPerPage}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
return items.length ? table : emptyTip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Content.propTypes = {
|
||||
loading: PropTypes.bool.isRequired,
|
||||
errorMsg: PropTypes.string.isRequired,
|
||||
items: PropTypes.array.isRequired,
|
||||
getLogsByPage: PropTypes.func,
|
||||
resetPerPage: PropTypes.func,
|
||||
currentPage: PropTypes.number,
|
||||
perPage: PropTypes.number,
|
||||
pageInfo: PropTypes.object,
|
||||
hasNextPage: PropTypes.bool,
|
||||
};
|
||||
|
||||
class Item extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isOpIconShown: false,
|
||||
};
|
||||
}
|
||||
|
||||
handleMouseOver = () => {
|
||||
this.setState({
|
||||
isOpIconShown: true
|
||||
});
|
||||
};
|
||||
|
||||
handleMouseOut = () => {
|
||||
this.setState({
|
||||
isOpIconShown: false
|
||||
});
|
||||
};
|
||||
|
||||
getGroupName = (item) => {
|
||||
if (item.group_name) {
|
||||
return <Link to={`${siteRoot}sys/groups/${item.group_id}/libraries/`}>{item.group_name}</Link>;
|
||||
} else {
|
||||
return gettext('Deleted');
|
||||
}
|
||||
};
|
||||
|
||||
getActionTextByEType = (operation) => {
|
||||
if (operation.indexOf('group_member_add') != -1) {
|
||||
return gettext('Add member');
|
||||
} else if (operation.indexOf('group_member_delete') != -1) {
|
||||
return gettext('Delete member');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
let { item } = this.props;
|
||||
return (
|
||||
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
||||
<td>{<UserLink email={item.user_email} name={item.user_name} />}</td>
|
||||
<td>{this.getGroupName(item)}</td>
|
||||
<td>{<UserLink email={item.operator_email} name={item.operator_name} />}</td>
|
||||
<td>{this.getActionTextByEType(item.operation)}</td>
|
||||
<td>{dayjs(item.date).fromNow()}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Item.propTypes = {
|
||||
item: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
class GroupMemberAuditLogs extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
logList: [],
|
||||
perPage: 100,
|
||||
currentPage: 1,
|
||||
hasNextPage: false,
|
||||
};
|
||||
this.initPage = 1;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let urlParams = (new URL(window.location)).searchParams;
|
||||
const { currentPage, perPage } = this.state;
|
||||
this.setState({
|
||||
perPage: parseInt(urlParams.get('per_page') || perPage),
|
||||
currentPage: parseInt(urlParams.get('page') || currentPage)
|
||||
}, () => {
|
||||
this.getLogsByPage(this.state.currentPage);
|
||||
});
|
||||
}
|
||||
|
||||
getLogsByPage = (page) => {
|
||||
let { perPage } = this.state;
|
||||
seafileAPI.sysAdminListGroupInviteLogs(page, perPage).then((res) => {
|
||||
this.setState({
|
||||
logList: res.data.group_invite_log_list,
|
||||
loading: false,
|
||||
currentPage: page,
|
||||
hasNextPage: res.data.has_next_page,
|
||||
});
|
||||
}).catch((error) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: Utils.getErrorMsg(error, true)
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
resetPerPage = (newPerPage) => {
|
||||
this.setState({
|
||||
perPage: newPerPage,
|
||||
}, () => this.getLogsByPage(this.initPage));
|
||||
};
|
||||
|
||||
render() {
|
||||
let { logList, currentPage, perPage, hasNextPage } = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
<MainPanelTopbar {...this.props} />
|
||||
<div className="main-panel-center flex-row">
|
||||
<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}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default GroupMemberAuditLogs;
|
@ -17,6 +17,7 @@ class Nav extends React.Component {
|
||||
{ name: 'fileUpdateLogs', urlPart: 'logs/file-update', text: gettext('File Update') },
|
||||
{ name: 'sharePermissionLogs', urlPart: 'logs/share-permission', text: gettext('Permission') },
|
||||
{ name: 'fileTransfer', urlPart: 'logs/repo-transfer', text: gettext('Repo Transfer') },
|
||||
{ name: 'groupMember', urlPart: 'logs/group-member-audit', text: gettext('Group Member') },
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -481,6 +481,14 @@ class OrgAdminAPI {
|
||||
}
|
||||
|
||||
// org admin logs
|
||||
orgAdminListGroupInvite(page, perPage) {
|
||||
let url = this.server + '/api/v2.1/org/admin/logs/group-member-audit/';
|
||||
let params = {
|
||||
page: page,
|
||||
per_page: perPage
|
||||
};
|
||||
return this.req.get(url, { params: params });
|
||||
}
|
||||
orgAdminListFileTransfer(page, perPage) {
|
||||
let url = this.server + '/api/v2.1/org/admin/logs/repo-transfer/';
|
||||
let params = {
|
||||
|
@ -2235,6 +2235,15 @@ 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 });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let seafileAPI = new SeafileAPI();
|
||||
|
@ -22,6 +22,10 @@ GROUP_CREATE = 'group_create'
|
||||
GROUP_TRANSFER = 'group_transfer'
|
||||
# 'group_delete': {'id': group_id, 'name': group_name, 'owner': group_owner}
|
||||
GROUP_DELETE = 'group_delete'
|
||||
# 'group_invite': {'id': group_id, 'name': group_name, 'user': user}
|
||||
GROUP_MEMBER_ADD = 'group_member_add'
|
||||
# 'group_member_delete': {'id': group_id, 'name': group_name, 'user': user}
|
||||
GROUP_MEMBER_DELETE = 'group_member_delete'
|
||||
|
||||
# 'user_add': {'email': new_user}
|
||||
USER_ADD = 'user_add'
|
||||
@ -32,8 +36,8 @@ USER_DELETE = 'user_delete'
|
||||
USER_MIGRATE = 'user_migrate'
|
||||
|
||||
ADMIN_LOG_OPERATION_TYPE = (REPO_TRANSFER, REPO_DELETE,
|
||||
GROUP_CREATE, GROUP_TRANSFER, GROUP_DELETE,
|
||||
USER_ADD, USER_DELETE, USER_MIGRATE)
|
||||
GROUP_CREATE, GROUP_TRANSFER, GROUP_DELETE, GROUP_MEMBER_ADD,
|
||||
GROUP_MEMBER_DELETE, USER_ADD, USER_DELETE, USER_MIGRATE)
|
||||
|
||||
|
||||
class AdminLogManager(models.Manager):
|
||||
|
@ -9,8 +9,10 @@ from rest_framework import status
|
||||
|
||||
from seaserv import seafile_api, ccnet_api
|
||||
|
||||
from seahub.admin_log.models import GROUP_MEMBER_ADD, GROUP_MEMBER_DELETE
|
||||
from seahub.group.utils import get_group_member_info, is_group_member, get_group_members_info
|
||||
from seahub.group.signals import add_user_to_group
|
||||
from seahub.admin_log.signals import admin_operation
|
||||
from seahub.avatar.settings import AVATAR_DEFAULT_SIZE
|
||||
from seahub.base.accounts import User
|
||||
from seahub.base.templatetags.seahub_tags import email2nickname
|
||||
@ -99,6 +101,11 @@ class AdminGroupMembers(APIView):
|
||||
error_msg = 'Group %d not found.' % group_id
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
is_org = ccnet_api.is_org_group(group_id)
|
||||
if is_org:
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
emails = request.POST.getlist('email', '')
|
||||
if not emails:
|
||||
error_msg = 'Email invalid.'
|
||||
@ -146,6 +153,14 @@ class AdminGroupMembers(APIView):
|
||||
group_id=group_id,
|
||||
added_user=email)
|
||||
|
||||
admin_op_detail = {
|
||||
'id': group_id,
|
||||
'name': group.group_name,
|
||||
'user': email
|
||||
}
|
||||
admin_operation.send(sender=None, admin_name=request.user.username,
|
||||
operation=GROUP_MEMBER_ADD, detail=admin_op_detail)
|
||||
|
||||
return Response(result)
|
||||
|
||||
|
||||
@ -172,6 +187,11 @@ class AdminGroupMember(APIView):
|
||||
error_msg = 'Group %d not found.' % group_id
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
is_org = ccnet_api.is_org_group(group_id)
|
||||
if is_org:
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
try:
|
||||
User.objects.get(email=email)
|
||||
except User.DoesNotExist:
|
||||
@ -222,6 +242,11 @@ class AdminGroupMember(APIView):
|
||||
error_msg = 'Group %d not found.' % group_id
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
is_org = ccnet_api.is_org_group(group_id)
|
||||
if is_org:
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
# delete member from group
|
||||
try:
|
||||
if not is_group_member(group_id, email):
|
||||
@ -239,6 +264,14 @@ class AdminGroupMember(APIView):
|
||||
ccnet_api.group_remove_member(group_id, group.creator_name, email)
|
||||
# remove repo-group share info of all 'email' owned repos
|
||||
seafile_api.remove_group_repos_by_owner(group_id, email)
|
||||
admin_op_detail = {
|
||||
'id': group_id,
|
||||
'name': group.group_name,
|
||||
'user': email
|
||||
}
|
||||
admin_operation.send(sender=None, admin_name=request.user.username,
|
||||
operation=GROUP_MEMBER_DELETE, detail=admin_op_detail)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
|
@ -20,7 +20,7 @@ from seahub.utils import get_file_audit_events, generate_file_audit_event_type,
|
||||
get_file_update_events, get_perm_audit_events, is_valid_email
|
||||
from seahub.utils.timeutils import datetime_to_isoformat_timestr, utc_datetime_to_isoformat_timestr
|
||||
from seahub.utils.repo import is_valid_repo_id_format
|
||||
from seahub.base.models import RepoTransfer
|
||||
from seahub.base.models import RepoTransfer, GroupMemberAudit
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -564,3 +564,107 @@ class AdminLogsFileTransferLogs(APIView):
|
||||
}
|
||||
|
||||
return Response(resp)
|
||||
|
||||
|
||||
class AdminLogGroupMemberAuditLogs(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAdminUser, IsProVersion)
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def get(self, request):
|
||||
""" Get all group member audit logs.
|
||||
|
||||
Permission checking:
|
||||
1. only admin can perform this action.
|
||||
"""
|
||||
if not request.user.admin_permissions.can_view_user_log():
|
||||
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||
|
||||
try:
|
||||
current_page = int(request.GET.get('page', '1'))
|
||||
per_page = int(request.GET.get('per_page', '100'))
|
||||
except ValueError:
|
||||
current_page = 1
|
||||
per_page = 100
|
||||
|
||||
start = per_page * (current_page - 1)
|
||||
limit = per_page
|
||||
|
||||
if start < 0:
|
||||
error_msg = 'start invalid'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if limit < 0:
|
||||
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]
|
||||
if len(events) > limit:
|
||||
has_next_page = True
|
||||
events = events[:limit]
|
||||
else:
|
||||
has_next_page = False
|
||||
|
||||
# Use dict to reduce memcache fetch cost in large for-loop.
|
||||
nickname_dict = {}
|
||||
contact_email_dict = {}
|
||||
group_name_dict = {}
|
||||
|
||||
user_email_set = set()
|
||||
group_id_set = set()
|
||||
|
||||
for event in events:
|
||||
if is_valid_email(event.user):
|
||||
user_email_set.add(event.user)
|
||||
if is_valid_email(event.operator):
|
||||
user_email_set.add(event.operator)
|
||||
if event.group_id not in group_id_set:
|
||||
group_id_set.add(event.group_id)
|
||||
|
||||
for e in user_email_set:
|
||||
if e not in nickname_dict:
|
||||
nickname_dict[e] = email2nickname(e)
|
||||
if e not in contact_email_dict:
|
||||
contact_email_dict[e] = email2contact_email(e)
|
||||
|
||||
for group_id in group_id_set:
|
||||
if group_id not in group_name_dict:
|
||||
group = ccnet_api.get_group(int(group_id))
|
||||
if group:
|
||||
group_name_dict[group_id] = group.group_name
|
||||
|
||||
|
||||
events_info = []
|
||||
for ev in events:
|
||||
data = {
|
||||
'user_email': '',
|
||||
'user_name': '',
|
||||
'user_contact_email': '',
|
||||
'group_id': '',
|
||||
'group_name': '',
|
||||
'operator_email': '',
|
||||
'operator_name': '',
|
||||
'operator_contact_email': '',
|
||||
}
|
||||
user_email = ev.user
|
||||
data['user_email'] = user_email
|
||||
data['user_name'] = nickname_dict.get(user_email, '')
|
||||
data['user_contact_email'] = contact_email_dict.get(user_email, '')
|
||||
|
||||
operator_email = ev.operator
|
||||
data['operator_email'] = operator_email
|
||||
data['operator_name'] = nickname_dict.get(operator_email, '')
|
||||
data['operator_contact_email'] = contact_email_dict.get(operator_email, '')
|
||||
data['date'] = datetime_to_isoformat_timestr(ev.timestamp)
|
||||
|
||||
data['operation'] = ev.operation
|
||||
data['group_id'] = ev.group_id
|
||||
data['group_name'] = group_name_dict.get(ev.group_id, '')
|
||||
|
||||
events_info.append(data)
|
||||
|
||||
resp = {
|
||||
'group_invite_log_list': events_info,
|
||||
'has_next_page': has_next_page,
|
||||
}
|
||||
return Response(resp)
|
||||
|
@ -25,12 +25,15 @@ from seahub.group.models import GroupInviteLinkModel
|
||||
from seahub.utils.ms_excel import write_xls
|
||||
from seahub.utils.error_msg import file_type_error_msg
|
||||
from seahub.base.accounts import User
|
||||
from seahub.base.models import GROUP_MEMBER_ADD, GROUP_MEMBER_DELETE
|
||||
from seahub.group.signals import add_user_to_group
|
||||
from seahub.group.views import group_invite
|
||||
from seahub.organizations.views import get_org_id_by_group
|
||||
from seahub.group.utils import is_group_member, is_group_admin, \
|
||||
is_group_owner, is_group_admin_or_owner, get_group_member_info
|
||||
from seahub.profile.models import Profile
|
||||
from seahub.settings import MULTI_TENANCY
|
||||
from seahub.signals import group_member_audit
|
||||
|
||||
from .utils import api_check_group
|
||||
|
||||
@ -242,12 +245,20 @@ class GroupMember(APIView):
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
|
||||
username = request.user.username
|
||||
org_id = get_org_id_by_group(group_id)
|
||||
# user leave group
|
||||
if username == email:
|
||||
try:
|
||||
ccnet_api.quit_group(group_id, username)
|
||||
# remove repo-group share info of all 'email' owned repos
|
||||
seafile_api.remove_group_repos_by_owner(group_id, email)
|
||||
# add group invite log
|
||||
group_member_audit.send(sender=None,
|
||||
org_id=org_id if org_id else -1,
|
||||
group_id=group_id,
|
||||
users=[email],
|
||||
operator=username,
|
||||
operation=GROUP_MEMBER_DELETE)
|
||||
return Response({'success': True})
|
||||
except SearpcError as e:
|
||||
logger.error(e)
|
||||
@ -260,6 +271,12 @@ class GroupMember(APIView):
|
||||
# group owner can delete all group member
|
||||
ccnet_api.group_remove_member(group_id, username, email)
|
||||
seafile_api.remove_group_repos_by_owner(group_id, email)
|
||||
group_member_audit.send(sender=None,
|
||||
org_id=org_id if org_id else -1,
|
||||
group_id=group_id,
|
||||
users=[email],
|
||||
operator=username,
|
||||
operation=GROUP_MEMBER_DELETE)
|
||||
return Response({'success': True})
|
||||
|
||||
elif is_group_admin(group_id, username):
|
||||
@ -267,6 +284,12 @@ class GroupMember(APIView):
|
||||
if not is_group_admin_or_owner(group_id, email):
|
||||
ccnet_api.group_remove_member(group_id, username, email)
|
||||
seafile_api.remove_group_repos_by_owner(group_id, email)
|
||||
group_member_audit.send(sender=None,
|
||||
org_id=org_id if org_id else -1,
|
||||
group_id=group_id,
|
||||
users=[email],
|
||||
operator=username,
|
||||
operation=GROUP_MEMBER_DELETE)
|
||||
return Response({'success': True})
|
||||
else:
|
||||
error_msg = 'Permission denied.'
|
||||
@ -355,11 +378,13 @@ class GroupMembersBulk(APIView):
|
||||
emails_need_add.append(email)
|
||||
|
||||
# Add user to group.
|
||||
emails_added = []
|
||||
for email in emails_need_add:
|
||||
try:
|
||||
ccnet_api.group_add_member(group_id, username, email)
|
||||
member_info = get_group_member_info(request, group_id, email)
|
||||
result['success'].append(member_info)
|
||||
emails_added.append(email)
|
||||
except SearpcError as e:
|
||||
logger.error(e)
|
||||
result['failed'].append({
|
||||
@ -371,6 +396,13 @@ class GroupMembersBulk(APIView):
|
||||
group_staff=username,
|
||||
group_id=group_id,
|
||||
added_user=email)
|
||||
# add group invite log
|
||||
group_member_audit.send(sender=None,
|
||||
org_id=org_id if org_id else -1,
|
||||
group_id=group_id,
|
||||
users=emails_added,
|
||||
operator=username,
|
||||
operation=GROUP_MEMBER_ADD)
|
||||
return Response(result)
|
||||
|
||||
|
||||
@ -503,11 +535,13 @@ class GroupMembersImport(APIView):
|
||||
emails_need_add.append(email)
|
||||
|
||||
# Add user to group.
|
||||
emails_added = []
|
||||
for email in emails_need_add:
|
||||
try:
|
||||
ccnet_api.group_add_member(group_id, username, email)
|
||||
member_info = get_group_member_info(request, group_id, email)
|
||||
result['success'].append(member_info)
|
||||
emails_added.append(email)
|
||||
except SearpcError as e:
|
||||
logger.error(e)
|
||||
result['failed'].append({
|
||||
@ -519,6 +553,14 @@ class GroupMembersImport(APIView):
|
||||
group_staff=username,
|
||||
group_id=group_id,
|
||||
added_user=email)
|
||||
|
||||
group_member_audit.send(sender=None,
|
||||
org_id=org_id if org_id else -1,
|
||||
group_id=group_id,
|
||||
users=emails_added,
|
||||
operator=username,
|
||||
operation=GROUP_MEMBER_ADD)
|
||||
|
||||
return Response(result)
|
||||
|
||||
|
||||
|
@ -10,6 +10,7 @@ from seaserv import seafile_api
|
||||
|
||||
from seahub.auth.signals import user_logged_in
|
||||
from seahub.organizations.signals import org_last_activity
|
||||
from seahub.signals import group_member_audit
|
||||
from seahub.utils import within_time_range, gen_token, \
|
||||
normalize_file_path, normalize_dir_path
|
||||
from seahub.utils.timeutils import datetime_to_isoformat_timestr
|
||||
@ -475,3 +476,42 @@ class RepoTransfer(models.Model):
|
||||
|
||||
class Meta:
|
||||
db_table = 'RepoTransfer'
|
||||
|
||||
|
||||
|
||||
GROUP_MEMBER_ADD = 'group_member_add'
|
||||
GROUP_MEMBER_DELETE = 'group_member_delete'
|
||||
|
||||
class GroupMemberAudit(models.Model):
|
||||
org_id = models.IntegerField(db_index=True)
|
||||
group_id = models.IntegerField(db_index=True)
|
||||
user = models.EmailField(db_index=True)
|
||||
operator = models.CharField(max_length=255, db_index=True)
|
||||
operation = models.CharField(max_length=128)
|
||||
timestamp = models.DateTimeField(default=timezone.now, db_index=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'group_member_audit'
|
||||
|
||||
|
||||
|
||||
###### signal handler ###############
|
||||
|
||||
from django.dispatch import receiver
|
||||
|
||||
|
||||
@receiver(group_member_audit)
|
||||
def add_group_invite_log(sender, org_id, group_id, users, operator, operation, **kwargs):
|
||||
if operation not in [GROUP_MEMBER_ADD, GROUP_MEMBER_DELETE]:
|
||||
return
|
||||
|
||||
group_member_audit_list = []
|
||||
for user in users:
|
||||
group_member_audit_list.append(GroupMemberAudit(
|
||||
org_id=org_id,
|
||||
group_id=group_id,
|
||||
user=user,
|
||||
operator=operator,
|
||||
operation=operation
|
||||
))
|
||||
GroupMemberAudit.objects.bulk_create(group_member_audit_list)
|
||||
|
@ -16,6 +16,7 @@ from seaserv import ccnet_threaded_rpc, ccnet_api, get_group
|
||||
|
||||
from seahub.auth import REDIRECT_FIELD_NAME
|
||||
from seahub.base.decorators import sys_staff_required, require_POST
|
||||
from seahub.base.models import GROUP_MEMBER_ADD
|
||||
from seahub.group.utils import validate_group_name, BadGroupNameError, \
|
||||
ConflictGroupNameError, is_group_member
|
||||
from seahub.group.models import GroupInviteLinkModel
|
||||
@ -23,6 +24,7 @@ from seahub.settings import SITE_ROOT, SERVICE_URL, MULTI_TENANCY
|
||||
from seahub.utils import send_html_email, is_org_context, \
|
||||
get_site_name, render_error
|
||||
from seahub.share.models import ExtraGroupsSharePermission
|
||||
from seahub.signals import group_member_audit
|
||||
|
||||
|
||||
# Get an instance of a logger
|
||||
@ -195,6 +197,12 @@ def group_invite(request, token):
|
||||
|
||||
try:
|
||||
ccnet_api.group_add_member(group_invite_link.group_id, group_invite_link.created_by, email)
|
||||
group_member_audit.send(sender=None,
|
||||
org_id=-1,
|
||||
group_id=group_invite_link.group_id,
|
||||
users=[email],
|
||||
operator=group_invite_link.created_by,
|
||||
operation=GROUP_MEMBER_ADD)
|
||||
except Exception as e:
|
||||
logger.error(f'group invite add user failed. {e}')
|
||||
return render_error(request, 'Internal Server Error')
|
||||
|
@ -17,12 +17,11 @@ from seahub.api2.endpoints.utils import get_user_name_dict, \
|
||||
get_user_contact_email_dict, get_repo_dict, get_group_dict
|
||||
|
||||
from seahub.base.templatetags.seahub_tags import email2nickname, email2contact_email
|
||||
from seahub.base.models import RepoTransfer
|
||||
from seahub.base.models import RepoTransfer, GroupMemberAudit
|
||||
from seahub.utils import EVENTS_ENABLED, get_file_audit_events, get_file_update_events, get_perm_audit_events, is_valid_email
|
||||
from seahub.utils.timeutils import timestamp_to_isoformat_timestr, datetime_to_isoformat_timestr
|
||||
|
||||
from seahub.organizations.api.permissions import IsOrgAdmin
|
||||
from seahub.organizations.views import org_user_exists
|
||||
from seahub.organizations.api.utils import update_log_perm_audit_type
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -401,3 +400,101 @@ class OrgAdminLogsFileTransfer(APIView):
|
||||
'page': page,
|
||||
'page_next': page_next,
|
||||
})
|
||||
|
||||
|
||||
class OrgAdminLogsGroupMemberAudit(APIView):
|
||||
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
permission_classes = (IsProVersion, IsOrgAdmin)
|
||||
|
||||
def get(self, request):
|
||||
"""List organization group member audit logs
|
||||
"""
|
||||
try:
|
||||
page = int(request.GET.get('page', '1'))
|
||||
per_page = int(request.GET.get('per_page', '25'))
|
||||
except ValueError:
|
||||
page = 1
|
||||
per_page = 25
|
||||
|
||||
start = per_page * (page - 1)
|
||||
limit = per_page
|
||||
|
||||
org_id = request.user.org.org_id
|
||||
events = GroupMemberAudit.objects.filter(org_id=org_id).all().order_by('-timestamp')[start:start+limit+1]
|
||||
if len(events) > limit:
|
||||
page_next = True
|
||||
events = events[:limit]
|
||||
else:
|
||||
page_next = False
|
||||
|
||||
event_list = []
|
||||
if not events:
|
||||
return Response({
|
||||
'log_list': event_list,
|
||||
'page': page,
|
||||
'page_next': False
|
||||
})
|
||||
|
||||
# Use dict to reduce memcache fetch cost in large for-loop.
|
||||
nickname_dict = {}
|
||||
contact_email_dict = {}
|
||||
group_name_dict = {}
|
||||
|
||||
user_email_set = set()
|
||||
group_id_set = set()
|
||||
|
||||
for event in events:
|
||||
if is_valid_email(event.user):
|
||||
user_email_set.add(event.user)
|
||||
if is_valid_email(event.operator):
|
||||
user_email_set.add(event.operator)
|
||||
if event.group_id not in group_id_set:
|
||||
group_id_set.add(event.group_id)
|
||||
|
||||
for e in user_email_set:
|
||||
if e not in nickname_dict:
|
||||
nickname_dict[e] = email2nickname(e)
|
||||
if e not in contact_email_dict:
|
||||
contact_email_dict[e] = email2contact_email(e)
|
||||
|
||||
for group_id in group_id_set:
|
||||
if group_id not in group_name_dict:
|
||||
group = ccnet_api.get_group(int(group_id))
|
||||
if group:
|
||||
group_name_dict[group_id] = group.group_name
|
||||
event_list = []
|
||||
for ev in events:
|
||||
data = {
|
||||
'user_email': '',
|
||||
'user_name': '',
|
||||
'user_contact_email': '',
|
||||
'group_id': '',
|
||||
'group_name': '',
|
||||
'operator_email': '',
|
||||
'operator_name': '',
|
||||
'operator_contact_email': '',
|
||||
}
|
||||
user_email = ev.user
|
||||
data['user_email'] = user_email
|
||||
data['user_name'] = nickname_dict.get(user_email, '')
|
||||
data['user_contact_email'] = contact_email_dict.get(user_email, '')
|
||||
|
||||
operator_email = ev.operator
|
||||
data['operator_email'] = operator_email
|
||||
data['operator_name'] = nickname_dict.get(operator_email, '')
|
||||
data['operator_contact_email'] = contact_email_dict.get(operator_email, '')
|
||||
|
||||
data['group_id'] = ev.group_id
|
||||
data['group_name'] = group_name_dict.get(ev.group_id, '')
|
||||
data['operation'] = ev.operation
|
||||
data['date'] = datetime_to_isoformat_timestr(ev.timestamp)
|
||||
|
||||
event_list.append(data)
|
||||
|
||||
return Response({
|
||||
'log_list': event_list,
|
||||
'page': page,
|
||||
'page_next': page_next,
|
||||
})
|
||||
|
@ -20,7 +20,8 @@ from .api.admin.trash_libraries import OrgAdminTrashLibraries, OrgAdminTrashLibr
|
||||
from .api.admin.info import OrgAdminInfo
|
||||
from .api.admin.links import OrgAdminLinks, OrgAdminLink
|
||||
from .api.admin.web_settings import OrgAdminWebSettings
|
||||
from .api.admin.logs import OrgAdminLogsFileAccess, OrgAdminLogsFileUpdate, OrgAdminLogsPermAudit, OrgAdminLogsFileTransfer
|
||||
from .api.admin.logs import OrgAdminLogsFileAccess, OrgAdminLogsFileUpdate, OrgAdminLogsPermAudit, \
|
||||
OrgAdminLogsFileTransfer, OrgAdminLogsGroupMemberAudit
|
||||
from .api.admin.user_repos import OrgAdminUserRepos, OrgAdminUserBesharedRepos
|
||||
|
||||
from .api.admin.devices import OrgAdminDevices, OrgAdminDevicesErrors
|
||||
@ -103,6 +104,7 @@ urlpatterns = [
|
||||
path('admin/logs/file-update/', OrgAdminLogsFileUpdate.as_view(), name='api-v2.1-org-admin-logs-file-update'),
|
||||
path('admin/logs/repo-permission/', OrgAdminLogsPermAudit.as_view(), name='api-v2.1-org-admin-logs-repo-permission'),
|
||||
path('admin/logs/repo-transfer/', OrgAdminLogsFileTransfer.as_view(), name='api-v2.1-org-admin-logs-repo-transfer'),
|
||||
path('admin/logs/group-member-audit/', OrgAdminLogsGroupMemberAudit.as_view(), name='api-v2.1-org-admin-logs-group-member-audit'),
|
||||
path('<int:org_id>/admin/departments/', OrgAdminDepartments.as_view(), name='api-v2.1-org-admin-departments'),
|
||||
path('<int:org_id>/admin/logs/export-excel/', OrgLogsExport.as_view(), name='api-v2.1-org-logs-export-excel'),
|
||||
path('admin/log/export-excel/', org_log_export_excel, name='org_log_export_excel'),
|
||||
|
@ -36,6 +36,7 @@ urlpatterns = [
|
||||
path('logadmin/file-update/', react_fake_view, name='org_log_file_update'),
|
||||
path('logadmin/perm-audit/', react_fake_view, name='org_log_perm_audit'),
|
||||
path('logadmin/repo-transfer/', react_fake_view, name='org_log_file_transfer'),
|
||||
path('logadmin/group-member-audit/', react_fake_view, name='org_log_group_member_audit'),
|
||||
|
||||
path('info/', react_fake_view, name='org_info'),
|
||||
path('settings/', react_fake_view, name='org_settings'),
|
||||
|
@ -11,3 +11,4 @@ upload_file_successful = Signal()
|
||||
upload_folder_successful = Signal()
|
||||
comment_file_successful = Signal()
|
||||
institution_deleted = Signal()
|
||||
group_member_audit = Signal()
|
||||
|
@ -194,7 +194,7 @@ from seahub.api2.endpoints.admin.file_scan_records import AdminFileScanRecords
|
||||
from seahub.api2.endpoints.admin.notifications import AdminNotificationsView
|
||||
from seahub.api2.endpoints.admin.sys_notifications import AdminSysNotificationsView, AdminSysNotificationView
|
||||
from seahub.api2.endpoints.admin.logs import AdminLogsLoginLogs, AdminLogsFileAccessLogs, AdminLogsFileUpdateLogs, \
|
||||
AdminLogsSharePermissionLogs, AdminLogsFileTransferLogs
|
||||
AdminLogsSharePermissionLogs, AdminLogsFileTransferLogs, AdminLogGroupMemberAuditLogs
|
||||
from seahub.api2.endpoints.admin.terms_and_conditions import AdminTermsAndConditions, AdminTermAndCondition
|
||||
from seahub.api2.endpoints.admin.work_weixin import AdminWorkWeixinDepartments, \
|
||||
AdminWorkWeixinDepartmentMembers, AdminWorkWeixinUsersBatch, AdminWorkWeixinDepartmentsImport
|
||||
@ -699,6 +699,7 @@ urlpatterns = [
|
||||
re_path(r'^api/v2.1/admin/logs/file-update-logs/$', AdminLogsFileUpdateLogs.as_view(), name='api-v2.1-admin-logs-file-update-logs'),
|
||||
re_path(r'^api/v2.1/admin/logs/share-permission-logs/$', AdminLogsSharePermissionLogs.as_view(), name='api-v2.1-admin-logs-share-permission-logs'),
|
||||
re_path(r'^api/v2.1/admin/logs/repo-transfer-logs/$', AdminLogsFileTransferLogs.as_view(), name='api-v2.1-admin-logs-repo-transfer-logs'),
|
||||
re_path(r'^api/v2.1/admin/logs/group-member-audit/$', AdminLogGroupMemberAuditLogs.as_view(), name='api-v2.1-admin-logs-group-member-audit'),
|
||||
|
||||
## admin::admin logs
|
||||
re_path(r'^api/v2.1/admin/admin-logs/$', AdminOperationLogs.as_view(), name='api-v2.1-admin-admin-operation-logs'),
|
||||
@ -875,6 +876,7 @@ urlpatterns = [
|
||||
path('sys/logs/file-update/', sysadmin_react_fake_view, name="sys_logs_file_update"),
|
||||
path('sys/logs/share-permission/', sysadmin_react_fake_view, name="sys_logs_share_permission"),
|
||||
path('sys/logs/repo-transfer/', sysadmin_react_fake_view, name="sys_logs_file_transfer"),
|
||||
path('sys/logs/group-member-audit/', sysadmin_react_fake_view, name="sys_logs_group_member_audit"),
|
||||
path('sys/admin-logs/operation/', sysadmin_react_fake_view, name="sys_admin_logs_operation"),
|
||||
path('sys/admin-logs/login/', sysadmin_react_fake_view, name="sys_admin_logs_login"),
|
||||
path('sys/organizations/', sysadmin_react_fake_view, name="sys_organizations"),
|
||||
|
@ -1624,4 +1624,20 @@ CREATE TABLE `org_last_active_time` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `org_id` (`org_id`),
|
||||
KEY `ix_org_last_active_time_org_id` (`org_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
CREATE TABLE `group_member_audit` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`org_id` int(11) NOT NULL,
|
||||
`operator` varchar(255) NOT NULL,
|
||||
`user` varchar(255) NOT NULL,
|
||||
`group_id` int(11) NOT NULL,
|
||||
`operation` varchar(128) NOT NULL,
|
||||
`timestamp` datetime NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_group_member_audit_org_id` (`org_id`),
|
||||
KEY `idx_group_member_audit_timestamp` (`timestamp`),
|
||||
KEY `idx_group_member_audit_operator` (`operator`),
|
||||
KEY `idx_group_member_audit_user` (`user`),
|
||||
KEY `idx_group_member_audit_group_id` (`group_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
Loading…
Reference in New Issue
Block a user