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

optimize ui and filter repo

This commit is contained in:
孙永强
2025-03-06 15:50:32 +08:00
parent 779fee29b6
commit 75a0cb4fcc
10 changed files with 170 additions and 198 deletions

View File

@@ -12,7 +12,6 @@
.cur-activity-modifiers {
margin-left: -0.5rem;
padding: 0.15rem 0;
}
.cur-activity-modifiers:hover {

View File

@@ -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())
);
systemAdminAPI.sysAdminSearchRepos(value).then((res) => {
this.setState({
searchResults: filteredItems,
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 (
<div className="mt-4 position-relative" ref={this.dropdownRef}>
<div className="position-relative d-inline-block" ref={this.dropdownRef}>
<span className="cur-activity-modifiers d-inline-block p-2 rounded" onClick={this.onToggleClick}>
{selectedItems.length > 0 ? (
<>
@@ -123,7 +117,7 @@ class LogRepoSelector extends Component {
</li>
) : (
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 (
<li key={index}
className="activity-user-item h-6 p-1 rounded d-flex justify-content-between align-items-center"
@@ -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;

View File

@@ -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 (
<div className="position-relative" ref={this.dropdownRef}>
<div className="position-relative d-inline-block ml-2" ref={this.dropdownRef}>
<span className="cur-activity-modifiers d-inline-block p-2 rounded" onClick={this.onToggleClick}>
{selectedItems.length > 0 ? (
<>
@@ -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;

View File

@@ -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 = (
<Fragment>
<div>
{userFilteredBy && (
<ToggleFilter
filterBy={items[0].name}
toggleFilter={this.toggleFilterByUser}
/>
)}
{repoFilteredBy && (
<ToggleFilter
filterBy={items[0].repo_name}
toggleFilter={this.toggleFilterByRepo}
/>
)}
</div>
<table>
<thead>
<tr>
@@ -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}
/>);
})}
</tbody>
@@ -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 (
<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>{dayjs(item.time).fromNow()}</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>
@@ -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({
@@ -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 (
<Fragment>
@@ -384,19 +353,27 @@ class FileAccessLogs extends Component {
<LogsNav currentItem="fileAccessLogs" />
<div className="cur-view-content">
<Fragment>
<div className="d-flex align-items-center mb-2">
<LogUserSelector
items={availableUsers}
selectedItems={selectedUsers}
onSelect={this.handleUserFilter}
isOpen={this.state.openSelector === 'user'}
onToggle={() => this.handleSelectorToggle('user')}
/>
<div className="mx-3"></div>
<LogRepoSelector
items={availableRepos}
selectedItems={selectedRepos}
onSelect={this.handleRepoFilter}
isOpen={this.state.openSelector === 'repo'}
onToggle={() => this.handleSelectorToggle('repo')}
/>
</div>
<Content
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

@@ -15,7 +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'
import LogUserSelector from '../../dashboard/log-user-selector';
dayjs.extend(relativeTime);

View File

@@ -141,6 +141,7 @@ class LoginLogs extends Component {
isExportExcelDialogOpen: false,
availableUsers: [],
selectedUsers: [],
isUserSelectorOpen: false,
};
this.initPage = 1;
}
@@ -198,7 +199,7 @@ class LoginLogs extends Component {
}, () => this.getLogsByPage(this.initPage));
};
handleUserFilter = (user, shouldFetchData = true) => {
handleUserFilter = (user) => {
const { selectedUsers } = this.state;
let newSelectedUsers;
@@ -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 {
<div className="cur-view-content">
<Fragment>
<LogUserSelector
label={gettext('User')}
items={availableUsers}
selectedItems={selectedUsers}
onSelect={this.handleUserFilter}
isOpen={this.state.isUserSelectorOpen}
onToggle={this.toggleUserSelector}
/>
<Content
loading={this.state.loading}

View File

@@ -692,7 +692,7 @@ class SystemAdminAPI {
return this.req.get(url, { params: params });
}
sysAdminListFileAccessLogs(page, perPage, emails, repoID) {
sysAdminListFileAccessLogs(page, perPage, emails, repos) {
const url = this.server + '/api/v2.1/admin/logs/file-access-logs/';
let params = {
page: page,
@@ -701,8 +701,8 @@ class SystemAdminAPI {
if (emails && emails.length) {
params.emails = emails.join(',');
}
if (repoID != undefined) {
params.repo_id = repoID;
if (repos && repos.length) {
params.repos = repos.map(repo => repo.id).join(',');
}
return this.req.get(url, { params: params });
}

View File

@@ -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
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]