1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-04 08:28:11 +00:00

add org logs (#3190)

* add org logs

* add filter

* refactor code

* fix warnings

* add get repo dict

* Adjust variable position
This commit is contained in:
陈钦亮
2019-04-03 16:54:24 +08:00
committed by Daniel Pan
parent a3b8fdcf61
commit 1b673b5cd5
16 changed files with 1070 additions and 10 deletions

View File

@@ -0,0 +1,130 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalHeader, ModalBody } from 'reactstrap';
import { seafileAPI } from '../../utils/seafile-api';
import { gettext } from '../../utils/constants';
const propTypes = {
repoID: PropTypes.string.isRequired,
commitID: PropTypes.string.isRequired,
toggleCancel: PropTypes.func.isRequired,
};
class FileUpdateDetailDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
time: '',
renamed: [],
deldir: [],
modified: [],
newdir: [],
newfile: [],
removed: [],
};
}
componentDidMount() {
seafileAPI.orgAdminGetFileUpdateDetail(this.props.repoID, this.props.commitID).then(res => {
this.setState({
time: res.data.date_time,
renamed: this.state.renamed.concat(res.data.renamed),
deldir: this.state.deldir.concat(res.data.deldir),
modified: this.state.modified.concat(res.data.modified),
newdir: this.state.newdir.concat(res.data.newdir),
newfile: this.state.newfile.concat(res.data.new),
removed: this.state.removed.concat(res.data.removed),
});
});
}
renderContentItem = (items) => {
let con = '';
con += '<ul class="list-group list-group-flush">';
for (var i = 0, len = items.length; i < len; i++) {
con += '<li class="list-group-item">' + items[i] + '</li>';
}
con += '</ul>';
return {__html: con};
}
renderContent = () => {
if (this.state.newfile.length > 0) {
return (
<div>
<h4>{gettext('New files')}</h4>
<p>{this.state.time}</p>
<div dangerouslySetInnerHTML= {this.renderContentItem(this.state.newfile)} />
</div>
);
}
if (this.state.removed.length > 0) {
return (
<div>
<h4>{gettext('Deleted files')}</h4>
<p>{this.state.time}</p>
<div dangerouslySetInnerHTML= {this.renderContentItem(this.state.removed)} />
</div>
);
}
if (this.state.renamed.length > 0) {
return (
<div>
<h4>{gettext('Renamed or Moved files')}</h4>
<p>{this.state.time}</p>
<div dangerouslySetInnerHTML= {this.renderContentItem(this.state.renamed)} />
</div>
);
}
if (this.state.modified.length > 0) {
return (
<div>
<h4>{gettext('Modified files')}</h4>
<p>{this.state.time}</p>
<div dangerouslySetInnerHTML= {this.renderContentItem(this.state.modified)} />
</div>
);
}
if (this.state.newdir.length > 0) {
return (
<div>
<h4>{gettext('New directories')}</h4>
<p>{this.state.time}</p>
<div dangerouslySetInnerHTML= {this.renderContentItem(this.state.newdir)} />
</div>
);
}
if (this.state.deldir.length > 0) {
return (
<div>
<h4>{gettext('Deleted directories')}</h4>
<p>{this.state.time}</p>
<div dangerouslySetInnerHTML= {this.renderContentItem(this.state.deldir)} />
</div>
);
}
}
render() {
return (
<Modal isOpen={true} toggle={this.props.toggleCancel}>
<ModalHeader toggle={this.props.toggleCancel}>
{gettext('Modification Details')}
</ModalHeader>
<ModalBody>
{this.renderContent()}
</ModalBody>
</Modal>
);
}
}
FileUpdateDetailDialog.propTypes = propTypes;
export default FileUpdateDetailDialog;

View File

@@ -0,0 +1,21 @@
.audit-unselect-item {
display: inline-block;
border: 1px solid #ccc;
border-radius: 2px;
padding: 1px 8px;
background: #f2f2f2;
cursor: pointer;
font-size: 14px;
margin: 10px 5px 0;
}
.audit-unselect-item:hover {
background-color: #ddd;
}
.no-deco,
.no-deco:hover,
.no-deco:focus {
text-decoration: none;
}
.cur-view-path .nav .nav-item a {
padding: 7px 10px;
}

View File

@@ -1,5 +1,3 @@
import { Utils } from '../utils/utils';
class OrgAdminRepo {
constructor(object) {
this.repoID = object.repo_id;

View File

@@ -1,4 +1,3 @@
import { Utils } from '../utils/utils';
import { lang } from '../utils/constants';
import moment from 'moment';

View File

@@ -0,0 +1,22 @@
import { lang } from '../utils/constants';
import moment from 'moment';
moment.locale(lang);
class OrgLogsFileAuditEvent {
constructor(object) {
this.ip = object.ip;
this.type = object.type;
this.device = object.device;
this.repo_id = object.repo_id;
this.repo_name = object.repo_name;
this.file_name = object.file_name;
this.file_path = object.file_path;
this.user_name = object.user_name;
this.user_email = object.user_email;
this.user_contact_email = object.user_contact_email;
this.time = moment(object.time).format('YYYY-MM-DD HH:mm:ss');
}
}
export default OrgLogsFileAuditEvent;

View File

@@ -0,0 +1,20 @@
import { lang } from '../utils/constants';
import moment from 'moment';
moment.locale(lang);
class OrgLogsFileUpdateEvent {
constructor(object) {
this.repo_id = object.repo_id;
this.description = object.description;
this.repo_name = object.repo_name;
this.user_name = object.user_name;
this.user_email = object.user_email;
this.repo_encrypted = object.repo_encrypted;
this.repo_commit_id = object.repo_commit_id;
this.user_contact_email = object.user_contact_email;
this.time = moment(object.time).format('YYYY-MM-DD HH:mm:ss');
}
}
export default OrgLogsFileUpdateEvent;

View File

@@ -0,0 +1,26 @@
import { lang } from '../utils/constants';
import moment from 'moment';
moment.locale(lang);
class OrgLogsPermAuditEvent {
constructor(object) {
this.from_user_name = object.from_user_name;
this.from_user_email = object.from_user_email;
this.from_user_contact_email = object.from_user_contact_email;
this.to_user_email = object.to_user_email;
this.to_user_name = object.to_user_name;
this.to_user_contact_email = object.to_user_contact_email;
this.to_group_name = object.to_group_name;
this.to_group_id = object.to_group_id;
this.type = object.type;
this.repo_id = object.repo_id;
this.repo_name = object.repo_name;
this.folder_name = object.folder_name;
this.folder_path = object.folder_path;
this.time = moment(object.time).format('YYYY-MM-DD HH:mm:ss');
this.permission = object.permission;
}
}
export default OrgLogsPermAuditEvent;

View File

@@ -15,6 +15,10 @@ import OrgLinks from './org-links';
import OrgDepartments from './org-departments';
import OrgDepartmentsList from './org-departments-list';
import OrgDepartmentItem from './org-department-item';
import OrgLogs from './org-logs';
import OrgLogsFileAudit from './org-logs-file-audit';
import OrgLogsFileUpdate from './org-logs-file-update';
import OrgLogsPermAudit from './org-logs-perm-audit';
import '../../assets/css/fa-solid.css';
import '../../assets/css/fa-regular.css';
@@ -97,6 +101,15 @@ class Org extends React.Component {
<OrgDepartmentsList path='/' />
<OrgDepartmentItem path={newPath} />
</OrgDepartments>
<OrgLogs
path={siteRoot + 'org/logadmin'}
currentTab={currentTab}
tabItemClick={this.tabItemClick}
>
<OrgLogsFileAudit path='/' currentTab={currentTab} />
<OrgLogsFileUpdate path={siteRoot + 'file-update'} currentTab={currentTab} />
<OrgLogsPermAudit path={siteRoot + 'perm-audit'} currentTab={currentTab} />
</OrgLogs>
</Router>
</MainPanel>
</div>

View File

@@ -107,7 +107,8 @@ class OrgAdminList extends React.Component {
onFreezedItem={this.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem}
/>
)})}
);
})}
</tbody>
</table>
{this.props.isShowAddOrgAdminDialog && (

View File

@@ -1,7 +1,6 @@
import React, { Fragment, Component } from 'react';
import React, { Component } from 'react';
import { seafileAPI } from '../../utils/seafile-api';
import { gettext, orgID, orgMemberQuotaEnabled} from '../../utils/constants';
import { gettext, orgMemberQuotaEnabled} from '../../utils/constants';
import { Utils } from '../../utils/utils';
class OrgInfo extends Component {

View File

@@ -0,0 +1,250 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import { seafileAPI } from '../../utils/seafile-api';
import { siteRoot, gettext } from '../../utils/constants';
import OrgLogsFileAuditEvent from '../../models/org-logs-file-audit';
import '../../css/org-logs.css';
class OrgLogsFileAudit extends React.Component {
constructor(props) {
super(props);
this.state = {
page: 1,
pageNext: false,
eventList: [],
userSelected: '',
repoSelected: '',
isItemFreezed: false
};
}
componentDidMount() {
let page = this.state.page;
let email = this.state.userSelected;
let repoID = this.state.repoSelected;
this.initData(email, repoID, page);
}
initData = (email, repoID, page) => {
seafileAPI.orgAdminListFileAudit(email, repoID, page).then(res => {
let eventList = res.data.log_list.map(item => {
return new OrgLogsFileAuditEvent(item);
});
this.setState({
eventList: eventList,
pageNext: res.data.page_next,
page: res.data.page,
userSelected: res.data.user_selected,
repoSelected: res.data.repo_selected
});
});
}
onChangePageNum = (e, num) => {
e.preventDefault();
let page = this.state.page;
if (num == 1) {
page = page + 1;
} else {
page = page - 1;
}
this.initData(page);
}
filterUser = (userSelected) => {
this.setState({ userSelected: userSelected });
}
filterRepo = (repoSelected) => {
this.setState({ repoSelected: repoSelected });
}
render() {
let eventList = this.state.eventList;
return (
<div className="cur-view-content">
{
(this.state.userSelected || this.state.repoSelected) &&
<React.Fragment>
{this.state.userSelected &&
<span className="audit-unselect-item" onClick={this.filterUser.bind(this, null)}>
<span className="no-deco">{this.state.userSelected}</span>{' '}
</span>
}
{this.state.repoSelected &&
<span className="audit-unselect-item" onClick={this.filterRepo.bind(this, null)}>
<span className="no-deco">{this.state.repoSelected}</span>{' '}
</span>
}
</React.Fragment>
}
<table>
<thead>
<tr>
<th width="24%">{gettext('User')}</th>
<th width="10%">{gettext('Type')}</th>
<th width="13%">{gettext('IP')}</th>
<th width="17%">{gettext('Date')}</th>
<th width="18%">{gettext('Library')}</th>
<th width="18%">{gettext('File')}</th>
</tr>
</thead>
<tbody>
{eventList.map((item, index) => {
return (
<FileAuditItem
key={index}
fileEvent={item}
isItemFreezed={this.state.isItemFreezed}
filterUser={this.filterUser}
filterRepo={this.filterRepo}
userSelected={this.state.userSelected}
repoSelected={this.state.repoSelected}
/>
);
})}
</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 = {
filterUser: PropTypes.func.isRequired,
filterRepo: PropTypes.func.isRequired,
isItemFreezed: PropTypes.bool.isRequired,
};
class FileAuditItem extends React.Component {
constructor(props) {
super(props);
this.state = {
highlight: false,
showMenu: false,
isItemMenuShow: false,
userDropdownOpen: false,
repoDropdownOpen: false,
};
}
onMouseEnter = () => {
if (!this.props.isItemFreezed) {
this.setState({
showMenu: true,
highlight: true,
});
}
}
onMouseLeave = () => {
if (!this.props.isItemFreezed) {
this.setState({
showMenu: false,
highlight: false
});
}
}
toggleUserDropdown = () => {
this.setState({ userDropdownOpen: !this.state.userDropdownOpen });
}
renderUser = (fileEvent) => {
if (!fileEvent.user_email) {
return gettext('Anonymous User');
}
return (
<span>
<a href={siteRoot + 'org/useradmin/info/' + fileEvent.user_email + '/'}>{fileEvent.user_name}</a>{' '}
<Dropdown size='sm' isOpen={this.state.userDropdownOpen} toggle={this.toggleUserDropdown}
className={this.state.highlight ? '' : 'vh'} tag="span">
<DropdownToggle tag="i" className="sf-dropdown-toggle sf2-icon-caret-down"></DropdownToggle>
<DropdownMenu>
<DropdownItem onClick={this.props.filterUser.bind(this, fileEvent.user_email)}>
{gettext('Only Show')}{' '}
<span className="font-weight-bold">{fileEvent.user_name}</span>
</DropdownItem>
</DropdownMenu>
</Dropdown>
</span>
);
}
renderType = (type) => {
if (type.indexOf('web') != -1) {
type = 'web';
}
if (type.indexOf('api') != -1) {
type = 'api';
}
if (type.indexOf('share-link') != -1) {
type = 'share link';
}
return type;
}
toggleRepoDropdown = () => {
this.setState({ repoDropdownOpen: !this.state.repoDropdownOpen });
}
renderRepo = (fileEvent) => {
let repoName = 'Deleted';
if (fileEvent.repo_name) {
repoName = fileEvent.repo_name;
}
return (
<span>
<span>{repoName}</span>
{ fileEvent.repo_name &&
<Dropdown size='sm' isOpen={this.state.repoDropdownOpen} toggle={this.toggleRepoDropdown}
className={this.state.highlight ? '' : 'vh'} >
<DropdownToggle tag="i" className="sf-dropdown-toggle sf2-icon-caret-down"></DropdownToggle>
<DropdownMenu>
<DropdownItem size='sm' onClick={this.props.filterRepo.bind(this, fileEvent.repo_name)}>
{gettext('Only Show')}{' '}<span className="font-weight-bold">{fileEvent.repo_name}</span></DropdownItem>
</DropdownMenu>
</Dropdown>
}
</span>
);
}
render() {
let { fileEvent } = this.props;
if (this.props.userSelected && fileEvent.user_email !== this.props.userSelected ) {
return null;
} else if (this.props.repoSelected && fileEvent.repo_name !== this.props.repoSelected) {
return null;
} else {
return (
<tr className={this.state.highlight ? 'tr-highlight' : ''}
onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<td>{this.renderUser(fileEvent)}</td>
<td>{this.renderType(fileEvent.type)}</td>
<td>{fileEvent.ip}</td>
<td>{fileEvent.time}</td>
<td>{this.renderRepo(fileEvent)}</td>
<td><span title={fileEvent.file_path}>{fileEvent.file_name}</span></td>
</tr>
);
}
}
}
FileAuditItem.propTypes = propTypes;
export default OrgLogsFileAudit;

View File

@@ -0,0 +1,278 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import { seafileAPI } from '../../utils/seafile-api';
import { siteRoot, gettext } from '../../utils/constants';
import OrgLogsFileUpdateEvent from '../../models/org-logs-file-update';
import ModalPortal from '../../components/modal-portal';
import FileUpdateDetailDialog from '../../components/dialog/org-logs-file-update-detail';
import '../../css/org-logs.css';
class OrgLogsFileUpdate extends Component {
constructor(props) {
super(props);
this.state = {
page: 1,
pageNext: false,
eventList: [],
userSelected: '',
repoSelected: '',
isItemFreezed: false,
showDetails: false,
repoID: '',
commitID: ''
};
}
componentDidMount() {
let page = this.state.page;
let email = this.state.userSelected;
let repoID = this.state.repoSelected;
this.initData(email, repoID, page);
}
initData = (email, repoID, page) => {
seafileAPI.orgAdminListFileUpdate(email, repoID, page).then(res => {
let eventList = res.data.log_list.map(item => {
return new OrgLogsFileUpdateEvent(item);
});
this.setState({
eventList: eventList,
pageNext: res.data.page_next,
page: res.data.page,
});
});
}
onChangePageNum = (e, num) => {
e.preventDefault();
let page = this.state.page;
if (num == 1) {
page = page + 1;
} else {
page = page - 1;
}
this.initData(page);
}
toggleCancelDetail = () => {
this.setState({
showDetails: !this.state.showDetails
});
}
onDetails = (e, fileEvent) => {
e.preventDefault();
this.setState({
showDetails: !this.state.showDetails,
repoID: fileEvent.repo_id,
commitID: fileEvent.repo_commit_id
});
}
filterUser = (userSelected) => {
this.setState({ userSelected: userSelected });
}
filterRepo = (repoSelected) => {
this.setState({ repoSelected: repoSelected });
}
render() {
let eventList = this.state.eventList;
return (
<div className="cur-view-content">
{
(this.state.userSelected || this.state.repoSelected) &&
<React.Fragment>
{this.state.userSelected &&
<span className="audit-unselect-item" onClick={this.filterUser.bind(this, null)}>
<span className="no-deco">{this.state.userSelected}</span>{' '}
</span>
}
{this.state.repoSelected &&
<span className="audit-unselect-item" onClick={this.filterRepo.bind(this, null)}>
<span className="no-deco">{this.state.repoSelected}</span>{' '}
</span>
}
</React.Fragment>
}
<table>
<thead>
<tr>
<th width="25%">{gettext('User')}</th>
<th width="17%">{gettext('Date')}</th>
<th width="25%">{gettext('Library')}</th>
<th width="33%">{gettext('Action')}</th>
</tr>
</thead>
<tbody>
{eventList.map((item, index) => {
return (
<FileUpdateItem
key={index}
fileEvent={item}
isItemFreezed={this.state.isItemFreezed}
onDetails={this.onDetails}
filterUser={this.filterUser}
filterRepo={this.filterRepo}
userSelected={this.state.userSelected}
repoSelected={this.state.repoSelected}
/>
);
})}
</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>
{this.state.showDetails &&
<ModalPortal>
<FileUpdateDetailDialog
repoID={this.state.repoID}
commitID={this.state.commitID}
toggleCancel={this.toggleCancelDetail}
/>
</ModalPortal>
}
</div>
);
}
}
const propTypes = {
filterUser: PropTypes.func.isRequired,
filterRepo: PropTypes.func.isRequired,
onDetails: PropTypes.func.isRequired,
userSelected: PropTypes.string.isRequired,
repoSelected: PropTypes.string.isRequired,
isItemFreezed: PropTypes.bool.isRequired,
};
class FileUpdateItem extends React.Component {
constructor(props) {
super(props);
this.state = {
highlight: false,
showMenu: false,
isItemMenuShow: false,
userDropdownOpen: false,
repoDropdownOpen: false,
};
}
onMouseEnter = () => {
if (!this.props.isItemFreezed) {
this.setState({
showMenu: true,
highlight: true,
});
}
}
onMouseLeave = () => {
if (!this.props.isItemFreezed) {
this.setState({
showMenu: false,
highlight: false
});
}
}
toggleUserDropdown = () => {
this.setState({ userDropdownOpen: !this.state.userDropdownOpen });
}
renderUser = (fileEvent) => {
if (!fileEvent.user_email) {
return gettext('Anonymous User');
}
return (
<span>
<a href={siteRoot + 'org/useradmin/info/' + fileEvent.user_email + '/'}>{fileEvent.user_name}</a>{' '}
<Dropdown size='sm' isOpen={this.state.userDropdownOpen} toggle={this.toggleUserDropdown}
className={this.state.highlight ? '' : 'vh'} tag="span">
<DropdownToggle tag="i" className="sf-dropdown-toggle sf2-icon-caret-down"></DropdownToggle>
<DropdownMenu>
<DropdownItem onClick={this.props.filterUser.bind(this, fileEvent.user_email)}>
{gettext('Only Show')}{' '}<span className="font-weight-bold">{fileEvent.user_name}</span>
</DropdownItem>
</DropdownMenu>
</Dropdown>
</span>
);
}
toggleRepoDropdown = () => {
this.setState({ repoDropdownOpen: !this.state.repoDropdownOpen });
}
renderRepo = (fileEvent) => {
let repoName = 'Deleted';
if (fileEvent.repo_name) {
repoName = fileEvent.repo_name;
}
return (
<span>
<span>{repoName}</span>
{ fileEvent.repo_name &&
<Dropdown size='sm' isOpen={this.state.repoDropdownOpen} toggle={this.toggleRepoDropdown}
className={this.state.highlight ? '' : 'vh'} >
<DropdownToggle tag="i" className="sf-dropdown-toggle sf2-icon-caret-down"></DropdownToggle>
<DropdownMenu>
<DropdownItem size='sm' onClick={this.props.filterRepo.bind(this, fileEvent.repo_name)}>
{gettext('Only Show')}{' '}
<span className="font-weight-bold">{fileEvent.repo_name}</span>
</DropdownItem>
</DropdownMenu>
</Dropdown>
}
</span>
);
}
renderAction = (fileEvent) => {
if (fileEvent.repo_encrypted || !fileEvent.repo_id) {
return <td>{fileEvent.description}</td>;
}
return (
<td>{fileEvent.description}
<a className="font-weight-normal text-muted ml-1" href='#'
onClick={(e) => this.props.onDetails(e, fileEvent)}>{gettext('Details')}</a>
</td>
);
}
render() {
let { fileEvent } = this.props;
if (this.props.userSelected && fileEvent.user_email !== this.props.userSelected ) {
return null;
} else if (this.props.repoSelected && fileEvent.repo_name !== this.props.repoSelected) {
return null;
} else {
return (
<tr className={this.state.highlight ? 'tr-highlight' : ''}
onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<td>{this.renderUser(fileEvent)}</td>
<td>{fileEvent.time}</td>
<td>{this.renderRepo(fileEvent)}</td>
{this.renderAction(fileEvent)}
</tr>
);
}
}
}
FileUpdateItem.propTypes = propTypes;
export default OrgLogsFileUpdate;

View File

@@ -0,0 +1,237 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import { Utils } from '../../utils/utils';
import { seafileAPI } from '../../utils/seafile-api';
import { siteRoot, gettext } from '../../utils/constants';
import OrgLogsFilePermEvent from '../../models/org-logs-perm-audit';
import '../../css/org-logs.css';
class OrgLogsFileUpdate extends Component {
constructor(props) {
super(props);
this.state = {
page: 1,
pageNext: false,
eventList: [],
userSelected: '',
repoSelected: '',
isItemFreezed: false
};
}
componentDidMount() {
let page = this.state.page;
let email = this.state.userSelected;
let repoID = this.state.repoSelected;
this.initData(email, repoID, page);
}
initData = (email, repoID, page) => {
seafileAPI.orgAdminListPermAudit(email, repoID, page).then(res => {
let eventList = res.data.log_list.map(item => {
return new OrgLogsFilePermEvent(item);
});
this.setState({
eventList: eventList,
pageNext: res.data.page_next,
page: res.data.page,
});
});
}
onChangePageNum = (e, num) => {
e.preventDefault();
let page = this.state.page;
if (num == 1) {
page = page + 1;
} else {
page = page - 1;
}
this.initData(page);
}
filterUser = (userSelected) => {
this.setState({ userSelected: userSelected });
}
render() {
let eventList = this.state.eventList;
return (
<div className="cur-view-content">
{this.state.userSelected &&
<span className="audit-unselect-item" onClick={this.filterUser.bind(this, null)}>
<span className="no-deco">{this.state.userSelected}</span>{' '}
</span>
}
<table>
<thead>
<tr>
<th width="18%">{gettext('Share From')}</th>
<th width="15%">{gettext('Share To')}</th>
<th width="8%">{gettext('Actions')}</th>
<th width="13%">{gettext('Permission')}</th>
<th width="15%">{gettext('Library')}</th>
<th width="15%">{gettext('Folder')}</th>
<th width="16%">{gettext('Date')}</th>
</tr>
</thead>
<tbody>
{eventList.map((item, index) => {
return (
<PermAuditItem
key={index}
permEvent={item}
isItemFreezed={this.state.isItemFreezed}
filterUser={this.filterUser}
userSelected={this.state.userSelected}
/>
);
})}
</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 = {
filterUser: PropTypes.func.isRequired,
isItemFreezed: PropTypes.bool.isRequired,
userSelected: PropTypes.string.isRequired,
};
class PermAuditItem extends React.Component {
constructor(props) {
super(props);
this.state = {
highlight: false,
showMenu: false,
isItemMenuShow: false,
userDropdownOpen: false,
};
}
onMouseEnter = () => {
if (!this.props.isItemFreezed) {
this.setState({
showMenu: true,
highlight: true,
});
}
}
onMouseLeave = () => {
if (!this.props.isItemFreezed) {
this.setState({
showMenu: false,
highlight: false
});
}
}
renderFromUser = (permEvent) => {
if (!permEvent.from_user_email) {
return gettext('Anonymous User');
}
return (
<span>
<a href={siteRoot + 'org/useradmin/info/' + permEvent.from_user_email + '/'}>{permEvent.from_user_name}</a>{' '}
<Dropdown size='sm' isOpen={this.state.userDropdownOpen} toggle={this.toggleUserDropdown}
className={this.state.highlight ? '' : 'vh'} tag="span">
<DropdownToggle tag="i" className="sf-dropdown-toggle sf2-icon-caret-down"></DropdownToggle>
<DropdownMenu>
<DropdownItem onClick={this.props.filterUser.bind(this, permEvent.from_user_email)}>
{gettext('Only Show')}{' '}
<span className="font-weight-bold">{permEvent.from_user_name}</span>
</DropdownItem>
</DropdownMenu>
</Dropdown>
</span>
);
}
toggleUserDropdown = () => {
this.setState({ userDropdownOpen: !this.state.userDropdownOpen });
}
renderToUser = (permEvent) => {
if (permEvent.type.indexOf('public') != -1) {
return <a href={siteRoot + 'org/'}>{gettext('Organization')}</a>;
}
if (permEvent.type.indexOf('group') != -1) {
if (permEvent.to_group_name) {
return <a href={siteRoot + 'org/groupadmin/' + permEvent.to_group_id + '/'}>{permEvent.to_group_name}</a>;
}
return 'Deleted';
}
if (permEvent.type.indexOf('user') != -1) {
return <a href={siteRoot + 'org/useradmin/info/' + permEvent.to_user_email + '/'}>{permEvent.to_user_name}</a>;
}
}
renderType = (type) => {
if (type.indexOf('add') != -1) {
type = 'Add';
}
if (type.indexOf('modify') != -1) {
type = 'Modify';
}
if (type.indexOf('delete') != -1) {
type = 'Delete';
}
return type;
}
renderRepo = (permEvent) => {
let repoName = 'Deleted';
if (permEvent.repo_name) {
repoName = permEvent.repo_name;
}
return repoName;
}
renderFolder = (name) => {
let folderName = '/';
if (name) {
folderName = name;
}
return folderName;
}
render() {
let { permEvent } = this.props;
if (this.props.userSelected && permEvent.from_user_email !== this.props.userSelected ) {
return null;
} else {
return (
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<td>{this.renderFromUser(permEvent)}</td>
<td>{this.renderToUser(permEvent)}</td>
<td>{this.renderType(permEvent.type)}</td>
<td>{Utils.sharePerms(permEvent.permission)}</td>
<td>{this.renderRepo(permEvent)}</td>
<td>{this.renderFolder(permEvent.folder_name)}</td>
<td>{permEvent.time}</td>
</tr>
);
}
}
}
PermAuditItem.propTypes = propTypes;
export default OrgLogsFileUpdate;

View File

@@ -0,0 +1,39 @@
import React, { Component } from 'react';
import { Link } from '@reach/router';
import { siteRoot, gettext } from '../../utils/constants';
class OrgLogs extends Component {
constructor(props) {
super(props);
}
tabItemClick = (param) => {
this.props.tabItemClick(param);
}
render() {
return (
<div className="main-panel-center flex-row">
<div className="cur-view-container">
<div className="cur-view-path org-user-nav">
<ul className="nav">
<li className="nav-item" onClick={() => this.tabItemClick('logadmin')}>
<Link className={`nav-link ${this.props.currentTab === 'logadmin' ? 'active': ''}`} to={siteRoot + 'org/logadmin/'} title={gettext('File Access')}>{gettext('File Access')}</Link>
</li>
<li className="nav-item" onClick={() => this.tabItemClick('file-update')}>
<Link className={`nav-link ${this.props.currentTab === 'file-update' ? 'active': ''}`} to={siteRoot + 'org/logadmin/file-update/'} title={gettext('File Update')}>{gettext('File Update')}</Link>
</li>
<li className="nav-item" onClick={() => this.tabItemClick('perm-audit')}>
<Link className={`nav-link ${this.props.currentTab === 'perm-audit' ? 'active': ''}`} to={siteRoot + 'org/logadmin/perm-audit/'} title={gettext('Permission')}>{gettext('Permission')}</Link>
</li>
</ul>
</div>
{this.props.children}
</div>
</div>
);
}
}
export default OrgLogs;

View File

@@ -69,10 +69,10 @@ class SidePanel extends React.Component {
</Link>
</li>
<li className="nav-item">
<a href="/org/file-audit-admin/" className="nav-link ellipsis">
<Link className={`nav-link ellipsis ${this.getActiveClass('logadmin') || this.getActiveClass('file-update') || this.getActiveClass('perm-audit')}`} to={siteRoot + 'org/logadmin/'} onClick={() => this.tabItemClick('logadmin')} >
<span className="sf2-icon-clock"></span>
<span className="nav-text">{gettext('Logs')}</span>
</a>
</Link>
</li>
</ul>
</div>

View File

@@ -7,7 +7,7 @@ import logging
from rest_framework import status
from seaserv import ccnet_api
from seaserv import ccnet_api, seafile_api
from pysearpc import SearpcError
from seahub.api2.utils import api_error
@@ -93,6 +93,33 @@ def get_user_name_dict(email_list):
return user_name_dict
def get_repo_dict(repo_id_list):
repo_id_list = set(repo_id_list)
repo_dict = {}
for repo_id in repo_id_list:
if not repo_dict.has_key(repo_id):
repo_dict[repo_id] = ''
repo = seafile_api.get_repo(repo_id)
if repo:
repo_dict[repo_id] = repo
return repo_dict
def get_group_dict(group_id_list):
group_id_list = set(group_id_list)
group_dict = {}
for group_id in group_id_list:
if not group_dict.has_key(group_id):
group_dict[group_id] = ''
group = ccnet_api.get_group(int(group_id))
print group
if group:
group_dict[group_id] = group
return group_dict
def check_time_period_valid(start, end):
if not start or not end:
return False