mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-13 05:39:59 +00:00
optimize log user selector
This commit is contained in:
@@ -1,7 +1,3 @@
|
||||
|
||||
|
||||
|
||||
|
||||
.activity-details {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
@@ -16,6 +12,7 @@
|
||||
|
||||
.cur-activity-modifiers {
|
||||
margin-left: -0.5rem;
|
||||
padding: 0.15rem 0;
|
||||
}
|
||||
|
||||
.cur-activity-modifiers:hover {
|
||||
|
155
frontend/src/pages/dashboard/log-repo-selector.js
Normal file
155
frontend/src/pages/dashboard/log-repo-selector.js
Normal file
@@ -0,0 +1,155 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Input } from 'reactstrap';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import '../../css/log-filter.css';
|
||||
|
||||
class LogRepoSelector extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isOpen: false,
|
||||
query: '',
|
||||
isLoading: false,
|
||||
searchResults: []
|
||||
};
|
||||
this.dropdownRef = React.createRef();
|
||||
this.finalValue = '';
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('click', this.handleClickOutside);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('click', this.handleClickOutside);
|
||||
}
|
||||
|
||||
handleClickOutside = (e) => {
|
||||
const { isOpen } = this.state;
|
||||
if (isOpen && !this.repoSelector.contains(e.target)) {
|
||||
this.togglePopover();
|
||||
}
|
||||
};
|
||||
|
||||
togglePopover = () => {
|
||||
const { isOpen } = this.state;
|
||||
if (isOpen) {
|
||||
this.props.onSelect(null, true);
|
||||
}
|
||||
this.setState({
|
||||
isOpen: !isOpen
|
||||
});
|
||||
};
|
||||
|
||||
onToggleClick = (e) => {
|
||||
e.stopPropagation();
|
||||
this.togglePopover();
|
||||
};
|
||||
|
||||
onQueryChange = (e) => {
|
||||
const value = e.target.value;
|
||||
this.setState({ query: value });
|
||||
this.searchRepos(value);
|
||||
};
|
||||
|
||||
searchRepos = (value) => {
|
||||
this.finalValue = value;
|
||||
if (value.length > 0) {
|
||||
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
|
||||
});
|
||||
}
|
||||
}, 300);
|
||||
} else {
|
||||
this.setState({ searchResults: [] });
|
||||
}
|
||||
};
|
||||
|
||||
toggleSelectItem = (e, item) => {
|
||||
e.stopPropagation();
|
||||
this.props.onSelect(item, false);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isOpen, query, isLoading, searchResults } = this.state;
|
||||
const { selectedItems } = this.props;
|
||||
const displayItems = query.trim() ? searchResults : this.props.items;
|
||||
|
||||
return (
|
||||
<div className="mt-4 position-relative" ref={this.dropdownRef}>
|
||||
<span className="cur-activity-modifiers d-inline-block p-2 rounded" onClick={this.onToggleClick}>
|
||||
{selectedItems.length > 0 ? (
|
||||
<>
|
||||
<span>{gettext('Libraries:')}</span>
|
||||
<span className="d-inline-block ml-1">{selectedItems.map(item => item.name).join(', ')}</span>
|
||||
</>
|
||||
) : gettext('Libraries')}
|
||||
<i className="sf3-font sf3-font-down ml-2 toggle-icon"></i>
|
||||
</span>
|
||||
{isOpen && (
|
||||
<div className="position-absolute activity-modifier-selector-container rounded shadow" ref={ref => this.repoSelector = ref}>
|
||||
<ul className="activity-selected-modifiers px-3 py-1 list-unstyled">
|
||||
{selectedItems.map((item, index) => (
|
||||
<li key={index} className="activity-selected-modifier">
|
||||
<i className="fas fa-folder"></i>
|
||||
<span className="activity-user-name ml-2">{item.name}</span>
|
||||
<i className="sf2-icon-close unselect-activity-user ml-2" onClick={(e) => {this.toggleSelectItem(e, item);}}></i>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="px-3 pt-3">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={gettext('Find libraries')}
|
||||
value={query}
|
||||
onChange={this.onQueryChange}
|
||||
/>
|
||||
</div>
|
||||
<ul className="activity-user-list list-unstyled p-3 o-auto">
|
||||
{isLoading ? (
|
||||
<li className="text-center">{gettext('Loading...')}</li>
|
||||
) : displayItems.length === 0 ? (
|
||||
<li className="text-center">
|
||||
{query ? gettext('Library not found') : gettext('Enter characters to start searching')}
|
||||
</li>
|
||||
) : (
|
||||
displayItems.map((item, index) => {
|
||||
const isSelected = selectedItems.some(selected => selected.repo_id === item.repo_id);
|
||||
return (
|
||||
<li key={index}
|
||||
className="activity-user-item h-6 p-1 rounded d-flex justify-content-between align-items-center"
|
||||
onClick={(e) => {this.toggleSelectItem(e, item);}}
|
||||
>
|
||||
<div>
|
||||
<i className="fas fa-folder"></i>
|
||||
<span className="activity-user-name ml-2">{item.name}</span>
|
||||
</div>
|
||||
{isSelected && <i className="sf2-icon-tick text-gray font-weight-bold"></i>}
|
||||
</li>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LogRepoSelector.propTypes = {
|
||||
items: PropTypes.array.isRequired,
|
||||
selectedItems: PropTypes.array.isRequired,
|
||||
onSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default LogRepoSelector;
|
@@ -3,17 +3,22 @@ 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 { Utils } from '../../utils/utils';
|
||||
import toaster from '../../components/toast';
|
||||
|
||||
class LogFilter extends Component {
|
||||
class LogUserSelector extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isOpen: false,
|
||||
searchText: '',
|
||||
query: ''
|
||||
query: '',
|
||||
searchResults: [],
|
||||
isLoading: false
|
||||
};
|
||||
this.dropdownRef = React.createRef();
|
||||
this.finalValue = '';
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -46,12 +51,33 @@ class LogFilter extends Component {
|
||||
this.togglePopover();
|
||||
};
|
||||
|
||||
handleSearch = (e) => {
|
||||
this.setState({ searchText: e.target.value });
|
||||
onQueryChange = (e) => {
|
||||
const value = e.target.value;
|
||||
this.setState({ query: value });
|
||||
this.searchUsers(value);
|
||||
};
|
||||
|
||||
onQueryChange = (e) => {
|
||||
this.setState({ query: e.target.value });
|
||||
searchUsers = (value) => {
|
||||
this.finalValue = value;
|
||||
if (value.length > 0) {
|
||||
this.setState({ isLoading: true });
|
||||
setTimeout(() => {
|
||||
if (this.finalValue === value) {
|
||||
systemAdminAPI.sysAdminSearchUsers(value).then((res) => {
|
||||
this.setState({
|
||||
searchResults: res.data.user_list,
|
||||
isLoading: false
|
||||
});
|
||||
}).catch(error => {
|
||||
this.setState({ isLoading: false });
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
}, 500);
|
||||
} else {
|
||||
this.setState({ searchResults: [] });
|
||||
}
|
||||
};
|
||||
|
||||
toggleSelectItem = (e, item) => {
|
||||
@@ -60,16 +86,12 @@ class LogFilter extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isOpen, query } = this.state;
|
||||
const { items, selectedItems } = this.props;
|
||||
const filteredItems = query.trim() ?
|
||||
items.filter(item =>
|
||||
item.email.toLowerCase().includes(query.trim().toLowerCase()) ||
|
||||
item.name.toLowerCase().includes(query.trim().toLowerCase())
|
||||
) : items;
|
||||
const { isOpen, query, searchResults, isLoading } = this.state;
|
||||
const { selectedItems } = this.props;
|
||||
const displayItems = query.trim() ? searchResults : this.props.items;
|
||||
|
||||
return (
|
||||
<div className="mt-4 position-relative" ref={this.dropdownRef}>
|
||||
<div className="position-relative" ref={this.dropdownRef}>
|
||||
<span className="cur-activity-modifiers d-inline-block p-2 rounded" onClick={this.onToggleClick}>
|
||||
{selectedItems.length > 0 ? (
|
||||
<>
|
||||
@@ -101,17 +123,29 @@ class LogFilter extends Component {
|
||||
/>
|
||||
</div>
|
||||
<ul className="activity-user-list list-unstyled p-3 o-auto">
|
||||
{filteredItems.map((item, index) => {
|
||||
{isLoading ? (
|
||||
<li className="text-center">{gettext('Loading...')}</li>
|
||||
) : displayItems.length === 0 ? (
|
||||
<li className="text-center">
|
||||
{query ? gettext('User not found') : gettext('Enter characters to start searching')}
|
||||
</li>
|
||||
) : (
|
||||
displayItems.map((item, index) => {
|
||||
const isSelected = selectedItems.some(selected => selected.email === item.email);
|
||||
return (
|
||||
<li key={index} className="activity-user-item h-6 p-1 rounded d-flex justify-content-between align-items-center" onClick={(e) => {this.toggleSelectItem(e, item);}}>
|
||||
<li key={index}
|
||||
className="activity-user-item h-6 p-1 rounded d-flex justify-content-between align-items-center"
|
||||
onClick={(e) => {this.toggleSelectItem(e, item);}}
|
||||
>
|
||||
<div>
|
||||
<img src={item.avatar_url} className="avatar w-5 h-5" alt="" />
|
||||
<span className="activity-user-name ml-2">{item.name}</span>
|
||||
</div>
|
||||
{item.isSelected && <i className="sf2-icon-tick text-gray font-weight-bold"></i>}
|
||||
{isSelected && <i className="sf2-icon-tick text-gray font-weight-bold"></i>}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
})
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
@@ -120,10 +154,10 @@ class LogFilter extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
LogFilter.propTypes = {
|
||||
LogUserSelector.propTypes = {
|
||||
items: PropTypes.array.isRequired,
|
||||
selectedItems: PropTypes.array.isRequired,
|
||||
onSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default LogFilter;
|
||||
export default LogUserSelector;
|
@@ -17,6 +17,8 @@ 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 LogRepoSelector from '../../dashboard/log-repo-selector';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
@@ -248,6 +250,8 @@ class FileAccessLogs extends Component {
|
||||
currentPage: 1,
|
||||
hasNextPage: false,
|
||||
isExportExcelDialogOpen: false,
|
||||
availableUsers: [],
|
||||
selectedUsers: [],
|
||||
};
|
||||
this.initPage = 1;
|
||||
}
|
||||
@@ -265,13 +269,15 @@ class FileAccessLogs extends Component {
|
||||
userFilteredBy: urlParams.get('email'),
|
||||
repoFilteredBy: urlParams.get('repo_id')
|
||||
}, () => {
|
||||
// this.getAvailableUsers();
|
||||
this.getLogsByPage(this.state.currentPage);
|
||||
});
|
||||
}
|
||||
|
||||
getLogsByPage = (page) => {
|
||||
const { perPage, userFilteredBy, repoFilteredBy } = this.state;
|
||||
systemAdminAPI.sysAdminListFileAccessLogs(page, perPage, userFilteredBy, repoFilteredBy).then((res) => {
|
||||
const { perPage, userFilteredBy, repoFilteredBy, selectedUsers } = this.state;
|
||||
let emails = selectedUsers.map(user => user.email);
|
||||
systemAdminAPI.sysAdminListFileAccessLogs(page, perPage, emails, repoFilteredBy).then((res) => {
|
||||
this.setState({
|
||||
logList: res.data.file_access_log_list,
|
||||
loading: false,
|
||||
@@ -322,12 +328,51 @@ class FileAccessLogs extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
getAvailableUsers = () => {
|
||||
systemAdminAPI.sysAdminListUsers().then((res) => {
|
||||
this.setState({
|
||||
availableUsers: res.data.data
|
||||
});
|
||||
}).catch((error) => {
|
||||
this.setState({
|
||||
errorMsg: Utils.getErrorMsg(error, true)
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
handleUserFilter = (user, shouldFetchData = true) => {
|
||||
const { selectedUsers } = this.state;
|
||||
let newSelectedUsers;
|
||||
|
||||
if (user === null) {
|
||||
newSelectedUsers = selectedUsers;
|
||||
} else {
|
||||
const isSelected = selectedUsers.find(item => item.email === user.email);
|
||||
if (isSelected) {
|
||||
newSelectedUsers = selectedUsers.filter(item => item.email !== user.email);
|
||||
} else {
|
||||
newSelectedUsers = [...selectedUsers, user];
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
selectedUsers: newSelectedUsers,
|
||||
currentPage: 1
|
||||
}, () => {
|
||||
if (shouldFetchData) {
|
||||
this.getLogsByPage(1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
logList,
|
||||
userFilteredBy, repoFilteredBy,
|
||||
currentPage, perPage, hasNextPage,
|
||||
isExportExcelDialogOpen
|
||||
isExportExcelDialogOpen,
|
||||
availableUsers,
|
||||
selectedUsers
|
||||
} = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
@@ -338,6 +383,12 @@ class FileAccessLogs extends Component {
|
||||
<div className="cur-view-container">
|
||||
<LogsNav currentItem="fileAccessLogs" />
|
||||
<div className="cur-view-content">
|
||||
<Fragment>
|
||||
<LogUserSelector
|
||||
items={availableUsers}
|
||||
selectedItems={selectedUsers}
|
||||
onSelect={this.handleUserFilter}
|
||||
/>
|
||||
<Content
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
@@ -352,6 +403,7 @@ class FileAccessLogs extends Component {
|
||||
getLogsByPage={this.getLogsByPage}
|
||||
resetPerPage={this.resetPerPage}
|
||||
/>
|
||||
</Fragment>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { Link } from '@gatsbyjs/reach-router';
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { systemAdminAPI } from '../../../utils/system-admin-api';
|
||||
import { gettext, siteRoot } from '../../../utils/constants';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
@@ -12,6 +12,7 @@ import Paginator from '../../../components/paginator';
|
||||
import MainPanelTopbar from '../main-panel-topbar';
|
||||
import UserLink from '../user-link';
|
||||
import LogsNav from './logs-nav';
|
||||
import LogUserSelector from '../../dashboard/log-user-selector';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
@@ -162,6 +163,8 @@ class FIleTransferLogs extends Component {
|
||||
perPage: 100,
|
||||
currentPage: 1,
|
||||
hasNextPage: false,
|
||||
availableUsers: [],
|
||||
selectedUsers: [],
|
||||
};
|
||||
this.initPage = 1;
|
||||
}
|
||||
@@ -178,8 +181,9 @@ class FIleTransferLogs extends Component {
|
||||
}
|
||||
|
||||
getLogsByPage = (page) => {
|
||||
let { perPage } = this.state;
|
||||
seafileAPI.sysAdminListFileTransferLogs(page, perPage).then((res) => {
|
||||
let { perPage, selectedUsers } = this.state;
|
||||
let emails = selectedUsers.map(user => user.email);
|
||||
systemAdminAPI.sysAdminListFileTransferLogs(page, perPage, emails).then((res) => {
|
||||
this.setState({
|
||||
logList: res.data.repo_transfer_log_list,
|
||||
loading: false,
|
||||
@@ -200,8 +204,33 @@ class FIleTransferLogs extends Component {
|
||||
}, () => this.getLogsByPage(this.initPage));
|
||||
};
|
||||
|
||||
handleUserFilter = (user, shouldFetchData = true) => {
|
||||
const { selectedUsers } = this.state;
|
||||
let newSelectedUsers;
|
||||
|
||||
if (user === null) {
|
||||
newSelectedUsers = selectedUsers;
|
||||
} else {
|
||||
const isSelected = selectedUsers.find(item => item.email === user.email);
|
||||
if (isSelected) {
|
||||
newSelectedUsers = selectedUsers.filter(item => item.email !== user.email);
|
||||
} else {
|
||||
newSelectedUsers = [...selectedUsers, user];
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
selectedUsers: newSelectedUsers,
|
||||
currentPage: 1
|
||||
}, () => {
|
||||
if (shouldFetchData) {
|
||||
this.getLogsByPage(1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
let { logList, currentPage, perPage, hasNextPage } = this.state;
|
||||
let { logList, currentPage, perPage, hasNextPage, availableUsers, selectedUsers } = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
<MainPanelTopbar {...this.props} />
|
||||
@@ -209,6 +238,13 @@ class FIleTransferLogs extends Component {
|
||||
<div className="cur-view-container">
|
||||
<LogsNav currentItem="fileTransfer" />
|
||||
<div className="cur-view-content">
|
||||
<Fragment>
|
||||
<LogUserSelector
|
||||
label={gettext('User')}
|
||||
items={availableUsers}
|
||||
selectedItems={selectedUsers}
|
||||
onSelect={this.handleUserFilter}
|
||||
/>
|
||||
<Content
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
@@ -219,6 +255,7 @@ class FIleTransferLogs extends Component {
|
||||
getLogsByPage={this.getLogsByPage}
|
||||
resetPerPage={this.resetPerPage}
|
||||
/>
|
||||
</Fragment>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -15,6 +15,7 @@ import UserLink from '../user-link';
|
||||
import ModalPortal from '../../../components/modal-portal';
|
||||
import CommitDetails from '../../../components/dialog/commit-details';
|
||||
import LogsExportExcelDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-logs-export-excel-dialog';
|
||||
import LogUserSelector from '../../dashboard/log-user-selector'
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
@@ -173,6 +174,8 @@ class FileUpdateLogs extends Component {
|
||||
currentPage: 1,
|
||||
hasNextPage: false,
|
||||
isExportExcelDialogOpen: false,
|
||||
availableUsers: [],
|
||||
selectedUsers: [],
|
||||
};
|
||||
this.initPage = 1;
|
||||
}
|
||||
@@ -193,8 +196,10 @@ class FileUpdateLogs extends Component {
|
||||
}
|
||||
|
||||
getLogsByPage = (page) => {
|
||||
let { perPage } = this.state;
|
||||
systemAdminAPI.sysAdminListFileUpdateLogs(page, perPage).then((res) => {
|
||||
let { perPage, selectedUsers } = this.state;
|
||||
let emails = selectedUsers.map(user => user.email);
|
||||
|
||||
systemAdminAPI.sysAdminListFileUpdateLogs(page, perPage, emails).then((res) => {
|
||||
this.setState({
|
||||
logList: res.data.file_update_log_list,
|
||||
loading: false,
|
||||
@@ -215,8 +220,34 @@ class FileUpdateLogs extends Component {
|
||||
}, () => this.getLogsByPage(this.initPage));
|
||||
};
|
||||
|
||||
|
||||
handleUserFilter = (user, shouldFetchData = true) => {
|
||||
const { selectedUsers } = this.state;
|
||||
let newSelectedUsers;
|
||||
|
||||
if (user === null) {
|
||||
newSelectedUsers = selectedUsers;
|
||||
} else {
|
||||
const isSelected = selectedUsers.find(item => item.email === user.email);
|
||||
if (isSelected) {
|
||||
newSelectedUsers = selectedUsers.filter(item => item.email !== user.email);
|
||||
} else {
|
||||
newSelectedUsers = [...selectedUsers, user];
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
selectedUsers: newSelectedUsers,
|
||||
currentPage: 1
|
||||
}, () => {
|
||||
if (shouldFetchData) {
|
||||
this.getLogsByPage(1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
let { logList, currentPage, perPage, hasNextPage, isExportExcelDialogOpen } = this.state;
|
||||
let { logList, currentPage, perPage, hasNextPage, isExportExcelDialogOpen, availableUsers, selectedUsers } = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
<MainPanelTopbar {...this.props}>
|
||||
@@ -226,6 +257,13 @@ class FileUpdateLogs extends Component {
|
||||
<div className="cur-view-container">
|
||||
<LogsNav currentItem="fileUpdateLogs" />
|
||||
<div className="cur-view-content">
|
||||
<Fragment>
|
||||
<LogUserSelector
|
||||
label={gettext('User')}
|
||||
items={availableUsers}
|
||||
selectedItems={selectedUsers}
|
||||
onSelect={this.handleUserFilter}
|
||||
/>
|
||||
<Content
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
@@ -236,6 +274,7 @@ class FileUpdateLogs extends Component {
|
||||
getLogsByPage={this.getLogsByPage}
|
||||
resetPerPage={this.resetPerPage}
|
||||
/>
|
||||
</Fragment>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -14,7 +14,7 @@ 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 LogFilter from '../../dashboard/log-filter';
|
||||
import LogUserSelector from '../../dashboard/log-user-selector';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
@@ -156,7 +156,7 @@ class LoginLogs extends Component {
|
||||
perPage: parseInt(urlParams.get('per_page') || perPage),
|
||||
currentPage: parseInt(urlParams.get('page') || currentPage)
|
||||
}, () => {
|
||||
this.getAvailableUsers();
|
||||
// this.getAvailableUsers();
|
||||
this.getLogsByPage(this.state.currentPage);
|
||||
});
|
||||
}
|
||||
@@ -235,7 +235,7 @@ class LoginLogs extends Component {
|
||||
<LogsNav currentItem="loginLogs" />
|
||||
<div className="cur-view-content">
|
||||
<Fragment>
|
||||
<LogFilter
|
||||
<LogUserSelector
|
||||
label={gettext('User')}
|
||||
items={availableUsers}
|
||||
selectedItems={selectedUsers}
|
||||
|
@@ -15,6 +15,7 @@ import Paginator from '../../../components/paginator';
|
||||
import MainPanelTopbar from '../main-panel-topbar';
|
||||
import UserLink from '../user-link';
|
||||
import LogsNav from './logs-nav';
|
||||
import LogUserSelector from '../../dashboard/log-user-selector';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
@@ -151,6 +152,8 @@ class SharePermissionLogs extends Component {
|
||||
currentPage: 1,
|
||||
hasNextPage: false,
|
||||
isExportExcelDialogOpen: false,
|
||||
availableUsers: [],
|
||||
selectedUsers: [],
|
||||
};
|
||||
this.initPage = 1;
|
||||
}
|
||||
@@ -171,8 +174,9 @@ class SharePermissionLogs extends Component {
|
||||
}
|
||||
|
||||
getLogsByPage = (page) => {
|
||||
let { perPage } = this.state;
|
||||
systemAdminAPI.sysAdminListSharePermissionLogs(page, perPage).then((res) => {
|
||||
let { perPage, selectedUsers } = this.state;
|
||||
let emails = selectedUsers.map(user => user.email);
|
||||
systemAdminAPI.sysAdminListSharePermissionLogs(page, perPage, emails).then((res) => {
|
||||
this.setState({
|
||||
logList: res.data.share_permission_log_list,
|
||||
loading: false,
|
||||
@@ -193,8 +197,33 @@ class SharePermissionLogs extends Component {
|
||||
}, () => this.getLogsByPage(this.initPage));
|
||||
};
|
||||
|
||||
handleUserFilter = (user, shouldFetchData = true) => {
|
||||
const { selectedUsers } = this.state;
|
||||
let newSelectedUsers;
|
||||
|
||||
if (user === null) {
|
||||
newSelectedUsers = selectedUsers;
|
||||
} else {
|
||||
const isSelected = selectedUsers.find(item => item.email === user.email);
|
||||
if (isSelected) {
|
||||
newSelectedUsers = selectedUsers.filter(item => item.email !== user.email);
|
||||
} else {
|
||||
newSelectedUsers = [...selectedUsers, user];
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
selectedUsers: newSelectedUsers,
|
||||
currentPage: 1
|
||||
}, () => {
|
||||
if (shouldFetchData) {
|
||||
this.getLogsByPage(1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
let { logList, currentPage, perPage, hasNextPage, isExportExcelDialogOpen } = this.state;
|
||||
let { logList, currentPage, perPage, hasNextPage, isExportExcelDialogOpen, availableUsers, selectedUsers } = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
<MainPanelTopbar {...this.props}>
|
||||
@@ -204,6 +233,13 @@ class SharePermissionLogs extends Component {
|
||||
<div className="cur-view-container">
|
||||
<LogsNav currentItem="sharePermissionLogs" />
|
||||
<div className="cur-view-content">
|
||||
<Fragment>
|
||||
<LogUserSelector
|
||||
label={gettext('User')}
|
||||
items={availableUsers}
|
||||
selectedItems={selectedUsers}
|
||||
onSelect={this.handleUserFilter}
|
||||
/>
|
||||
<Content
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
@@ -214,6 +250,7 @@ class SharePermissionLogs extends Component {
|
||||
getLogsByPage={this.getLogsByPage}
|
||||
resetPerPage={this.resetPerPage}
|
||||
/>
|
||||
</Fragment>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -692,14 +692,14 @@ class SystemAdminAPI {
|
||||
return this.req.get(url, { params: params });
|
||||
}
|
||||
|
||||
sysAdminListFileAccessLogs(page, perPage, email, repoID) {
|
||||
sysAdminListFileAccessLogs(page, perPage, emails, repoID) {
|
||||
const url = this.server + '/api/v2.1/admin/logs/file-access-logs/';
|
||||
let params = {
|
||||
page: page,
|
||||
per_page: perPage
|
||||
};
|
||||
if (email != undefined) {
|
||||
params.email = email;
|
||||
if (emails && emails.length) {
|
||||
params.emails = emails.join(',');
|
||||
}
|
||||
if (repoID != undefined) {
|
||||
params.repo_id = repoID;
|
||||
@@ -707,21 +707,48 @@ class SystemAdminAPI {
|
||||
return this.req.get(url, { params: params });
|
||||
}
|
||||
|
||||
sysAdminListFileUpdateLogs(page, perPage) {
|
||||
sysAdminListFileUpdateLogs(page, perPage, emails, repoID) {
|
||||
const url = this.server + '/api/v2.1/admin/logs/file-update-logs/';
|
||||
let params = {
|
||||
page: page,
|
||||
per_page: perPage
|
||||
};
|
||||
if (emails && emails.length) {
|
||||
params.emails = emails.join(',');
|
||||
}
|
||||
if (repoID != undefined) {
|
||||
params.repo_id = repoID;
|
||||
}
|
||||
return this.req.get(url, { params: params });
|
||||
}
|
||||
|
||||
sysAdminListSharePermissionLogs(page, perPage) {
|
||||
sysAdminListSharePermissionLogs(page, perPage, emails, repoID) {
|
||||
const url = this.server + '/api/v2.1/admin/logs/share-permission-logs/';
|
||||
let params = {
|
||||
page: page,
|
||||
per_page: perPage
|
||||
};
|
||||
if (emails && emails.length) {
|
||||
params.emails = emails.join(',');
|
||||
}
|
||||
if (repoID != undefined) {
|
||||
params.repo_id = repoID;
|
||||
}
|
||||
return this.req.get(url, { params: params });
|
||||
}
|
||||
|
||||
sysAdminListFileTransferLogs(page, perPage, emails, repoID) {
|
||||
const url = this.server + '/api/v2.1/admin/logs/repo-transfer-logs/';
|
||||
let params = {
|
||||
page: page,
|
||||
per_page: perPage
|
||||
};
|
||||
if (emails && emails.length) {
|
||||
params.emails = emails.join(',');
|
||||
}
|
||||
if (repoID != undefined) {
|
||||
params.repo_id = repoID;
|
||||
}
|
||||
return this.req.get(url, { params: params });
|
||||
}
|
||||
|
||||
|
@@ -6,7 +6,7 @@ from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import status
|
||||
|
||||
from django.db.models import Q
|
||||
from seaserv import ccnet_api, seafile_api
|
||||
|
||||
from seahub.api2.authentication import TokenAuthentication
|
||||
@@ -16,7 +16,7 @@ from seahub.api2.utils import api_error
|
||||
|
||||
from seahub.base.templatetags.seahub_tags import email2nickname, \
|
||||
email2contact_email, translate_commit_desc
|
||||
from seahub.utils import get_file_audit_events, generate_file_audit_event_type, \
|
||||
from seahub.utils import get_log_events_by_type_users_repo, generate_file_audit_event_type, \
|
||||
get_file_update_events, get_perm_audit_events, is_valid_email
|
||||
from seahub.utils.timeutils import datetime_to_isoformat_timestr, utc_datetime_to_isoformat_timestr
|
||||
from seahub.utils.repo import is_valid_repo_id_format
|
||||
@@ -114,8 +114,10 @@ class AdminLogsFileAccessLogs(APIView):
|
||||
current_page = 1
|
||||
per_page = 100
|
||||
|
||||
user_selected = request.GET.get('email', None)
|
||||
if user_selected and not is_valid_email(user_selected):
|
||||
emails = request.GET.get('emails')
|
||||
emails = emails.split(',') if emails else []
|
||||
for user_selected in emails:
|
||||
if not is_valid_email(user_selected):
|
||||
error_msg = 'email %s invalid.' % user_selected
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
@@ -127,18 +129,7 @@ class AdminLogsFileAccessLogs(APIView):
|
||||
start = per_page * (current_page - 1)
|
||||
limit = per_page + 1
|
||||
|
||||
if user_selected:
|
||||
org_id = -1
|
||||
orgs = ccnet_api.get_orgs_by_user(user_selected)
|
||||
if orgs:
|
||||
org_id = orgs[0].org_id
|
||||
elif repo_id_selected:
|
||||
org_id = seafile_api.get_org_id_by_repo_id(repo_id_selected)
|
||||
else:
|
||||
org_id = 0
|
||||
|
||||
# org_id = 0, show all file audit
|
||||
events = get_file_audit_events(user_selected, org_id, repo_id_selected, start, limit) or []
|
||||
events = get_log_events_by_type_users_repo('file_audit', emails, repo_id_selected, start, limit) or []
|
||||
|
||||
if len(events) > per_page:
|
||||
events = events[:per_page]
|
||||
@@ -220,8 +211,10 @@ class AdminLogsFileUpdateLogs(APIView):
|
||||
current_page = 1
|
||||
per_page = 100
|
||||
|
||||
user_selected = request.GET.get('email', None)
|
||||
if user_selected and not is_valid_email(user_selected):
|
||||
emails = request.GET.get('emails')
|
||||
emails = emails.split(',') if emails else []
|
||||
for user_selected in emails:
|
||||
if not is_valid_email(user_selected):
|
||||
error_msg = 'email %s invalid.' % user_selected
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
@@ -233,8 +226,7 @@ class AdminLogsFileUpdateLogs(APIView):
|
||||
start = per_page * (current_page - 1)
|
||||
limit = per_page
|
||||
|
||||
# org_id = 0, show all file audit
|
||||
events = get_file_update_events(user_selected, 0, repo_id_selected, start, limit) or []
|
||||
events = get_log_events_by_type_users_repo('file_update', emails, repo_id_selected, start, limit)
|
||||
|
||||
has_next_page = True if len(events) == per_page else False
|
||||
|
||||
@@ -306,8 +298,10 @@ class AdminLogsSharePermissionLogs(APIView):
|
||||
current_page = 1
|
||||
per_page = 100
|
||||
|
||||
user_selected = request.GET.get('email', None)
|
||||
if user_selected and not is_valid_email(user_selected):
|
||||
emails = request.GET.get('emails')
|
||||
emails = emails.split(',') if emails else []
|
||||
for user_selected in emails:
|
||||
if not is_valid_email(user_selected):
|
||||
error_msg = 'email %s invalid.' % user_selected
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
@@ -319,9 +313,7 @@ class AdminLogsSharePermissionLogs(APIView):
|
||||
start = per_page * (current_page - 1)
|
||||
limit = per_page
|
||||
|
||||
# org_id = 0, show all file audit
|
||||
events = get_perm_audit_events(user_selected, 0, repo_id_selected, start, limit) or []
|
||||
|
||||
events = get_log_events_by_type_users_repo('perm_audit', emails, repo_id_selected, start, limit) or []
|
||||
has_next_page = True if len(events) == per_page else False
|
||||
|
||||
# Use dict to reduce memcache fetch cost in large for-loop.
|
||||
@@ -461,6 +453,19 @@ class AdminLogsFileTransferLogs(APIView):
|
||||
error_msg = 'limit invalid'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
emails = request.GET.get('emails')
|
||||
emails = emails.split(',') if emails else []
|
||||
for user_selected in emails:
|
||||
if not is_valid_email(user_selected):
|
||||
error_msg = 'email %s invalid.' % user_selected
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
if emails:
|
||||
events = RepoTransfer.objects.filter(
|
||||
Q(from_user__in=emails) |
|
||||
Q(to__in=emails) |
|
||||
Q(operator__in=emails)
|
||||
).order_by('-timestamp')[start:start+limit+1]
|
||||
else:
|
||||
events = RepoTransfer.objects.all().order_by('-timestamp')[start:start+limit+1]
|
||||
if len(events) > limit:
|
||||
has_next_page = True
|
||||
|
@@ -1162,6 +1162,8 @@ class AdminSearchUser(APIView):
|
||||
has_appended.append(user.email)
|
||||
|
||||
info = {}
|
||||
url, is_default, date_uploaded = api_avatar_url(user.email)
|
||||
info['avatar_url'] = url
|
||||
info['email'] = user.email
|
||||
info['name'] = email2nickname(user.email)
|
||||
info['contact_email'] = email2contact_email(user.email)
|
||||
|
@@ -733,6 +733,12 @@ if EVENTS_CONFIG_FILE:
|
||||
|
||||
return events if events else None
|
||||
|
||||
def get_log_events_by_type_users_repo(type, emails, repo_ids, start, limit):
|
||||
with _get_seafevents_session() as session:
|
||||
events = seafevents_api.get_log_events_by_type_users_repo(session, type, emails, repo_ids, start, limit)
|
||||
|
||||
return events if events else None
|
||||
|
||||
def get_file_ops_stats_by_day(start, end, offset):
|
||||
""" return file audit record of sepcifiy time group by day.
|
||||
"""
|
||||
@@ -886,6 +892,8 @@ else:
|
||||
pass
|
||||
def get_file_audit_events():
|
||||
pass
|
||||
def get_log_events_by_type_users_repo():
|
||||
pass
|
||||
def get_file_ops_stats_by_day():
|
||||
pass
|
||||
def get_org_file_ops_stats_by_day():
|
||||
|
Reference in New Issue
Block a user