1
0
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:
孙永强
2025-03-06 13:42:16 +08:00
parent 4f16bbb0d6
commit 779fee29b6
12 changed files with 508 additions and 115 deletions

View File

@@ -1,7 +1,3 @@
.activity-details { .activity-details {
text-decoration: underline; text-decoration: underline;
cursor: pointer; cursor: pointer;
@@ -16,6 +12,7 @@
.cur-activity-modifiers { .cur-activity-modifiers {
margin-left: -0.5rem; margin-left: -0.5rem;
padding: 0.15rem 0;
} }
.cur-activity-modifiers:hover { .cur-activity-modifiers:hover {

View 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;

View File

@@ -3,17 +3,22 @@ import PropTypes from 'prop-types';
import { Input } from 'reactstrap'; import { Input } from 'reactstrap';
import { gettext } from '../../utils/constants'; import { gettext } from '../../utils/constants';
import '../../css/log-filter.css'; 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) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
isOpen: false, isOpen: false,
searchText: '', query: '',
query: '' searchResults: [],
isLoading: false
}; };
this.dropdownRef = React.createRef(); this.dropdownRef = React.createRef();
this.finalValue = '';
} }
componentDidMount() { componentDidMount() {
@@ -46,12 +51,33 @@ class LogFilter extends Component {
this.togglePopover(); this.togglePopover();
}; };
handleSearch = (e) => { onQueryChange = (e) => {
this.setState({ searchText: e.target.value }); const value = e.target.value;
this.setState({ query: value });
this.searchUsers(value);
}; };
onQueryChange = (e) => { searchUsers = (value) => {
this.setState({ query: e.target.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) => { toggleSelectItem = (e, item) => {
@@ -60,16 +86,12 @@ class LogFilter extends Component {
}; };
render() { render() {
const { isOpen, query } = this.state; const { isOpen, query, searchResults, isLoading } = this.state;
const { items, selectedItems } = this.props; const { selectedItems } = this.props;
const filteredItems = query.trim() ? const displayItems = query.trim() ? searchResults : this.props.items;
items.filter(item =>
item.email.toLowerCase().includes(query.trim().toLowerCase()) ||
item.name.toLowerCase().includes(query.trim().toLowerCase())
) : items;
return ( 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}> <span className="cur-activity-modifiers d-inline-block p-2 rounded" onClick={this.onToggleClick}>
{selectedItems.length > 0 ? ( {selectedItems.length > 0 ? (
<> <>
@@ -101,17 +123,29 @@ class LogFilter extends Component {
/> />
</div> </div>
<ul className="activity-user-list list-unstyled p-3 o-auto"> <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 ( 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> <div>
<img src={item.avatar_url} className="avatar w-5 h-5" alt="" /> <img src={item.avatar_url} className="avatar w-5 h-5" alt="" />
<span className="activity-user-name ml-2">{item.name}</span> <span className="activity-user-name ml-2">{item.name}</span>
</div> </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> </li>
); );
})} })
)}
</ul> </ul>
</div> </div>
)} )}
@@ -120,10 +154,10 @@ class LogFilter extends Component {
} }
} }
LogFilter.propTypes = { LogUserSelector.propTypes = {
items: PropTypes.array.isRequired, items: PropTypes.array.isRequired,
selectedItems: PropTypes.array.isRequired, selectedItems: PropTypes.array.isRequired,
onSelect: PropTypes.func.isRequired onSelect: PropTypes.func.isRequired
}; };
export default LogFilter; export default LogUserSelector;

View File

@@ -17,6 +17,8 @@ import FilterMenu from './file-access-item-menu';
import ToggleFilter from './file-access-toggle-filter'; import ToggleFilter from './file-access-toggle-filter';
import MainPanelTopbar from '../main-panel-topbar'; import MainPanelTopbar from '../main-panel-topbar';
import UserLink from '../user-link'; import UserLink from '../user-link';
import LogUserSelector from '../../dashboard/log-user-selector'
import LogRepoSelector from '../../dashboard/log-repo-selector';
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
@@ -248,6 +250,8 @@ class FileAccessLogs extends Component {
currentPage: 1, currentPage: 1,
hasNextPage: false, hasNextPage: false,
isExportExcelDialogOpen: false, isExportExcelDialogOpen: false,
availableUsers: [],
selectedUsers: [],
}; };
this.initPage = 1; this.initPage = 1;
} }
@@ -265,13 +269,15 @@ class FileAccessLogs extends Component {
userFilteredBy: urlParams.get('email'), userFilteredBy: urlParams.get('email'),
repoFilteredBy: urlParams.get('repo_id') repoFilteredBy: urlParams.get('repo_id')
}, () => { }, () => {
// this.getAvailableUsers();
this.getLogsByPage(this.state.currentPage); this.getLogsByPage(this.state.currentPage);
}); });
} }
getLogsByPage = (page) => { getLogsByPage = (page) => {
const { perPage, userFilteredBy, repoFilteredBy } = this.state; const { perPage, userFilteredBy, repoFilteredBy, selectedUsers } = this.state;
systemAdminAPI.sysAdminListFileAccessLogs(page, perPage, userFilteredBy, repoFilteredBy).then((res) => { let emails = selectedUsers.map(user => user.email);
systemAdminAPI.sysAdminListFileAccessLogs(page, perPage, emails, 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,
@@ -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() { render() {
const { const {
logList, logList,
userFilteredBy, repoFilteredBy, userFilteredBy, repoFilteredBy,
currentPage, perPage, hasNextPage, currentPage, perPage, hasNextPage,
isExportExcelDialogOpen isExportExcelDialogOpen,
availableUsers,
selectedUsers
} = this.state; } = this.state;
return ( return (
<Fragment> <Fragment>
@@ -338,6 +383,12 @@ class FileAccessLogs extends Component {
<div className="cur-view-container"> <div className="cur-view-container">
<LogsNav currentItem="fileAccessLogs" /> <LogsNav currentItem="fileAccessLogs" />
<div className="cur-view-content"> <div className="cur-view-content">
<Fragment>
<LogUserSelector
items={availableUsers}
selectedItems={selectedUsers}
onSelect={this.handleUserFilter}
/>
<Content <Content
loading={this.state.loading} loading={this.state.loading}
errorMsg={this.state.errorMsg} errorMsg={this.state.errorMsg}
@@ -352,6 +403,7 @@ class FileAccessLogs extends Component {
getLogsByPage={this.getLogsByPage} getLogsByPage={this.getLogsByPage}
resetPerPage={this.resetPerPage} resetPerPage={this.resetPerPage}
/> />
</Fragment>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { Link } from '@gatsbyjs/reach-router'; import { Link } from '@gatsbyjs/reach-router';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime'; 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 { gettext, siteRoot } 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';
@@ -12,6 +12,7 @@ import Paginator from '../../../components/paginator';
import MainPanelTopbar from '../main-panel-topbar'; import MainPanelTopbar from '../main-panel-topbar';
import UserLink from '../user-link'; import UserLink from '../user-link';
import LogsNav from './logs-nav'; import LogsNav from './logs-nav';
import LogUserSelector from '../../dashboard/log-user-selector';
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
@@ -162,6 +163,8 @@ class FIleTransferLogs extends Component {
perPage: 100, perPage: 100,
currentPage: 1, currentPage: 1,
hasNextPage: false, hasNextPage: false,
availableUsers: [],
selectedUsers: [],
}; };
this.initPage = 1; this.initPage = 1;
} }
@@ -178,8 +181,9 @@ class FIleTransferLogs extends Component {
} }
getLogsByPage = (page) => { getLogsByPage = (page) => {
let { perPage } = this.state; let { perPage, selectedUsers } = this.state;
seafileAPI.sysAdminListFileTransferLogs(page, perPage).then((res) => { let emails = selectedUsers.map(user => user.email);
systemAdminAPI.sysAdminListFileTransferLogs(page, perPage, emails).then((res) => {
this.setState({ this.setState({
logList: res.data.repo_transfer_log_list, logList: res.data.repo_transfer_log_list,
loading: false, loading: false,
@@ -200,8 +204,33 @@ class FIleTransferLogs extends Component {
}, () => this.getLogsByPage(this.initPage)); }, () => 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() { render() {
let { logList, currentPage, perPage, hasNextPage } = this.state; let { logList, currentPage, perPage, hasNextPage, availableUsers, selectedUsers } = this.state;
return ( return (
<Fragment> <Fragment>
<MainPanelTopbar {...this.props} /> <MainPanelTopbar {...this.props} />
@@ -209,6 +238,13 @@ class FIleTransferLogs extends Component {
<div className="cur-view-container"> <div className="cur-view-container">
<LogsNav currentItem="fileTransfer" /> <LogsNav currentItem="fileTransfer" />
<div className="cur-view-content"> <div className="cur-view-content">
<Fragment>
<LogUserSelector
label={gettext('User')}
items={availableUsers}
selectedItems={selectedUsers}
onSelect={this.handleUserFilter}
/>
<Content <Content
loading={this.state.loading} loading={this.state.loading}
errorMsg={this.state.errorMsg} errorMsg={this.state.errorMsg}
@@ -219,6 +255,7 @@ class FIleTransferLogs extends Component {
getLogsByPage={this.getLogsByPage} getLogsByPage={this.getLogsByPage}
resetPerPage={this.resetPerPage} resetPerPage={this.resetPerPage}
/> />
</Fragment>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -15,6 +15,7 @@ import UserLink from '../user-link';
import ModalPortal from '../../../components/modal-portal'; import ModalPortal from '../../../components/modal-portal';
import CommitDetails from '../../../components/dialog/commit-details'; import CommitDetails from '../../../components/dialog/commit-details';
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 LogUserSelector from '../../dashboard/log-user-selector'
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
@@ -173,6 +174,8 @@ class FileUpdateLogs extends Component {
currentPage: 1, currentPage: 1,
hasNextPage: false, hasNextPage: false,
isExportExcelDialogOpen: false, isExportExcelDialogOpen: false,
availableUsers: [],
selectedUsers: [],
}; };
this.initPage = 1; this.initPage = 1;
} }
@@ -193,8 +196,10 @@ class FileUpdateLogs extends Component {
} }
getLogsByPage = (page) => { getLogsByPage = (page) => {
let { perPage } = this.state; let { perPage, selectedUsers } = this.state;
systemAdminAPI.sysAdminListFileUpdateLogs(page, perPage).then((res) => { let emails = selectedUsers.map(user => user.email);
systemAdminAPI.sysAdminListFileUpdateLogs(page, perPage, emails).then((res) => {
this.setState({ this.setState({
logList: res.data.file_update_log_list, logList: res.data.file_update_log_list,
loading: false, loading: false,
@@ -215,8 +220,34 @@ class FileUpdateLogs extends Component {
}, () => this.getLogsByPage(this.initPage)); }, () => 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() { render() {
let { logList, currentPage, perPage, hasNextPage, isExportExcelDialogOpen } = this.state; let { logList, currentPage, perPage, hasNextPage, isExportExcelDialogOpen, availableUsers, selectedUsers } = this.state;
return ( return (
<Fragment> <Fragment>
<MainPanelTopbar {...this.props}> <MainPanelTopbar {...this.props}>
@@ -226,6 +257,13 @@ class FileUpdateLogs extends Component {
<div className="cur-view-container"> <div className="cur-view-container">
<LogsNav currentItem="fileUpdateLogs" /> <LogsNav currentItem="fileUpdateLogs" />
<div className="cur-view-content"> <div className="cur-view-content">
<Fragment>
<LogUserSelector
label={gettext('User')}
items={availableUsers}
selectedItems={selectedUsers}
onSelect={this.handleUserFilter}
/>
<Content <Content
loading={this.state.loading} loading={this.state.loading}
errorMsg={this.state.errorMsg} errorMsg={this.state.errorMsg}
@@ -236,6 +274,7 @@ class FileUpdateLogs extends Component {
getLogsByPage={this.getLogsByPage} getLogsByPage={this.getLogsByPage}
resetPerPage={this.resetPerPage} resetPerPage={this.resetPerPage}
/> />
</Fragment>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -14,7 +14,7 @@ import MainPanelTopbar from '../main-panel-topbar';
import UserLink from '../user-link'; 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 LogFilter from '../../dashboard/log-filter'; import LogUserSelector from '../../dashboard/log-user-selector';
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
@@ -156,7 +156,7 @@ class LoginLogs extends Component {
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)
}, () => { }, () => {
this.getAvailableUsers(); // this.getAvailableUsers();
this.getLogsByPage(this.state.currentPage); this.getLogsByPage(this.state.currentPage);
}); });
} }
@@ -235,7 +235,7 @@ class LoginLogs extends Component {
<LogsNav currentItem="loginLogs" /> <LogsNav currentItem="loginLogs" />
<div className="cur-view-content"> <div className="cur-view-content">
<Fragment> <Fragment>
<LogFilter <LogUserSelector
label={gettext('User')} label={gettext('User')}
items={availableUsers} items={availableUsers}
selectedItems={selectedUsers} selectedItems={selectedUsers}

View File

@@ -15,6 +15,7 @@ import Paginator from '../../../components/paginator';
import MainPanelTopbar from '../main-panel-topbar'; import MainPanelTopbar from '../main-panel-topbar';
import UserLink from '../user-link'; import UserLink from '../user-link';
import LogsNav from './logs-nav'; import LogsNav from './logs-nav';
import LogUserSelector from '../../dashboard/log-user-selector';
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
@@ -151,6 +152,8 @@ class SharePermissionLogs extends Component {
currentPage: 1, currentPage: 1,
hasNextPage: false, hasNextPage: false,
isExportExcelDialogOpen: false, isExportExcelDialogOpen: false,
availableUsers: [],
selectedUsers: [],
}; };
this.initPage = 1; this.initPage = 1;
} }
@@ -171,8 +174,9 @@ class SharePermissionLogs extends Component {
} }
getLogsByPage = (page) => { getLogsByPage = (page) => {
let { perPage } = this.state; let { perPage, selectedUsers } = this.state;
systemAdminAPI.sysAdminListSharePermissionLogs(page, perPage).then((res) => { let emails = selectedUsers.map(user => user.email);
systemAdminAPI.sysAdminListSharePermissionLogs(page, perPage, emails).then((res) => {
this.setState({ this.setState({
logList: res.data.share_permission_log_list, logList: res.data.share_permission_log_list,
loading: false, loading: false,
@@ -193,8 +197,33 @@ class SharePermissionLogs extends Component {
}, () => this.getLogsByPage(this.initPage)); }, () => 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() { render() {
let { logList, currentPage, perPage, hasNextPage, isExportExcelDialogOpen } = this.state; let { logList, currentPage, perPage, hasNextPage, isExportExcelDialogOpen, availableUsers, selectedUsers } = this.state;
return ( return (
<Fragment> <Fragment>
<MainPanelTopbar {...this.props}> <MainPanelTopbar {...this.props}>
@@ -204,6 +233,13 @@ class SharePermissionLogs extends Component {
<div className="cur-view-container"> <div className="cur-view-container">
<LogsNav currentItem="sharePermissionLogs" /> <LogsNav currentItem="sharePermissionLogs" />
<div className="cur-view-content"> <div className="cur-view-content">
<Fragment>
<LogUserSelector
label={gettext('User')}
items={availableUsers}
selectedItems={selectedUsers}
onSelect={this.handleUserFilter}
/>
<Content <Content
loading={this.state.loading} loading={this.state.loading}
errorMsg={this.state.errorMsg} errorMsg={this.state.errorMsg}
@@ -214,6 +250,7 @@ class SharePermissionLogs extends Component {
getLogsByPage={this.getLogsByPage} getLogsByPage={this.getLogsByPage}
resetPerPage={this.resetPerPage} resetPerPage={this.resetPerPage}
/> />
</Fragment>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -692,14 +692,14 @@ class SystemAdminAPI {
return this.req.get(url, { params: params }); 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/'; const url = this.server + '/api/v2.1/admin/logs/file-access-logs/';
let params = { let params = {
page: page, page: page,
per_page: perPage per_page: perPage
}; };
if (email != undefined) { if (emails && emails.length) {
params.email = email; params.emails = emails.join(',');
} }
if (repoID != undefined) { if (repoID != undefined) {
params.repo_id = repoID; params.repo_id = repoID;
@@ -707,21 +707,48 @@ class SystemAdminAPI {
return this.req.get(url, { params: params }); 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/'; const url = this.server + '/api/v2.1/admin/logs/file-update-logs/';
let params = { let params = {
page: page, page: page,
per_page: perPage 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 }); 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/'; const url = this.server + '/api/v2.1/admin/logs/share-permission-logs/';
let params = { let params = {
page: page, page: page,
per_page: perPage 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 }); return this.req.get(url, { params: params });
} }

View File

@@ -6,7 +6,7 @@ from rest_framework.permissions import IsAdminUser
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework import status from rest_framework import status
from django.db.models import Q
from seaserv import ccnet_api, seafile_api from seaserv import ccnet_api, seafile_api
from seahub.api2.authentication import TokenAuthentication 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, \ from seahub.base.templatetags.seahub_tags import email2nickname, \
email2contact_email, translate_commit_desc 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 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.timeutils import datetime_to_isoformat_timestr, utc_datetime_to_isoformat_timestr
from seahub.utils.repo import is_valid_repo_id_format from seahub.utils.repo import is_valid_repo_id_format
@@ -114,8 +114,10 @@ class AdminLogsFileAccessLogs(APIView):
current_page = 1 current_page = 1
per_page = 100 per_page = 100
user_selected = request.GET.get('email', None) emails = request.GET.get('emails')
if user_selected and not is_valid_email(user_selected): 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 error_msg = 'email %s invalid.' % user_selected
return api_error(status.HTTP_400_BAD_REQUEST, error_msg) return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
@@ -127,18 +129,7 @@ class AdminLogsFileAccessLogs(APIView):
start = per_page * (current_page - 1) start = per_page * (current_page - 1)
limit = per_page + 1 limit = per_page + 1
if user_selected: events = get_log_events_by_type_users_repo('file_audit', emails, repo_id_selected, start, limit) or []
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 []
if len(events) > per_page: if len(events) > per_page:
events = events[:per_page] events = events[:per_page]
@@ -220,8 +211,10 @@ class AdminLogsFileUpdateLogs(APIView):
current_page = 1 current_page = 1
per_page = 100 per_page = 100
user_selected = request.GET.get('email', None) emails = request.GET.get('emails')
if user_selected and not is_valid_email(user_selected): 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 error_msg = 'email %s invalid.' % user_selected
return api_error(status.HTTP_400_BAD_REQUEST, error_msg) return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
@@ -233,8 +226,7 @@ class AdminLogsFileUpdateLogs(APIView):
start = per_page * (current_page - 1) start = per_page * (current_page - 1)
limit = per_page limit = per_page
# org_id = 0, show all file audit events = get_log_events_by_type_users_repo('file_update', emails, repo_id_selected, start, limit)
events = get_file_update_events(user_selected, 0, repo_id_selected, start, limit) or []
has_next_page = True if len(events) == per_page else False has_next_page = True if len(events) == per_page else False
@@ -306,8 +298,10 @@ class AdminLogsSharePermissionLogs(APIView):
current_page = 1 current_page = 1
per_page = 100 per_page = 100
user_selected = request.GET.get('email', None) emails = request.GET.get('emails')
if user_selected and not is_valid_email(user_selected): 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 error_msg = 'email %s invalid.' % user_selected
return api_error(status.HTTP_400_BAD_REQUEST, error_msg) return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
@@ -319,9 +313,7 @@ class AdminLogsSharePermissionLogs(APIView):
start = per_page * (current_page - 1) start = per_page * (current_page - 1)
limit = per_page limit = per_page
# org_id = 0, show all file audit events = get_log_events_by_type_users_repo('perm_audit', emails, repo_id_selected, start, limit) or []
events = get_perm_audit_events(user_selected, 0, repo_id_selected, start, limit) or []
has_next_page = True if len(events) == per_page else False has_next_page = True if len(events) == per_page else False
# Use dict to reduce memcache fetch cost in large for-loop. # Use dict to reduce memcache fetch cost in large for-loop.
@@ -461,6 +453,19 @@ class AdminLogsFileTransferLogs(APIView):
error_msg = 'limit invalid' error_msg = 'limit invalid'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg) 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] events = RepoTransfer.objects.all().order_by('-timestamp')[start:start+limit+1]
if len(events) > limit: if len(events) > limit:
has_next_page = True has_next_page = True

View File

@@ -1162,6 +1162,8 @@ class AdminSearchUser(APIView):
has_appended.append(user.email) has_appended.append(user.email)
info = {} info = {}
url, is_default, date_uploaded = api_avatar_url(user.email)
info['avatar_url'] = url
info['email'] = user.email info['email'] = user.email
info['name'] = email2nickname(user.email) info['name'] = email2nickname(user.email)
info['contact_email'] = email2contact_email(user.email) info['contact_email'] = email2contact_email(user.email)

View File

@@ -733,6 +733,12 @@ if EVENTS_CONFIG_FILE:
return events if events else None 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): def get_file_ops_stats_by_day(start, end, offset):
""" return file audit record of sepcifiy time group by day. """ return file audit record of sepcifiy time group by day.
""" """
@@ -886,6 +892,8 @@ else:
pass pass
def get_file_audit_events(): def get_file_audit_events():
pass pass
def get_log_events_by_type_users_repo():
pass
def get_file_ops_stats_by_day(): def get_file_ops_stats_by_day():
pass pass
def get_org_file_ops_stats_by_day(): def get_org_file_ops_stats_by_day():