diff --git a/frontend/src/components/dialog/org-logs-file-update-detail.js b/frontend/src/components/dialog/org-logs-file-update-detail.js
new file mode 100644
index 0000000000..c7a8b988d0
--- /dev/null
+++ b/frontend/src/components/dialog/org-logs-file-update-detail.js
@@ -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 += '
';
+ for (var i = 0, len = items.length; i < len; i++) {
+ con += '- ' + items[i] + '
';
+ }
+ con += '
';
+ return {__html: con};
+ }
+
+ renderContent = () => {
+ if (this.state.newfile.length > 0) {
+ return (
+
+
{gettext('New files')}
+
{this.state.time}
+
+
+ );
+ }
+
+ if (this.state.removed.length > 0) {
+ return (
+
+
{gettext('Deleted files')}
+
{this.state.time}
+
+
+ );
+ }
+
+ if (this.state.renamed.length > 0) {
+ return (
+
+
{gettext('Renamed or Moved files')}
+
{this.state.time}
+
+
+ );
+ }
+
+ if (this.state.modified.length > 0) {
+ return (
+
+
{gettext('Modified files')}
+
{this.state.time}
+
+
+ );
+ }
+
+ if (this.state.newdir.length > 0) {
+ return (
+
+
{gettext('New directories')}
+
{this.state.time}
+
+
+ );
+ }
+
+ if (this.state.deldir.length > 0) {
+ return (
+
+
{gettext('Deleted directories')}
+
{this.state.time}
+
+
+ );
+ }
+ }
+
+ render() {
+ return (
+
+
+ {gettext('Modification Details')}
+
+
+ {this.renderContent()}
+
+
+ );
+ }
+}
+
+FileUpdateDetailDialog.propTypes = propTypes;
+
+export default FileUpdateDetailDialog;
diff --git a/frontend/src/css/org-logs.css b/frontend/src/css/org-logs.css
new file mode 100644
index 0000000000..9e558b03fd
--- /dev/null
+++ b/frontend/src/css/org-logs.css
@@ -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;
+}
\ No newline at end of file
diff --git a/frontend/src/models/org-admin-repo.js b/frontend/src/models/org-admin-repo.js
index 31d49b59c8..e5974d4654 100644
--- a/frontend/src/models/org-admin-repo.js
+++ b/frontend/src/models/org-admin-repo.js
@@ -1,5 +1,3 @@
-import { Utils } from '../utils/utils';
-
class OrgAdminRepo {
constructor(object) {
this.repoID = object.repo_id;
diff --git a/frontend/src/models/org-group.js b/frontend/src/models/org-group.js
index ab55144f69..276154357b 100644
--- a/frontend/src/models/org-group.js
+++ b/frontend/src/models/org-group.js
@@ -1,4 +1,3 @@
-import { Utils } from '../utils/utils';
import { lang } from '../utils/constants';
import moment from 'moment';
diff --git a/frontend/src/models/org-logs-file-audit.js b/frontend/src/models/org-logs-file-audit.js
new file mode 100644
index 0000000000..3449fc4391
--- /dev/null
+++ b/frontend/src/models/org-logs-file-audit.js
@@ -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;
diff --git a/frontend/src/models/org-logs-file-update.js b/frontend/src/models/org-logs-file-update.js
new file mode 100644
index 0000000000..635ff80775
--- /dev/null
+++ b/frontend/src/models/org-logs-file-update.js
@@ -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;
diff --git a/frontend/src/models/org-logs-perm-audit.js b/frontend/src/models/org-logs-perm-audit.js
new file mode 100644
index 0000000000..dc9a64d2d8
--- /dev/null
+++ b/frontend/src/models/org-logs-perm-audit.js
@@ -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;
diff --git a/frontend/src/pages/org-admin/index.js b/frontend/src/pages/org-admin/index.js
index f2206efdf5..42d6bc4b00 100644
--- a/frontend/src/pages/org-admin/index.js
+++ b/frontend/src/pages/org-admin/index.js
@@ -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 {
+
+
+
+
+
diff --git a/frontend/src/pages/org-admin/org-admin-list.js b/frontend/src/pages/org-admin/org-admin-list.js
index 495d7eeead..d10d51efd6 100644
--- a/frontend/src/pages/org-admin/org-admin-list.js
+++ b/frontend/src/pages/org-admin/org-admin-list.js
@@ -107,7 +107,8 @@ class OrgAdminList extends React.Component {
onFreezedItem={this.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem}
/>
- )})}
+ );
+ })}
{this.props.isShowAddOrgAdminDialog && (
diff --git a/frontend/src/pages/org-admin/org-info.js b/frontend/src/pages/org-admin/org-info.js
index 1dc6daf4d2..131df26a15 100644
--- a/frontend/src/pages/org-admin/org-info.js
+++ b/frontend/src/pages/org-admin/org-info.js
@@ -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 {
diff --git a/frontend/src/pages/org-admin/org-logs-file-audit.js b/frontend/src/pages/org-admin/org-logs-file-audit.js
new file mode 100644
index 0000000000..170abda18b
--- /dev/null
+++ b/frontend/src/pages/org-admin/org-logs-file-audit.js
@@ -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 (
+
+ {
+ (this.state.userSelected || this.state.repoSelected) &&
+
+ {this.state.userSelected &&
+
+ {this.state.userSelected}{' ✖'}
+
+ }
+ {this.state.repoSelected &&
+
+ {this.state.repoSelected}{' ✖'}
+
+ }
+
+ }
+
+
+
+ {gettext('User')} |
+ {gettext('Type')} |
+ {gettext('IP')} |
+ {gettext('Date')} |
+ {gettext('Library')} |
+ {gettext('File')} |
+
+
+
+ {eventList.map((item, index) => {
+ return (
+
+ );
+ })}
+
+
+
+
+ );
+ }
+}
+
+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 (
+
+ {fileEvent.user_name}{' '}
+
+
+
+
+ {gettext('Only Show')}{' '}
+ {fileEvent.user_name}
+
+
+
+
+ );
+
+ }
+
+ 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 (
+
+ {repoName}
+ { fileEvent.repo_name &&
+
+
+
+
+ {gettext('Only Show')}{' '}{fileEvent.repo_name}
+
+
+ }
+
+ );
+ }
+
+ 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 (
+
+ {this.renderUser(fileEvent)} |
+ {this.renderType(fileEvent.type)} |
+ {fileEvent.ip} |
+ {fileEvent.time} |
+ {this.renderRepo(fileEvent)} |
+ {fileEvent.file_name} |
+
+ );
+ }
+ }
+}
+
+FileAuditItem.propTypes = propTypes;
+
+export default OrgLogsFileAudit;
diff --git a/frontend/src/pages/org-admin/org-logs-file-update.js b/frontend/src/pages/org-admin/org-logs-file-update.js
new file mode 100644
index 0000000000..d62236c4ad
--- /dev/null
+++ b/frontend/src/pages/org-admin/org-logs-file-update.js
@@ -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 (
+
+ {
+ (this.state.userSelected || this.state.repoSelected) &&
+
+ {this.state.userSelected &&
+
+ {this.state.userSelected}{' ✖'}
+
+ }
+ {this.state.repoSelected &&
+
+ {this.state.repoSelected}{' ✖'}
+
+ }
+
+ }
+
+
+
+ {gettext('User')} |
+ {gettext('Date')} |
+ {gettext('Library')} |
+ {gettext('Action')} |
+
+
+
+ {eventList.map((item, index) => {
+ return (
+
+ );
+ })}
+
+
+
+ {this.state.showDetails &&
+
+
+
+ }
+
+ );
+ }
+}
+
+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 (
+
+ {fileEvent.user_name}{' '}
+
+
+
+
+ {gettext('Only Show')}{' '}{fileEvent.user_name}
+
+
+
+
+ );
+ }
+
+ toggleRepoDropdown = () => {
+ this.setState({ repoDropdownOpen: !this.state.repoDropdownOpen });
+ }
+
+ renderRepo = (fileEvent) => {
+ let repoName = 'Deleted';
+ if (fileEvent.repo_name) {
+ repoName = fileEvent.repo_name;
+ }
+ return (
+
+ {repoName}
+ { fileEvent.repo_name &&
+
+
+
+
+ {gettext('Only Show')}{' '}
+ {fileEvent.repo_name}
+
+
+
+ }
+
+ );
+ }
+
+ renderAction = (fileEvent) => {
+ if (fileEvent.repo_encrypted || !fileEvent.repo_id) {
+ return {fileEvent.description} | ;
+ }
+
+ return (
+ {fileEvent.description}
+ this.props.onDetails(e, fileEvent)}>{gettext('Details')}
+ |
+ );
+ }
+
+ 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 (
+
+ {this.renderUser(fileEvent)} |
+ {fileEvent.time} |
+ {this.renderRepo(fileEvent)} |
+ {this.renderAction(fileEvent)}
+
+ );
+ }
+ }
+}
+
+FileUpdateItem.propTypes = propTypes;
+
+export default OrgLogsFileUpdate;
diff --git a/frontend/src/pages/org-admin/org-logs-perm-audit.js b/frontend/src/pages/org-admin/org-logs-perm-audit.js
new file mode 100644
index 0000000000..b564f62daa
--- /dev/null
+++ b/frontend/src/pages/org-admin/org-logs-perm-audit.js
@@ -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 (
+
+ {this.state.userSelected &&
+
+ {this.state.userSelected}{' ✖'}
+
+ }
+
+
+
+ {gettext('Share From')} |
+ {gettext('Share To')} |
+ {gettext('Actions')} |
+ {gettext('Permission')} |
+ {gettext('Library')} |
+ {gettext('Folder')} |
+ {gettext('Date')} |
+
+
+
+ {eventList.map((item, index) => {
+ return (
+
+ );
+ })}
+
+
+
+
+ );
+ }
+}
+
+
+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 (
+
+ {permEvent.from_user_name}{' '}
+
+
+
+
+ {gettext('Only Show')}{' '}
+ {permEvent.from_user_name}
+
+
+
+
+ );
+ }
+
+ toggleUserDropdown = () => {
+ this.setState({ userDropdownOpen: !this.state.userDropdownOpen });
+ }
+
+ renderToUser = (permEvent) => {
+ if (permEvent.type.indexOf('public') != -1) {
+ return {gettext('Organization')};
+ }
+
+ if (permEvent.type.indexOf('group') != -1) {
+ if (permEvent.to_group_name) {
+ return {permEvent.to_group_name};
+ }
+ return 'Deleted';
+ }
+
+ if (permEvent.type.indexOf('user') != -1) {
+ return {permEvent.to_user_name};
+ }
+
+ }
+
+ 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 (
+
+ {this.renderFromUser(permEvent)} |
+ {this.renderToUser(permEvent)} |
+ {this.renderType(permEvent.type)} |
+ {Utils.sharePerms(permEvent.permission)} |
+ {this.renderRepo(permEvent)} |
+ {this.renderFolder(permEvent.folder_name)} |
+ {permEvent.time} |
+
+ );
+ }
+ }
+}
+
+PermAuditItem.propTypes = propTypes;
+
+export default OrgLogsFileUpdate;
diff --git a/frontend/src/pages/org-admin/org-logs.js b/frontend/src/pages/org-admin/org-logs.js
new file mode 100644
index 0000000000..7487f5a204
--- /dev/null
+++ b/frontend/src/pages/org-admin/org-logs.js
@@ -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 (
+
+
+
+
+ - this.tabItemClick('logadmin')}>
+ {gettext('File Access')}
+
+ - this.tabItemClick('file-update')}>
+ {gettext('File Update')}
+
+ - this.tabItemClick('perm-audit')}>
+ {gettext('Permission')}
+
+
+
+ {this.props.children}
+
+
+ );
+ }
+}
+
+export default OrgLogs;
diff --git a/frontend/src/pages/org-admin/side-panel.js b/frontend/src/pages/org-admin/side-panel.js
index 2559d8e6ed..3e0943b7cf 100644
--- a/frontend/src/pages/org-admin/side-panel.js
+++ b/frontend/src/pages/org-admin/side-panel.js
@@ -69,10 +69,10 @@ class SidePanel extends React.Component {
-
+ this.tabItemClick('logadmin')} >
{gettext('Logs')}
-
+
diff --git a/seahub/api2/endpoints/utils.py b/seahub/api2/endpoints/utils.py
index 0174c2be1c..da212a0b87 100644
--- a/seahub/api2/endpoints/utils.py
+++ b/seahub/api2/endpoints/utils.py
@@ -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