mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-16 06:03:35 +00:00
[system admin / logs] file access: added 'filter by user / library' (#4731)
This commit is contained in:
parent
9c11444518
commit
3d79d9fad8
@ -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;
|
@ -1,23 +1,29 @@
|
|||||||
import React, { Component, Fragment } from 'react';
|
import React, { Component, Fragment } from 'react';
|
||||||
import { seafileAPI } from '../../../utils/seafile-api';
|
import { seafileAPI } from '../../../utils/seafile-api';
|
||||||
import { gettext, siteRoot } from '../../../utils/constants';
|
import { gettext } from '../../../utils/constants';
|
||||||
import { Utils } from '../../../utils/utils';
|
import { Utils } from '../../../utils/utils';
|
||||||
import EmptyTip from '../../../components/empty-tip';
|
import EmptyTip from '../../../components/empty-tip';
|
||||||
import { Button } from 'reactstrap';
|
import { Button } from 'reactstrap';
|
||||||
|
import { navigate } from '@reach/router';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import Loading from '../../../components/loading';
|
import Loading from '../../../components/loading';
|
||||||
import Paginator from '../../../components/paginator';
|
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 LogsExportExcelDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-logs-export-excel-dialog';
|
||||||
import ModalPortal from '../../../components/modal-portal';
|
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 {
|
class Content extends Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isItemFreezed: false
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getPreviousPage = () => {
|
getPreviousPage = () => {
|
||||||
@ -28,8 +34,26 @@ class Content extends Component {
|
|||||||
this.props.getLogsByPage(this.props.currentPage + 1);
|
this.props.getLogsByPage(this.props.currentPage + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleFilterByUser = () => {
|
||||||
|
this.props.filterByUser(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleFilterByRepo = () => {
|
||||||
|
this.props.filterByRepo(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleFreezeItem = (freezed) => {
|
||||||
|
this.setState({
|
||||||
|
isItemFreezed: freezed
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { loading, errorMsg, items, perPage, currentPage, hasNextPage } = this.props;
|
const {
|
||||||
|
loading, errorMsg, items,
|
||||||
|
userFilteredBy, repoFilteredBy,
|
||||||
|
perPage, currentPage, hasNextPage
|
||||||
|
} = this.props;
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
} else if (errorMsg) {
|
} else if (errorMsg) {
|
||||||
@ -42,7 +66,21 @@ class Content extends Component {
|
|||||||
);
|
);
|
||||||
const table = (
|
const table = (
|
||||||
<Fragment>
|
<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>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th width="20%">{gettext('Name')}</th>
|
<th width="20%">{gettext('Name')}</th>
|
||||||
@ -59,6 +97,12 @@ class Content extends Component {
|
|||||||
return (<Item
|
return (<Item
|
||||||
key={index}
|
key={index}
|
||||||
item={item}
|
item={item}
|
||||||
|
isFreezed={this.state.isItemFreezed}
|
||||||
|
toggleFreezeItem={this.toggleFreezeItem}
|
||||||
|
userFilteredBy={userFilteredBy}
|
||||||
|
repoFilteredBy={repoFilteredBy}
|
||||||
|
filterByUser={this.props.filterByUser}
|
||||||
|
filterByRepo={this.props.filterByRepo}
|
||||||
/>);
|
/>);
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -84,31 +128,77 @@ class Item extends Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
isOpIconShown: false,
|
isHighlighted: false,
|
||||||
|
isOpIconShown: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseOver = () => {
|
handleMouseEnter = () => {
|
||||||
this.setState({
|
if (!this.props.isFreezed) {
|
||||||
isOpIconShown: true
|
this.setState({
|
||||||
});
|
isHighlighted: true,
|
||||||
|
isOpIconShown: true
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseOut = () => {
|
handleMouseLeave = () => {
|
||||||
this.setState({
|
if (!this.props.isFreezed) {
|
||||||
isOpIconShown: false
|
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() {
|
render() {
|
||||||
let { item } = this.props;
|
const { isHighlighted, isOpIconShown } = this.state;
|
||||||
|
const { item, userFilteredBy, repoFilteredBy } = this.props;
|
||||||
return (
|
return (
|
||||||
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
<tr className={isHighlighted ? 'tr-highlight' : ''} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||||
<td><UserLink email={item.email} name={item.name} /></td>
|
<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.event_type}</td>
|
||||||
<td>{item.ip}{' / '}{item.device}</td>
|
<td>{item.ip}{' / '}{item.device || '--'}</td>
|
||||||
<td>{moment(item.time).fromNow()}</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>
|
<td>{item.file_or_dir_name}</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
@ -140,15 +230,17 @@ class FileAccessLogs extends Component {
|
|||||||
const { currentPage, perPage } = this.state;
|
const { currentPage, perPage } = this.state;
|
||||||
this.setState({
|
this.setState({
|
||||||
perPage: parseInt(urlParams.get('per_page') || perPage),
|
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);
|
this.getLogsByPage(this.state.currentPage);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getLogsByPage = (page) => {
|
getLogsByPage = (page) => {
|
||||||
let { perPage } = this.state;
|
const { perPage, userFilteredBy, repoFilteredBy } = this.state;
|
||||||
seafileAPI.sysAdminListFileAccessLogs(page, perPage).then((res) => {
|
seafileAPI.sysAdminListFileAccessLogs(page, perPage, userFilteredBy, repoFilteredBy).then((res) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
logList: res.data.file_access_log_list,
|
logList: res.data.file_access_log_list,
|
||||||
loading: false,
|
loading: false,
|
||||||
@ -169,8 +261,43 @@ class FileAccessLogs extends Component {
|
|||||||
}, () => this.getLogsByPage(this.initPage));
|
}, () => 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() {
|
render() {
|
||||||
let { logList, currentPage, perPage, hasNextPage, isExportExcelDialogOpen } = this.state;
|
const {
|
||||||
|
logList,
|
||||||
|
userFilteredBy, repoFilteredBy,
|
||||||
|
currentPage, perPage, hasNextPage,
|
||||||
|
isExportExcelDialogOpen
|
||||||
|
} = this.state;
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<MainPanelTopbar>
|
<MainPanelTopbar>
|
||||||
@ -184,6 +311,10 @@ class FileAccessLogs extends Component {
|
|||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
errorMsg={this.state.errorMsg}
|
errorMsg={this.state.errorMsg}
|
||||||
items={logList}
|
items={logList}
|
||||||
|
userFilteredBy={userFilteredBy}
|
||||||
|
repoFilteredBy={repoFilteredBy}
|
||||||
|
filterByUser={this.filterByUser}
|
||||||
|
filterByRepo={this.filterByRepo}
|
||||||
currentPage={currentPage}
|
currentPage={currentPage}
|
||||||
perPage={perPage}
|
perPage={perPage}
|
||||||
hasNextPage={hasNextPage}
|
hasNextPage={hasNextPage}
|
||||||
|
@ -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;
|
Loading…
Reference in New Issue
Block a user