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:
130
frontend/src/components/dialog/org-logs-file-update-detail.js
Normal file
130
frontend/src/components/dialog/org-logs-file-update-detail.js
Normal 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;
|
21
frontend/src/css/org-logs.css
Normal file
21
frontend/src/css/org-logs.css
Normal 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;
|
||||
}
|
@@ -1,5 +1,3 @@
|
||||
import { Utils } from '../utils/utils';
|
||||
|
||||
class OrgAdminRepo {
|
||||
constructor(object) {
|
||||
this.repoID = object.repo_id;
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { Utils } from '../utils/utils';
|
||||
import { lang } from '../utils/constants';
|
||||
import moment from 'moment';
|
||||
|
||||
|
22
frontend/src/models/org-logs-file-audit.js
Normal file
22
frontend/src/models/org-logs-file-audit.js
Normal 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;
|
20
frontend/src/models/org-logs-file-update.js
Normal file
20
frontend/src/models/org-logs-file-update.js
Normal 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;
|
26
frontend/src/models/org-logs-perm-audit.js
Normal file
26
frontend/src/models/org-logs-perm-audit.js
Normal 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;
|
@@ -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>
|
||||
|
@@ -107,7 +107,8 @@ class OrgAdminList extends React.Component {
|
||||
onFreezedItem={this.onFreezedItem}
|
||||
onUnfreezedItem={this.onUnfreezedItem}
|
||||
/>
|
||||
)})}
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
{this.props.isShowAddOrgAdminDialog && (
|
||||
|
@@ -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 {
|
||||
|
250
frontend/src/pages/org-admin/org-logs-file-audit.js
Normal file
250
frontend/src/pages/org-admin/org-logs-file-audit.js
Normal 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;
|
278
frontend/src/pages/org-admin/org-logs-file-update.js
Normal file
278
frontend/src/pages/org-admin/org-logs-file-update.js
Normal 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;
|
237
frontend/src/pages/org-admin/org-logs-perm-audit.js
Normal file
237
frontend/src/pages/org-admin/org-logs-perm-audit.js
Normal 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;
|
39
frontend/src/pages/org-admin/org-logs.js
Normal file
39
frontend/src/pages/org-admin/org-logs.js
Normal 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;
|
@@ -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>
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user