1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-08-01 15:23:05 +00:00

[system admin / logs] file access: added 'filter by user / library' (#4731)

This commit is contained in:
llj 2020-12-04 14:18:34 +08:00 committed by GitHub
parent 9c11444518
commit 3d79d9fad8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 228 additions and 24 deletions

View File

@ -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 (
<Dropdown isOpen={this.state.isMenuShown} toggle={this.toggleMenu}>
<DropdownToggle
tag="i"
className="sf-dropdown-toggle sf2-icon-caret-down"
title={gettext('More Operations')}
data-toggle="dropdown"
aria-expanded={this.state.isMenuShown}
/>
<DropdownMenu>
<DropdownItem onClick={this.onItemClick}>{gettext('only show {placeholder}').replace('{placeholder}', filterBy)}</DropdownItem>
</DropdownMenu>
</Dropdown>
);
}
}
export default FilterMenu;

View File

@ -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 <Loading />;
} else if (errorMsg) {
@ -42,7 +66,21 @@ class Content extends Component {
);
const table = (
<Fragment>
<table className="table-hover">
<div>
{userFilteredBy && (
<ToggleFilter
filterBy={items[0].name}
toggleFilter={this.toggleFilterByUser}
/>
)}
{repoFilteredBy && (
<ToggleFilter
filterBy={items[0].repo_name}
toggleFilter={this.toggleFilterByRepo}
/>
)}
</div>
<table>
<thead>
<tr>
<th width="20%">{gettext('Name')}</th>
@ -59,6 +97,12 @@ class Content extends Component {
return (<Item
key={index}
item={item}
isFreezed={this.state.isItemFreezed}
toggleFreezeItem={this.toggleFreezeItem}
userFilteredBy={userFilteredBy}
repoFilteredBy={repoFilteredBy}
filterByUser={this.props.filterByUser}
filterByRepo={this.props.filterByRepo}
/>);
})}
</tbody>
@ -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 (
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
<td><UserLink email={item.email} name={item.name} /></td>
<tr className={isHighlighted ? 'tr-highlight' : ''} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<td>
<UserLink email={item.email} name={item.name} />
{isOpIconShown && !userFilteredBy && (
<FilterMenu
filterBy={item.name}
filterItems={this.filterByUser}
toggleFreezeItem={this.toggleFreezeItem}
/>
)}
</td>
<td>{item.event_type}</td>
<td>{item.ip}{' / '}{item.device}</td>
<td>{item.ip}{' / '}{item.device || '--'}</td>
<td>{moment(item.time).fromNow()}</td>
<td>{item.repo_name ? item.repo_name : gettext('Deleted')}</td>
<td>
{item.repo_name ? item.repo_name : gettext('Deleted')}
{isOpIconShown && item.repo_name && !repoFilteredBy && (
<FilterMenu
filterBy={item.repo_name}
filterItems={this.filterByRepo}
toggleFreezeItem={this.toggleFreezeItem}
/>
)}
</td>
<td>{item.file_or_dir_name}</td>
</tr>
);
@ -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 (
<Fragment>
<MainPanelTopbar>
@ -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}

View File

@ -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 (
<Button
color='secondary'
className="my-2 mr-2"
onClick={this.props.toggleFilter}
>
<span className="text-primary">{filterBy}</span>
<span className="ml-2 close" style={{fontSize: '1.2rem'}}>x</span>
</Button>
);
}
}
export default ToggleFilter;