1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-08 18:30:53 +00:00

improve org admin manage department

This commit is contained in:
王健辉
2022-02-11 11:43:17 +08:00
parent a5c0d4cad6
commit 1c305d5f69
4 changed files with 400 additions and 29 deletions

View File

@@ -0,0 +1,108 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Input, Form, FormGroup, Label } from 'reactstrap';
import { gettext } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import toaster from '../toast';
const propTypes = {
groupID: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]).isRequired,
orgID: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]).isRequired,
name: PropTypes.string.isRequired,
toggle: PropTypes.func.isRequired,
onDepartmentNameChanged: PropTypes.func.isRequired
};
class RenameDepartmentDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
departmentName: this.props.name,
errMessage: ''
};
this.newInput = React.createRef();
}
componentDidMount() {
this.newInput.select();
this.newInput.focus();
}
handleSubmit = () => {
let isValid = this.validateName();
const { orgID, groupID } = this.props;
if (isValid) {
seafileAPI.orgAdminUpdateDepartGroup(orgID, groupID, this.state.departmentName.trim()).then((res) => {
this.props.toggle();
this.props.onDepartmentNameChanged(res.data);
toaster.success(gettext('Success'));
}).catch(error => {
let errorMsg = Utils.getErrorMsg(error);
this.setState({ errMessage: errorMsg });
});
}
}
validateName = () => {
let errMessage = '';
const name = this.state.departmentName.trim();
if (!name.length) {
errMessage = gettext('Name is required');
this.setState({ errMessage: errMessage });
return false;
}
return true;
}
handleChange = (e) => {
this.setState({
departmentName: e.target.value
});
}
handleKeyPress = (e) => {
if (e.key === 'Enter') {
this.handleSubmit();
e.preventDefault();
}
}
render() {
let header = gettext('Rename Department');
return (
<Modal isOpen={true} toggle={this.props.toggle}>
<ModalHeader toggle={this.props.toggle}>{header}</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<Label for="departmentName">{gettext('Name')}</Label>
<Input
id="departmentName"
onKeyPress={this.handleKeyPress}
value={this.state.departmentName}
onChange={this.handleChange}
innerRef={input => {this.newInput = input;}}
/>
</FormGroup>
</Form>
{this.state.errMessage && <p className="error">{this.state.errMessage}</p>}
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
</ModalFooter>
</Modal>
);
}
}
RenameDepartmentDialog.propTypes = propTypes;
export default RenameDepartmentDialog;

View File

@@ -15,6 +15,8 @@ import AddRepoDialog from '../../components/dialog/org-add-repo-dialog';
import DeleteRepoDialog from '../../components/dialog/org-delete-repo-dialog';
import DeleteDepartDialog from '../../components/dialog/org-delete-department-dialog';
import SetGroupQuotaDialog from '../../components/dialog/org-set-group-quota-dialog';
import RenameDepartmentDialog from '../../components/dialog/org-rename-department-dialog';
import OpMenu from '../../components/dialog/op-menu';
import { serviceURL, siteRoot, gettext, orgID, lang } from '../../utils/constants';
import '../../css/org-department-item.css';
@@ -27,6 +29,7 @@ class OrgDepartmentItem extends React.Component {
this.state = {
groupName: '',
isItemFreezed: false,
isDepartFreezed: false,
ancestorGroups: [],
members: [],
deletedMember: {},
@@ -99,6 +102,25 @@ class OrgDepartmentItem extends React.Component {
});
}
onFreezedDepart = () => {
this.setState({isDepartFreezed: true});
}
onUnfreezedDepart = () => {
this.setState({isDepartFreezed: false});
}
onDepartmentNameChanged = (dept) => {
this.setState({
groups: this.state.groups.map(item => {
if (item.id == dept.id) {
item.name = dept.name;
}
return item;
})
});
}
onSubDepartChanged = () => {
this.listSubDepartGroups(this.props.groupID);
}
@@ -237,6 +259,10 @@ class OrgDepartmentItem extends React.Component {
<React.Fragment key={group.id}>
<GroupItem
group={group}
isItemFreezed={this.state.isDepartFreezed}
onFreezedItem={this.onFreezedDepart}
onUnfreezedItem={this.onUnfreezedDepart}
onDepartmentNameChanged={this.onDepartmentNameChanged}
showDeleteDepartDialog={this.showDeleteDepartDialog}
showSetGroupQuotaDialog={this.showSetGroupQuotaDialog}
/>
@@ -490,22 +516,79 @@ class GroupItem extends React.Component {
super(props);
this.state = {
highlight: false,
isOpIconShown: false,
isRenameDialogOpen: false,
};
}
onMouseEnter = () => {
this.setState({ highlight: true });
if (!this.props.isItemFreezed) {
this.setState({
isOpIconShown: true,
highlight: true
});
}
}
onMouseLeave = () => {
this.setState({ highlight: false });
if (!this.props.isItemFreezed) {
this.setState({
isOpIconShown: false,
highlight: false
});
}
}
translateOperations = (item) => {
let translateResult = '';
switch(item) {
case 'Rename':
translateResult = gettext('Rename');
break;
case 'Delete':
translateResult = gettext('Delete');
break;
default:
break;
}
return translateResult;
}
onMenuItemClick = (operation) => {
const { group } = this.props;
switch(operation) {
case 'Rename':
this.toggleRenameDialog();
break;
case 'Delete':
this.props.showDeleteDepartDialog(group);
break;
default:
break;
}
}
onUnfreezedItem = () => {
this.setState({
highlight: false,
isOpIconShow: false
});
this.props.onUnfreezedItem();
}
toggleRenameDialog = () => {
this.setState({
isRenameDialogOpen: !this.state.isRenameDialogOpen
});
}
render() {
const group = this.props.group;
const highlight = this.state.highlight;
const { highlight, isOpIconShown, isRenameDialogOpen } = this.state;
const newHref = siteRoot+ 'org/departmentadmin/groups/' + group.id + '/';
return (
<Fragment>
<tr className={highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<td><Link to={newHref}>{group.name}</Link></td>
<td>{moment(group.created_at).fromNow()}</td>
@@ -513,10 +596,28 @@ class GroupItem extends React.Component {
{Utils.bytesToSize(group.quota)}{' '}
<span title="Edit Quota" className={`fa fa-pencil-alt attr-action-icon ${highlight ? '' : 'vh'}`}></span>
</td>
<td className="cursor-pointer text-center" onClick={this.props.showDeleteDepartDialog.bind(this, group)}>
<span className={`sf2-icon-delete action-icon ${highlight ? '' : 'vh'}`} title="Delete"></span>
<td>
{isOpIconShown &&
<OpMenu
operations={['Rename', 'Delete']}
translateOperations={this.translateOperations}
onMenuItemClick={this.onMenuItemClick}
onFreezedItem={this.props.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem}
/>
}
</td>
</tr>
{isRenameDialogOpen && (
<RenameDepartmentDialog
orgID={orgID}
groupID={group.id}
name={group.name}
toggle={this.toggleRenameDialog}
onDepartmentNameChanged={this.props.onDepartmentNameChanged}
/>
)}
</Fragment>
);
}
}
@@ -524,6 +625,10 @@ class GroupItem extends React.Component {
const GroupItemPropTypes = {
group: PropTypes.object.isRequired,
groupID: PropTypes.string,
isItemFreezed: PropTypes.bool.isRequired,
onFreezedItem: PropTypes.func.isRequired,
onUnfreezedItem: PropTypes.func.isRequired,
onDepartmentNameChanged: PropTypes.func.isRequired,
showSetGroupQuotaDialog: PropTypes.func.isRequired,
showDeleteDepartDialog: PropTypes.func.isRequired,
isSubdepartChanged: PropTypes.bool,

View File

@@ -9,6 +9,8 @@ import ModalPortal from '../../components/modal-portal';
import AddDepartDialog from '../../components/dialog/org-add-department-dialog';
import DeleteDepartDialog from '../../components/dialog/org-delete-department-dialog';
import SetGroupQuotaDialog from '../../components/dialog/org-set-group-quota-dialog';
import RenameDepartmentDialog from '../../components/dialog/org-rename-department-dialog';
import OpMenu from '../../components/dialog/op-menu';
import { siteRoot, gettext, orgID, lang } from '../../utils/constants';
import '../../css/org-department-item.css';
@@ -25,6 +27,7 @@ class OrgDepartmentsList extends React.Component {
showDeleteDepartDialog: false,
showSetGroupQuotaDialog: false,
isShowAddDepartDialog: false,
isItemFreezed: false,
};
}
@@ -38,6 +41,25 @@ class OrgDepartmentsList extends React.Component {
});
}
onFreezedItem = () => {
this.setState({isItemFreezed: true});
}
onUnfreezedItem = () => {
this.setState({isItemFreezed: false});
}
onDepartmentNameChanged = (dept) => {
this.setState({
groups: this.state.groups.map(item => {
if (item.id == dept.id) {
item.name = dept.name;
}
return item;
})
});
}
showDeleteDepartDialog = (group) => {
this.setState({ showDeleteDepartDialog: true, groupID: group.id, groupName: group.name });
}
@@ -105,6 +127,10 @@ class OrgDepartmentsList extends React.Component {
<React.Fragment key={group.id}>
<GroupItem
group={group}
isItemFreezed={this.state.isItemFreezed}
onFreezedItem={this.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem}
onDepartmentNameChanged={this.onDepartmentNameChanged}
showDeleteDepartDialog={this.showDeleteDepartDialog}
showSetGroupQuotaDialog={this.showSetGroupQuotaDialog}
/>
@@ -151,22 +177,79 @@ class GroupItem extends React.Component {
super(props);
this.state = {
highlight: false,
isOpIconShown: false,
isRenameDialogOpen: false,
};
}
onMouseEnter = () => {
this.setState({ highlight: true });
if (!this.props.isItemFreezed) {
this.setState({
isOpIconShown: true,
highlight: true
});
}
}
onMouseLeave = () => {
this.setState({ highlight: false });
if (!this.props.isItemFreezed) {
this.setState({
isOpIconShown: false,
highlight: false
});
}
}
translateOperations = (item) => {
let translateResult = '';
switch(item) {
case 'Rename':
translateResult = gettext('Rename');
break;
case 'Delete':
translateResult = gettext('Delete');
break;
default:
break;
}
return translateResult;
}
onMenuItemClick = (operation) => {
const { group } = this.props;
switch(operation) {
case 'Rename':
this.toggleRenameDialog();
break;
case 'Delete':
this.props.showDeleteDepartDialog(group);
break;
default:
break;
}
}
onUnfreezedItem = () => {
this.setState({
highlight: false,
isOpIconShow: false
});
this.props.onUnfreezedItem();
}
toggleRenameDialog = () => {
this.setState({
isRenameDialogOpen: !this.state.isRenameDialogOpen
});
}
render() {
const group = this.props.group;
const highlight = this.state.highlight;
const { highlight, isOpIconShown, isRenameDialogOpen } = this.state;
const newHref = siteRoot+ 'org/departmentadmin/groups/' + group.id + '/';
return (
<Fragment>
<tr className={highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<td><Link to={newHref}>{group.name}</Link></td>
<td>{moment(group.created_at).fromNow()}</td>
@@ -174,16 +257,38 @@ class GroupItem extends React.Component {
{Utils.bytesToSize(group.quota)}{' '}
<span title="Edit Quota" className={`fa fa-pencil-alt attr-action-icon ${highlight ? '' : 'vh'}`}></span>
</td>
<td className="cursor-pointer text-center" onClick={this.props.showDeleteDepartDialog.bind(this, group)}>
<span className={`sf2-icon-delete action-icon ${highlight ? '' : 'vh'}`} title="Delete"></span>
<td>
{isOpIconShown &&
<OpMenu
operations={['Rename', 'Delete']}
translateOperations={this.translateOperations}
onMenuItemClick={this.onMenuItemClick}
onFreezedItem={this.props.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem}
/>
}
</td>
</tr>
{isRenameDialogOpen && (
<RenameDepartmentDialog
orgID={orgID}
groupID={group.id}
name={group.name}
toggle={this.toggleRenameDialog}
onDepartmentNameChanged={this.props.onDepartmentNameChanged}
/>
)}
</Fragment>
);
}
}
const GroupItemPropTypes = {
group: PropTypes.object.isRequired,
isItemFreezed: PropTypes.bool.isRequired,
onFreezedItem: PropTypes.func.isRequired,
onUnfreezedItem: PropTypes.func.isRequired,
onDepartmentNameChanged: PropTypes.func.isRequired,
showSetGroupQuotaDialog: PropTypes.func.isRequired,
showDeleteDepartDialog: PropTypes.func.isRequired,
};

View File

@@ -1,10 +1,11 @@
import logging
from rest_framework.authentication import SessionAuthentication
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from seaserv import ccnet_api
from seaserv import ccnet_api, seafile_api
from seahub.api2.utils import api_error
from seahub.organizations.views import get_org_id_by_group
@@ -17,6 +18,10 @@ from seahub.api2.endpoints.admin.address_book.groups import (
)
from seahub.organizations.api.permissions import IsOrgAdmin
from seahub.organizations.api.utils import check_org_admin
from seahub.utils import is_pro_version
from seahub.base.templatetags.seahub_tags import email2nickname
from seahub.utils.timeutils import timestamp_to_isoformat_timestr
from seahub.group.utils import validate_group_name, set_group_name_cache
logger = logging.getLogger(__name__)
@@ -83,3 +88,51 @@ class AdminAddressBookGroup(APIView):
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
return SysAdminAddressBookGroup().delete(request, group_id)
@check_org_admin
def put(self, request, org_id, group_id):
""" Update an org address book group.
"""
# resource check
org_id = int(org_id)
if not ccnet_api.get_org_by_id(org_id):
error_msg = 'Organization %s not found.' % org_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# permission check
group_id = int(group_id)
if get_org_id_by_group(group_id) != org_id:
error_msg = 'Group %s not found.' % group_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
new_group_name = request.data.get('group_name', '').strip()
if not new_group_name:
error_msg = 'name %s invalid.' % new_group_name
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# Check whether group name is validate.
if not validate_group_name(new_group_name):
error_msg = 'Group name can only contain letters, numbers, blank, hyphen, dot, single quote or underscore'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
try:
ccnet_api.set_group_name(group_id, new_group_name)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
set_group_name_cache(group_id, new_group_name)
group = ccnet_api.get_group(group_id)
isoformat_timestr = timestamp_to_isoformat_timestr(group.timestamp)
group_info = {
"id": group.id,
"name": group.group_name,
"owner": group.creator_name,
"owner_name": email2nickname(group.creator_name),
"created_at": isoformat_timestr,
"quota": seafile_api.get_group_quota(group_id) if is_pro_version() else 0,
"parent_group_id": group.parent_group_id if is_pro_version() else 0
}
return Response(group_info)