diff --git a/frontend/src/pages/sys-admin/index.js b/frontend/src/pages/sys-admin/index.js index 97600abcbb..eb5eaeef92 100644 --- a/frontend/src/pages/sys-admin/index.js +++ b/frontend/src/pages/sys-admin/index.js @@ -16,6 +16,7 @@ import Users from './users/users'; import AdminUsers from './users/admin-users'; import LDAPImportedUsers from './users/ldap-imported-users'; import LDAPUsers from './users/ldap-users'; +import SearchUsers from './users/search-users'; import User from './users/user-info'; import UserOwnedRepos from './users/user-repos'; import UserSharedRepos from './users/user-shared-repos'; @@ -108,7 +109,7 @@ class SysAdmin extends React.Component { }, { tab: 'users', - urlPartList: ['users/'] + urlPartList: ['users/', 'search-users/'] }, { tab: 'groups', @@ -206,6 +207,7 @@ class SysAdmin extends React.Component { + diff --git a/frontend/src/pages/sys-admin/users/search-users.js b/frontend/src/pages/sys-admin/users/search-users.js new file mode 100644 index 0000000000..d852d6dc60 --- /dev/null +++ b/frontend/src/pages/sys-admin/users/search-users.js @@ -0,0 +1,341 @@ +import React, { Component, Fragment } from 'react'; +import { Button, Form, FormGroup, Input, Col } from 'reactstrap'; +import { Utils } from '../../../utils/utils'; +import { seafileAPI } from '../../../utils/seafile-api'; +import { gettext, loginUrl } from '../../../utils/constants'; +import toaster from '../../../components/toast'; +import SysAdminUserSetQuotaDialog from '../../../components/dialog/sysadmin-dialog/set-quota'; +import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog'; +import MainPanelTopbar from '../main-panel-topbar'; +import Content from './users-content'; + + +class SearchUsers extends Component { + + constructor(props) { + super(props); + this.state = { + query: '', + isSubmitBtnActive: false, + loading: true, + errorMsg: '', + userList: [], + hasUserSelected: false, + selectedUserList: [], + isAllUsersSelected: false, + isBatchSetQuotaDialogOpen: false, + isBatchDeleteUserDialogOpen: false + }; + } + + componentDidMount () { + let params = (new URL(document.location)).searchParams; + this.setState({ + query: params.get('query') || '' + }, this.getItems); + } + + toggleBatchSetQuotaDialog = () => { + this.setState({isBatchSetQuotaDialogOpen: !this.state.isBatchSetQuotaDialogOpen}); + } + + toggleBatchDeleteUserDialog = () => { + this.setState({isBatchDeleteUserDialogOpen: !this.state.isBatchDeleteUserDialogOpen}); + } + + onUserSelected = (item) => { + let hasUserSelected = false; + let selectedUserList = []; + // traverse all users, toggle its selected status + let users = this.state.userList.map(user => { + // toggle status + if (user.email === item.email) { + user.isSelected = !user.isSelected; + } + // update selectedUserList + // if current user is now selected, push it to selectedUserList + // if current user is now not selected, drop it from selectedUserList + if (user.isSelected == true) { + hasUserSelected = true; + selectedUserList.push(user); + } else { + selectedUserList = selectedUserList.filter(thisuser => { + return thisuser.email != user.email; + }); + } + return user; + }); + // finally update state + this.setState({ + userList: users, + hasUserSelected: hasUserSelected, + selectedUserList: selectedUserList, + }); + } + + toggleSelectAllUsers = () => { + if (this.state.isAllUsersSelected) { + // if previous state is allSelected, toggle to not select + let users = this.state.userList.map(user => { + user.isSelected = false; + return user; + }); + this.setState({ + userList: users, + hasUserSelected: false, + isAllUsersSelected: false, + selectedUserList: [], + }); + } else { + // if previous state is not allSelected, toggle to selectAll + let users = this.state.userList.map(user => { + user.isSelected = true; + return user; + }); + this.setState({ + userList: users, + hasUserSelected: true, + isAllUsersSelected: true, + selectedUserList: users + }); + } + } + + getItems = () => { + seafileAPI.sysAdminSearchUsers(this.state.query.trim()).then(res => { + this.setState({ + userList: res.data.user_list, + loading: false + }); + }).catch((error) => { + if (error.response) { + if (error.response.status == 403) { + this.setState({ + loading: false, + errorMsg: gettext('Permission denied') + }); + location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`; + } else { + this.setState({ + loading: false, + errorMsg: gettext('Error') + }); + } + } else { + this.setState({ + loading: false, + errorMsg: gettext('Please check the network.') + }); + } + }); + } + + deleteUser = (email) => { + seafileAPI.sysAdminDeleteUser(email).then(res => { + let newUserList = this.state.userList.filter(item => { + return item.email != email; + }); + this.setState({userList: newUserList}); + toaster.success(gettext('Successfully deleted 1 item.')); + }).catch((error) => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + + setUserQuotaInBatch = (quotaTotal) => { + let emails = this.state.selectedUserList.map(user => { + return user.email; + }); + seafileAPI.sysAdminSetUserQuotaInBatch(emails, quotaTotal).then(res => { + let userList = this.state.userList.map(item => { + res.data.success.map(resultUser => { + if (item.email == resultUser.email) { + item.quota_total = resultUser.quota_total; + } + }); + return item; + }); + this.setState({userList: userList}); + }).catch((error) => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + + deleteUserInBatch = () => { + let emails = this.state.selectedUserList.map(user => { + return user.email; + }); + seafileAPI.sysAdminDeleteUserInBatch(emails).then(res => { + if (res.data.success.length) { + let oldUserList = this.state.userList; + let newUserList = oldUserList.filter(oldUser => { + return !res.data.success.some(deletedUser =>{ + return deletedUser.email == oldUser.email; + }); + }); + this.setState({ + userList: newUserList, + hasUserSelected: emails.length != res.data.success.length + }); + const length = res.data.success.length; + const msg = length == 1 ? + gettext('Successfully deleted 1 user.') : + gettext('Successfully deleted {user_number_placeholder} users.') + .replace('{user_number_placeholder}', length); + toaster.success(msg); + } + res.data.failed.map(item => { + const msg = `${item.email}: ${item.error_msg}`; + toaster.danger(msg); + }); + }).catch((error) => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + + updateUser = (email, key, value) => { + seafileAPI.sysAdminUpdateUser(email, key, value).then(res => { + let newUserList = this.state.userList.map(item => { + if (item.email == email) { + item[key]= res.data[key]; + } + return item; + }); + this.setState({userList: newUserList}); + const msg = (key == 'is_active' && value) ? + res.data.update_status_tip : gettext('Edit succeeded'); + toaster.success(msg); + }).catch((error) => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + + updateAdminRole = (email, role) => { + seafileAPI.sysAdminUpdateAdminRole(email, role).then(res => { + let newUserList = this.state.userList.map(item => { + if (item.email == email) { + item.admin_role = res.data.role; + } + return item; + }); + this.setState({userList: newUserList}); + toaster.success(gettext('Edit succeeded')); + }).catch((error) => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + + revokeAdmin = (email, name) => { + seafileAPI.sysAdminUpdateUser(email, 'is_staff', false).then(res => { + let userList = this.state.userList.filter(item => { + return item.email != email; + }); + this.setState({ + userList: userList + }); + toaster.success(gettext('Successfully revoked the admin permission of {placeholder}'.replace('{placeholder}', name))); + }).catch((error) => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + + handleInputChange = (e) => { + this.setState({ + query: e.target.value + }, this.checkSubmitBtnActive); + } + + checkSubmitBtnActive = () => { + const { email } = this.state; + this.setState({ + isSubmitBtnActive: email.trim() + }); + } + + render() { + const { query, isSubmitBtnActive } = this.state; + const { + hasUserSelected, + isBatchDeleteUserDialogOpen, + isBatchSetQuotaDialogOpen + } = this.state; + return ( + + {hasUserSelected ? + + + + + + : + + } +
+
+
+

{gettext('Users')}

+
+
+
+

{gettext('Search Users')}

+
+ + + + + + + + + + +
+
+
+

{gettext('Result')}

+ +
+
+
+
+ {isBatchSetQuotaDialogOpen && + + } + {isBatchDeleteUserDialogOpen && + + } +
+ ); + } +} + +export default SearchUsers; diff --git a/frontend/src/pages/sys-admin/users/users-content.js b/frontend/src/pages/sys-admin/users/users-content.js new file mode 100644 index 0000000000..f3606ab60e --- /dev/null +++ b/frontend/src/pages/sys-admin/users/users-content.js @@ -0,0 +1,452 @@ +import React, { Component, Fragment } from 'react'; +import moment from 'moment'; +import { Link } from '@reach/router'; +import { Utils } from '../../../utils/utils'; +import { seafileAPI } from '../../../utils/seafile-api'; +import { isPro, username, gettext, multiInstitution, siteRoot } from '../../../utils/constants'; +import toaster from '../../../components/toast'; +import EmptyTip from '../../../components/empty-tip'; +import Loading from '../../../components/loading'; +import Paginator from '../../../components/paginator'; +import SysAdminUserStatusEditor from '../../../components/select-editor/sysadmin-user-status-editor'; +import SysAdminUserRoleEditor from '../../../components/select-editor/sysadmin-user-role-editor'; +import SelectEditor from '../../../components/select-editor/select-editor'; +import SysAdminUserSetQuotaDialog from '../../../components/dialog/sysadmin-dialog/set-quota'; +import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog'; +import UserLink from '../user-link'; +import OpMenu from './user-op-menu'; + +const { availableRoles, availableAdminRoles, institutions } = window.sysadmin.pageOptions; + +class Content extends Component { + + constructor(props) { + super(props); + this.state = { + isItemFreezed: false + }; + } + + onFreezedItem = () => { + this.setState({isItemFreezed: true}); + } + + onUnfreezedItem = () => { + this.setState({isItemFreezed: false}); + } + + getPreviousPage = () => { + this.props.getListByPage(this.props.currentPage - 1); + } + + getNextPage = () => { + this.props.getListByPage(this.props.currentPage + 1); + } + + render() { + const { isAdmin, loading, errorMsg, items, isAllUsersSelected, curPerPage, hasNextPage, currentPage } = this.props; + if (loading) { + return ; + } else if (errorMsg) { + return

{errorMsg}

; + } else { + const emptyTip = ( + +

{gettext('No users')}

+
+ ); + + let columns = []; + const colNameText = `${gettext('Name')} / ${gettext('Contact Email')}`; + const colSpaceText = `${gettext('Space Used')} / ${gettext('Quota')}`; + const colCreatedText = `${gettext('Created At')} / ${gettext('Last Login')}`; + if (isPro) { + columns.push( + {width: '20%', text: colNameText}, + {width: '15%', text: gettext('Status')}, + {width: '15%', text: gettext('Role')} + ); + } else { + columns.push( + {width: '30%', text: colNameText}, + {width: '20%', text: gettext('Status')} + ); + } + if (multiInstitution && !isAdmin) { + columns.push( + {width: '14%', text: colSpaceText}, + {width: '14%', text: gettext('Institution')}, + {width: '14%', text: colCreatedText}, + {width: '5%', text: ''} + ); + } else { + columns.push( + {width: '20%', text: colSpaceText}, + {width: '22%', text: colCreatedText}, + {width: '5%', text: ''} + ); + } + + const table = ( + + + + + + {columns.map((item, index) => { + return ; + })} + + + + {items.map((item, index) => { + return (); + })} + +
+ + {item.text}
+ {(!this.props.isAdmin && !this.props.isSearchResult) && + + } +
+ ); + + return items.length ? table : emptyTip; + } + } +} + +class Item extends Component { + + constructor(props) { + super(props); + this.state = { + isOpIconShown: false, + highlight: false, + isSetQuotaDialogOpen: false, + isDeleteUserDialogOpen: false, + isResetUserPasswordDialogOpen: false, + isRevokeAdminDialogOpen: false + }; + } + + handleMouseEnter = () => { + if (!this.props.isItemFreezed) { + this.setState({ + isOpIconShown: true, + highlight: true + }); + } + } + + handleMouseLeave = () => { + if (!this.props.isItemFreezed) { + this.setState({ + isOpIconShown: false, + highlight: false + }); + } + } + + onUnfreezedItem = () => { + this.setState({ + highlight: false, + isOpIconShow: false + }); + this.props.onUnfreezedItem(); + } + + toggleSetQuotaDialog = () => { + this.setState({isSetQuotaDialogOpen: !this.state.isSetQuotaDialogOpen}); + } + + toggleDeleteUserDialog = () => { + this.setState({isDeleteUserDialogOpen: !this.state.isDeleteUserDialogOpen}); + } + + toggleResetUserPasswordDialog = () => { + this.setState({isResetUserPasswordDialogOpen: !this.state.isResetUserPasswordDialogOpen}); + } + + toggleRevokeAdminDialog = () => { + this.setState({isRevokeAdminDialogOpen: !this.state.isRevokeAdminDialogOpen}); + } + + onUserSelected = () => { + this.props.onUserSelected(this.props.item); + } + + updateStatus= (value) => { + const isActive = value == 'active'; + if (isActive) { + toaster.notify(gettext('It may take some time, please wait.')); + } + this.props.updateUser(this.props.item.email, 'is_active', isActive); + } + + updateRole = (value) => { + this.props.updateUser(this.props.item.email, 'role', value); + } + + updateAdminRole = (value) => { + this.props.updateAdminRole(this.props.item.email, value); + } + + translateAdminRole = (role) => { + switch (role) { + case 'default_admin': + return gettext('Default Admin'); + case 'system_admin': + return gettext('System Admin'); + case 'daily_admin': + return gettext('Daily Admin'); + case 'audit_admin': + return gettext('Audit Admin'); + default: + return role; + } + } + + updateInstitution = (value) => { + this.props.updateUser(this.props.item.email, 'institution', value); + } + + translateInstitution = (inst) => { + return inst; + } + + updateQuota = (value) => { + this.props.updateUser(this.props.item.email, 'quota_total', value); + } + + deleteUser = () => { + this.props.deleteUser(this.props.item.email); + } + + resetPassword = () => { + toaster.notify(gettext('It may take some time, please wait.')); + seafileAPI.sysAdminResetUserPassword(this.props.item.email).then(res => { + toaster.success(res.data.reset_tip); + }).catch((error) => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + + revokeAdmin = () => { + const { item } = this.props; + this.props.revokeAdmin(item.email, item.name); + } + + getMenuOperations = () => { + const { + isAdmin, isLDAPImported, + isSearchResult, item + } = this.props; + let list = ['Delete']; + if (!isLDAPImported || + (isSearchResult && item.source == 'db')) { + list.push('Reset Password'); + } + if (isAdmin) { + list = ['Revoke Admin']; + } + return list; + } + + translateOperations = (item) => { + let translateResult = ''; + switch (item) { + case 'Delete': + translateResult = gettext('Delete'); + break; + case 'Reset Password': + translateResult = gettext('Reset Password'); + break; + case 'Revoke Admin': + translateResult = gettext('Revoke Admin'); + break; + } + + return translateResult; + } + + onMenuItemClick = (operation) => { + switch(operation) { + case 'Delete': + this.toggleDeleteUserDialog(); + break; + case 'Reset Password': + this.toggleResetUserPasswordDialog(); + break; + case 'Revoke Admin': + this.toggleRevokeAdminDialog(); + break; + default: + break; + } + } + + render() { + const { item, isAdmin } = this.props; + const { + isOpIconShown, + isSetQuotaDialogOpen, + isDeleteUserDialogOpen, + isResetUserPasswordDialogOpen, + isRevokeAdminDialogOpen + } = this.state; + + const itemName = '' + Utils.HTMLescape(item.name) + ''; + const deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', itemName); + const resetPasswordDialogMsg = gettext('Are you sure you want to reset the password of {placeholder} ?').replace('{placeholder}', itemName); + const revokeAdminDialogMsg = gettext('Are you sure you want to revoke the admin permission of {placeholder} ?').replace('{placeholder}', itemName); + + return ( + + + + + + + + {item.contact_email && + +
+ {item.contact_email} +
} + {item.org_id && + +
+ ({item.org_name}) +
+ } + + + + + {isPro && + + {isAdmin ? + : + + } + + } + + {`${Utils.bytesToSize(item.quota_usage)} / ${item.quota_total > 0 ? Utils.bytesToSize(item.quota_total) : '--'}`} + + + + {(multiInstitution && !isAdmin) && + + 0} + options={institutions} + currentOption={item.institution} + onOptionChanged={this.updateInstitution} + translateOption={this.translateInstitution} + /> + + } + + {`${item.create_time ? moment(item.create_time).format('YYYY-MM-DD HH:mm') : '--'} /`} +
+ {`${item.last_login ? moment(item.last_login).fromNow() : '--'}`} + + + {(item.email != username && isOpIconShown) && + + } + + + {isSetQuotaDialogOpen && + + } + {isDeleteUserDialogOpen && + + } + {isResetUserPasswordDialogOpen && + + } + {isRevokeAdminDialogOpen && + + } +
+ ); + } +} + +export default Content; diff --git a/frontend/src/pages/sys-admin/users/users.js b/frontend/src/pages/sys-admin/users/users.js index 2cf00ed047..b96c5c1778 100644 --- a/frontend/src/pages/sys-admin/users/users.js +++ b/frontend/src/pages/sys-admin/users/users.js @@ -1,17 +1,10 @@ import React, { Component, Fragment } from 'react'; -import { Link } from '@reach/router'; +import { navigate } from '@reach/router'; import { Button } from 'reactstrap'; -import moment from 'moment'; import { Utils } from '../../../utils/utils'; import { seafileAPI } from '../../../utils/seafile-api'; -import { isPro, username, gettext, multiInstitution, siteRoot, loginUrl } from '../../../utils/constants'; +import { isPro, gettext, siteRoot, loginUrl } from '../../../utils/constants'; import toaster from '../../../components/toast'; -import EmptyTip from '../../../components/empty-tip'; -import Loading from '../../../components/loading'; -import Paginator from '../../../components/paginator'; -import SysAdminUserStatusEditor from '../../../components/select-editor/sysadmin-user-status-editor'; -import SysAdminUserRoleEditor from '../../../components/select-editor/sysadmin-user-role-editor'; -import SelectEditor from '../../../components/select-editor/select-editor'; import SysAdminUserSetQuotaDialog from '../../../components/dialog/sysadmin-dialog/set-quota'; import SysAdminImportUserDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-import-user-dialog'; import SysAdminAddUserDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-user-dialog'; @@ -20,438 +13,11 @@ import CommonOperationConfirmationDialog from '../../../components/dialog/common import SysAdminUser from '../../../models/sysadmin-user'; import SysAdminAdminUser from '../../../models/sysadmin-admin-user'; import MainPanelTopbar from '../main-panel-topbar'; -import UserLink from '../user-link'; +import Search from '../search'; import UsersNav from './users-nav'; -import OpMenu from './user-op-menu'; - -const { availableRoles, availableAdminRoles, institutions } = window.sysadmin.pageOptions; - -class Content extends Component { - - constructor(props) { - super(props); - this.state = { - isItemFreezed: false - }; - } - - onFreezedItem = () => { - this.setState({isItemFreezed: true}); - } - - onUnfreezedItem = () => { - this.setState({isItemFreezed: false}); - } - - getPreviousPage = () => { - this.props.getListByPage(this.props.currentPage - 1); - } - - getNextPage = () => { - this.props.getListByPage(this.props.currentPage + 1); - } - - render() { - const { isAdmin, loading, errorMsg, items, pageInfo, isAllUsersSelected, curPerPage, hasNextPage, currentPage } = this.props; - if (loading) { - return ; - } else if (errorMsg) { - return

{errorMsg}

; - } else { - const emptyTip = ( - -

{gettext('No users')}

-
- ); - - let columns = []; - const colNameText = `${gettext('Name')} / ${gettext('Contact Email')}`; - const colSpaceText = `${gettext('Space Used')} / ${gettext('Quota')}`; - const colCreatedText = `${gettext('Created At')} / ${gettext('Last Login')}`; - if (isPro) { - columns.push( - {width: '20%', text: colNameText}, - {width: '15%', text: gettext('Status')}, - {width: '15%', text: gettext('Role')} - ); - } else { - columns.push( - {width: '30%', text: colNameText}, - {width: '20%', text: gettext('Status')} - ); - } - if (multiInstitution && !isAdmin) { - columns.push( - {width: '14%', text: colSpaceText}, - {width: '14%', text: gettext('Institution')}, - {width: '14%', text: colCreatedText}, - {width: '5%', text: ''} - ); - } else { - columns.push( - {width: '20%', text: colSpaceText}, - {width: '22%', text: colCreatedText}, - {width: '5%', text: ''} - ); - } - - const table = ( - - - - - - {columns.map((item, index) => { - return ; - })} - - - - {items.map((item, index) => { - return (); - })} - -
- - {item.text}
- {!this.props.isAdmin && - - } -
- ); - - return items.length ? table : emptyTip; - } - } -} - -class Item extends Component { - - constructor(props) { - super(props); - this.state = { - isOpIconShown: false, - highlight: false, - isSetQuotaDialogOpen: false, - isDeleteUserDialogOpen: false, - isResetUserPasswordDialogOpen: false, - isRevokeAdminDialogOpen: false - }; - } - - handleMouseEnter = () => { - if (!this.props.isItemFreezed) { - this.setState({ - isOpIconShown: true, - highlight: true - }); - } - } - - handleMouseLeave = () => { - if (!this.props.isItemFreezed) { - this.setState({ - isOpIconShown: false, - highlight: false - }); - } - } - - onUnfreezedItem = () => { - this.setState({ - highlight: false, - isOpIconShow: false - }); - this.props.onUnfreezedItem(); - } - - toggleSetQuotaDialog = () => { - this.setState({isSetQuotaDialogOpen: !this.state.isSetQuotaDialogOpen}); - } - - toggleDeleteUserDialog = () => { - this.setState({isDeleteUserDialogOpen: !this.state.isDeleteUserDialogOpen}); - } - - toggleResetUserPasswordDialog = () => { - this.setState({isResetUserPasswordDialogOpen: !this.state.isResetUserPasswordDialogOpen}); - } - - toggleRevokeAdminDialog = () => { - this.setState({isRevokeAdminDialogOpen: !this.state.isRevokeAdminDialogOpen}); - } - - onUserSelected = () => { - this.props.onUserSelected(this.props.item); - } - - updateStatus= (value) => { - const isActive = value == 'active'; - if (isActive) { - toaster.notify(gettext('It may take some time, please wait.')); - } - this.props.updateUser(this.props.item.email, 'is_active', isActive); - } - - updateRole = (value) => { - this.props.updateUser(this.props.item.email, 'role', value); - } - - updateAdminRole = (value) => { - this.props.updateAdminRole(this.props.item.email, value); - } - - translateAdminRole = (role) => { - switch (role) { - case 'default_admin': - return gettext('Default Admin'); - case 'system_admin': - return gettext('System Admin'); - case 'daily_admin': - return gettext('Daily Admin'); - case 'audit_admin': - return gettext('Audit Admin'); - default: - return role; - } - } - - updateInstitution = (value) => { - this.props.updateUser(this.props.item.email, 'institution', value); - } - - translateInstitution = (inst) => { - return inst; - } - - updateQuota = (value) => { - this.props.updateUser(this.props.item.email, 'quota_total', value); - } - - deleteUser = () => { - this.props.deleteUser(this.props.item.email); - } - - resetPassword = () => { - seafileAPI.sysAdminResetUserPassword(this.props.item.email).then(res => { - toaster.success(res.data.reset_tip); - }).catch((error) => { - let errMessage = Utils.getErrorMsg(error); - toaster.danger(errMessage); - }); - } - - revokeAdmin = () => { - const { item } = this.props; - this.props.revokeAdmin(item.email, item.name); - } - - getMenuOperations = () => { - const { isAdmin, isLDAPImported } = this.props; - let list = ['Delete']; - if (!isLDAPImported) { - list.push('Reset Password'); - } - if (isAdmin) { - list = ['Revoke Admin']; - } - return list; - } - - translateOperations = (item) => { - let translateResult = ''; - switch (item) { - case 'Delete': - translateResult = gettext('Delete'); - break; - case 'Reset Password': - translateResult = gettext('Reset Password'); - break; - case 'Revoke Admin': - translateResult = gettext('Revoke Admin'); - break; - } - - return translateResult; - } - - onMenuItemClick = (operation) => { - switch(operation) { - case 'Delete': - this.toggleDeleteUserDialog(); - break; - case 'Reset Password': - this.toggleResetUserPasswordDialog(); - break; - case 'Revoke Admin': - this.toggleRevokeAdminDialog(); - break; - default: - break; - } - } - - render() { - const { item, isAdmin } = this.props; - const { - isOpIconShown, - isSetQuotaDialogOpen, - isDeleteUserDialogOpen, - isResetUserPasswordDialogOpen, - isRevokeAdminDialogOpen - } = this.state; - - const itemName = '' + Utils.HTMLescape(item.name) + ''; - const deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', itemName); - const resetPasswordDialogMsg = gettext('Are you sure you want to reset the password of {placeholder} ?').replace('{placeholder}', itemName); - const revokeAdminDialogMsg = gettext('Are you sure you want to revoke the admin permission of {placeholder} ?').replace('{placeholder}', itemName); - - return ( - - - - - - - - {item.contact_email && - -
- {item.contact_email} -
} - {item.org_id && - -
- ({item.org_name}) -
- } - - - - - {isPro && - - {isAdmin ? - : - - } - - } - - {`${Utils.bytesToSize(item.quota_usage)} / ${item.quota_total > 0 ? Utils.bytesToSize(item.quota_total) : '--'}`} - - - - {(multiInstitution && !isAdmin) && - - 0} - options={institutions} - currentOption={item.institution} - onOptionChanged={this.updateInstitution} - translateOption={this.translateInstitution} - /> - - } - - {`${item.create_time ? moment(item.create_time).format('YYYY-MM-DD HH:mm') : '--'} /`} -
- {`${item.last_login ? moment(item.last_login).fromNow() : '--'}`} - - - {(item.email != username && isOpIconShown) && - - } - - - {isSetQuotaDialogOpen && - - } - {isDeleteUserDialogOpen && - - } - {isResetUserPasswordDialogOpen && - - } - {isRevokeAdminDialogOpen && - - } -
- ); - } -} +import Content from './users-content'; +const { availableRoles } = window.sysadmin.pageOptions; class Users extends Component { @@ -840,6 +406,21 @@ class Users extends Component { }); } + getSearch = () => { + if (this.props.isAdmin) { + return null; + } + // offer 'Search' for 'DB' & 'LDAPImported' users + return ; + } + + searchItems = (keyword) => { + navigate(`${siteRoot}sys/search-users/?query=${encodeURIComponent(keyword)}`); + } + render() { const { isAdmin, isLDAPImported } = this.props; const { @@ -852,7 +433,7 @@ class Users extends Component { } = this.state; return ( - + {hasUserSelected ? diff --git a/seahub/urls.py b/seahub/urls.py index 0d85b8280a..94e11de86d 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -717,6 +717,7 @@ urlpatterns = [ url(r'^sys/departments/$', sysadmin_react_fake_view, name="sys_departments"), url(r'^sys/departments/(?P\d+)/$', sysadmin_react_fake_view, name="sys_department"), url(r'^sys/users/$', sysadmin_react_fake_view, name="sys_users"), + url(r'^sys/search-users/$', sysadmin_react_fake_view, name="sys_search_users"), url(r'^sys/users/admins/$', sysadmin_react_fake_view, name="sys_users_admin"), url(r'^sys/users/ldap/$', sysadmin_react_fake_view, name="sys_users_ldap"), url(r'^sys/users/ldap-imported/$', sysadmin_react_fake_view, name="sys_users_ldap_imported"),