diff --git a/frontend/src/pages/sys-admin/logs-page/file-access-item-menu.js b/frontend/src/pages/sys-admin/logs-page/file-access-item-menu.js new file mode 100644 index 0000000000..0a6e1a6832 --- /dev/null +++ b/frontend/src/pages/sys-admin/logs-page/file-access-item-menu.js @@ -0,0 +1,46 @@ +import React from 'react'; +import { gettext } from '../../../utils/constants'; +import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap'; + +class FilterMenu extends React.Component { + + constructor(props) { + super(props); + this.state = { + isMenuShown: false + }; + } + + toggleMenu = () => { + this.setState({ + isMenuShown: !this.state.isMenuShown + }, () => { + this.props.toggleFreezeItem(this.state.isMenuShown); + }); + } + + onItemClick = () => { + this.props.filterItems(); + this.props.toggleFreezeItem(false); + } + + render() { + const { filterBy } = this.props; + return ( + + + + {gettext('only show {placeholder}').replace('{placeholder}', filterBy)} + + + ); + } +} + +export default FilterMenu; diff --git a/frontend/src/pages/sys-admin/logs-page/file-access-logs.js b/frontend/src/pages/sys-admin/logs-page/file-access-logs.js index cabb2f0231..5bf885a66a 100644 --- a/frontend/src/pages/sys-admin/logs-page/file-access-logs.js +++ b/frontend/src/pages/sys-admin/logs-page/file-access-logs.js @@ -1,23 +1,29 @@ import React, { Component, Fragment } from 'react'; import { seafileAPI } from '../../../utils/seafile-api'; -import { gettext, siteRoot } from '../../../utils/constants'; +import { gettext } from '../../../utils/constants'; import { Utils } from '../../../utils/utils'; import EmptyTip from '../../../components/empty-tip'; import { Button } from 'reactstrap'; +import { navigate } from '@reach/router'; import moment from 'moment'; import Loading from '../../../components/loading'; import Paginator from '../../../components/paginator'; -import LogsNav from './logs-nav'; -import MainPanelTopbar from '../main-panel-topbar'; -import UserLink from '../user-link'; import LogsExportExcelDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-logs-export-excel-dialog'; import ModalPortal from '../../../components/modal-portal'; +import LogsNav from './logs-nav'; +import FilterMenu from './file-access-item-menu'; +import ToggleFilter from './file-access-toggle-filter'; +import MainPanelTopbar from '../main-panel-topbar'; +import UserLink from '../user-link'; class Content extends Component { constructor(props) { super(props); + this.state = { + isItemFreezed: false + }; } getPreviousPage = () => { @@ -28,8 +34,26 @@ class Content extends Component { this.props.getLogsByPage(this.props.currentPage + 1); } + toggleFilterByUser = () => { + this.props.filterByUser(null); + } + + toggleFilterByRepo = () => { + this.props.filterByRepo(null); + } + + toggleFreezeItem = (freezed) => { + this.setState({ + isItemFreezed: freezed + }); + } + render() { - const { loading, errorMsg, items, perPage, currentPage, hasNextPage } = this.props; + const { + loading, errorMsg, items, + userFilteredBy, repoFilteredBy, + perPage, currentPage, hasNextPage + } = this.props; if (loading) { return ; } else if (errorMsg) { @@ -42,7 +66,21 @@ class Content extends Component { ); const table = ( - +
+ {userFilteredBy && ( + + )} + {repoFilteredBy && ( + + )} +
+
@@ -59,6 +97,12 @@ class Content extends Component { return (); })} @@ -84,31 +128,77 @@ class Item extends Component { constructor(props) { super(props); this.state = { - isOpIconShown: false, + isHighlighted: false, + isOpIconShown: false }; } - handleMouseOver = () => { - this.setState({ - isOpIconShown: true - }); + handleMouseEnter = () => { + if (!this.props.isFreezed) { + this.setState({ + isHighlighted: true, + isOpIconShown: true + }); + } } - handleMouseOut = () => { - this.setState({ - isOpIconShown: false - }); + handleMouseLeave = () => { + if (!this.props.isFreezed) { + this.setState({ + isHighlighted: false, + isOpIconShown: false + }); + } + } + + filterByUser = () => { + const { item } = this.props; + this.props.filterByUser(item.email); + } + + filterByRepo = () => { + const { item } = this.props; + this.props.filterByRepo(item.repo_id); + } + + toggleFreezeItem = (freezed) => { + this.props.toggleFreezeItem(freezed); + if (!freezed) { + this.setState({ + isHighlighted: false, + isOpIconShown: false + }); + } } render() { - let { item } = this.props; + const { isHighlighted, isOpIconShown } = this.state; + const { item, userFilteredBy, repoFilteredBy } = this.props; return ( - - + + - + - + ); @@ -140,15 +230,17 @@ class FileAccessLogs extends Component { const { currentPage, perPage } = this.state; this.setState({ perPage: parseInt(urlParams.get('per_page') || perPage), - currentPage: parseInt(urlParams.get('page') || currentPage) + currentPage: parseInt(urlParams.get('page') || currentPage), + userFilteredBy: urlParams.get('email'), + repoFilteredBy: urlParams.get('repo_id') }, () => { this.getLogsByPage(this.state.currentPage); }); } getLogsByPage = (page) => { - let { perPage } = this.state; - seafileAPI.sysAdminListFileAccessLogs(page, perPage).then((res) => { + const { perPage, userFilteredBy, repoFilteredBy } = this.state; + seafileAPI.sysAdminListFileAccessLogs(page, perPage, userFilteredBy, repoFilteredBy).then((res) => { this.setState({ logList: res.data.file_access_log_list, loading: false, @@ -169,8 +261,43 @@ class FileAccessLogs extends Component { }, () => this.getLogsByPage(this.initPage)); } + updateURL = (obj) => { + let url = new URL(location.href); + let searchParams = new URLSearchParams(url.search); + for (let key in obj) { + obj[key] == null ? + searchParams.delete(key) : + searchParams.set(key, obj[key]); + } + url.search = searchParams.toString(); + navigate(url.toString()); + } + + filterByUser = (email) => { + this.setState({ + userFilteredBy: email + }, () => { + this.getLogsByPage(this.initPage); + this.updateURL({'email': email}); + }); + } + + filterByRepo = (repoID) => { + this.setState({ + repoFilteredBy: repoID + }, () => { + this.getLogsByPage(this.initPage); + this.updateURL({'repo_id': repoID}); + }); + } + render() { - let { logList, currentPage, perPage, hasNextPage, isExportExcelDialogOpen } = this.state; + const { + logList, + userFilteredBy, repoFilteredBy, + currentPage, perPage, hasNextPage, + isExportExcelDialogOpen + } = this.state; return ( @@ -184,6 +311,10 @@ class FileAccessLogs extends Component { loading={this.state.loading} errorMsg={this.state.errorMsg} items={logList} + userFilteredBy={userFilteredBy} + repoFilteredBy={repoFilteredBy} + filterByUser={this.filterByUser} + filterByRepo={this.filterByRepo} currentPage={currentPage} perPage={perPage} hasNextPage={hasNextPage} diff --git a/frontend/src/pages/sys-admin/logs-page/file-access-toggle-filter.js b/frontend/src/pages/sys-admin/logs-page/file-access-toggle-filter.js new file mode 100644 index 0000000000..a9d64ca949 --- /dev/null +++ b/frontend/src/pages/sys-admin/logs-page/file-access-toggle-filter.js @@ -0,0 +1,27 @@ +import React from 'react'; +import { Button } from 'reactstrap'; + +class ToggleFilter extends React.Component { + + constructor(props) { + super(props); + } + + render() { + const { filterBy } = this.props; + return ( + + ); + + } + +} + +export default ToggleFilter;
{gettext('Name')}
+ + {isOpIconShown && !userFilteredBy && ( + + )} + {item.event_type}{item.ip}{' / '}{item.device}{item.ip}{' / '}{item.device || '--'} {moment(item.time).fromNow()}{item.repo_name ? item.repo_name : gettext('Deleted')} + {item.repo_name ? item.repo_name : gettext('Deleted')} + {isOpIconShown && item.repo_name && !repoFilteredBy && ( + + )} + {item.file_or_dir_name}