mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-07 18:13:56 +00:00
Update transfer repo add audit log (#7480)
* add admin log * update * update * update * add org transfer log * optimize * Update file-transfer-log.js * add db_index * add-operator * update * file --> repo * Update mysql.sql * update * 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
83d615d3ed
commit
d91cdad79e
29
frontend/src/models/org-logs-file-transfer.js
Normal file
29
frontend/src/models/org-logs-file-transfer.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { lang } from '../utils/constants';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
dayjs.locale(lang);
|
||||||
|
|
||||||
|
class OrgLogsFileTransferEvent {
|
||||||
|
constructor(object) {
|
||||||
|
this.from_group_id = object.from_group_id;
|
||||||
|
this.from_group_name = object.from_group_name;
|
||||||
|
this.from_type = object.from_type;
|
||||||
|
this.from_user_contact_email = object.from_user_contact_email;
|
||||||
|
this.from_user_email = object.from_user_email;
|
||||||
|
this.from_user_name = object.from_user_name;
|
||||||
|
this.repo_id = object.repo_id;
|
||||||
|
this.repo_name = object.repo_name;
|
||||||
|
this.to_group_id = object.to_group_id;
|
||||||
|
this.to_group_name = object.to_group_name;
|
||||||
|
this.to_type = object.to_type;
|
||||||
|
this.to_user_contact_email = object.to_user_contact_email;
|
||||||
|
this.to_user_email = object.to_user_email;
|
||||||
|
this.to_user_name = object.to_user_name;
|
||||||
|
this.operator_email = object.operator_email;
|
||||||
|
this.operator_name = object.operator_name;
|
||||||
|
this.operator_contact_email = object.operator_contact_email;
|
||||||
|
this.time = dayjs(object.date).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrgLogsFileTransferEvent;
|
@ -37,6 +37,7 @@ import OrgLogs from './org-logs';
|
|||||||
import OrgLogsFileAudit from './org-logs-file-audit';
|
import OrgLogsFileAudit from './org-logs-file-audit';
|
||||||
import OrgLogsFileUpdate from './org-logs-file-update';
|
import OrgLogsFileUpdate from './org-logs-file-update';
|
||||||
import OrgLogsPermAudit from './org-logs-perm-audit';
|
import OrgLogsPermAudit from './org-logs-perm-audit';
|
||||||
|
import OrgLogsFileTransfer from './org-logs-file-transfer';
|
||||||
import OrgSAMLConfig from './org-saml-config';
|
import OrgSAMLConfig from './org-saml-config';
|
||||||
import OrgSubscription from './org-subscription';
|
import OrgSubscription from './org-subscription';
|
||||||
|
|
||||||
@ -126,6 +127,7 @@ class Org extends React.Component {
|
|||||||
<OrgLogsFileAudit path='/' />
|
<OrgLogsFileAudit path='/' />
|
||||||
<OrgLogsFileUpdate path='file-update' />
|
<OrgLogsFileUpdate path='file-update' />
|
||||||
<OrgLogsPermAudit path='perm-audit' />
|
<OrgLogsPermAudit path='perm-audit' />
|
||||||
|
<OrgLogsFileTransfer path='repo-transfer' />
|
||||||
</OrgLogs>
|
</OrgLogs>
|
||||||
{enableMultiADFS &&
|
{enableMultiADFS &&
|
||||||
<OrgSAMLConfig path={siteRoot + 'org/samlconfig/'}/>
|
<OrgSAMLConfig path={siteRoot + 'org/samlconfig/'}/>
|
||||||
|
@ -215,7 +215,7 @@ class RepoItem extends React.Component {
|
|||||||
onTransferRepo = (email, reshare) => {
|
onTransferRepo = (email, reshare) => {
|
||||||
let repo = this.props.repo;
|
let repo = this.props.repo;
|
||||||
orgAdminAPI.orgAdminTransferOrgRepo(orgID, repo.repoID, email, reshare).then(res => {
|
orgAdminAPI.orgAdminTransferOrgRepo(orgID, repo.repoID, email, reshare).then(res => {
|
||||||
const { owner_name, owner_email } = res;
|
const { owner_name, owner_email } = res.data;
|
||||||
this.props.transferRepoItem(repo.repoID, owner_name, owner_email);
|
this.props.transferRepoItem(repo.repoID, owner_name, owner_email);
|
||||||
let msg = gettext('Successfully transferred the library.');
|
let msg = gettext('Successfully transferred the library.');
|
||||||
toaster.success(msg);
|
toaster.success(msg);
|
||||||
|
178
frontend/src/pages/org-admin/org-logs-file-transfer.js
Normal file
178
frontend/src/pages/org-admin/org-logs-file-transfer.js
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
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 OrgLogsFileTransferEvent from '../../models/org-logs-file-transfer';
|
||||||
|
import '../../css/org-logs.css';
|
||||||
|
import UserLink from './user-link';
|
||||||
|
import { Link } from '@gatsbyjs/reach-router';
|
||||||
|
|
||||||
|
dayjs.locale(lang);
|
||||||
|
|
||||||
|
class OrgLogsFileTransfer extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
page: 1,
|
||||||
|
perPage: 25,
|
||||||
|
pageNext: false,
|
||||||
|
eventList: [],
|
||||||
|
isItemFreezed: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
let page = this.state.page;
|
||||||
|
let perPage = this.state.perPage;
|
||||||
|
this.initData(page, perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
initData = (page, perPage) => {
|
||||||
|
orgAdminAPI.orgAdminListFileTransfer(page, perPage).then(res => {
|
||||||
|
let eventList = res.data.log_list.map(item => {
|
||||||
|
return new OrgLogsFileTransferEvent(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('Transfer From')}</th>
|
||||||
|
<th width="20%">{gettext('Transfer To')}</th>
|
||||||
|
<th width="20%">{gettext('Operator')}</th>
|
||||||
|
<th width="25%">{gettext('Library')}</th>
|
||||||
|
<th width="15%">{gettext('Date')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{eventList.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<FileTransferItem
|
||||||
|
key={index}
|
||||||
|
fileEvent={item}
|
||||||
|
isItemFreezed={this.state.isItemFreezed}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</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 = {
|
||||||
|
isItemFreezed: PropTypes.bool.isRequired,
|
||||||
|
fileEvent: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileTransferItem extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
highlight: false,
|
||||||
|
showMenu: false,
|
||||||
|
isItemMenuShow: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseEnter = () => {
|
||||||
|
if (!this.props.isItemFreezed) {
|
||||||
|
this.setState({
|
||||||
|
showMenu: true,
|
||||||
|
highlight: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMouseLeave = () => {
|
||||||
|
if (!this.props.isItemFreezed) {
|
||||||
|
this.setState({
|
||||||
|
showMenu: false,
|
||||||
|
highlight: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
getTransferTo = (item) => {
|
||||||
|
switch (item.to_type) {
|
||||||
|
case 'user':
|
||||||
|
return <UserLink email={item.to_user_email} name={item.to_user_name} />;
|
||||||
|
case 'group':
|
||||||
|
return <Link to={`${siteRoot}org/groupadmin/${item.to_group_id}/`}>{item.to_group_name}</Link>;
|
||||||
|
default:
|
||||||
|
return gettext('Deleted');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getTransferFrom = (item) => {
|
||||||
|
switch (item.from_type) {
|
||||||
|
case 'user':
|
||||||
|
return <UserLink email={item.from_user_email} name={item.from_user_name} />;
|
||||||
|
case 'group':
|
||||||
|
return <Link to={`${siteRoot}org/groupadmin/${item.from_group_id}/`}>{item.from_group_name}</Link>;
|
||||||
|
default:
|
||||||
|
return gettext('Deleted');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getOperator = (item) => {
|
||||||
|
return <UserLink email={item.operator_email} name={item.operator_name} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { fileEvent } = this.props;
|
||||||
|
return (
|
||||||
|
<tr className={this.state.highlight ? 'tr-highlight' : ''}
|
||||||
|
onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||||
|
<td>{this.getTransferFrom(fileEvent)}</td>
|
||||||
|
<td>{this.getTransferTo(fileEvent)}</td>
|
||||||
|
<td>{this.getOperator(fileEvent)}</td>
|
||||||
|
<td>{fileEvent.repo_name ? fileEvent.repo_name : gettext('Deleted')}</td>
|
||||||
|
<td>{dayjs(fileEvent.time).fromNow()}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileTransferItem.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default OrgLogsFileTransfer;
|
@ -40,9 +40,13 @@ class OrgLogs extends Component {
|
|||||||
const { isExportExcelDialogOpen, logType } = this.state;
|
const { isExportExcelDialogOpen, logType } = this.state;
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<MainPanelTopbar>
|
{this.props.currentTab === 'repo-transfer' ?
|
||||||
<Button className="btn btn-secondary operation-item" onClick={this.toggleExportExcelDialog}>{gettext('Export Excel')}</Button>
|
<MainPanelTopbar />
|
||||||
</MainPanelTopbar>
|
:
|
||||||
|
<MainPanelTopbar>
|
||||||
|
<Button className="btn btn-secondary operation-item" onClick={this.toggleExportExcelDialog}>{gettext('Export Excel')}</Button>
|
||||||
|
</MainPanelTopbar>
|
||||||
|
}
|
||||||
<div className="main-panel-center flex-row">
|
<div className="main-panel-center flex-row">
|
||||||
<div className="cur-view-container h-100">
|
<div className="cur-view-container h-100">
|
||||||
<div className="cur-view-path org-user-nav">
|
<div className="cur-view-path org-user-nav">
|
||||||
@ -65,6 +69,12 @@ class OrgLogs extends Component {
|
|||||||
to={siteRoot + 'org/logadmin/perm-audit/'} title={gettext('Permission')}>{gettext('Permission')}
|
to={siteRoot + 'org/logadmin/perm-audit/'} title={gettext('Permission')}>{gettext('Permission')}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
|
<li className="nav-item" onClick={() => this.tabItemClick('repo-transfer')}>
|
||||||
|
<Link
|
||||||
|
className={`nav-link ${this.props.currentTab === 'repo-transfer' ? 'active' : ''}`}
|
||||||
|
to={siteRoot + 'org/logadmin/repo-transfer/'} title={gettext('Repo Transfer')}>{gettext('Repo Transfer')}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-100 o-auto">
|
<div className="h-100 o-auto">
|
||||||
|
@ -67,6 +67,7 @@ import LoginLogs from './logs-page/login-logs';
|
|||||||
import FileAccessLogs from './logs-page/file-access-logs';
|
import FileAccessLogs from './logs-page/file-access-logs';
|
||||||
import FileUpdateLogs from './logs-page/file-update-logs';
|
import FileUpdateLogs from './logs-page/file-update-logs';
|
||||||
import SharePermissionLogs from './logs-page/share-permission-logs';
|
import SharePermissionLogs from './logs-page/share-permission-logs';
|
||||||
|
import FIleTransferLogs from './logs-page/file-transfer-log';
|
||||||
|
|
||||||
import WebSettings from './web-settings/web-settings';
|
import WebSettings from './web-settings/web-settings';
|
||||||
import Notifications from './notifications/notifications';
|
import Notifications from './notifications/notifications';
|
||||||
@ -251,6 +252,7 @@ class SysAdmin extends React.Component {
|
|||||||
<InstitutionAdmins path={siteRoot + 'sys/institutions/:institutionID/admins'} {...commonProps} />
|
<InstitutionAdmins path={siteRoot + 'sys/institutions/:institutionID/admins'} {...commonProps} />
|
||||||
<LoginLogs path={siteRoot + 'sys/logs/login'} {...commonProps} />
|
<LoginLogs path={siteRoot + 'sys/logs/login'} {...commonProps} />
|
||||||
<FileAccessLogs path={siteRoot + 'sys/logs/file-access'} {...commonProps} />
|
<FileAccessLogs path={siteRoot + 'sys/logs/file-access'} {...commonProps} />
|
||||||
|
<FIleTransferLogs path={siteRoot + 'sys/logs/repo-transfer'} {...commonProps} />
|
||||||
<FileUpdateLogs path={siteRoot + 'sys/logs/file-update'} {...commonProps} />
|
<FileUpdateLogs path={siteRoot + 'sys/logs/file-update'} {...commonProps} />
|
||||||
<SharePermissionLogs path={siteRoot + 'sys/logs/share-permission'} {...commonProps} />
|
<SharePermissionLogs path={siteRoot + 'sys/logs/share-permission'} {...commonProps} />
|
||||||
<AdminOperationLogs path={siteRoot + 'sys/admin-logs/operation'} {...commonProps} />
|
<AdminOperationLogs path={siteRoot + 'sys/admin-logs/operation'} {...commonProps} />
|
||||||
|
230
frontend/src/pages/sys-admin/logs-page/file-transfer-log.js
Normal file
230
frontend/src/pages/sys-admin/logs-page/file-transfer-log.js
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
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 transfer logs')}>
|
||||||
|
</EmptyTip>
|
||||||
|
);
|
||||||
|
const table = (
|
||||||
|
<Fragment>
|
||||||
|
<table className="table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="20%">{gettext('Transfer From')}</th>
|
||||||
|
<th width="20%">{gettext('Transfer To')}</th>
|
||||||
|
<th width="20%">{gettext('Operator')}</th>
|
||||||
|
<th width="25%">{gettext('Library')}</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
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
getTransferTo = (item) => {
|
||||||
|
switch (item.to_type) {
|
||||||
|
case 'user':
|
||||||
|
return <UserLink email={item.to_user_email} name={item.to_user_name} />;
|
||||||
|
case 'group':
|
||||||
|
return <Link to={`${siteRoot}sys/groups/${item.to_group_id}/libraries/`}>{item.to_group_name}</Link>;
|
||||||
|
default:
|
||||||
|
return gettext('Deleted');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getTransferFrom = (item) => {
|
||||||
|
switch (item.from_type) {
|
||||||
|
case 'user':
|
||||||
|
return <UserLink email={item.from_user_email} name={item.from_user_name} />;
|
||||||
|
case 'group':
|
||||||
|
return <Link to={`${siteRoot}sys/groups/${item.from_group_id}/libraries/`}>{item.from_group_name}</Link>;
|
||||||
|
default:
|
||||||
|
return gettext('Deleted');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getOperator = (item) => {
|
||||||
|
return <UserLink email={item.operator_email} name={item.operator_name} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { item } = this.props;
|
||||||
|
return (
|
||||||
|
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
||||||
|
<td>{this.getTransferFrom(item)}</td>
|
||||||
|
<td>{this.getTransferTo(item)}</td>
|
||||||
|
<td>{this.getOperator(item)}</td>
|
||||||
|
<td>{item.repo_name ? item.repo_name : gettext('Deleted')}</td>
|
||||||
|
<td>{dayjs(item.date).fromNow()}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item.propTypes = {
|
||||||
|
item: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class FIleTransferLogs 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.sysAdminListFileTransferLogs(page, perPage).then((res) => {
|
||||||
|
this.setState({
|
||||||
|
logList: res.data.repo_transfer_log_list,
|
||||||
|
loading: false,
|
||||||
|
currentPage: page,
|
||||||
|
hasNextPage: res.data.has_next_page,
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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="fileTransfer" />
|
||||||
|
<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 FIleTransferLogs;
|
@ -16,6 +16,7 @@ class Nav extends React.Component {
|
|||||||
{ name: 'fileAccessLogs', urlPart: 'logs/file-access', text: gettext('File Access') },
|
{ name: 'fileAccessLogs', urlPart: 'logs/file-access', text: gettext('File Access') },
|
||||||
{ name: 'fileUpdateLogs', urlPart: 'logs/file-update', text: gettext('File Update') },
|
{ name: 'fileUpdateLogs', urlPart: 'logs/file-update', text: gettext('File Update') },
|
||||||
{ name: 'sharePermissionLogs', urlPart: 'logs/share-permission', text: gettext('Permission') },
|
{ name: 'sharePermissionLogs', urlPart: 'logs/share-permission', text: gettext('Permission') },
|
||||||
|
{ name: 'fileTransfer', urlPart: 'logs/repo-transfer', text: gettext('Repo Transfer') },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Utils } from "../../utils/utils";
|
import { Utils } from '../../utils/utils';
|
||||||
|
|
||||||
describe('getFileExtension', () => {
|
describe('getFileExtension', () => {
|
||||||
it('should return the file extension with dot', () => {
|
it('should return the file extension with dot', () => {
|
||||||
|
@ -481,6 +481,15 @@ class OrgAdminAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// org admin logs
|
// org admin logs
|
||||||
|
orgAdminListFileTransfer(page, perPage) {
|
||||||
|
let url = this.server + '/api/v2.1/org/admin/logs/repo-transfer/';
|
||||||
|
let params = {
|
||||||
|
page: page,
|
||||||
|
per_page: perPage
|
||||||
|
};
|
||||||
|
return this.req.get(url, { params: params });
|
||||||
|
}
|
||||||
|
|
||||||
orgAdminListFileAudit(email, repoID, page) {
|
orgAdminListFileAudit(email, repoID, page) {
|
||||||
let url = this.server + '/api/v2.1/org/admin/logs/file-access/?page=' + page;
|
let url = this.server + '/api/v2.1/org/admin/logs/file-access/?page=' + page;
|
||||||
if (email) {
|
if (email) {
|
||||||
|
@ -2169,6 +2169,15 @@ class SeafileAPI {
|
|||||||
return this._sendPostRequest(url, formData);
|
return this._sendPostRequest(url, formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sysAdminListFileTransferLogs(page, perPage) {
|
||||||
|
const url = this.server + '/api/v2.1/admin/logs/repo-transfer-logs/';
|
||||||
|
let params = {
|
||||||
|
page: page,
|
||||||
|
per_page: perPage
|
||||||
|
};
|
||||||
|
return this.req.get(url, { params: params });
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let seafileAPI = new SeafileAPI();
|
let seafileAPI = new SeafileAPI();
|
||||||
|
@ -28,6 +28,7 @@ from seahub.utils.timeutils import timestamp_to_isoformat_timestr
|
|||||||
|
|
||||||
from seahub.api2.endpoints.group_owned_libraries import get_group_id_by_repo_owner
|
from seahub.api2.endpoints.group_owned_libraries import get_group_id_by_repo_owner
|
||||||
from seahub.constants import PERMISSION_READ_WRITE
|
from seahub.constants import PERMISSION_READ_WRITE
|
||||||
|
from seahub.base.models import RepoTransfer
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from seahub.settings import MULTI_TENANCY
|
from seahub.settings import MULTI_TENANCY
|
||||||
@ -423,6 +424,11 @@ class AdminLibrary(APIView):
|
|||||||
# transfer repo
|
# transfer repo
|
||||||
try:
|
try:
|
||||||
transfer_repo(repo_id, new_owner, is_share)
|
transfer_repo(repo_id, new_owner, is_share)
|
||||||
|
RepoTransfer.objects.create(from_user=repo_owner,
|
||||||
|
to=new_owner,
|
||||||
|
repo_id=repo_id,
|
||||||
|
org_id=-1,
|
||||||
|
operator=request.user.username)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
error_msg = 'Internal Server Error'
|
error_msg = 'Internal Server Error'
|
||||||
|
@ -20,6 +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
|
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.timeutils import datetime_to_isoformat_timestr, utc_datetime_to_isoformat_timestr
|
||||||
from seahub.utils.repo import is_valid_repo_id_format
|
from seahub.utils.repo import is_valid_repo_id_format
|
||||||
|
from seahub.base.models import RepoTransfer
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -420,3 +421,146 @@ class AdminLogsSharePermissionLogs(APIView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Response(resp)
|
return Response(resp)
|
||||||
|
|
||||||
|
|
||||||
|
class AdminLogsFileTransferLogs(APIView):
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
permission_classes = (IsAdminUser, IsProVersion)
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
""" Get all transfer repo 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 = RepoTransfer.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 = {}
|
||||||
|
repo_dict = {}
|
||||||
|
group_name_dict = {}
|
||||||
|
|
||||||
|
user_email_set = set()
|
||||||
|
repo_id_set = set()
|
||||||
|
group_id_set = set()
|
||||||
|
|
||||||
|
for event in events:
|
||||||
|
repo_id_set.add(event.repo_id)
|
||||||
|
if is_valid_email(event.from_user):
|
||||||
|
user_email_set.add(event.from_user)
|
||||||
|
if is_valid_email(event.to):
|
||||||
|
user_email_set.add(event.to)
|
||||||
|
if is_valid_email(event.operator):
|
||||||
|
user_email_set.add(event.operator)
|
||||||
|
if '@seafile_group' in event.to:
|
||||||
|
group_id = int(event.to.split('@')[0])
|
||||||
|
group_id_set.add(group_id)
|
||||||
|
if '@seafile_group' in event.from_user:
|
||||||
|
group_id = int(event.from_user.split('@')[0])
|
||||||
|
group_id_set.add(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 e in repo_id_set:
|
||||||
|
if e not in repo_dict:
|
||||||
|
repo_dict[e] = seafile_api.get_repo(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 = {
|
||||||
|
'from_user_email': '',
|
||||||
|
'from_user_name': '',
|
||||||
|
'from_user_contact_email': '',
|
||||||
|
'from_group_id': '',
|
||||||
|
'from_group_name': '',
|
||||||
|
'to_user_email': '',
|
||||||
|
'to_user_name': '',
|
||||||
|
'to_user_contact_email': '',
|
||||||
|
'to_group_id': '',
|
||||||
|
'to_group_name': '',
|
||||||
|
'operator_email': '',
|
||||||
|
'operator_name': '',
|
||||||
|
'operator_contact_email': '',
|
||||||
|
}
|
||||||
|
from_user_email = ev.from_user
|
||||||
|
data['from_user_email'] = from_user_email
|
||||||
|
data['from_user_name'] = nickname_dict.get(from_user_email, '')
|
||||||
|
data['from_user_contact_email'] = contact_email_dict.get(from_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, '')
|
||||||
|
|
||||||
|
if is_valid_email(from_user_email):
|
||||||
|
data['from_type'] = 'user'
|
||||||
|
if '@seafile_group' in from_user_email:
|
||||||
|
from_group_id = int(from_user_email.split('@')[0])
|
||||||
|
data['from_group_id'] = from_group_id
|
||||||
|
data['from_group_name'] = group_name_dict.get(from_group_id, '')
|
||||||
|
data['from_type'] = 'group'
|
||||||
|
|
||||||
|
repo_id = ev.repo_id
|
||||||
|
data['repo_id'] = repo_id
|
||||||
|
repo = repo_dict.get(repo_id, None)
|
||||||
|
data['repo_name'] = repo.name if repo else ''
|
||||||
|
data['date'] = datetime_to_isoformat_timestr(ev.timestamp)
|
||||||
|
|
||||||
|
if is_valid_email(ev.to):
|
||||||
|
to_user_email = ev.to
|
||||||
|
data['to_user_email'] = to_user_email
|
||||||
|
data['to_user_name'] = nickname_dict.get(to_user_email, '')
|
||||||
|
data['to_user_contact_email'] = contact_email_dict.get(to_user_email, '')
|
||||||
|
data['to_type'] = 'user'
|
||||||
|
if '@seafile_group' in ev.to:
|
||||||
|
to_group_id = int(ev.to.split('@')[0])
|
||||||
|
data['to_group_id'] = to_group_id
|
||||||
|
data['to_group_name'] = group_name_dict.get(to_group_id, '')
|
||||||
|
data['to_type'] = 'group'
|
||||||
|
|
||||||
|
events_info.append(data)
|
||||||
|
|
||||||
|
resp = {
|
||||||
|
'repo_transfer_log_list': events_info,
|
||||||
|
'has_next_page': has_next_page,
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response(resp)
|
||||||
|
@ -50,6 +50,7 @@ from seahub.views import check_folder_permission
|
|||||||
from seahub.settings import ENABLE_STORAGE_CLASSES, STORAGE_CLASS_MAPPING_POLICY, \
|
from seahub.settings import ENABLE_STORAGE_CLASSES, STORAGE_CLASS_MAPPING_POLICY, \
|
||||||
ENCRYPTED_LIBRARY_VERSION, ENCRYPTED_LIBRARY_PWD_HASH_ALGO, \
|
ENCRYPTED_LIBRARY_VERSION, ENCRYPTED_LIBRARY_PWD_HASH_ALGO, \
|
||||||
ENCRYPTED_LIBRARY_PWD_HASH_PARAMS
|
ENCRYPTED_LIBRARY_PWD_HASH_PARAMS
|
||||||
|
from seahub.base.models import RepoTransfer
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -1497,6 +1498,12 @@ class GroupOwnedLibraryTransferView(APIView):
|
|||||||
# transfer repo
|
# transfer repo
|
||||||
try:
|
try:
|
||||||
transfer_repo(repo_id, new_owner, is_share, org_id)
|
transfer_repo(repo_id, new_owner, is_share, org_id)
|
||||||
|
org_id = seafile_api.get_org_id_by_repo_id(repo_id)
|
||||||
|
RepoTransfer.objects.create(from_user=repo_owner,
|
||||||
|
to=new_owner,
|
||||||
|
repo_id=repo_id,
|
||||||
|
org_id=org_id,
|
||||||
|
operator=request.user.username)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
error_msg = 'Internal Server Error'
|
error_msg = 'Internal Server Error'
|
||||||
|
@ -14,6 +14,7 @@ from seahub.share.models import UploadLinkShare, FileShare, check_share_link_acc
|
|||||||
from seaserv import seafile_api
|
from seaserv import seafile_api
|
||||||
from seahub.utils.repo import parse_repo_perm
|
from seahub.utils.repo import parse_repo_perm
|
||||||
from seahub.views.file import send_file_access_msg
|
from seahub.views.file import send_file_access_msg
|
||||||
|
from seahub.utils import normalize_file_path
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -146,6 +147,7 @@ class InternalCheckFileOperationAccess(APIView):
|
|||||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
file_path = request.data.get('path', '/')
|
file_path = request.data.get('path', '/')
|
||||||
|
file_path = normalize_file_path(file_path)
|
||||||
repo = seafile_api.get_repo(repo_id)
|
repo = seafile_api.get_repo(repo_id)
|
||||||
if not repo:
|
if not repo:
|
||||||
return api_error(status.HTTP_404_NOT_FOUND, 'Library %s not found.' % repo_id)
|
return api_error(status.HTTP_404_NOT_FOUND, 'Library %s not found.' % repo_id)
|
||||||
|
@ -46,7 +46,7 @@ from seahub.avatar.templatetags.avatar_tags import api_avatar_url, avatar
|
|||||||
from seahub.avatar.templatetags.group_avatar_tags import api_grp_avatar_url, \
|
from seahub.avatar.templatetags.group_avatar_tags import api_grp_avatar_url, \
|
||||||
grp_avatar
|
grp_avatar
|
||||||
from seahub.base.accounts import User
|
from seahub.base.accounts import User
|
||||||
from seahub.base.models import UserStarredFiles, DeviceToken, RepoSecretKey, FileComment
|
from seahub.base.models import UserStarredFiles, DeviceToken, RepoSecretKey, FileComment, RepoTransfer
|
||||||
from seahub.share.models import ExtraSharePermission, ExtraGroupsSharePermission
|
from seahub.share.models import ExtraSharePermission, ExtraGroupsSharePermission
|
||||||
from seahub.share.utils import is_repo_admin, check_group_share_in_permission, normalize_custom_permission_name
|
from seahub.share.utils import is_repo_admin, check_group_share_in_permission, normalize_custom_permission_name
|
||||||
from seahub.base.templatetags.seahub_tags import email2nickname, \
|
from seahub.base.templatetags.seahub_tags import email2nickname, \
|
||||||
@ -1871,6 +1871,12 @@ class RepoOwner(APIView):
|
|||||||
# transfer repo
|
# transfer repo
|
||||||
try:
|
try:
|
||||||
transfer_repo(repo_id, new_owner, is_share, org_id)
|
transfer_repo(repo_id, new_owner, is_share, org_id)
|
||||||
|
org_id = seafile_api.get_org_id_by_repo_id(repo_id)
|
||||||
|
RepoTransfer.objects.create(from_user=repo_owner,
|
||||||
|
to=new_owner,
|
||||||
|
repo_id=repo_id,
|
||||||
|
operator=username,
|
||||||
|
org_id=org_id)
|
||||||
except SearpcError as e:
|
except SearpcError as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
error_msg = 'Internal Server Error'
|
error_msg = 'Internal Server Error'
|
||||||
|
@ -451,3 +451,15 @@ class ClientSSOToken(models.Model):
|
|||||||
if not self.token:
|
if not self.token:
|
||||||
self.token = self.gen_token()
|
self.token = self.gen_token()
|
||||||
return super(ClientSSOToken, self).save(*args, **kwargs)
|
return super(ClientSSOToken, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class RepoTransfer(models.Model):
|
||||||
|
repo_id = models.CharField(max_length=36)
|
||||||
|
org_id = models.IntegerField(db_index=True)
|
||||||
|
from_user = models.CharField(max_length=255)
|
||||||
|
to = models.CharField(max_length=255)
|
||||||
|
operator = models.CharField(max_length=255)
|
||||||
|
timestamp = models.DateTimeField(default=timezone.now, db_index=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'RepoTransfer'
|
||||||
|
@ -17,7 +17,8 @@ from seahub.api2.endpoints.utils import get_user_name_dict, \
|
|||||||
get_user_contact_email_dict, get_repo_dict, get_group_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.templatetags.seahub_tags import email2nickname, email2contact_email
|
||||||
from seahub.utils import EVENTS_ENABLED, get_file_audit_events, get_file_update_events, get_perm_audit_events
|
from seahub.base.models import RepoTransfer
|
||||||
|
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.utils.timeutils import timestamp_to_isoformat_timestr, datetime_to_isoformat_timestr
|
||||||
|
|
||||||
from seahub.organizations.api.permissions import IsOrgAdmin
|
from seahub.organizations.api.permissions import IsOrgAdmin
|
||||||
@ -263,3 +264,140 @@ class OrgAdminLogsPermAudit(APIView):
|
|||||||
'page': page,
|
'page': page,
|
||||||
'page_next': page_next,
|
'page_next': page_next,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class OrgAdminLogsFileTransfer(APIView):
|
||||||
|
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
permission_classes = (IsProVersion, IsOrgAdmin)
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
"""List organization file transfer in 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 = RepoTransfer.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 = {}
|
||||||
|
repo_dict = {}
|
||||||
|
group_name_dict = {}
|
||||||
|
|
||||||
|
user_email_set = set()
|
||||||
|
repo_id_set = set()
|
||||||
|
group_id_set = set()
|
||||||
|
|
||||||
|
for event in events:
|
||||||
|
repo_id_set.add(event.repo_id)
|
||||||
|
if is_valid_email(event.from_user):
|
||||||
|
user_email_set.add(event.from_user)
|
||||||
|
if is_valid_email(event.to):
|
||||||
|
user_email_set.add(event.to)
|
||||||
|
if is_valid_email(event.operator):
|
||||||
|
user_email_set.add(event.operator)
|
||||||
|
if '@seafile_group' in event.to:
|
||||||
|
group_id = int(event.to.split('@')[0])
|
||||||
|
group_id_set.add(group_id)
|
||||||
|
if '@seafile_group' in event.from_user:
|
||||||
|
group_id = int(event.from_user.split('@')[0])
|
||||||
|
group_id_set.add(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 e in repo_id_set:
|
||||||
|
if e not in repo_dict:
|
||||||
|
repo_dict[e] = seafile_api.get_repo(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 = {
|
||||||
|
'from_user_email': '',
|
||||||
|
'from_user_name': '',
|
||||||
|
'from_user_contact_email': '',
|
||||||
|
'from_group_id': '',
|
||||||
|
'from_group_name': '',
|
||||||
|
'to_user_email': '',
|
||||||
|
'to_user_name': '',
|
||||||
|
'to_user_contact_email': '',
|
||||||
|
'to_group_id': '',
|
||||||
|
'to_group_name': '',
|
||||||
|
'operator_email': '',
|
||||||
|
'operator_name': '',
|
||||||
|
'operator_contact_email': '',
|
||||||
|
}
|
||||||
|
from_user_email = ev.from_user
|
||||||
|
data['from_user_email'] = from_user_email
|
||||||
|
data['from_user_name'] = nickname_dict.get(from_user_email, '')
|
||||||
|
data['from_user_contact_email'] = contact_email_dict.get(from_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, '')
|
||||||
|
|
||||||
|
if is_valid_email(from_user_email):
|
||||||
|
data['from_type'] = 'user'
|
||||||
|
if '@seafile_group' in from_user_email:
|
||||||
|
from_group_id = int(from_user_email.split('@')[0])
|
||||||
|
data['from_group_id'] = from_group_id
|
||||||
|
data['from_group_name'] = group_name_dict.get(from_group_id, '')
|
||||||
|
data['from_type'] = 'group'
|
||||||
|
|
||||||
|
repo_id = ev.repo_id
|
||||||
|
data['repo_id'] = repo_id
|
||||||
|
repo = repo_dict.get(repo_id, None)
|
||||||
|
data['repo_name'] = repo.name if repo else ''
|
||||||
|
data['date'] = datetime_to_isoformat_timestr(ev.timestamp)
|
||||||
|
|
||||||
|
if is_valid_email(ev.to):
|
||||||
|
to_user_email = ev.to
|
||||||
|
data['to_user_email'] = to_user_email
|
||||||
|
data['to_user_name'] = nickname_dict.get(to_user_email, '')
|
||||||
|
data['to_user_contact_email'] = contact_email_dict.get(to_user_email, '')
|
||||||
|
data['to_type'] = 'user'
|
||||||
|
if '@seafile_group' in ev.to:
|
||||||
|
to_group_id = int(ev.to.split('@')[0])
|
||||||
|
data['to_group_id'] = to_group_id
|
||||||
|
data['to_group_name'] = group_name_dict.get(to_group_id, '')
|
||||||
|
data['to_type'] = 'group'
|
||||||
|
|
||||||
|
event_list.append(data)
|
||||||
|
|
||||||
|
return Response({
|
||||||
|
'log_list': event_list,
|
||||||
|
'page': page,
|
||||||
|
'page_next': page_next,
|
||||||
|
})
|
||||||
|
@ -15,6 +15,7 @@ from seahub.api2.throttling import UserRateThrottle
|
|||||||
from seahub.api2.authentication import TokenAuthentication
|
from seahub.api2.authentication import TokenAuthentication
|
||||||
from seahub.api2.utils import api_error
|
from seahub.api2.utils import api_error
|
||||||
from seahub.base.templatetags.seahub_tags import email2nickname, email2contact_email
|
from seahub.base.templatetags.seahub_tags import email2nickname, email2contact_email
|
||||||
|
from seahub.base.models import RepoTransfer
|
||||||
from seahub.group.utils import group_id_to_name
|
from seahub.group.utils import group_id_to_name
|
||||||
from seahub.utils.timeutils import timestamp_to_isoformat_timestr
|
from seahub.utils.timeutils import timestamp_to_isoformat_timestr
|
||||||
from seahub.utils import is_valid_email, transfer_repo
|
from seahub.utils import is_valid_email, transfer_repo
|
||||||
@ -166,6 +167,11 @@ class OrgAdminRepo(APIView):
|
|||||||
# transfer repo
|
# transfer repo
|
||||||
try:
|
try:
|
||||||
transfer_repo(repo_id, new_owner, is_share, org_id)
|
transfer_repo(repo_id, new_owner, is_share, org_id)
|
||||||
|
RepoTransfer.objects.create(from_user=repo_owner,
|
||||||
|
to=new_owner,
|
||||||
|
repo_id=repo_id,
|
||||||
|
org_id=org_id,
|
||||||
|
operator=request.user.username)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
error_msg = 'Internal Server Error'
|
error_msg = 'Internal Server Error'
|
||||||
@ -182,9 +188,15 @@ class OrgAdminRepo(APIView):
|
|||||||
break
|
break
|
||||||
|
|
||||||
repo_info = {}
|
repo_info = {}
|
||||||
|
|
||||||
repo_info['owner_email'] = new_owner
|
repo_info['owner_email'] = new_owner
|
||||||
repo_info['owner_name'] = email2nickname(new_owner)
|
if '@seafile_group' in new_owner:
|
||||||
repo_info['encrypted'] = repo.encrypted
|
group_id = get_group_id_by_repo_owner(new_owner)
|
||||||
|
repo_info['group_name'] = group_id_to_name(group_id)
|
||||||
|
repo_info['owner_name'] = group_id_to_name(group_id)
|
||||||
|
else:
|
||||||
|
repo_info['owner_name'] = email2nickname(new_owner)
|
||||||
|
repo_info['encrypted'] = repo.encrypted
|
||||||
repo_info['repo_id'] = repo.repo_id
|
repo_info['repo_id'] = repo.repo_id
|
||||||
repo_info['repo_name'] = repo.name
|
repo_info['repo_name'] = repo.name
|
||||||
repo_info['is_department_repo'] = False
|
repo_info['is_department_repo'] = False
|
||||||
|
@ -20,7 +20,7 @@ from .api.admin.trash_libraries import OrgAdminTrashLibraries, OrgAdminTrashLibr
|
|||||||
from .api.admin.info import OrgAdminInfo
|
from .api.admin.info import OrgAdminInfo
|
||||||
from .api.admin.links import OrgAdminLinks, OrgAdminLink
|
from .api.admin.links import OrgAdminLinks, OrgAdminLink
|
||||||
from .api.admin.web_settings import OrgAdminWebSettings
|
from .api.admin.web_settings import OrgAdminWebSettings
|
||||||
from .api.admin.logs import OrgAdminLogsFileAccess, OrgAdminLogsFileUpdate, OrgAdminLogsPermAudit
|
from .api.admin.logs import OrgAdminLogsFileAccess, OrgAdminLogsFileUpdate, OrgAdminLogsPermAudit, OrgAdminLogsFileTransfer
|
||||||
from .api.admin.user_repos import OrgAdminUserRepos, OrgAdminUserBesharedRepos
|
from .api.admin.user_repos import OrgAdminUserRepos, OrgAdminUserBesharedRepos
|
||||||
|
|
||||||
from .api.admin.devices import OrgAdminDevices, OrgAdminDevicesErrors
|
from .api.admin.devices import OrgAdminDevices, OrgAdminDevicesErrors
|
||||||
@ -102,6 +102,7 @@ urlpatterns = [
|
|||||||
path('admin/logs/file-access/', OrgAdminLogsFileAccess.as_view(), name='api-v2.1-org-admin-logs-file-access'),
|
path('admin/logs/file-access/', OrgAdminLogsFileAccess.as_view(), name='api-v2.1-org-admin-logs-file-access'),
|
||||||
path('admin/logs/file-update/', OrgAdminLogsFileUpdate.as_view(), name='api-v2.1-org-admin-logs-file-update'),
|
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-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('<int:org_id>/admin/departments/', OrgAdminDepartments.as_view(), name='api-v2.1-org-admin-departments'),
|
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('<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'),
|
path('admin/log/export-excel/', org_log_export_excel, name='org_log_export_excel'),
|
||||||
|
@ -35,6 +35,7 @@ urlpatterns = [
|
|||||||
path('logadmin/', react_fake_view, name='org_log_file_audit'),
|
path('logadmin/', react_fake_view, name='org_log_file_audit'),
|
||||||
path('logadmin/file-update/', react_fake_view, name='org_log_file_update'),
|
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/perm-audit/', react_fake_view, name='org_log_perm_audit'),
|
||||||
|
path('logadmin/repo-transfer/', react_fake_view, name='org_log_file_transfer'),
|
||||||
|
|
||||||
path('info/', react_fake_view, name='org_info'),
|
path('info/', react_fake_view, name='org_info'),
|
||||||
path('settings/', react_fake_view, name='org_settings'),
|
path('settings/', react_fake_view, name='org_settings'),
|
||||||
|
@ -193,7 +193,7 @@ from seahub.api2.endpoints.admin.file_scan_records import AdminFileScanRecords
|
|||||||
from seahub.api2.endpoints.admin.notifications import AdminNotificationsView
|
from seahub.api2.endpoints.admin.notifications import AdminNotificationsView
|
||||||
from seahub.api2.endpoints.admin.sys_notifications import AdminSysNotificationsView, AdminSysNotificationView
|
from seahub.api2.endpoints.admin.sys_notifications import AdminSysNotificationsView, AdminSysNotificationView
|
||||||
from seahub.api2.endpoints.admin.logs import AdminLogsLoginLogs, AdminLogsFileAccessLogs, AdminLogsFileUpdateLogs, \
|
from seahub.api2.endpoints.admin.logs import AdminLogsLoginLogs, AdminLogsFileAccessLogs, AdminLogsFileUpdateLogs, \
|
||||||
AdminLogsSharePermissionLogs
|
AdminLogsSharePermissionLogs, AdminLogsFileTransferLogs
|
||||||
from seahub.api2.endpoints.admin.terms_and_conditions import AdminTermsAndConditions, AdminTermAndCondition
|
from seahub.api2.endpoints.admin.terms_and_conditions import AdminTermsAndConditions, AdminTermAndCondition
|
||||||
from seahub.api2.endpoints.admin.work_weixin import AdminWorkWeixinDepartments, \
|
from seahub.api2.endpoints.admin.work_weixin import AdminWorkWeixinDepartments, \
|
||||||
AdminWorkWeixinDepartmentMembers, AdminWorkWeixinUsersBatch, AdminWorkWeixinDepartmentsImport
|
AdminWorkWeixinDepartmentMembers, AdminWorkWeixinUsersBatch, AdminWorkWeixinDepartmentsImport
|
||||||
@ -691,6 +691,7 @@ urlpatterns = [
|
|||||||
re_path(r'^api/v2.1/admin/logs/file-access-logs/$', AdminLogsFileAccessLogs.as_view(), name='api-v2.1-admin-logs-file-access-logs'),
|
re_path(r'^api/v2.1/admin/logs/file-access-logs/$', AdminLogsFileAccessLogs.as_view(), name='api-v2.1-admin-logs-file-access-logs'),
|
||||||
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/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/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'),
|
||||||
|
|
||||||
## admin::admin logs
|
## admin::admin logs
|
||||||
re_path(r'^api/v2.1/admin/admin-logs/$', AdminOperationLogs.as_view(), name='api-v2.1-admin-admin-operation-logs'),
|
re_path(r'^api/v2.1/admin/admin-logs/$', AdminOperationLogs.as_view(), name='api-v2.1-admin-admin-operation-logs'),
|
||||||
@ -865,6 +866,7 @@ urlpatterns = [
|
|||||||
path('sys/logs/file-access/', sysadmin_react_fake_view, name="sys_logs_file_access"),
|
path('sys/logs/file-access/', sysadmin_react_fake_view, name="sys_logs_file_access"),
|
||||||
path('sys/logs/file-update/', sysadmin_react_fake_view, name="sys_logs_file_update"),
|
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/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/admin-logs/operation/', sysadmin_react_fake_view, name="sys_admin_logs_operation"),
|
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/admin-logs/login/', sysadmin_react_fake_view, name="sys_admin_logs_login"),
|
||||||
path('sys/organizations/', sysadmin_react_fake_view, name="sys_organizations"),
|
path('sys/organizations/', sysadmin_react_fake_view, name="sys_organizations"),
|
||||||
|
@ -1575,3 +1575,16 @@ CREATE TABLE `wiki_wiki2_publish` (
|
|||||||
UNIQUE KEY `publish_url` (`publish_url`),
|
UNIQUE KEY `publish_url` (`publish_url`),
|
||||||
KEY `ix_wiki2_publish_repo_id` (`repo_id`)
|
KEY `ix_wiki2_publish_repo_id` (`repo_id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
CREATE TABLE `RepoTransfer` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`repo_id` varchar(36) NOT NULL,
|
||||||
|
`org_id` int(11) NOT NULL,
|
||||||
|
`from_user` varchar(255) NOT NULL,
|
||||||
|
`to` varchar(255) NOT NULL,
|
||||||
|
`timestamp` datetime NOT NULL,
|
||||||
|
`operator` varchar(255) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_file_transfer_org_id` (`org_id`),
|
||||||
|
KEY `idx_file_transfer_timestamp` (`timestamp`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
Loading…
Reference in New Issue
Block a user