diff --git a/frontend/src/app.js b/frontend/src/app.js index 4799f72070..ebc30d5ca0 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -157,19 +157,6 @@ class App extends Component { } }; - onGroupChanged = (groupID) => { - setTimeout(function () { - let url; - if (groupID) { - url = siteRoot + 'group/' + groupID + '/'; - } - else { - url = siteRoot + 'libraries/'; - } - window.location = url.toString(); - }, 1); - }; - tabItemClick = (tabName, groupID) => { let pathPrefix = []; if (groupID || this.dirViewPanels.indexOf(tabName) > -1) { @@ -343,7 +330,7 @@ class App extends Component { - + diff --git a/frontend/src/components/dialog/dismiss-group-dialog.js b/frontend/src/components/dialog/dismiss-group-dialog.js index cb8825172f..fe26b696fa 100644 --- a/frontend/src/components/dialog/dismiss-group-dialog.js +++ b/frontend/src/components/dialog/dismiss-group-dialog.js @@ -9,14 +9,11 @@ import SeahubModalHeader from '@/components/common/seahub-modal-header'; class DismissGroupDialog extends React.Component { - constructor(props) { - super(props); - } - dismissGroup = () => { - let that = this; - seafileAPI.deleteGroup(this.props.groupID).then((res) => { - that.props.onGroupChanged(); + const { groupID } = this.props; + seafileAPI.deleteGroup(groupID).then((res) => { + this.props.onGroupDeleted(); + toaster.success(gettext('Group deleted')); }).catch(error => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); @@ -25,13 +22,13 @@ class DismissGroupDialog extends React.Component { render() { return ( - - {gettext('Delete Group')} + + {gettext('Delete Group')} {gettext('Really want to delete this group?')} - + @@ -40,11 +37,9 @@ class DismissGroupDialog extends React.Component { } const DismissGroupDialogPropTypes = { - showDismissGroupDialog: PropTypes.bool.isRequired, - toggleDismissGroupDialog: PropTypes.func.isRequired, - loadGroup: PropTypes.func.isRequired, - groupID: PropTypes.string, - onGroupChanged: PropTypes.func.isRequired, + groupID: PropTypes.number.isRequired, + toggleDialog: PropTypes.func.isRequired, + onGroupDeleted: PropTypes.func.isRequired }; DismissGroupDialog.propTypes = DismissGroupDialogPropTypes; diff --git a/frontend/src/components/dialog/group-members-dialog.js b/frontend/src/components/dialog/group-members-dialog.js index 0d845e8d6d..535177c915 100644 --- a/frontend/src/components/dialog/group-members-dialog.js +++ b/frontend/src/components/dialog/group-members-dialog.js @@ -9,7 +9,7 @@ import SeahubModalHeader from '@/components/common/seahub-modal-header'; import Loading from '../loading'; const propTypes = { - groupID: PropTypes.string.isRequired, + groupID: PropTypes.number.isRequired, toggleDialog: PropTypes.func.isRequired }; diff --git a/frontend/src/components/dialog/import-members-dialog.js b/frontend/src/components/dialog/import-members-dialog.js index 7547d3f02e..abeb08cacb 100644 --- a/frontend/src/components/dialog/import-members-dialog.js +++ b/frontend/src/components/dialog/import-members-dialog.js @@ -5,7 +5,7 @@ import { gettext, siteRoot, groupImportMembersExtraMsg } from '../../utils/const import SeahubModalHeader from '@/components/common/seahub-modal-header'; const propTypes = { - toggleImportMembersDialog: PropTypes.func.isRequired, + toggleDialog: PropTypes.func.isRequired, importMembersInBatch: PropTypes.func.isRequired, }; @@ -19,7 +19,7 @@ class ImportMembersDialog extends React.Component { } toggle = () => { - this.props.toggleImportMembersDialog(); + this.props.toggleDialog(); }; openFileInput = () => { @@ -49,9 +49,8 @@ class ImportMembersDialog extends React.Component { return ( {gettext('Import members from a .xlsx file')} - -

{groupImportMembersExtraMsg}

+ {groupImportMembersExtraMsg &&

{groupImportMembersExtraMsg}

}

{gettext('Download an example file')}

diff --git a/frontend/src/components/dialog/leave-group-dialog.js b/frontend/src/components/dialog/leave-group-dialog.js index 1f30156cec..a80d4e939a 100644 --- a/frontend/src/components/dialog/leave-group-dialog.js +++ b/frontend/src/components/dialog/leave-group-dialog.js @@ -9,13 +9,10 @@ import SeahubModalHeader from '@/components/common/seahub-modal-header'; class LeaveGroupDialog extends React.Component { - constructor(props) { - super(props); - } - leaveGroup = () => { - seafileAPI.quitGroup(this.props.groupID, username).then((res) => { - this.props.onGroupChanged(); + const { groupID } = this.props; + seafileAPI.quitGroup(groupID, username).then((res) => { + this.props.onLeavingGroup(); }).catch(error => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); @@ -24,13 +21,13 @@ class LeaveGroupDialog extends React.Component { render() { return ( - - {gettext('Leave Group')} + + {gettext('Leave Group')}

{gettext('Really want to leave this group?')}

- +
@@ -39,9 +36,9 @@ class LeaveGroupDialog extends React.Component { } const LeaveGroupDialogPropTypes = { - toggleLeaveGroupDialog: PropTypes.func.isRequired, - groupID: PropTypes.string, - onGroupChanged: PropTypes.func.isRequired, + groupID: PropTypes.number.isRequired, + onLeavingGroup: PropTypes.func.isRequired, + toggleDialog: PropTypes.func.isRequired }; LeaveGroupDialog.propTypes = LeaveGroupDialogPropTypes; diff --git a/frontend/src/components/dialog/manage-members-dialog.js b/frontend/src/components/dialog/manage-members-dialog.js index cea7a01631..8b3b4c7dbc 100644 --- a/frontend/src/components/dialog/manage-members-dialog.js +++ b/frontend/src/components/dialog/manage-members-dialog.js @@ -8,7 +8,7 @@ import SeahubModalHeader from '@/components/common/seahub-modal-header'; import '../../css/manage-members-dialog.css'; const propTypes = { - groupID: PropTypes.string, + groupID: PropTypes.number.isRequired, isOwner: PropTypes.bool.isRequired, toggleManageMembersDialog: PropTypes.func, toggleDepartmentDetailDialog: PropTypes.func, diff --git a/frontend/src/components/dialog/org-set-group-quota-dialog.js b/frontend/src/components/dialog/org-set-group-quota-dialog.js index d4890b43d9..440f304ae3 100644 --- a/frontend/src/components/dialog/org-set-group-quota-dialog.js +++ b/frontend/src/components/dialog/org-set-group-quota-dialog.js @@ -9,7 +9,7 @@ import toaster from '../toast'; const propTypes = { toggle: PropTypes.func.isRequired, - groupID: PropTypes.number.isRequired, + group: PropTypes.object.isRequired, onSetQuota: PropTypes.func.isRequired, }; @@ -29,7 +29,7 @@ class SetGroupQuotaDialog extends React.Component { if ((quota.length && myReg.test(quota)) || quota == -2) { this.setState({ errMessage: '' }); let newQuota = this.state.quota == -2 ? this.state.quota : this.state.quota * 1000000; - orgAdminAPI.orgAdminSetGroupQuota(orgID, this.props.groupID, newQuota).then((res) => { + orgAdminAPI.orgAdminSetGroupQuota(orgID, this.props.group.id, newQuota).then((res) => { this.props.toggle(); this.props.onSetQuota(res.data); }).catch(error => { @@ -55,10 +55,15 @@ class SetGroupQuotaDialog extends React.Component { }; render() { + const group = this.props.group; + const oldQuota = Utils.bytesToSize(group.quota); + const message = gettext('The current quota for {group_name} is {quota}').replace('{group_name}', group.name).replace('{quota}', oldQuota); return ( {gettext('Set Quota')} +

{message}

+

{gettext('Please enter a new quota')}

{ - let name = this.state.newGroupName.trim(); - if (name) { - let that = this; - seafileAPI.renameGroup(this.props.groupID, name).then((res) => { - that.props.loadGroup(this.props.groupID); - that.props.onGroupChanged(res.data.id); - }).catch(error => { - let errMessage = Utils.getErrorMsg(error); - toaster.danger(errMessage); - }); - } - this.setState({ - newGroupName: '', + handleSubmit = () => { + const { groupID } = this.props; + const { newGroupName } = this.state; + seafileAPI.renameGroup(groupID, newGroupName.trim()).then((res) => { + const { name } = res.data; + this.props.onGroupNameChanged(name); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); }); - this.props.toggleRenameGroupDialog(); + this.props.toggleDialog(); }; handleKeyDown = (event) => { if (event.keyCode === 13) { - this.renameGroup(); + this.handleSubmit(); } }; render() { return ( - - {gettext('Rename Group')} + + {gettext('Rename Group')} - + - - + + ); @@ -79,12 +73,10 @@ class RenameGroupDialog extends React.Component { } const RenameGroupDialogPropTypes = { - showRenameGroupDialog: PropTypes.bool.isRequired, - toggleRenameGroupDialog: PropTypes.func.isRequired, - loadGroup: PropTypes.func.isRequired, - groupID: PropTypes.string, - onGroupChanged: PropTypes.func.isRequired, - currentGroupName: PropTypes.string.isRequired, + toggleDialog: PropTypes.func.isRequired, + groupID: PropTypes.number, + onGroupNameChanged: PropTypes.func.isRequired, + groupName: PropTypes.string.isRequired, }; RenameGroupDialog.propTypes = RenameGroupDialogPropTypes; diff --git a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-set-group-quota-dialog.js b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-set-group-quota-dialog.js index a2d6088753..112b0ca708 100644 --- a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-set-group-quota-dialog.js +++ b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-set-group-quota-dialog.js @@ -9,7 +9,7 @@ import SeahubModalHeader from '@/components/common/seahub-modal-header'; const propTypes = { toggle: PropTypes.func.isRequired, - groupID: PropTypes.number.isRequired, + group: PropTypes.object.isRequired, onSetQuota: PropTypes.func.isRequired, }; @@ -29,7 +29,7 @@ class SetGroupQuotaDialog extends React.Component { if ((quota.length && numberReg.test(quota)) || quota == -2) { this.setState({ errMessage: '' }); let newQuota = this.state.quota == -2 ? this.state.quota : this.state.quota * 1000000; - systemAdminAPI.sysAdminUpdateDepartmentQuota(this.props.groupID, newQuota).then((res) => { + systemAdminAPI.sysAdminUpdateDepartmentQuota(this.props.group.id, newQuota).then((res) => { this.props.toggle(); this.props.onSetQuota(res.data); }).catch(error => { @@ -55,10 +55,15 @@ class SetGroupQuotaDialog extends React.Component { }; render() { + const group = this.props.group; + const oldQuota = Utils.bytesToSize(group.quota); + const message = gettext('The current quota for {group_name} is {quota}').replace('{group_name}', group.name).replace('{quota}', oldQuota); return ( {gettext('Set Quota')} +

{message}

+

{gettext('Please enter a new quota')}

{ this.setState({ - selectedOption: option, - errMessage: '', + selectedOption: option }); - this.options = []; }; transferGroup = () => { @@ -42,19 +37,21 @@ class TransferGroupDialog extends React.Component { if (selectedOption && selectedOption[0]) { email = selectedOption[0].email; } - if (email) { - seafileAPI.transferGroup(this.props.groupID, email).then((res) => { - this.props.toggleTransferGroupDialog(); - toaster.success(gettext('Group has been transfered')); - }).catch((error) => { - let errMessage = Utils.getErrorMsg(error); - this.setState({ errMessage: errMessage }); - }); + if (!email) { + return false; } + seafileAPI.transferGroup(this.props.groupID, email).then((res) => { + toaster.success(gettext('Group has been transfered')); + this.props.onGroupTransfered(res.data); + this.props.toggleDialog(); + }).catch((error) => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); }; toggle = () => { - this.props.toggleTransferGroupDialog(); + this.props.toggleDialog(); }; render() { @@ -69,7 +66,6 @@ class TransferGroupDialog extends React.Component { placeholder={gettext('Please enter 1 or more character')} onSelectChange={this.handleSelectChange} /> -
{this.state.errMessage}
diff --git a/frontend/src/components/group-members.js b/frontend/src/components/group-members.js index 3f15bb6e1f..2bda3f81ac 100644 --- a/frontend/src/components/group-members.js +++ b/frontend/src/components/group-members.js @@ -10,7 +10,7 @@ import OpIcon from './op-icon'; const propTypes = { groupMembers: PropTypes.array.isRequired, - groupID: PropTypes.string, + groupID: PropTypes.number.isRequired, isOwner: PropTypes.bool.isRequired, isItemFreezed: PropTypes.bool.isRequired, toggleItemFreezed: PropTypes.func.isRequired, @@ -61,7 +61,7 @@ const MemberPropTypes = { changeMember: PropTypes.func.isRequired, deleteMember: PropTypes.func.isRequired, toggleItemFreezed: PropTypes.func.isRequired, - groupID: PropTypes.string, + groupID: PropTypes.number.isRequired, isOwner: PropTypes.bool.isRequired, isItemFreezed: PropTypes.bool.isRequired }; diff --git a/frontend/src/components/list-and-add-group-members.js b/frontend/src/components/list-and-add-group-members.js index 89c1a0a36d..f2f473087a 100644 --- a/frontend/src/components/list-and-add-group-members.js +++ b/frontend/src/components/list-and-add-group-members.js @@ -12,7 +12,7 @@ import GroupMembers from './group-members'; const propTypes = { toggleManageMembersDialog: PropTypes.func, toggleDepartmentDetailDialog: PropTypes.func, - groupID: PropTypes.string, + groupID: PropTypes.number.isRequired, isOwner: PropTypes.bool.isRequired }; diff --git a/frontend/src/components/single-selector.js b/frontend/src/components/single-selector.js index 2eab4daa13..e34fcb97a1 100644 --- a/frontend/src/components/single-selector.js +++ b/frontend/src/components/single-selector.js @@ -73,8 +73,8 @@ class Selector extends Component {
{customSelectorToggle ? customSelectorToggle : ( - {currentSelectedOption.text} - {isDropdownToggleShown && } + {currentSelectedOption ? currentSelectedOption.text : ''} + {isDropdownToggleShown && } )}
diff --git a/frontend/src/pages/groups/group-item.js b/frontend/src/pages/groups/group-item.js index 09e2079968..f35e3571a1 100644 --- a/frontend/src/pages/groups/group-item.js +++ b/frontend/src/pages/groups/group-item.js @@ -1,14 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { gettext, siteRoot, username } from '../../utils/constants'; +import { gettext, siteRoot } from '../../utils/constants'; import { seafileAPI } from '../../utils/seafile-api'; import { Utils } from '../../utils/utils'; import toaster from '../../components/toast'; import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view'; -import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar'; -import CreateRepoDialog from '../../components/dialog/create-repo-dialog'; -import Repo from '../../models/repo'; import { LIST_MODE } from '../../components/dir-view-mode/constants'; +import GroupOperationMenu from './group-op-menu'; const propTypes = { inAllLibs: PropTypes.bool, @@ -17,21 +15,18 @@ const propTypes = { onMonitorRepo: PropTypes.func, renameRelatedGroupsRepos: PropTypes.func, deleteRelatedGroupsRepos: PropTypes.func, - insertRepoIntoGroup: PropTypes.func, + addRepoToGroup: PropTypes.func, unshareRepoToGroup: PropTypes.func, onTransferRepo: PropTypes.func.isRequired, + onGroupNameChanged: PropTypes.func.isRequired, + onGroupTransfered: PropTypes.func.isRequired, + onGroupDeleted: PropTypes.func.isRequired, + onLeavingGroup: PropTypes.func.isRequired }; class GroupItem extends React.Component { - constructor(props) { - super(props); - this.state = { - isCreateRepoDialogOpen: false - }; - } - onItemUnshare = (repo) => { const { group } = this.props; const { id: group_id } = group; @@ -61,53 +56,45 @@ class GroupItem extends React.Component { this.props.onMonitorRepo(repo, monitored); }; - toggleCreateRepoDialog = () => { - this.setState({ - isCreateRepoDialogOpen: !this.state.isCreateRepoDialogOpen - }); - }; - - onCreateRepo = (repo) => { + addNewRepo = (newRepo) => { const { group } = this.props; const { id: group_id } = group; - seafileAPI.createGroupOwnedLibrary(group_id, repo).then(res => { - let object = { - repo_id: res.data.id, - repo_name: res.data.name, - owner_name: res.data.group_name, - owner_email: res.data.owner, - permission: res.data.permission, - mtime: res.data.mtime, - size: res.data.size, - encrypted: res.data.encrypted, - }; - const newRepo = new Repo(object); - this.props.insertRepoIntoGroup({ repo: newRepo, group_id }); - }).catch(error => { - let errMessage = Utils.getErrorMsg(error); - toaster.danger(errMessage); - }); - this.toggleCreateRepoDialog(); + this.props.addRepoToGroup({ repo: newRepo, group_id }); + }; + + onGroupNameChanged = (newName) => { + const { group } = this.props; + this.props.onGroupNameChanged(newName, group.id); + }; + + onGroupDeleted = () => { + const { group } = this.props; + this.props.onGroupDeleted(group.id); + }; + + onLeavingGroup = () => { + const { group } = this.props; + this.props.onLeavingGroup(group.id); }; render() { const { inAllLibs = false, group, currentViewMode = LIST_MODE } = this.props; - const { parent_group_id, admins } = group; const emptyTip =

{gettext('No libraries')}

; - const isDeptAdmin = parent_group_id != 0 && admins.indexOf(username) > -1; return (

{group.name} - {isDeptAdmin && - - } +

{group.repos.length === 0 ? @@ -127,13 +114,6 @@ class GroupItem extends React.Component { currentViewMode={currentViewMode} /> } - {this.state.isCreateRepoDialogOpen && - - }
); } diff --git a/frontend/src/pages/groups/group-op-menu.js b/frontend/src/pages/groups/group-op-menu.js new file mode 100644 index 0000000000..b387d39b91 --- /dev/null +++ b/frontend/src/pages/groups/group-op-menu.js @@ -0,0 +1,273 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { gettext, username, canAddRepo } from '../../utils/constants'; +import { seafileAPI } from '../../utils/seafile-api'; +import { Utils } from '../../utils/utils'; +import toaster from '../../components/toast'; +import { Repo } from '../../models'; +import CreateRepoDialog from '../../components/dialog/create-repo-dialog'; +import GroupMembersDialog from '../../components/dialog/group-members-dialog'; +import DismissGroupDialog from '../../components/dialog/dismiss-group-dialog'; +import RenameGroupDialog from '../../components/dialog/rename-group-dialog'; +import TransferGroupDialog from '../../components/dialog/transfer-group-dialog'; +import ImportMembersDialog from '../../components/dialog/import-members-dialog'; +import ManageMembersDialog from '../../components/dialog/manage-members-dialog'; +import DepartmentDetailDialog from '../../components/dialog/department-detail-dialog'; +import LeaveGroupDialog from '../../components/dialog/leave-group-dialog'; +import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar'; + +import '../../css/group-view.css'; + +const propTypes = { + group: PropTypes.object.isRequired, + addNewRepo: PropTypes.func.isRequired, + onGroupNameChanged: PropTypes.func.isRequired, + onGroupTransfered: PropTypes.func.isRequired, + onGroupDeleted: PropTypes.func.isRequired, + onLeavingGroup: PropTypes.func.isRequired +}; + +class GroupOperationMenu extends React.Component { + + constructor(props) { + super(props); + this.state = { + isCreateRepoDialogOpen: false, + isShowDepartmentDetailDialog: false, + isRenameGroupDialogOpen: false, + isDeleteGroupDialogOpen: false, + isTransferGroupDialogOpen: false, + isImportMembersDialogOpen: false, + isManageMembersDialogOpen: false, + isLeaveGroupDialogOpen: false, + isMembersDialogOpen: false + }; + } + + onCreateRepoToggle = () => { + this.setState({ isCreateRepoDialogOpen: !this.state.isCreateRepoDialogOpen }); + }; + + onCreateRepo = (repo, groupType) => { + const { group } = this.props; + const groupId = group.id; + if (groupType && groupType === 'department') { + seafileAPI.createGroupOwnedLibrary(groupId, repo).then(res => { + let object = { + repo_id: res.data.id, + repo_name: res.data.name, + owner_name: res.data.group_name, + owner_email: res.data.owner, + permission: res.data.permission, + mtime: res.data.mtime, + size: res.data.size, + encrypted: res.data.encrypted, + }; + const repo = new Repo(object); + this.props.addNewRepo(repo); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + + } else { + seafileAPI.createGroupRepo(groupId, repo).then(res => { + const repo = new Repo(res.data); + this.props.addNewRepo(repo); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + this.onCreateRepoToggle(); + }; + + toggleDeleteGroupDialog = () => { + this.setState({ + isDeleteGroupDialogOpen: !this.state.isDeleteGroupDialogOpen, + }); + }; + + toggleRenameGroupDialog = () => { + this.setState({ + isRenameGroupDialogOpen: !this.state.isRenameGroupDialogOpen, + }); + }; + + toggleTransferGroupDialog = () => { + this.setState({ + isTransferGroupDialogOpen: !this.state.isTransferGroupDialogOpen, + }); + }; + + toggleImportMembersDialog = () => { + this.setState({ + isImportMembersDialogOpen: !this.state.isImportMembersDialogOpen + }); + }; + + importMembersInBatch = (file) => { + toaster.notify(gettext('It may take some time, please wait.'), { 'id': 'importing-members' }); + const { group } = this.props; + seafileAPI.importGroupMembersViaFile(group.id, file).then((res) => { + res.data.success.forEach(item => { + toaster.success(gettext('Successfully imported {user_placeholder}').replace('{user_placeholder}', `${item.contact_email}`), { 'id': 'importing-members' }); + }); + res.data.failed.forEach(item => { + toaster.danger(`${item.email}: ${item.error_msg}`, { 'id': 'importing-members' }); + }); + }).catch((error) => { + let errMsg = Utils.getErrorMsg(error); + toaster.danger(errMsg); + }); + }; + + toggleManageMembersDialog = () => { + this.setState({ + isManageMembersDialogOpen: !this.state.isManageMembersDialogOpen, + }); + }; + + toggleLeaveGroupDialog = () => { + this.setState({ + isLeaveGroupDialogOpen: !this.state.isLeaveGroupDialogOpen, + }); + }; + + toggleMembersDialog = () => { + this.setState({ + isMembersDialogOpen: !this.state.isMembersDialogOpen + }); + }; + + toggleDepartmentDetailDialog = () => { + this.setState({ + isShowDepartmentDetailDialog: !this.state.isShowDepartmentDetailDialog + }); + }; + + getOpList = () => { + const { group } = this.props; + const isDepartment = group.parent_group_id !== 0; + const isStaff = group.admins.indexOf(username) > -1; + const isOwner = group.owner === username; + const opList = []; + if ((!isDepartment && canAddRepo) || + (isDepartment && isStaff)) { + this.newLibraryEnabled = true; + opList.push({ 'text': gettext('New Library'), 'onClick': this.onCreateRepoToggle }, 'Divider'); + } + opList.push({ 'text': gettext('Members'), 'onClick': this.toggleMembersDialog }); + if (isStaff || isOwner) { + opList.push({ 'text': gettext('Import members'), 'onClick': this.toggleImportMembersDialog }); + opList.push({ 'text': gettext('Manage members'), 'onClick': this.toggleManageMembersDialog }); + opList.push('Divider'); + opList.push({ 'text': gettext('Rename'), 'onClick': this.toggleRenameGroupDialog }); + if (isOwner) { + opList.push({ 'text': gettext('Transfer'), 'onClick': this.toggleTransferGroupDialog }); + } + if (isOwner) { + opList.push({ 'text': gettext('Delete group'), 'onClick': this.toggleDeleteGroupDialog }); + } + } + + if (!isOwner && !isDepartment) { + opList.push({ 'text': gettext('Leave group'), 'onClick': this.toggleLeaveGroupDialog }); + } + + return opList; + }; + + + render() { + const { + isCreateRepoDialogOpen, + isMembersDialogOpen + } = this.state; + const { group } = this.props; + const { id: groupID, parent_group_id, owner } = group; + const isDepartment = parent_group_id !== 0; + const isOwner = owner === username; + + const opList = this.getOpList(); + return ( + + {group && ( + + )} + {isCreateRepoDialogOpen && + + } + {isMembersDialogOpen && + + } + {this.state.isRenameGroupDialogOpen && + + } + {this.state.isDeleteGroupDialogOpen && + + } + {this.state.isTransferGroupDialogOpen && + + } + {this.state.isImportMembersDialogOpen && + + } + {this.state.isManageMembersDialogOpen && + + } + {this.state.isShowDepartmentDetailDialog && + + } + {this.state.isLeaveGroupDialogOpen && + + } + + ); + } +} + +GroupOperationMenu.propTypes = propTypes; + +export default GroupOperationMenu; diff --git a/frontend/src/pages/groups/group-view.js b/frontend/src/pages/groups/group-view.js index 92e386d36f..2e023ec14d 100644 --- a/frontend/src/pages/groups/group-view.js +++ b/frontend/src/pages/groups/group-view.js @@ -2,36 +2,25 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import cookie from 'react-cookies'; import classnames from 'classnames'; -import { gettext, username, canAddRepo, isMultiTenancy } from '../../utils/constants'; +import { navigate } from '@gatsbyjs/reach-router'; +import { gettext, siteRoot, username } from '../../utils/constants'; import { seafileAPI } from '../../utils/seafile-api'; import { Utils } from '../../utils/utils'; import Loading from '../../components/loading'; import EmptyTip from '../../components/empty-tip'; -import ModalPortal from '../../components/modal-portal'; import toaster from '../../components/toast'; import { Group, Repo } from '../../models'; -import CreateRepoDialog from '../../components/dialog/create-repo-dialog'; -import GroupMembersDialog from '../../components/dialog/group-members-dialog'; -import DismissGroupDialog from '../../components/dialog/dismiss-group-dialog'; -import RenameGroupDialog from '../../components/dialog/rename-group-dialog'; -import TransferGroupDialog from '../../components/dialog/transfer-group-dialog'; -import ImportMembersDialog from '../../components/dialog/import-members-dialog'; -import ManageMembersDialog from '../../components/dialog/manage-members-dialog'; -import DepartmentDetailDialog from '../../components/dialog/department-detail-dialog'; -import LeaveGroupDialog from '../../components/dialog/leave-group-dialog'; import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view'; import SortOptionsDialog from '../../components/dialog/sort-options'; -import GroupInviteMembersDialog from '../../components/dialog/group-invite-members-dialog'; -import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar'; import ViewModes from '../../components/view-modes'; import ReposSortMenu from '../../components/sort-menu'; import { LIST_MODE } from '../../components/dir-view-mode/constants'; +import GroupOperationMenu from './group-op-menu'; import '../../css/group-view.css'; const propTypes = { - onGroupChanged: PropTypes.func.isRequired, - groupID: PropTypes.string, + groupID: PropTypes.string }; class GroupView extends React.Component { @@ -44,9 +33,6 @@ class GroupView extends React.Component { errMessage: '', emptyTip: null, currentGroup: null, - currentRepo: null, - isStaff: false, - isOwner: false, currentViewMode: localStorage.getItem('sf_repo_list_view_mode') || LIST_MODE, sortBy: cookie.load('seafile-repo-dir-sort-by') || 'name', // 'name' or 'time' or 'size' sortOrder: cookie.load('seafile-repo-dir-sort-order') || 'asc', // 'asc' or 'desc' @@ -55,20 +41,7 @@ class GroupView extends React.Component { currentPage: 1, perPage: 300, hasNextPage: false, - libraryType: 'group', - isCreateRepoDialogShow: false, isDepartmentGroup: false, - isShowDepartmentDetailDialog: false, - showGroupDropdown: false, - showGroupMembersPopover: false, - showRenameGroupDialog: false, - showDismissGroupDialog: false, - showTransferGroupDialog: false, - showImportMembersDialog: false, - showManageMembersDialog: false, - showInviteMembersDialog: false, - isLeaveGroupDialogOpen: false, - isMembersDialogOpen: false }; } @@ -88,9 +61,7 @@ class GroupView extends React.Component { this.setState({ emptyTip: this.getEmptyTip(currentGroup), currentGroup, - isStaff: currentGroup.admins.indexOf(username) > -1, // for item operations isDepartmentGroup: currentGroup.parent_group_id !== 0, - isOwner: currentGroup.owner === username, currentPage: 1, repoList: [] // empty it for the current group }, () => { @@ -162,45 +133,6 @@ class GroupView extends React.Component { return null; }; - onCreateRepoToggle = () => { - this.setState({ isCreateRepoDialogShow: !this.state.isCreateRepoDialogShow }); - }; - - onCreateRepo = (repo, groupOwnerType) => { - let groupId = this.props.groupID; - if (groupOwnerType && groupOwnerType === 'department') { - seafileAPI.createGroupOwnedLibrary(groupId, repo).then(res => { // need modify endpoint api - let object = { - repo_id: res.data.id, - repo_name: res.data.name, - owner_name: res.data.group_name, - owner_email: res.data.owner, - permission: res.data.permission, - mtime: res.data.mtime, - size: res.data.size, - encrypted: res.data.encrypted, - }; - let repo = new Repo(object); - let repoList = this.addRepoItem(repo); - this.setState({ repoList: repoList }); - }).catch(error => { - let errMessage = Utils.getErrorMsg(error); - toaster.danger(errMessage); - }); - - } else { - seafileAPI.createGroupRepo(groupId, repo).then(res => { - let repo = new Repo(res.data); - let repoList = this.addRepoItem(repo); - this.setState({ repoList: repoList }); - }).catch(error => { - let errMessage = Utils.getErrorMsg(error); - toaster.danger(errMessage); - }); - } - this.onCreateRepoToggle(); - }; - onItemDelete = (repo) => { let repoList = this.state.repoList.filter(item => { return item.repo_id !== repo.repo_id; @@ -217,12 +149,6 @@ class GroupView extends React.Component { this.loadGroup(this.props.groupID); }; - addRepoItem = (repo) => { - let newRepoList = this.state.repoList.map(item => {return item;}); - newRepoList.unshift(repo); - return newRepoList; - }; - onItemUnshare = (repo) => { let group = this.state.currentGroup; seafileAPI.unshareRepoToGroup(repo.repo_id, group.id).then(() => { @@ -262,66 +188,6 @@ class GroupView extends React.Component { this.setState({ repoList: repoList }); }; - toggleDismissGroupDialog = () => { - this.setState({ - showDismissGroupDialog: !this.state.showDismissGroupDialog, - showGroupDropdown: false, - }); - }; - - toggleRenameGroupDialog = () => { - this.setState({ - showRenameGroupDialog: !this.state.showRenameGroupDialog, - showGroupDropdown: false, - }); - }; - - toggleTransferGroupDialog = () => { - this.setState({ - showTransferGroupDialog: !this.state.showTransferGroupDialog, - showGroupDropdown: false, - }); - }; - - toggleImportMembersDialog = () => { - this.setState({ - showImportMembersDialog: !this.state.showImportMembersDialog - }); - }; - - toggleInviteMembersDialog = () => { - this.setState({ - showInviteMembersDialog: !this.state.showInviteMembersDialog, - showGroupDropdown: false, - }); - }; - - importMembersInBatch = (file) => { - toaster.notify(gettext('It may take some time, please wait.')); - seafileAPI.importGroupMembersViaFile(this.state.currentGroup.id, file).then((res) => { - res.data.failed.forEach(item => { - toaster.danger(`${item.email}: ${item.error_msg}`); - }); - }).catch((error) => { - let errMsg = Utils.getErrorMsg(error); - toaster.danger(errMsg); - }); - }; - - toggleManageMembersDialog = () => { - this.setState({ - showManageMembersDialog: !this.state.showManageMembersDialog, - showGroupDropdown: false, - }); - }; - - toggleLeaveGroupDialog = () => { - this.setState({ - isLeaveGroupDialogOpen: !this.state.isLeaveGroupDialogOpen, - showGroupDropdown: false, - }); - }; - sortItems = (sortBy, sortOrder) => { cookie.save('seafile-repo-dir-sort-by', sortBy); cookie.save('seafile-repo-dir-sort-order', sortOrder); @@ -332,18 +198,6 @@ class GroupView extends React.Component { }); }; - translateRole = (role) => { - if (role === 'Admin') { - return gettext('Admin'); - } - else if (role === 'Member') { - return gettext('Member'); - } - else if (role === 'Owner') { - return gettext('Owner'); - } - }; - toggleSortOptionsDialog = () => { this.setState({ isSortOptionsDialogOpen: !this.state.isSortOptionsDialogOpen @@ -366,47 +220,6 @@ class GroupView extends React.Component { } }; - toggleMembersDialog = () => { - this.setState({ - isMembersDialogOpen: !this.state.isMembersDialogOpen - }); - }; - - getOpList = () => { - const { currentGroup, isDepartmentGroup, isStaff, isOwner } = this.state; - const opList = []; - if ((!isDepartmentGroup && canAddRepo) || - (isDepartmentGroup && isStaff)) { - this.newLibraryEnalbed = true; - opList.push({ 'text': gettext('New Library'), 'onClick': this.onCreateRepoToggle }, 'Divider'); - } - opList.push({ 'text': gettext('Members'), 'onClick': this.toggleMembersDialog }); - if (currentGroup) { - if (isStaff || isOwner) { - opList.push({ 'text': gettext('Import members'), 'onClick': this.toggleImportMembersDialog }); - opList.push({ 'text': gettext('Manage members'), 'onClick': this.toggleManageMembersDialog }); - opList.push('Divider'); - opList.push({ 'text': gettext('Rename'), 'onClick': this.toggleRenameGroupDialog }); - if (isOwner) { - opList.push({ 'text': gettext('Transfer'), 'onClick': this.toggleTransferGroupDialog }); - } - if (isOwner) { - opList.push({ 'text': gettext('Delete group'), 'onClick': this.toggleDismissGroupDialog }); - } - } - - if (!isOwner && !isDepartmentGroup) { - opList.push({ 'text': gettext('Leave group'), 'onClick': this.toggleLeaveGroupDialog }); - } - - if (isOwner && this.state.currentGroup.owner !== 'system admin' && !isMultiTenancy) { - opList.push({ 'text': gettext('Invite members'), 'onClick': this.toggleInviteMembersDialog }); - } - } - - return opList; - }; - switchViewMode = (newMode) => { this.setState({ currentViewMode: newMode @@ -422,16 +235,38 @@ class GroupView extends React.Component { }); }; - toggleDepartmentDetailDialog = () => { + addNewRepo = (newRepo) => { + let { repoList } = this.state; + repoList.unshift(newRepo); + this.setState({ repoList: repoList }); + }; + + onGroupNameChanged = (newName) => { + const { currentGroup } = this.state; + currentGroup.name = newName; this.setState({ - isShowDepartmentDetailDialog: !this.state.isShowDepartmentDetailDialog + currentGroup: currentGroup }); }; + onGroupTransfered = (group) => { + this.setState({ + currentGroup: group + }); + }; + + onGroupDeleted = () => { + navigate(siteRoot); + }; + + onLeavingGroup = () => { + navigate(siteRoot); + }; + render() { const { isLoading, repoList, errMessage, emptyTip, - currentGroup, isDepartmentGroup, isMembersDialogOpen, + currentGroup, isDepartmentGroup, currentViewMode, sortBy, sortOrder } = this.state; @@ -440,7 +275,6 @@ class GroupView extends React.Component { useRate = currentGroup.group_quota_usage / currentGroup.group_quota * 100 + '%'; } - const opList = this.getOpList(); return (
@@ -453,9 +287,13 @@ class GroupView extends React.Component { } {currentGroup.name} -
@@ -523,91 +361,6 @@ class GroupView extends React.Component {
- {this.state.isCreateRepoDialogShow && !this.state.isDepartmentGroup && ( - - - - )} - {this.state.isCreateRepoDialogShow && this.state.isDepartmentGroup && - - } - {isMembersDialogOpen && - - } - {this.state.showRenameGroupDialog && - - } - {this.state.showDismissGroupDialog && - - } - {this.state.showTransferGroupDialog && - - } - { this.state.showImportMembersDialog && - - } - {this.state.showManageMembersDialog && - - } - {this.state.isShowDepartmentDetailDialog && - - } - {this.state.isLeaveGroupDialogOpen && - - } - {this.state.showInviteMembersDialog && - - }
); } diff --git a/frontend/src/pages/libraries/index.js b/frontend/src/pages/libraries/index.js index 5d39a8b752..99b297631a 100644 --- a/frontend/src/pages/libraries/index.js +++ b/frontend/src/pages/libraries/index.js @@ -52,7 +52,7 @@ class Libraries extends Component { const eventBus = EventBus.getInstance(); this.unsubscribeAddNewGroup = eventBus.subscribe(EVENT_BUS_TYPE.ADD_NEW_GROUP, this.addNewGroup); - this.unsubscribeAddSharedRepoIntoGroup = eventBus.subscribe(EVENT_BUS_TYPE.ADD_SHARED_REPO_INTO_GROUP, this.insertRepoIntoGroup); + this.unsubscribeAddSharedRepoIntoGroup = eventBus.subscribe(EVENT_BUS_TYPE.ADD_SHARED_REPO_INTO_GROUP, this.addRepoToGroup); this.unsubscribeUnsharedRepoToGroup = eventBus.subscribe(EVENT_BUS_TYPE.UN_SHARE_REPO_TO_GROUP, this.unshareRepoToGroup); } @@ -282,7 +282,7 @@ class Libraries extends Component { }; */ - insertRepoIntoGroup = ({ repo, group_id }) => { + addRepoToGroup = ({ repo, group_id }) => { if (!repo) { return; } @@ -299,11 +299,37 @@ class Libraries extends Component { } targetGroup.repos.unshift(repo); - targetGroup.repos = Utils.sortRepos(targetGroup.repos, this.state.sortBy, this.state.sortOrder); this.groupsReposManager.add(repo.repo_id, group_id); this.setState({ groupList: newGroupList }); }; + onGroupNameChanged = (newName, groupID) => { + const { groupList } = this.state; + let newGroupList = [...groupList]; + let targetGroup = newGroupList.find((group) => group.id === groupID); + targetGroup.name = newName; + this.setState({ groupList: newGroupList }); + }; + + onGroupTransfered = (group) => { + const { groupList } = this.state; + let newGroupList = [...groupList]; + let targetGroup = newGroupList.find((item) => item.id === group.id); + targetGroup.owner = group.owner; + targetGroup.admins = group.admins; + this.setState({ groupList: newGroupList }); + }; + + onGroupDeleted = (groupID) => { + const { groupList } = this.state; + this.setState({ groupList: groupList.filter(item => item.id != groupID) }); + }; + + onLeavingGroup = (groupID) => { + const { groupList } = this.state; + this.setState({ groupList: groupList.filter(item => item.id != groupID) }); + }; + unshareRepoToGroup = ({ repo_id, group_id }) => { const { groupList } = this.state; let newGroupList = [...groupList]; @@ -498,7 +524,11 @@ class Libraries extends Component { onMonitorRepo={this.onMonitorRepo} renameRelatedGroupsRepos={this.renameRelatedGroupsRepos} deleteRelatedGroupsRepos={this.deleteRelatedGroupsRepos} - insertRepoIntoGroup={this.insertRepoIntoGroup} + addRepoToGroup={this.addRepoToGroup} + onGroupNameChanged={this.onGroupNameChanged} + onGroupTransfered={this.onGroupTransfered} + onGroupDeleted={this.onGroupDeleted} + onLeavingGroup={this.onLeavingGroup} unshareRepoToGroup={this.unshareRepoToGroup} onTransferRepo={this.onGroupTransferRepo} currentViewMode={currentViewMode} diff --git a/frontend/src/pages/org-admin/departments/department-node.js b/frontend/src/pages/org-admin/departments/department-node.js index 47175b51cc..6df5412017 100644 --- a/frontend/src/pages/org-admin/departments/department-node.js +++ b/frontend/src/pages/org-admin/departments/department-node.js @@ -6,6 +6,7 @@ class DepartmentNode { this.children = props.children || []; this.parentNode = props.parentNode || null; this.orgId = props.orgId || ''; + this.quota = props.quota || -2; } findNodeById(nodeId) { diff --git a/frontend/src/pages/org-admin/departments/department.js b/frontend/src/pages/org-admin/departments/department.js index 5d4dfc83ff..3433fefc16 100644 --- a/frontend/src/pages/org-admin/departments/department.js +++ b/frontend/src/pages/org-admin/departments/department.js @@ -132,6 +132,7 @@ class Department extends React.Component { toggleDelete(node)}> {gettext('Delete')} + toggleSetQuotaDialog(node)}> + {gettext('Set quota')} + {`${gettext('Department ID')} : ${node.id}`} @@ -38,6 +41,7 @@ DepartmentNodeMenu.propTypes = { toggleAddMembers: PropTypes.func.isRequired, toggleAddDepartment: PropTypes.func.isRequired, toggleAddLibrary: PropTypes.func.isRequired, + toggleSetQuotaDialog: PropTypes.func.isRequired, }; export default DepartmentNodeMenu; diff --git a/frontend/src/pages/org-admin/departments/departments-tree-panel.js b/frontend/src/pages/org-admin/departments/departments-tree-panel.js index 0fc6071df0..d660e0e9a3 100644 --- a/frontend/src/pages/org-admin/departments/departments-tree-panel.js +++ b/frontend/src/pages/org-admin/departments/departments-tree-panel.js @@ -9,6 +9,7 @@ const DepartmentsTreePanelPropTypes = { onChangeDepartment: PropTypes.func, listSubDepartments: PropTypes.func, toggleAddDepartment: PropTypes.func, + toggleSetQuotaDialog: PropTypes.func, toggleAddLibrary: PropTypes.func, toggleAddMembers: PropTypes.func, toggleRename: PropTypes.func, @@ -29,6 +30,7 @@ class DepartmentsTreePanel extends Component { onChangeDepartment={this.props.onChangeDepartment} listSubDepartments={this.props.listSubDepartments} toggleAddDepartment={this.props.toggleAddDepartment} + toggleSetQuotaDialog={this.props.toggleSetQuotaDialog} toggleAddLibrary={this.props.toggleAddLibrary} toggleAddMembers={this.props.toggleAddMembers} toggleRename={this.props.toggleRename} diff --git a/frontend/src/pages/org-admin/departments/departments.js b/frontend/src/pages/org-admin/departments/departments.js index 30978e4194..6f2e58bd7a 100644 --- a/frontend/src/pages/org-admin/departments/departments.js +++ b/frontend/src/pages/org-admin/departments/departments.js @@ -4,6 +4,7 @@ import { Utils } from '../../../utils/utils'; import { gettext, orgID } from '../../../utils/constants'; import { orgAdminAPI } from '../../../utils/org-admin-api'; import toaster from '../../../components/toast'; +import SetGroupQuotaDialog from '../../../components/dialog/org-set-group-quota-dialog'; import AddDepartmentDialog from '../../../components/dialog/sysadmin-dialog/add-department-v2-dialog'; import AddDepartMemberDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-depart-member-v2-dialog'; import RenameDepartmentDialog from '../../../components/dialog/sysadmin-dialog/rename-department-v2-dialog'; @@ -30,6 +31,7 @@ class Departments extends React.Component { isRenameDepartmentDialogShow: false, isDeleteDepartmentDialogShow: false, isShowAddRepoDialog: false, + isSetQuotaDialogShow: false, membersList: [], isTopDepartmentLoading: false, isMembersListLoading: false, @@ -48,6 +50,7 @@ class Departments extends React.Component { id: item.id, name: item.name, orgId: item.org_id, + quota: item.quota, }); return node; }); @@ -102,6 +105,7 @@ class Departments extends React.Component { parentGroupId: department.parent_group_id, orgId: department.org_id, parentNode: node, + quota: department.quota, })); node.setChildren(childrenNodes); cb && cb(childrenNodes); @@ -285,6 +289,28 @@ class Departments extends React.Component { }); }; + toggleSetQuotaDialog = (node) => { + this.setState({ operateNode: node, isSetQuotaDialogShow: !this.state.isSetQuotaDialogShow }); + }; + + onSetQuota = (newNode) => { + const rootNodes = this.state.rootNodes.slice(0); + this._setQuota(rootNodes[0], newNode); + this.setState({ + rootNodes: rootNodes + }); + }; + + _setQuota = (node, newNode) => { + if (node.id === newNode.id) { + node.quota = newNode.quota; + } else { + node.children.forEach(child => { + this._setQuota(child, newNode); + }); + } + }; + render() { const { rootNodes, operateNode, checkedDepartmentId, isAddDepartmentDialogShow, isAddMembersDialogShow, @@ -308,6 +334,7 @@ class Departments extends React.Component { onChangeDepartment={this.onChangeDepartment} listSubDepartments={this.listSubDepartments} toggleAddDepartment={this.toggleAddDepartment} + toggleSetQuotaDialog={this.toggleSetQuotaDialog} toggleAddLibrary={this.toggleAddLibrary} toggleAddMembers={this.toggleAddMembers} toggleRename={this.toggleRename} @@ -328,6 +355,7 @@ class Departments extends React.Component { deleteGroup={this.deleteGroup} createGroup={this.createGroup} toggleAddDepartment={this.toggleAddDepartment} + toggleSetQuotaDialog={this.toggleSetQuotaDialog} toggleAddLibrary={this.toggleAddLibrary} toggleAddMembers={this.toggleAddMembers} toggleRename={this.toggleRename} @@ -384,6 +412,13 @@ class Departments extends React.Component { groupID={String(operateNode.id)} /> )} + {this.state.isSetQuotaDialogShow && + + } ); } diff --git a/frontend/src/pages/org-admin/departments/tree-node.js b/frontend/src/pages/org-admin/departments/tree-node.js index 8f81f78d33..673b140136 100644 --- a/frontend/src/pages/org-admin/departments/tree-node.js +++ b/frontend/src/pages/org-admin/departments/tree-node.js @@ -11,6 +11,7 @@ const departmentsV2TreeNodePropTypes = { listSubDepartments: PropTypes.func, onChangeDepartment: PropTypes.func, toggleAddDepartment: PropTypes.func, + toggleSetQuotaDialog: PropTypes.func, toggleAddLibrary: PropTypes.func, toggleAddMembers: PropTypes.func, toggleRename: PropTypes.func, @@ -85,6 +86,7 @@ class DepartmentsV2TreeNode extends Component { toggleRename={this.props.toggleRename} toggleDelete={this.props.toggleDelete} toggleAddLibrary={this.props.toggleAddLibrary} + toggleSetQuotaDialog={this.props.toggleSetQuotaDialog} /> ); }); @@ -151,6 +153,7 @@ class DepartmentsV2TreeNode extends Component { toggleAddDepartment={this.props.toggleAddDepartment} toggleAddLibrary={this.props.toggleAddLibrary} toggleAddMembers={this.props.toggleAddMembers} + toggleSetQuotaDialog={this.props.toggleSetQuotaDialog} toggleRename={this.props.toggleRename} toggleDelete={this.props.toggleDelete} /> diff --git a/frontend/src/pages/sys-admin/departments/department-node.js b/frontend/src/pages/sys-admin/departments/department-node.js index 47175b51cc..6df5412017 100644 --- a/frontend/src/pages/sys-admin/departments/department-node.js +++ b/frontend/src/pages/sys-admin/departments/department-node.js @@ -6,6 +6,7 @@ class DepartmentNode { this.children = props.children || []; this.parentNode = props.parentNode || null; this.orgId = props.orgId || ''; + this.quota = props.quota || -2; } findNodeById(nodeId) { diff --git a/frontend/src/pages/sys-admin/departments/department.js b/frontend/src/pages/sys-admin/departments/department.js index 7a7c0951e7..b727bf5742 100644 --- a/frontend/src/pages/sys-admin/departments/department.js +++ b/frontend/src/pages/sys-admin/departments/department.js @@ -155,6 +155,7 @@ class Department extends React.Component { toggleDelete(node)}> {gettext('Delete')} + toggleSetQuotaDialog(node)}> + {gettext('Set quota')} + {`${gettext('Department ID')} : ${node.id}`} @@ -38,6 +41,7 @@ DepartmentNodeMenu.propTypes = { toggleAddMembers: PropTypes.func.isRequired, toggleAddDepartment: PropTypes.func.isRequired, toggleAddLibrary: PropTypes.func.isRequired, + toggleSetQuotaDialog: PropTypes.func.isRequired, }; export default DepartmentNodeMenu; diff --git a/frontend/src/pages/sys-admin/departments/departments-tree-panel.js b/frontend/src/pages/sys-admin/departments/departments-tree-panel.js index 8deb67fabc..ef6bc6af83 100644 --- a/frontend/src/pages/sys-admin/departments/departments-tree-panel.js +++ b/frontend/src/pages/sys-admin/departments/departments-tree-panel.js @@ -9,6 +9,7 @@ const DepartmentsTreePanelPropTypes = { onChangeDepartment: PropTypes.func, listSubDepartments: PropTypes.func, toggleAddDepartment: PropTypes.func, + toggleSetQuotaDialog: PropTypes.func, toggleAddLibrary: PropTypes.func, toggleAddMembers: PropTypes.func, toggleRename: PropTypes.func, @@ -29,6 +30,7 @@ class DepartmentsTreePanel extends Component { onChangeDepartment={this.props.onChangeDepartment} listSubDepartments={this.props.listSubDepartments} toggleAddDepartment={this.props.toggleAddDepartment} + toggleSetQuotaDialog={this.props.toggleSetQuotaDialog} toggleAddLibrary={this.props.toggleAddLibrary} toggleAddMembers={this.props.toggleAddMembers} toggleRename={this.props.toggleRename} diff --git a/frontend/src/pages/sys-admin/departments/departments.js b/frontend/src/pages/sys-admin/departments/departments.js index 81ea27a2dd..89c1e1a0c9 100644 --- a/frontend/src/pages/sys-admin/departments/departments.js +++ b/frontend/src/pages/sys-admin/departments/departments.js @@ -5,6 +5,7 @@ import { Utils } from '../../../utils/utils'; import { systemAdminAPI } from '../../../utils/system-admin-api'; import Loading from '../../../components/loading'; import toaster from '../../../components/toast'; +import SetGroupQuotaDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-set-group-quota-dialog'; import AddDepartmentV2Dialog from '../../../components/dialog/sysadmin-dialog/add-department-v2-dialog'; import AddDepartMemberV2Dialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-depart-member-v2-dialog'; import RenameDepartmentV2Dialog from '../../../components/dialog/sysadmin-dialog/rename-department-v2-dialog'; @@ -25,6 +26,7 @@ class Departments extends React.Component { rootNodes: [], checkedDepartmentId: -1, operateNode: null, + isSetQuotaDialogShow: false, isAddDepartmentDialogShow: false, isAddMembersDialogShow: false, isRenameDepartmentDialogShow: false, @@ -51,6 +53,7 @@ class Departments extends React.Component { id: item.id, name: item.name, orgId: item.org_id, + quota: item.quota, }); return node; }); @@ -135,6 +138,7 @@ class Departments extends React.Component { parentGroupId: department.parent_group_id, orgId: department.org_id, parentNode: node, + quota: department.quota, })); node.setChildren(childrenNodes); cb && cb(childrenNodes); @@ -168,6 +172,10 @@ class Departments extends React.Component { this.setState({ operateNode: node, isAddDepartmentDialogShow: !this.state.isAddDepartmentDialogShow }); }; + toggleSetQuotaDialog = (node) => { + this.setState({ operateNode: node, isSetQuotaDialogShow: !this.state.isSetQuotaDialogShow }); + }; + toggleAddMembers = (node) => { this.setState({ operateNode: node, isAddMembersDialogShow: !this.state.isAddMembersDialogShow }); }; @@ -318,6 +326,24 @@ class Departments extends React.Component { }); }; + onSetQuota = (newNode) => { + const rootNodes = this.state.rootNodes.slice(0); + this._setQuota(rootNodes[0], newNode); + this.setState({ + rootNodes: rootNodes + }); + }; + + _setQuota = (node, newNode) => { + if (node.id === newNode.id) { + node.quota = newNode.quota; + } else { + node.children.forEach(child => { + this._setQuota(child, newNode); + }); + } + }; + render() { const { rootNodes, operateNode, checkedDepartmentId, isAddDepartmentDialogShow, isAddMembersDialogShow, @@ -341,6 +367,7 @@ class Departments extends React.Component { onChangeDepartment={this.onChangeDepartment} listSubDepartments={this.listSubDepartments} toggleAddDepartment={this.toggleAddDepartment} + toggleSetQuotaDialog={this.toggleSetQuotaDialog} toggleAddLibrary={this.toggleAddLibrary} toggleAddMembers={this.toggleAddMembers} toggleRename={this.toggleRename} @@ -361,6 +388,7 @@ class Departments extends React.Component { deleteGroup={this.deleteGroup} createGroup={this.createGroup} toggleAddDepartment={this.toggleAddDepartment} + toggleSetQuotaDialog={this.toggleSetQuotaDialog} toggleAddLibrary={this.toggleAddLibrary} toggleAddMembers={this.toggleAddMembers} toggleRename={this.toggleRename} @@ -419,6 +447,13 @@ class Departments extends React.Component { groupID={String(operateNode.id)} /> )} + {this.state.isSetQuotaDialogShow && + + } ); } diff --git a/frontend/src/pages/sys-admin/departments/tree-node.js b/frontend/src/pages/sys-admin/departments/tree-node.js index 91d11a2f5a..28408d05a0 100644 --- a/frontend/src/pages/sys-admin/departments/tree-node.js +++ b/frontend/src/pages/sys-admin/departments/tree-node.js @@ -11,6 +11,7 @@ const departmentsTreeNodePropTypes = { listSubDepartments: PropTypes.func, onChangeDepartment: PropTypes.func, toggleAddDepartment: PropTypes.func, + toggleSetQuotaDialog: PropTypes.func, toggleAddLibrary: PropTypes.func, toggleAddMembers: PropTypes.func, toggleRename: PropTypes.func, @@ -81,6 +82,7 @@ class DepartmentsTreeNode extends Component { checkedDepartmentId={this.props.checkedDepartmentId} listSubDepartments={this.props.listSubDepartments} toggleAddDepartment={this.props.toggleAddDepartment} + toggleSetQuotaDialog={this.props.toggleSetQuotaDialog} toggleAddMembers={this.props.toggleAddMembers} toggleRename={this.props.toggleRename} toggleDelete={this.props.toggleDelete} @@ -149,6 +151,7 @@ class DepartmentsTreeNode extends Component { { - this.props.getListByPage(this.props.currentPage - 1, this.props.is_active, this.props.role); + this.props.getListByPage(this.props.currentPage - 1); }; getNextPage = () => { - this.props.getListByPage(this.props.currentPage + 1, this.props.is_active, this.props.role); + this.props.getListByPage(this.props.currentPage + 1); }; sortByQuotaUsage = (e) => { @@ -164,14 +163,6 @@ class Content extends Component { return (
- {this.props.currentItem === 'database' && - - } {items.length ? table : }
); @@ -201,12 +192,7 @@ Content.propTypes = { onUserSelected: PropTypes.func, curPerPage: PropTypes.number, hasNextPage: PropTypes.bool, - sortOrder: PropTypes.string, - is_active: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - role: PropTypes.string, - currentItem: PropTypes.string, - onStatusChange: PropTypes.func, - onRoleChange: PropTypes.func + sortOrder: PropTypes.string }; class Item extends Component { diff --git a/frontend/src/pages/sys-admin/users/users-filter-bar.css b/frontend/src/pages/sys-admin/users/users-filter-bar.css index c11bd48102..78a2746cb1 100644 --- a/frontend/src/pages/sys-admin/users/users-filter-bar.css +++ b/frontend/src/pages/sys-admin/users/users-filter-bar.css @@ -1,29 +1,4 @@ -.users-filter-bar .users-filter-bar-dropdown-toggle { - height: 30px; - line-height: 30px; - padding-left: 8px; +.users-filter-bar .filter-item, +.users-filter-bar .cur-option { font-size: 14px; - border-radius: 5px; -} - -.users-filter-bar .users-filter-bar-dropdown-toggle .sf3-font-down { - color: #999; - margin-left: auto; - display: inline-flex; - justify-content: center; - align-items: center; - font-size: 12px; - width: 24px; - height: 24px; -} - -.users-filter-bar .users-filter-bar-dropdown-toggle:hover { - background-color: #f5f5f5; - cursor: pointer; -} - -.users-filter-bar .dropdown-menu .dropdown-item { - padding: 0.25rem 0.5rem; - display: flex; - justify-content: space-between; } diff --git a/frontend/src/pages/sys-admin/users/users-filter-bar.js b/frontend/src/pages/sys-admin/users/users-filter-bar.js index 64e2634c7c..d1689b8970 100644 --- a/frontend/src/pages/sys-admin/users/users-filter-bar.js +++ b/frontend/src/pages/sys-admin/users/users-filter-bar.js @@ -1,117 +1,83 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { Dropdown, DropdownToggle, DropdownItem, DropdownMenu } from 'reactstrap'; import { gettext } from '../../../utils/constants'; +import Selector from '../../../components/single-selector'; import './users-filter-bar.css'; const { availableRoles } = window.sysadmin.pageOptions; class UsersFilterBar extends Component { - constructor(props) { - super(props); - this.state = { - isStatusOpen: false, - isRoleOpen: false, - }; - } - - translateStatus = (status) => { - switch (status) { - case '0': - return gettext('Inactive'); - case '1': - return gettext('Active'); - default: - return gettext('All'); - } - }; - translateRole = (role) => { switch (role) { + case '': + return gettext('All'); case 'default': return gettext('Default'); case 'guest': return gettext('Guest'); default: - return gettext('All'); + return role; } }; - toggleStatusDropdown = () => { - this.setState({ isStatusOpen: !this.state.isStatusOpen }); + selectStatusOption = (option) => { + this.props.onStatusChange(option.value); }; - toggleRoleDropdown = () => { - this.setState({ isRoleOpen: !this.state.isRoleOpen }); - }; - - renderCheck = () => { - return ; + selectRoleOption = (option) => { + this.props.onRoleChange(option.value); }; render() { - const { onStatusChange, onRoleChange } = this.props; + const { isActive, role } = this.props; + + this.statusOptions = [ + { value: '', text: gettext('All') }, + { value: '1', text: gettext('Active') }, + { value: '0', text: gettext('Inactive') } + ].map(item => { + item.isSelected = isActive == item.value; + return item; + }); + const currentSelectedStatusOption = this.statusOptions.filter(item => item.isSelected)[0]; + + this.roleOptions = [''].concat(availableRoles).map(item => { + return { + value: item, + text: this.translateRole(item), + isSelected: item == role + }; + }); + const currentSelectedRoleOption = this.roleOptions.filter(item => item.isSelected)[0] || { // `|| {...}`: to be compatible with old data(roles not in the present `availableRoles` + value: role, + text: this.translateRole(role), + isSelected: true + }; + return ( -
- - - {gettext('Status')}{': '}{this.translateStatus(this.props.isActive)} - - - - { onStatusChange(''); }}> - {gettext('All')}{this.props.isActive === '' && this.renderCheck()} - - { onStatusChange('1'); }}> - {gettext('Active')}{this.props.isActive === '1' && this.renderCheck()} - - { onStatusChange('0'); }}> - {gettext('Inactive')}{this.props.isActive === '0' && this.renderCheck()} - - - - - - {gettext('Role')}{': '}{this.translateRole(this.props.role)} - - - - { onRoleChange(''); }}> - {gettext('All')} - {this.props.role === '' && this.renderCheck()} - - {availableRoles.map((item, index) => { - return ( - { onRoleChange(item); }}> - {this.translateRole(item)} - {this.props.role === item && this.renderCheck()} - - ); - })} - - +
+ {`${gettext('Status')}:`} + + + {`${gettext('Role')}:`} +
); } } UsersFilterBar.propTypes = { - loading: PropTypes.bool, - curPerPage: PropTypes.number, - sortBy: PropTypes.string, - currentPage: PropTypes.number, - sortOrder: PropTypes.string, onStatusChange: PropTypes.func, onRoleChange: PropTypes.func, role: PropTypes.string, diff --git a/frontend/src/pages/sys-admin/users/users.js b/frontend/src/pages/sys-admin/users/users.js index 0560ef5ac9..b63bef46e1 100644 --- a/frontend/src/pages/sys-admin/users/users.js +++ b/frontend/src/pages/sys-admin/users/users.js @@ -16,6 +16,7 @@ import SysAdminAdminUser from '../../../models/sysadmin-admin-user'; import MainPanelTopbar from '../main-panel-topbar'; import Search from '../search'; import UsersNav from './users-nav'; +import UsersFilterBar from './users-filter-bar'; import Content from './users-content'; const { availableRoles } = window.sysadmin.pageOptions; @@ -44,8 +45,8 @@ class Users extends Component { isBatchSetQuotaDialogOpen: false, isBatchDeleteUserDialogOpen: false, isBatchAddAdminDialogOpen: false, - is_active: null, - role: null, + is_active: '', + role: '' }; } @@ -59,15 +60,17 @@ class Users extends Component { sortBy = '', sortOrder = 'asc', is_active, - role, + role } = this.state; this.setState({ perPage: parseInt(urlParams.get('per_page') || perPage), currentPage: parseInt(urlParams.get('page') || currentPage), sortBy: urlParams.get('order_by') || sortBy, sortOrder: urlParams.get('direction') || sortOrder, + is_active: urlParams.get('is_active') || is_active, + role: urlParams.get('role') || role }, () => { - this.getUsersListByPage(this.state.currentPage, is_active, role); + this.getUsersListByPage(this.state.currentPage); }); } } @@ -164,8 +167,8 @@ class Users extends Component { }); }; - getUsersListByPage = (page, is_active, role) => { - const { perPage, sortBy, sortOrder } = this.state; + getUsersListByPage = (page) => { + const { perPage, sortBy, sortOrder, is_active, role } = this.state; const { isLDAPImported } = this.props; systemAdminAPI.sysAdminListUsers(page, perPage, isLDAPImported, sortBy, sortOrder, is_active, role).then(res => { let users = res.data.data.map(user => {return new SysAdminUser(user);}); @@ -183,11 +186,12 @@ class Users extends Component { }); }; - updateURL = (page, perPage) => { + updateURLSearchParams = (obj) => { let url = new URL(location.href); let searchParams = new URLSearchParams(url.search); - searchParams.set('page', page); - searchParams.set('per_page', perPage); + for (const key in obj) { + searchParams.set(key, obj[key]); + } url.search = searchParams.toString(); navigate(url.toString()); }; @@ -198,21 +202,28 @@ class Users extends Component { is_active: is_active, currentPage: 1 }, () => { - const { currentPage, perPage, is_active, role } = this.state; - this.updateURL(currentPage, perPage); - this.getUsersListByPage(currentPage, is_active, role); + const { currentPage, perPage } = this.state; + this.updateURLSearchParams({ + 'page': currentPage, + 'per_page': perPage, + 'is_active': is_active + }); + this.getUsersListByPage(currentPage); }); }; - // role: 'default', 'guest', '' onRoleChange = (role) => { this.setState({ role: role, currentPage: 1 }, () => { - const { currentPage, perPage, is_active, role } = this.state; - this.updateURL(currentPage, perPage); - this.getUsersListByPage(currentPage, is_active, role); + const { currentPage, perPage } = this.state; + this.updateURLSearchParams({ + 'page': currentPage, + 'per_page': perPage, + 'role': role + }); + this.getUsersListByPage(currentPage); }); }; @@ -222,15 +233,14 @@ class Users extends Component { sortOrder: this.state.sortOrder == 'asc' ? 'desc' : 'asc', currentPage: 1 }, () => { - let url = new URL(location.href); - let searchParams = new URLSearchParams(url.search); - const { currentPage, sortBy, sortOrder, is_active, role } = this.state; - searchParams.set('page', currentPage); - searchParams.set('order_by', sortBy); - searchParams.set('direction', sortOrder); - url.search = searchParams.toString(); - navigate(url.toString()); - this.getUsersListByPage(currentPage, is_active, role); + const { currentPage, perPage, sortBy, sortOrder } = this.state; + this.updateURLSearchParams({ + 'page': currentPage, + 'per_page': perPage, + 'order_by': sortBy, + 'direction': sortOrder + }); + this.getUsersListByPage(currentPage); }); }; @@ -346,7 +356,7 @@ class Users extends Component { this.setState({ perPage: perPage }, () => { - this.getUsersListByPage(1, this.state.is_active, this.state.role); + this.getUsersListByPage(1); }); }; @@ -471,6 +481,8 @@ class Users extends Component { render() { const { isAdmin, isLDAPImported } = this.props; const { + is_active, + role, hasUserSelected, isImportUserDialogOpen, isAddUserDialogOpen, @@ -478,6 +490,7 @@ class Users extends Component { isBatchSetQuotaDialogOpen, isBatchAddAdminDialogOpen } = this.state; + const curTab = this.getCurrentNavItem(); return ( @@ -491,8 +504,16 @@ class Users extends Component {
- +
+ {curTab == 'database' && + + }
diff --git a/scripts/setup-seafile-mysql.py b/scripts/setup-seafile-mysql.py index d956dafb0e..858a28a7b6 100644 --- a/scripts/setup-seafile-mysql.py +++ b/scripts/setup-seafile-mysql.py @@ -1259,6 +1259,9 @@ pidfile = os.path.join(pids_dir, 'seahub.pid') timeout = 1200 limit_request_line = 8190 + +# for forwarder headers +forwarder_headers = 'SCRIPT_NAME,PATH_INFO,REMOTE_USER' ''' text = template % dict(pids_dir=env_mgr.central_pids_dir,