diff --git a/frontend/src/css/log-filter.css b/frontend/src/css/log-filter.css index 79f8613977..612ff92a2c 100644 --- a/frontend/src/css/log-filter.css +++ b/frontend/src/css/log-filter.css @@ -12,7 +12,6 @@ .cur-activity-modifiers { margin-left: -0.5rem; - padding: 0.15rem 0; } .cur-activity-modifiers:hover { @@ -23,7 +22,7 @@ .cur-activity-modifiers .toggle-icon { color: #999; } - + .activity-modifier-selector-container { width: 320px; background: #fff; diff --git a/frontend/src/pages/dashboard/log-repo-selector.js b/frontend/src/pages/dashboard/log-repo-selector.js index 49c826a307..6799490d5a 100644 --- a/frontend/src/pages/dashboard/log-repo-selector.js +++ b/frontend/src/pages/dashboard/log-repo-selector.js @@ -3,13 +3,15 @@ import PropTypes from 'prop-types'; import { Input } from 'reactstrap'; import { gettext } from '../../utils/constants'; import '../../css/log-filter.css'; +import { Utils } from '../../utils/utils'; +import toaster from '../../components/toast'; +import { systemAdminAPI } from '../../utils/system-admin-api'; class LogRepoSelector extends Component { constructor(props) { super(props); this.state = { - isOpen: false, query: '', isLoading: false, searchResults: [] @@ -27,25 +29,14 @@ class LogRepoSelector extends Component { } handleClickOutside = (e) => { - const { isOpen } = this.state; - if (isOpen && !this.repoSelector.contains(e.target)) { - this.togglePopover(); + if (this.props.isOpen && !this.repoSelector.contains(e.target)) { + this.props.onToggle(); } }; - togglePopover = () => { - const { isOpen } = this.state; - if (isOpen) { - this.props.onSelect(null, true); - } - this.setState({ - isOpen: !isOpen - }); - }; - onToggleClick = (e) => { e.stopPropagation(); - this.togglePopover(); + this.props.onToggle(); }; onQueryChange = (e) => { @@ -60,15 +51,18 @@ class LogRepoSelector extends Component { this.setState({ isLoading: true }); setTimeout(() => { if (this.finalValue === value) { - const filteredItems = this.props.items.filter(item => - item.name.toLowerCase().includes(value.trim().toLowerCase()) - ); - this.setState({ - searchResults: filteredItems, - isLoading: false + systemAdminAPI.sysAdminSearchRepos(value).then((res) => { + this.setState({ + searchResults: res.data.repo_list, + isLoading: false + }); + }).catch(error => { + this.setState({ isLoading: false }); + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); }); } - }, 300); + }, 500); } else { this.setState({ searchResults: [] }); } @@ -80,12 +74,12 @@ class LogRepoSelector extends Component { }; render() { - const { isOpen, query, isLoading, searchResults } = this.state; - const { selectedItems } = this.props; + const { query, isLoading, searchResults } = this.state; + const { selectedItems, isOpen } = this.props; const displayItems = query.trim() ? searchResults : this.props.items; return ( -
+
{selectedItems.length > 0 ? ( <> @@ -123,10 +117,10 @@ class LogRepoSelector extends Component { ) : ( displayItems.map((item, index) => { - const isSelected = selectedItems.some(selected => selected.repo_id === item.repo_id); + const isSelected = selectedItems.some(selected => selected.id === item.id); return ( -
  • {this.toggleSelectItem(e, item);}} >
    @@ -149,7 +143,9 @@ class LogRepoSelector extends Component { LogRepoSelector.propTypes = { items: PropTypes.array.isRequired, selectedItems: PropTypes.array.isRequired, - onSelect: PropTypes.func.isRequired + onSelect: PropTypes.func.isRequired, + isOpen: PropTypes.bool.isRequired, + onToggle: PropTypes.func.isRequired }; export default LogRepoSelector; diff --git a/frontend/src/pages/dashboard/log-user-selector.js b/frontend/src/pages/dashboard/log-user-selector.js index 568adc49ed..61d510dfc8 100644 --- a/frontend/src/pages/dashboard/log-user-selector.js +++ b/frontend/src/pages/dashboard/log-user-selector.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { Input } from 'reactstrap'; import { gettext } from '../../utils/constants'; import '../../css/log-filter.css'; -import { systemAdminAPI } from '../../utils/system-admin-api' +import { systemAdminAPI } from '../../utils/system-admin-api'; import { Utils } from '../../utils/utils'; import toaster from '../../components/toast'; @@ -12,7 +12,6 @@ class LogUserSelector extends Component { constructor(props) { super(props); this.state = { - isOpen: false, query: '', searchResults: [], isLoading: false @@ -30,25 +29,14 @@ class LogUserSelector extends Component { } handleClickOutside = (e) => { - const { isOpen } = this.state; - if (isOpen && !this.userSelector.contains(e.target)) { - this.togglePopover(); + if (this.props.isOpen && !this.userSelector.contains(e.target)) { + this.props.onToggle(); } }; - togglePopover = () => { - const { isOpen } = this.state; - if (isOpen) { - this.props.onSelect(null, true); - } - this.setState({ - isOpen: !isOpen - }); - }; - onToggleClick = (e) => { e.stopPropagation(); - this.togglePopover(); + this.props.onToggle(); }; onQueryChange = (e) => { @@ -86,12 +74,12 @@ class LogUserSelector extends Component { }; render() { - const { isOpen, query, searchResults, isLoading } = this.state; - const { selectedItems } = this.props; + const { query, isLoading, searchResults } = this.state; + const { selectedItems, isOpen } = this.props; const displayItems = query.trim() ? searchResults : this.props.items; return ( -
    +
    {selectedItems.length > 0 ? ( <> @@ -133,8 +121,8 @@ class LogUserSelector extends Component { displayItems.map((item, index) => { const isSelected = selectedItems.some(selected => selected.email === item.email); return ( -
  • {this.toggleSelectItem(e, item);}} >
    @@ -157,7 +145,9 @@ class LogUserSelector extends Component { LogUserSelector.propTypes = { items: PropTypes.array.isRequired, selectedItems: PropTypes.array.isRequired, - onSelect: PropTypes.func.isRequired + onSelect: PropTypes.func.isRequired, + isOpen: PropTypes.bool.isRequired, + onToggle: PropTypes.func.isRequired }; export default LogUserSelector; 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 b979e29992..e04343dccf 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 @@ -13,11 +13,9 @@ import Paginator from '../../../components/paginator'; 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'; -import LogUserSelector from '../../dashboard/log-user-selector' +import LogUserSelector from '../../dashboard/log-user-selector'; import LogRepoSelector from '../../dashboard/log-repo-selector'; dayjs.extend(relativeTime); @@ -39,14 +37,6 @@ 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 @@ -56,7 +46,6 @@ class Content extends Component { render() { const { loading, errorMsg, items, - userFilteredBy, repoFilteredBy, perPage, currentPage, hasNextPage } = this.props; if (loading) { @@ -70,20 +59,6 @@ class Content extends Component { ); const table = ( -
    - {userFilteredBy && ( - - )} - {repoFilteredBy && ( - - )} -
    @@ -103,10 +78,6 @@ class Content extends Component { item={item} isFreezed={this.state.isItemFreezed} toggleFreezeItem={this.toggleFreezeItem} - userFilteredBy={userFilteredBy} - repoFilteredBy={repoFilteredBy} - filterByUser={this.props.filterByUser} - filterByRepo={this.props.filterByRepo} />); })} @@ -137,11 +108,7 @@ Content.propTypes = { perPage: PropTypes.number, pageInfo: PropTypes.object, hasNextPage: PropTypes.bool, - toggleFreezeItem: PropTypes.func, - userFilteredBy: PropTypes.string, - repoFilteredBy: PropTypes.string, - filterByUser: PropTypes.func, - filterByRepo: PropTypes.func, + toggleFreezeItem: PropTypes.func }; @@ -151,7 +118,6 @@ class Item extends Component { super(props); this.state = { isHighlighted: false, - isOpIconShown: false }; } @@ -159,7 +125,6 @@ class Item extends Component { if (!this.props.isFreezed) { this.setState({ isHighlighted: true, - isOpIconShown: true }); } }; @@ -168,58 +133,32 @@ class Item extends Component { 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() { - const { isHighlighted, isOpIconShown } = this.state; - const { item, userFilteredBy, repoFilteredBy } = this.props; + const { isHighlighted } = this.state; + const { item } = this.props; return ( @@ -232,10 +171,6 @@ Item.propTypes = { item: PropTypes.object, isFreezed: PropTypes.bool, toggleFreezeItem: PropTypes.func, - userFilteredBy: PropTypes.string, - repoFilteredBy: PropTypes.string, - filterByUser: PropTypes.func, - filterByRepo: PropTypes.func, }; class FileAccessLogs extends Component { @@ -252,6 +187,9 @@ class FileAccessLogs extends Component { isExportExcelDialogOpen: false, availableUsers: [], selectedUsers: [], + availableRepos: [], + selectedRepos: [], + openSelector: null, }; this.initPage = 1; } @@ -266,8 +204,6 @@ class FileAccessLogs extends Component { this.setState({ perPage: parseInt(urlParams.get('per_page') || perPage), currentPage: parseInt(urlParams.get('page') || currentPage), - userFilteredBy: urlParams.get('email'), - repoFilteredBy: urlParams.get('repo_id') }, () => { // this.getAvailableUsers(); this.getLogsByPage(this.state.currentPage); @@ -275,9 +211,9 @@ class FileAccessLogs extends Component { } getLogsByPage = (page) => { - const { perPage, userFilteredBy, repoFilteredBy, selectedUsers } = this.state; + const { perPage, selectedUsers, selectedRepos } = this.state; let emails = selectedUsers.map(user => user.email); - systemAdminAPI.sysAdminListFileAccessLogs(page, perPage, emails, repoFilteredBy).then((res) => { + systemAdminAPI.sysAdminListFileAccessLogs(page, perPage, emails, selectedRepos).then((res) => { this.setState({ logList: res.data.file_access_log_list, loading: false, @@ -310,24 +246,6 @@ class FileAccessLogs extends Component { 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 }); - }); - }; - getAvailableUsers = () => { systemAdminAPI.sysAdminListUsers().then((res) => { this.setState({ @@ -343,7 +261,7 @@ class FileAccessLogs extends Component { handleUserFilter = (user, shouldFetchData = true) => { const { selectedUsers } = this.state; let newSelectedUsers; - + if (user === null) { newSelectedUsers = selectedUsers; } else { @@ -365,14 +283,65 @@ class FileAccessLogs extends Component { }); }; + getAvailableRepos = () => { + systemAdminAPI.sysAdminListRepos().then((res) => { + this.setState({ + availableRepos: res.data.repos + }); + }).catch((error) => { + this.setState({ + errorMsg: Utils.getErrorMsg(error, true) + }); + }); + }; + + handleRepoFilter = (repo, shouldFetchData = true) => { + const { selectedRepos } = this.state; + let newSelectedRepos; + + if (repo === null) { + newSelectedRepos = []; + } else { + const isSelected = selectedRepos.find(item => item.id === repo.id); + if (isSelected) { + newSelectedRepos = selectedRepos.filter(item => item.id !== repo.id); + } else { + newSelectedRepos = [...selectedRepos, repo]; + } + } + + this.setState({ + selectedRepos: newSelectedRepos, + currentPage: 1 + }, () => { + if (shouldFetchData) { + this.getLogsByPage(1); + } + }); + }; + + handleSelectorToggle = (selectorType) => { + const { openSelector } = this.state; + const wasOpen = openSelector === selectorType; + + this.setState({ + openSelector: wasOpen ? null : selectorType + }, () => { + if (wasOpen) { + this.getLogsByPage(); + } + }); + }; + render() { const { logList, - userFilteredBy, repoFilteredBy, currentPage, perPage, hasNextPage, isExportExcelDialogOpen, availableUsers, - selectedUsers + selectedUsers, + availableRepos, + selectedRepos } = this.state; return ( @@ -384,25 +353,33 @@ class FileAccessLogs extends Component {
    - + this.handleSelectorToggle('user')} + /> +
    + this.handleSelectorToggle('repo')} + /> +
    + -
    diff --git a/frontend/src/pages/sys-admin/logs-page/file-transfer-log.js b/frontend/src/pages/sys-admin/logs-page/file-transfer-log.js index a47fd9f7f0..11827ade1c 100644 --- a/frontend/src/pages/sys-admin/logs-page/file-transfer-log.js +++ b/frontend/src/pages/sys-admin/logs-page/file-transfer-log.js @@ -207,7 +207,7 @@ class FIleTransferLogs extends Component { handleUserFilter = (user, shouldFetchData = true) => { const { selectedUsers } = this.state; let newSelectedUsers; - + if (user === null) { newSelectedUsers = selectedUsers; } else { @@ -239,12 +239,12 @@ class FIleTransferLogs extends Component {
    - + { const { selectedUsers } = this.state; let newSelectedUsers; - + if (user === null) { newSelectedUsers = selectedUsers; } else { @@ -259,10 +259,10 @@ class FileUpdateLogs extends Component {
    this.getLogsByPage(this.initPage)); }; - handleUserFilter = (user, shouldFetchData = true) => { + handleUserFilter = (user) => { const { selectedUsers } = this.state; let newSelectedUsers; - + if (user === null) { newSelectedUsers = selectedUsers; } else { @@ -216,8 +217,15 @@ class LoginLogs extends Component { this.setState({ selectedUsers: newSelectedUsers, currentPage: 1 + }); + }; + + toggleUserSelector = () => { + const { isUserSelectorOpen } = this.state; + this.setState({ + isUserSelectorOpen: !isUserSelectorOpen }, () => { - if (shouldFetchData) { + if (!this.state.isUserSelectorOpen) { this.getLogsByPage(1); } }); @@ -236,10 +244,11 @@ class LoginLogs extends Component {
    { const { selectedUsers } = this.state; let newSelectedUsers; - + if (user === null) { newSelectedUsers = selectedUsers; } else { @@ -234,12 +234,12 @@ class SharePermissionLogs extends Component {
    - + repo.id).join(','); } return this.req.get(url, { params: params }); } diff --git a/seahub/api2/endpoints/admin/logs.py b/seahub/api2/endpoints/admin/logs.py index c6f455697a..1710aeba80 100644 --- a/seahub/api2/endpoints/admin/logs.py +++ b/seahub/api2/endpoints/admin/logs.py @@ -120,16 +120,17 @@ class AdminLogsFileAccessLogs(APIView): if not is_valid_email(user_selected): error_msg = 'email %s invalid.' % user_selected return api_error(status.HTTP_400_BAD_REQUEST, error_msg) - - repo_id_selected = request.GET.get('repo_id', None) - if repo_id_selected and not is_valid_repo_id_format(repo_id_selected): - error_msg = 'repo_id %s invalid.' % repo_id_selected - return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + repos = request.GET.get('repos') + repos = repos.split(',') if repos else [] + for repo_selected in repos: + if not is_valid_repo_id_format(repo_selected): + error_msg = 'repo_id %s invalid.' % repo_selected + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) start = per_page * (current_page - 1) limit = per_page + 1 - events = get_log_events_by_type_users_repo('file_audit', emails, repo_id_selected, start, limit) or [] + events = get_log_events_by_type_users_repo('file_audit', emails, repos, start, limit) or [] if len(events) > per_page: events = events[:per_page]
    - {isOpIconShown && !userFilteredBy && ( - - )} {item.event_type} {item.ip}{' / '}{item.device || '--'} {dayjs(item.time).fromNow()} {item.repo_name ? item.repo_name : gettext('Deleted')} - {isOpIconShown && item.repo_name && !repoFilteredBy && ( - - )} {item.file_or_dir_name}