diff --git a/frontend/src/components/dialog/change-group-dialog.js b/frontend/src/components/dialog/change-group-dialog.js new file mode 100644 index 0000000000..b30fc541ef --- /dev/null +++ b/frontend/src/components/dialog/change-group-dialog.js @@ -0,0 +1,49 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Button, Modal, ModalHeader, ModalFooter, ModalBody } from 'reactstrap'; +import { Utils } from '../../utils/utils'; +import { gettext } from '../../utils/constants'; + +const propTypes = { + groupName: PropTypes.string.isRequired, + changeGroup2Department: PropTypes.func.isRequired, + toggleDialog: PropTypes.func.isRequired +}; + +class ChangeGroupDialog extends React.Component { + + constructor(props) { + super(props); + this.state = { + selectedOption: null + }; + } + + submit = () => { + this.props.changeGroup2Department(); + this.props.toggleDialog(); + }; + + render() { + const groupName = '' + Utils.HTMLescape(this.props.groupName) + ''; + const msg = gettext('Are you sure to change Group {placeholder} to Department ?').replace('{placeholder}', groupName); + return ( + + + {gettext('Change group to departmen')} + + +

+
+ + + + +
+ ); + } +} + +ChangeGroupDialog.propTypes = propTypes; + +export default ChangeGroupDialog; diff --git a/frontend/src/pages/org-admin/org-groups.js b/frontend/src/pages/org-admin/org-groups.js index 02ad3f79e5..8970b4bae4 100644 --- a/frontend/src/pages/org-admin/org-groups.js +++ b/frontend/src/pages/org-admin/org-groups.js @@ -8,6 +8,8 @@ import { Utils } from '../../utils/utils'; import toaster from '../../components/toast'; import OrgGroupInfo from '../../models/org-group'; import MainPanelTopbar from './main-panel-topbar'; +import ChangeGroupDialog from '../../components/dialog/change-group-dialog'; +import { orgAdminAPI } from '../../utils/org-admin-api'; class Search extends React.Component { @@ -132,6 +134,24 @@ class OrgGroups extends Component { }); }; + changeGroupItem = (group) => { + orgAdminAPI.orgAdminGroup2Department(orgID, group.id).then(res => { + let newGroupList = this.state.orgGroups.map(item => { + if (item.id == group.id) { + item = new OrgGroupInfo(res.data); + } + return item; + }); + this.setState({ + orgGroups: newGroupList + }); + toaster.success(gettext('Successfully Change the group.')); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + }; + searchItems = (keyword) => { navigate(`${siteRoot}org/groupadmin/search-groups/?query=${encodeURIComponent(keyword)}`); }; @@ -173,6 +193,7 @@ class OrgGroups extends Component { onFreezedItem={this.onFreezedItem} onUnfreezedItem={this.onUnfreezedItem} deleteGroupItem={this.deleteGroupItem} + changeGroupItem={this.changeGroupItem} /> ); })} @@ -197,6 +218,7 @@ const GroupItemPropTypes = { onFreezedItem: PropTypes.func.isRequired, onUnfreezedItem: PropTypes.func.isRequired, deleteGroupItem: PropTypes.func.isRequired, + changeGroupItem: PropTypes.func.isRequired, }; class GroupItem extends React.Component { @@ -206,7 +228,8 @@ class GroupItem extends React.Component { this.state = { highlight: false, showMenu: false, - isItemMenuShow: false + isItemMenuShow: false, + isChangeDialogOpen: false, }; } @@ -253,6 +276,16 @@ class GroupItem extends React.Component { toggleDelete = () => { this.props.deleteGroupItem(this.props.group); }; + toggleChange = () => { + this.props.changeGroupItem(this.props.group); + }; + + toggleChangeDialog = (e) => { + if (e) { + e.preventDefault(); + } + this.setState({ isChangeDialogOpen: !this.state.isChangeDialogOpen }); + }; renderGroupHref = (group) => { let groupInfoHref; @@ -304,11 +337,19 @@ class GroupItem extends React.Component { /> {gettext('Delete')} + {gettext('Change to department')} } + {this.state.isChangeDialogOpen && + + } + ); } diff --git a/frontend/src/pages/sys-admin/groups/groups-content.js b/frontend/src/pages/sys-admin/groups/groups-content.js index aea759fc24..b12d8cebba 100644 --- a/frontend/src/pages/sys-admin/groups/groups-content.js +++ b/frontend/src/pages/sys-admin/groups/groups-content.js @@ -10,6 +10,7 @@ import Paginator from '../../../components/paginator'; import OpMenu from '../../../components/dialog/op-menu'; import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog'; import SysAdminTransferGroupDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-group-transfer-dialog'; +import ChangeGroupDialog from '../../../components/dialog/change-group-dialog'; import UserLink from '../user-link'; class Content extends Component { @@ -70,6 +71,7 @@ class Content extends Component { onUnfreezedItem={this.onUnfreezedItem} deleteGroup={this.props.deleteGroup} transferGroup={this.props.transferGroup} + changeGroup2Department={this.props.changeGroup2Department} />); })} @@ -102,6 +104,7 @@ Content.propTypes = { getListByPage: PropTypes.func.isRequired, deleteGroup: PropTypes.func.isRequired, transferGroup: PropTypes.func.isRequired, + changeGroup2Department: PropTypes.func.isRequired, }; class Item extends Component { @@ -112,7 +115,8 @@ class Item extends Component { isOpIconShown: false, highlight: false, isDeleteDialogOpen: false, - isTransferDialogOpen: false + isTransferDialogOpen: false, + isChangeDialogOpen: false }; } @@ -150,6 +154,9 @@ class Item extends Component { case 'Transfer': this.toggleTransferDialog(); break; + case 'Change': + this.toggleChangeDialog(); + break; default: break; } @@ -169,6 +176,12 @@ class Item extends Component { this.setState({ isTransferDialogOpen: !this.state.isTransferDialogOpen }); }; + toggleChangeDialog = (e) => { + if (e) { + e.preventDefault(); + } + this.setState({ isChangeDialogOpen: !this.state.isChangeDialogOpen }); + }; deleteGroup = () => { this.props.deleteGroup(this.props.item.id); }; @@ -177,6 +190,10 @@ class Item extends Component { this.props.transferGroup(this.props.item.id, receiver); }; + changeGroup = () => { + this.props.changeGroup2Department(this.props.item.id); + }; + translateOperations = (item) => { let translateResult = ''; switch (item) { @@ -186,6 +203,9 @@ class Item extends Component { case 'Transfer': translateResult = gettext('Transfer'); break; + case 'Change': + translateResult = gettext('Change to department'); + break; } return translateResult; @@ -193,7 +213,7 @@ class Item extends Component { render() { const { item } = this.props; - const { isOpIconShown, isDeleteDialogOpen, isTransferDialogOpen } = this.state; + const { isOpIconShown, isDeleteDialogOpen, isTransferDialogOpen, isChangeDialogOpen } = this.state; let groupName = '' + Utils.HTMLescape(item.name) + ''; let deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', groupName); @@ -218,7 +238,7 @@ class Item extends Component { {(isOpIconShown && item.owner != 'system admin') && } + {isChangeDialogOpen && + + } ); } diff --git a/frontend/src/pages/sys-admin/groups/groups.js b/frontend/src/pages/sys-admin/groups/groups.js index b874b24b3e..7bae385cc1 100644 --- a/frontend/src/pages/sys-admin/groups/groups.js +++ b/frontend/src/pages/sys-admin/groups/groups.js @@ -9,6 +9,7 @@ import SysAdminCreateGroupDialog from '../../../components/dialog/sysadmin-dialo import MainPanelTopbar from '../main-panel-topbar'; import Search from '../search'; import Content from './groups-content'; +import { systemAdminAPI } from '../../../utils/system-admin-api'; class Groups extends Component { @@ -109,6 +110,24 @@ class Groups extends Component { }); }; + changeGroup2Department = (groupID) => { + systemAdminAPI.adminGroup2Department(groupID).then((res) => { + let newGroupList = this.state.groupList.map(item => { + if (item.id == groupID) { + item = res.data; + } + return item; + }); + this.setState({ + groupList: newGroupList + }); + toaster.success(gettext('Successfully Change the group.')); + }).catch((error) => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + }; + getSearch = () => { return /admin/search-group/', OrgAdminSearchGroup.as_view(), name='api-v2.1-org-admin-search-group'), path('/admin/groups//', OrgAdminGroup.as_view(), name='api-admin-group'), path('/admin/groups//libraries/', AdminGroupLibraries.as_view(), name='api-admin-group-libraries'), + path('/admin/groups//group-to-department/', OrgAdminGroupToDeptView.as_view(), name='api-admin-group-to-department'), re_path(r'^(?P\d+)/admin/groups/(?P\d+)/libraries/(?P[-0-9a-f]{36})/$', AdminGroupLibrary.as_view(), name='api-admin-group-library'), path('/admin/groups//group-owned-libraries/', AdminGroupOwnedLibraries.as_view(), name='api-admin-group-owned-libraries'), diff --git a/seahub/urls.py b/seahub/urls.py index 0244164040..0ab9813970 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -151,7 +151,8 @@ from seahub.api2.endpoints.admin.system_library import AdminSystemLibrary, \ AdminSystemLibraryUploadLink from seahub.api2.endpoints.admin.default_library import AdminDefaultLibrary from seahub.api2.endpoints.admin.trash_libraries import AdminTrashLibraries, AdminTrashLibrary -from seahub.api2.endpoints.admin.groups import AdminGroups, AdminGroup, AdminSearchGroup, AdminDepartments +from seahub.api2.endpoints.admin.groups import AdminGroups, AdminGroup, AdminSearchGroup, \ + AdminDepartments, AdminGroupToDeptView from seahub.api2.endpoints.admin.group_libraries import AdminGroupLibraries, AdminGroupLibrary from seahub.api2.endpoints.admin.group_members import AdminGroupMembers, AdminGroupMember from seahub.api2.endpoints.admin.shares import AdminShares @@ -655,7 +656,8 @@ urlpatterns = [ re_path(r'^api/v2.1/admin/groups/(?P\d+)/members/(?P[^/]+)/$', AdminGroupMember.as_view(), name='api-v2.1-admin-group-member'), re_path(r'^api/v2.1/admin/groups/(?P\d+)/group-owned-libraries/$', AdminGroupOwnedLibraries.as_view(), name='api-v2.1-admin-group-owned-libraries'), re_path(r'^api/v2.1/admin/groups/(?P\d+)/group-owned-libraries/(?P[-0-9a-f]{36})/$', AdminGroupOwnedLibrary.as_view(), name='api-v2.1-admin-owned-group-library'), - + re_path(r'^api/v2.1/admin/groups/(?P\d+)/group-to-department/', AdminGroupToDeptView.as_view(), name='api-v2.1-admin-group-to-department'), + ## admin::departments re_path(r'api/v2.1/admin/departments/$', AdminDepartments.as_view(), name='api-v2.1-admin-departments'), diff --git a/seahub/utils/ccnet_db.py b/seahub/utils/ccnet_db.py index 36124e67f9..a42c0d4538 100644 --- a/seahub/utils/ccnet_db.py +++ b/seahub/utils/ccnet_db.py @@ -162,7 +162,6 @@ class CcnetDB: users.append(users_obj) return users, total_count - def get_group_ids_admins_map(self, group_ids): group_admins = {} @@ -183,3 +182,21 @@ class CcnetDB: else: group_admins[group_id] = [user] return group_admins + + def change_groups_into_departments(self, group_id): + sql = f""" + UPDATE `{self.db_name}`.`Group` g + SET + g.creator_name = 'system admin', + g.parent_group_id = -1 + WHERE + g.group_id = {group_id} + """ + structure_sql = f""" + INSERT INTO `{self.db_name}`.`GroupStructure` (group_id, path) + VALUES ('{group_id}', '{group_id}') + """ + + with connection.cursor() as cursor: + cursor.execute(sql) + cursor.execute(structure_sql)