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

Sys department (#5471)

* [system admin - department] redesigned it (split it into 3 tabs/pages)

* [system admin - department] fixup & improvements; code cleanup
This commit is contained in:
llj
2023-05-15 10:57:46 +08:00
committed by GitHub
parent 57a135b9d5
commit 495141c287
19 changed files with 773 additions and 598 deletions

View File

@@ -8,7 +8,7 @@ const propTypes = {
groupID: PropTypes.string, groupID: PropTypes.string,
parentGroupID: PropTypes.string, parentGroupID: PropTypes.string,
toggle: PropTypes.func.isRequired, toggle: PropTypes.func.isRequired,
onDepartChanged: PropTypes.func.isRequired, onAddNewDepartment: PropTypes.func.isRequired
}; };
class AddDepartDialog extends React.Component { class AddDepartDialog extends React.Component {
@@ -30,7 +30,7 @@ class AddDepartDialog extends React.Component {
} }
seafileAPI.sysAdminAddNewDepartment(parentGroup, this.state.departName.trim()).then((res) => { seafileAPI.sysAdminAddNewDepartment(parentGroup, this.state.departName.trim()).then((res) => {
this.props.toggle(); this.props.toggle();
this.props.onDepartChanged(); this.props.onAddNewDepartment(res.data);
}).catch(error => { }).catch(error => {
let errorMsg = gettext(error.response.data.error_msg); let errorMsg = gettext(error.response.data.error_msg);
this.setState({ errMessage: errorMsg }); this.setState({ errMessage: errorMsg });
@@ -80,9 +80,10 @@ class AddDepartDialog extends React.Component {
/> />
</FormGroup> </FormGroup>
</Form> </Form>
{ this.state.errMessage && <p className="error">{this.state.errMessage}</p> } {this.state.errMessage && <p className="error">{this.state.errMessage}</p>}
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button> <Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
</ModalFooter> </ModalFooter>
</Modal> </Modal>

View File

@@ -10,7 +10,7 @@ import UserSelect from '../../user-select.js';
const propTypes = { const propTypes = {
toggle: PropTypes.func.isRequired, toggle: PropTypes.func.isRequired,
groupID: PropTypes.string.isRequired, groupID: PropTypes.string.isRequired,
onMemberChanged: PropTypes.func.isRequired onAddNewMembers: PropTypes.func.isRequired
}; };
class AddMemberDialog extends React.Component { class AddMemberDialog extends React.Component {
@@ -38,7 +38,7 @@ class AddMemberDialog extends React.Component {
this.setState({ errMessage: res.data.failed[0].error_msg }); this.setState({ errMessage: res.data.failed[0].error_msg });
} }
if (res.data.success.length > 0) { if (res.data.success.length > 0) {
this.props.onMemberChanged(); this.props.onAddNewMembers(res.data.success);
this.props.toggle(); this.props.toggle();
} }
}).catch(error => { }).catch(error => {
@@ -62,8 +62,8 @@ class AddMemberDialog extends React.Component {
{ this.state.errMessage && <p className="error">{this.state.errMessage}</p> } { this.state.errMessage && <p className="error">{this.state.errMessage}</p> }
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button> <Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
</ModalFooter> </ModalFooter>
</Modal> </Modal>
); );

View File

@@ -8,7 +8,7 @@ import { Utils } from '../../../utils/utils';
const propTypes = { const propTypes = {
toggle: PropTypes.func.isRequired, toggle: PropTypes.func.isRequired,
groupID: PropTypes.string.isRequired, groupID: PropTypes.string.isRequired,
onRepoChanged: PropTypes.func.isRequired, onAddNewRepo: PropTypes.func.isRequired
}; };
class AddRepoDialog extends React.Component { class AddRepoDialog extends React.Component {
@@ -26,7 +26,7 @@ class AddRepoDialog extends React.Component {
if (isValid) { if (isValid) {
seafileAPI.sysAdminAddRepoInDepartment(this.props.groupID, this.state.repoName.trim()).then((res) => { seafileAPI.sysAdminAddRepoInDepartment(this.props.groupID, this.state.repoName.trim()).then((res) => {
this.props.toggle(); this.props.toggle();
this.props.onRepoChanged(); this.props.onAddNewRepo(res.data);
}).catch(error => { }).catch(error => {
let errorMsg = Utils.getErrorMsg(error); let errorMsg = Utils.getErrorMsg(error);
this.setState({ errMessage: errorMsg }); this.setState({ errMessage: errorMsg });
@@ -78,6 +78,7 @@ class AddRepoDialog extends React.Component {
{this.state.errMessage && <p className="error">{this.state.errMessage}</p> } {this.state.errMessage && <p className="error">{this.state.errMessage}</p> }
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button> <Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
</ModalFooter> </ModalFooter>
</Modal> </Modal>

View File

@@ -7,13 +7,12 @@ import { Utils } from '../../../utils/utils';
import toaster from '../../../components/toast'; import toaster from '../../../components/toast';
const propTypes = { const propTypes = {
groupName: PropTypes.string, group: PropTypes.object.isRequired,
groupID: PropTypes.number.isRequired,
toggle: PropTypes.func.isRequired, toggle: PropTypes.func.isRequired,
onDepartChanged: PropTypes.func.isRequired onDeleteDepartment: PropTypes.func.isRequired
}; };
class DeleteDepartDialog extends React.Component { class DeleteDepartmentDialog extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@@ -21,8 +20,9 @@ class DeleteDepartDialog extends React.Component {
deleteDepart = () => { deleteDepart = () => {
this.props.toggle(); this.props.toggle();
seafileAPI.sysAdminDeleteDepartment(this.props.groupID).then((res) => { const { group } = this.props;
this.props.onDepartChanged(); seafileAPI.sysAdminDeleteDepartment(group.id).then((res) => {
this.props.onDeleteDepartment(group.id);
}).catch(error => { }).catch(error => {
let errMessage = Utils.getErrorMsg(error); let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage); toaster.danger(errMessage);
@@ -30,8 +30,10 @@ class DeleteDepartDialog extends React.Component {
} }
render() { render() {
const { group } = this.props;
let tipMessage = gettext('Are you sure you want to delete {placeholder} ?'); let tipMessage = gettext('Are you sure you want to delete {placeholder} ?');
tipMessage = tipMessage.replace('{placeholder}', '<span class="op-target">' + Utils.HTMLescape(this.props.groupName) + '</span>'); tipMessage = tipMessage.replace('{placeholder}', '<span class="op-target">' + Utils.HTMLescape(group.name) + '</span>');
return ( return (
<Modal isOpen={true} toggle={this.props.toggle}> <Modal isOpen={true} toggle={this.props.toggle}>
<ModalHeader toggle={this.props.toggle}>{gettext('Delete Department')}</ModalHeader> <ModalHeader toggle={this.props.toggle}>{gettext('Delete Department')}</ModalHeader>
@@ -47,6 +49,6 @@ class DeleteDepartDialog extends React.Component {
} }
} }
DeleteDepartDialog.propTypes = propTypes; DeleteDepartmentDialog.propTypes = propTypes;
export default DeleteDepartDialog; export default DeleteDepartmentDialog;

View File

@@ -42,8 +42,8 @@ class DeleteMemberDialog extends React.Component {
<div dangerouslySetInnerHTML={{__html: tipMessage}}></div> <div dangerouslySetInnerHTML={{__html: tipMessage}}></div>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button color="primary" onClick={this.deleteMember}>{gettext('Delete')}</Button>
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button> <Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.deleteMember}>{gettext('Delete')}</Button>
</ModalFooter> </ModalFooter>
</Modal> </Modal>
); );

View File

@@ -25,8 +25,9 @@ class DeleteRepoDialog extends React.Component {
} }
render() { render() {
const { repo } = this.props;
let tipMessage = gettext('Are you sure you want to delete {placeholder} ?'); let tipMessage = gettext('Are you sure you want to delete {placeholder} ?');
tipMessage = tipMessage.replace('{placeholder}', '<span class="op-target">' + Utils.HTMLescape(this.props.repo.name) + '</span>'); tipMessage = tipMessage.replace('{placeholder}', '<span class="op-target">' + Utils.HTMLescape(repo.name || repo.repo_name) + '</span>');
return ( return (
<Modal isOpen={true} toggle={this.props.toggle}> <Modal isOpen={true} toggle={this.props.toggle}>
<ModalHeader toggle={this.props.toggle}>{gettext('Delete Library')}</ModalHeader> <ModalHeader toggle={this.props.toggle}>{gettext('Delete Library')}</ModalHeader>
@@ -34,8 +35,8 @@ class DeleteRepoDialog extends React.Component {
<div dangerouslySetInnerHTML={{__html: tipMessage}}></div> <div dangerouslySetInnerHTML={{__html: tipMessage}}></div>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button color="primary" onClick={this.deleteRepo}>{gettext('Delete')}</Button>
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button> <Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.deleteRepo}>{gettext('Delete')}</Button>
</ModalFooter> </ModalFooter>
</Modal> </Modal>
); );

View File

@@ -92,6 +92,7 @@ class RenameDepartmentDialog extends React.Component {
{this.state.errMessage && <p className="error">{this.state.errMessage}</p>} {this.state.errMessage && <p className="error">{this.state.errMessage}</p>}
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button> <Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
</ModalFooter> </ModalFooter>
</Modal> </Modal>

View File

@@ -9,7 +9,7 @@ import toaster from '../../toast';
const propTypes = { const propTypes = {
toggle: PropTypes.func.isRequired, toggle: PropTypes.func.isRequired,
groupID: PropTypes.number.isRequired, groupID: PropTypes.number.isRequired,
onDepartChanged: PropTypes.func.isRequired, onSetQuota: PropTypes.func.isRequired,
}; };
class SetGroupQuotaDialog extends React.Component { class SetGroupQuotaDialog extends React.Component {
@@ -30,7 +30,7 @@ class SetGroupQuotaDialog extends React.Component {
let newQuota = this.state.quota == -2 ? this.state.quota : this.state.quota * 1000000; let newQuota = this.state.quota == -2 ? this.state.quota : this.state.quota * 1000000;
seafileAPI.sysAdminUpdateDepartmentQuota(this.props.groupID, newQuota).then((res) => { seafileAPI.sysAdminUpdateDepartmentQuota(this.props.groupID, newQuota).then((res) => {
this.props.toggle(); this.props.toggle();
this.props.onDepartChanged(); this.props.onSetQuota(res.data);
}).catch(error => { }).catch(error => {
let errMessage = Utils.getErrorMsg(error); let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage); toaster.danger(errMessage);
@@ -71,9 +71,10 @@ class SetGroupQuotaDialog extends React.Component {
<br/><span>{gettext('An integer that is greater than 0 or equal to -2.')}</span><br/> <br/><span>{gettext('An integer that is greater than 0 or equal to -2.')}</span><br/>
<span>{gettext('Tip: -2 means no limit.')}</span> <span>{gettext('Tip: -2 means no limit.')}</span>
</p> </p>
{ this.state.errMessage && <p className="error">{this.state.errMessage}</p> } {this.state.errMessage && <p className="error">{this.state.errMessage}</p>}
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.setGroupQuota}>{gettext('Submit')}</Button> <Button color="primary" onClick={this.setGroupQuota}>{gettext('Submit')}</Button>
</ModalFooter> </ModalFooter>
</Modal> </Modal>

View File

@@ -1,472 +0,0 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { Link } from '@gatsbyjs/reach-router';
import Paginator from '../../../components/paginator';
import { seafileAPI } from '../../../utils/seafile-api';
import { Utils } from '../../../utils/utils.js';
import toaster from '../../../components/toast';
import MainPanelTopbar from '../main-panel-topbar';
import ModalPortal from '../../../components/modal-portal';
import AddDepartDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-department-dialog';
import RenameDepartmentDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-rename-department-dialog';
import AddMemberDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-member-dialog';
import DeleteMemberDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-delete-member-dialog';
import AddRepoDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-repo-dialog';
import DeleteRepoDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-delete-repo-dialog';
import DeleteDepartDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-delete-department-dialog';
import SetGroupQuotaDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-set-group-quota-dialog';
import { siteRoot, gettext, lang } from '../../../utils/constants';
import GroupItem from './group-item';
import MemberItem from './member-item';
import RepoItem from './repo-item';
import '../../../css/org-department-item.css';
moment.locale(lang);
const DepartmentDetailPropTypes = {
groupID: PropTypes.string,
};
class DepartmentDetail extends React.Component {
constructor(props) {
super(props);
this.state = {
orgID: '',
groupName: '',
isItemFreezed: false,
ancestorGroups: [],
members: [],
membersErrorMsg: '',
membersPageInfo: {
},
membersPage: 1,
membersPerPage: 25,
deletedMember: {},
isShowAddMemberDialog: false,
showDeleteMemberDialog: false,
repos: [],
deletedRepo: {},
isShowRenameDepartmentDialog: false,
isShowAddRepoDialog: false,
showDeleteRepoDialog: false,
groups: [],
subGroupID: '',
subGroupName: '',
isShowAddDepartDialog: false,
showDeleteDepartDialog: false,
showSetGroupQuotaDialog: false,
};
}
componentDidMount() {
const groupID = this.props.groupID;
this.listGroupRepo(groupID);
this.getDepartmentInfo(groupID);
this.listMembers(groupID, this.state.membersPage, this.state.membersPerPage);
}
componentWillReceiveProps(nextProps) {
if (this.props.groupID !== nextProps.groupID) {
this.listGroupRepo(nextProps.groupID);
this.getDepartmentInfo(nextProps.groupID);
this.listMembers(nextProps.groupID, this.state.membersPage, this.state.membersPerPage);
}
}
listGroupRepo = (groupID) => {
seafileAPI.sysAdminListGroupRepos(groupID).then(res => {
this.setState({ repos: res.data.libraries });
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
getDepartmentInfo = (groupID) => {
seafileAPI.sysAdminGetDepartmentInfo(groupID, true).then(res => {
this.setState({
groups: res.data.groups,
ancestorGroups: res.data.ancestor_groups,
groupName: res.data.name,
orgID: res.data.org_id,
});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
listMembers = (groupID, page, perPage) => {
seafileAPI.sysAdminListGroupMembers(groupID, page, perPage).then((res) => {
this.setState({
members: res.data.members,
membersPageInfo: res.data.page_info
});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
this.setState({membersErrorMsg: errMessage});
});
}
getPreviousPageList = () => {
this.listMembers(this.props.groupID, this.state.membersPageInfo.current_page - 1, this.state.membersPerPage);
}
getNextPageList = () => {
this.listMembers(this.props.groupID, this.state.membersPageInfo.current_page + 1, this.state.membersPerPage);
}
resetPerPage = (perPage) => {
this.setState({
membersPerPage: perPage
}, () => {
this.listMembers(this.props.groupID, 1, perPage);
});
}
listSubDepartGroups = (groupID) => {
seafileAPI.sysAdminGetDepartmentInfo(groupID, true).then(res => {
this.setState({ groups: res.data.groups });
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
toggleCancel = () => {
this.setState({
showDeleteMemberDialog: false,
showDeleteRepoDialog: false,
showDeleteDepartDialog: false,
showSetGroupQuotaDialog: false,
});
}
onSubDepartChanged = () => {
this.listSubDepartGroups(this.props.groupID);
}
onDepartmentNameChanged = (dept) => {
this.setState({
groupName: dept.name
});
}
onSubDepartmentNameChanged = (dept) => {
this.setState({
groups: this.state.groups.map(item => {
if (item.id == dept.id) {
item.name = dept.name;
}
return item;
})
});
}
onRepoChanged = () => {
this.listGroupRepo(this.props.groupID);
}
onMemberChanged = () => {
this.listMembers(this.props.groupID, this.state.membersPageInfo.current_page, this.state.membersPerPage);
}
toggleItemFreezed = (isFreezed) => {
this.setState({ isItemFreezed: isFreezed });
}
onFreezedItem = () => {
this.setState({isItemFreezed: true});
}
onUnfreezedItem = () => {
this.setState({isItemFreezed: false});
}
showDeleteMemberDialog = (member) => {
this.setState({ showDeleteMemberDialog: true, deletedMember: member });
}
showDeleteRepoDialog = (repo) => {
this.setState({ showDeleteRepoDialog: true, deletedRepo: repo });
}
toggleRenameDepartmentDialog = () => {
this.setState({ isShowRenameDepartmentDialog: !this.state.isShowRenameDepartmentDialog });
}
toggleAddRepoDialog = () => {
this.setState({ isShowAddRepoDialog: !this.state.isShowAddRepoDialog });
}
toggleAddMemberDialog = () => {
this.setState({ isShowAddMemberDialog: !this.state.isShowAddMemberDialog });
}
toggleAddDepartDialog = () => {
this.setState({ isShowAddDepartDialog: !this.state.isShowAddDepartDialog});
}
showDeleteDepartDialog = (subGroup) => {
this.setState({
showDeleteDepartDialog: true,
subGroupID: subGroup.id,
subGroupName: subGroup.name
});
}
showSetGroupQuotaDialog = (subGroupID) => {
this.setState({
showSetGroupQuotaDialog: true,
subGroupID: subGroupID
});
}
render() {
const { members, membersErrorMsg, repos, groups, groupName } = this.state;
const groupID = this.props.groupID;
const topBtn = 'btn btn-secondary operation-item';
const topbarChildren = (
<Fragment>
{groupID &&
<Fragment>
<button className={topBtn} title={gettext('Rename Department')} onClick={this.toggleRenameDepartmentDialog}>{gettext('Rename Department')}</button>
<button className={topBtn} title={gettext('New Sub-department')} onClick={this.toggleAddDepartDialog}>{gettext('New Sub-department')}</button>
<button className={topBtn} title={gettext('Add Member')} onClick={this.toggleAddMemberDialog}>{gettext('Add Member')}</button>
<button className={topBtn} onClick={this.toggleAddRepoDialog} title={gettext('New Library')}>{gettext('New Library')}</button>
</Fragment>
}
{this.state.isShowRenameDepartmentDialog && (
<ModalPortal>
<RenameDepartmentDialog
groupID={groupID}
name={groupName}
toggle={this.toggleRenameDepartmentDialog}
onDepartmentNameChanged={this.onDepartmentNameChanged}
/>
</ModalPortal>
)}
{this.state.isShowAddMemberDialog && (
<ModalPortal>
<AddMemberDialog
toggle={this.toggleAddMemberDialog}
onMemberChanged={this.onMemberChanged}
groupID={groupID}
/>
</ModalPortal>
)}
{this.state.isShowAddRepoDialog && (
<ModalPortal>
<AddRepoDialog
toggle={this.toggleAddRepoDialog}
onRepoChanged={this.onRepoChanged}
groupID={groupID}
/>
</ModalPortal>
)}
{this.state.isShowAddDepartDialog && (
<ModalPortal>
<AddDepartDialog
onDepartChanged={this.onSubDepartChanged}
parentGroupID={groupID}
toggle={this.toggleAddDepartDialog}
/>
</ModalPortal>
)}
</Fragment>
);
return (
<Fragment>
<MainPanelTopbar {...this.props}>
{topbarChildren}
</MainPanelTopbar>
<div className="main-panel-center flex-row h-100">
<div className="cur-view-container o-auto">
<div className="cur-view-path">
<div className="fleft">
<h3 className="sf-heading">
{groupID ?
<Link to={siteRoot + 'sys/departments/'}>{gettext('Departments')}</Link>
: <span>{gettext('Departments')}</span>
}
{this.state.ancestorGroups.map(ancestor => {
let newHref = siteRoot + 'sys/departments/' + ancestor.id + '/';
return <span key={ancestor.id}>{' / '}<Link to={newHref}>{ancestor.name}</Link></span>;
})}
{groupID && <span>{' / '}{this.state.groupName}</span>}
</h3>
</div>
</div>
<div className="cur-view-subcontainer org-groups">
<div className="cur-view-path">
<div className="fleft"><h3 className="sf-heading">{gettext('Sub-departments')}</h3></div>
</div>
<div className="cur-view-content">
{groups && groups.length > 0 ?
<table>
<thead>
<tr>
<th width="40%">{gettext('Name')}</th>
<th width="25%">{gettext('Created At')}</th>
<th width="20%">{gettext('Quota')}</th>
<th width="15%"></th>
</tr>
</thead>
<tbody>
{groups.map((group, index) => {
return(
<Fragment key={group.id}>
<GroupItem
orgID={this.state.orgID}
isItemFreezed={this.state.isItemFreezed}
onFreezedItem={this.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem}
onDepartmentNameChanged={this.onSubDepartmentNameChanged}
group={group}
showDeleteDepartDialog={this.showDeleteDepartDialog}
showSetGroupQuotaDialog={this.showSetGroupQuotaDialog}
/>
</Fragment>
);
})}
</tbody>
</table>
: <p className="no-group">{gettext('No sub-departments')}</p>
}
</div>
</div>
<div className="cur-view-subcontainer org-members">
<div className="cur-view-path">
<div className="fleft"><h3 className="sf-heading">{gettext('Members')}</h3></div>
</div>
<div className="cur-view-content">
{membersErrorMsg ? <p className="error text-center">{membersErrorMsg}</p> :
members.length == 0 ?
<p className="no-member">{gettext('No members')}</p> :
<Fragment>
<table>
<thead>
<tr>
<th width="5%"></th>
<th width="50%">{gettext('Name')}</th>
<th width="15%">{gettext('Role')}</th>
<th width="30%"></th>
</tr>
</thead>
<tbody>
{members.map((member, index) => {
return (
<Fragment key={index}>
<MemberItem
orgID={this.state.orgID}
member={member}
showDeleteMemberDialog={this.showDeleteMemberDialog}
isItemFreezed={this.state.isItemFreezed}
onMemberChanged={this.onMemberChanged}
toggleItemFreezed={this.toggleItemFreezed}
groupID={groupID}
/>
</Fragment>
);
})}
</tbody>
</table>
{this.state.membersPageInfo &&
<Paginator
gotoPreviousPage={this.getPreviousPageList}
gotoNextPage={this.getNextPageList}
currentPage={this.state.membersPageInfo.current_page}
hasNextPage={this.state.membersPageInfo.has_next_page}
curPerPage={this.state.membersPerPage}
resetPerPage={this.resetPerPage}
/>
}
</Fragment>
}
</div>
</div>
<div className="cur-view-subcontainer org-libriries">
<div className="cur-view-path">
<div className="fleft"><h3 className="sf-heading">{gettext('Libraries')}</h3></div>
</div>
{ repos.length > 0 ?
<div className="cur-view-content">
<table>
<thead>
<tr>
<th width="5%"></th>
<th width="50%">{gettext('Name')}</th>
<th width="30%">{gettext('Size')}</th>
<th width="15%"></th>
</tr>
</thead>
<tbody>
{repos.map((repo, index) => {
return(
<Fragment key={index}>
<RepoItem repo={repo} orgID={this.state.orgID} showDeleteRepoDialog={this.showDeleteRepoDialog}/>
</Fragment>
);
})}
</tbody>
</table>
</div>
: <p className="no-libraty">{gettext('No libraries')}</p>
}
</div>
</div>
{this.state.showDeleteMemberDialog && (
<ModalPortal>
<DeleteMemberDialog
toggle={this.toggleCancel}
onMemberChanged={this.onMemberChanged}
member={this.state.deletedMember}
groupID={groupID}
/>
</ModalPortal>
)}
{this.state.showDeleteRepoDialog && (
<ModalPortal>
<DeleteRepoDialog
toggle={this.toggleCancel}
onRepoChanged={this.onRepoChanged}
repo={this.state.deletedRepo}
groupID={groupID}
/>
</ModalPortal>
)}
{this.state.showDeleteDepartDialog && (
<ModalPortal>
<DeleteDepartDialog
toggle={this.toggleCancel}
groupID={this.state.subGroupID}
groupName={this.state.subGroupName}
onDepartChanged={this.onSubDepartChanged}
/>
</ModalPortal>
)}
{this.state.showSetGroupQuotaDialog && (
<ModalPortal>
<SetGroupQuotaDialog
toggle={this.toggleCancel}
groupID={this.state.subGroupID}
onDepartChanged={this.onSubDepartChanged}
/>
</ModalPortal>
)}
</div>
</Fragment>
);
}
}
DepartmentDetail.propTypes = DepartmentDetailPropTypes;
export default DepartmentDetail;

View File

@@ -0,0 +1,118 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { seafileAPI } from '../../../utils/seafile-api';
import { Utils } from '../../../utils/utils.js';
import toaster from '../../../components/toast';
import ModalPortal from '../../../components/modal-portal';
import DeleteRepoDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-delete-repo-dialog';
import { gettext, lang } from '../../../utils/constants';
import RepoItem from './repo-item';
import Department from './department';
import '../../../css/org-department-item.css';
moment.locale(lang);
const DepartmentDetailPropTypes = {
groupID: PropTypes.string,
};
class DepartmentDetail extends React.Component {
constructor(props) {
super(props);
this.state = {
repos: [],
deletedRepo: {},
showDeleteRepoDialog: false
};
}
componentDidMount() {
const { groupID } = this.props;
this.listGroupRepo(groupID);
}
componentWillReceiveProps(nextProps) {
if (this.props.groupID !== nextProps.groupID) {
this.listGroupRepo(nextProps.groupID);
}
}
listGroupRepo = (groupID) => {
seafileAPI.sysAdminListGroupRepos(groupID).then(res => {
this.setState({ repos: res.data.libraries });
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
toggleCancel = () => {
this.setState({
showDeleteRepoDialog: false
});
}
onRepoChanged = () => {
this.listGroupRepo(this.props.groupID);
}
showDeleteRepoDialog = (repo) => {
this.setState({ showDeleteRepoDialog: true, deletedRepo: repo });
}
onAddNewRepo = (newRepo) => {
const { repos } = this.state;
repos.unshift(newRepo);
this.setState({ repos });
}
render() {
const { repos } = this.state;
const { groupID } = this.props;
return (
<Fragment>
<Department groupID={groupID} currentItem="repos" onAddNewRepo={this.onAddNewRepo}>
{repos.length > 0 ?
<div className="cur-view-content">
<table>
<thead>
<tr>
<th width="5%"></th>
<th width="50%">{gettext('Name')}</th>
<th width="30%">{gettext('Size')}</th>
<th width="15%"></th>
</tr>
</thead>
<tbody>
{repos.map((repo, index) => {
return (
<RepoItem key={index} repo={repo} showDeleteRepoDialog={this.showDeleteRepoDialog} />
);
})}
</tbody>
</table>
</div>
: <p className="no-libraty">{gettext('No libraries')}</p>
}
</Department>
{this.state.showDeleteRepoDialog && (
<ModalPortal>
<DeleteRepoDialog
toggle={this.toggleCancel}
onRepoChanged={this.onRepoChanged}
repo={this.state.deletedRepo}
groupID={groupID}
/>
</ModalPortal>
)}
</Fragment>
);
}
}
DepartmentDetail.propTypes = DepartmentDetailPropTypes;
export default DepartmentDetail;

View File

@@ -4,31 +4,26 @@ import { seafileAPI } from '../../../utils/seafile-api';
import MainPanelTopbar from '../main-panel-topbar'; import MainPanelTopbar from '../main-panel-topbar';
import ModalPortal from '../../../components/modal-portal'; import ModalPortal from '../../../components/modal-portal';
import AddDepartDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-department-dialog'; import AddDepartDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-department-dialog';
import DeleteDepartDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-delete-department-dialog';
import SetGroupQuotaDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-set-group-quota-dialog';
import { gettext, lang } from '../../../utils/constants'; import { gettext, lang } from '../../../utils/constants';
import GroupItem from './group-item'; import GroupItem from './group-item';
import '../../../css/org-department-item.css'; import '../../../css/org-department-item.css';
moment.locale(lang); moment.locale(lang);
class DepartmentsList extends React.Component { class DepartmentList extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
groups: null, groups: null,
groupID: '', groupID: '',
groupName: '',
showDeleteDepartDialog: false,
showSetGroupQuotaDialog: false,
isShowAddDepartDialog: false, isShowAddDepartDialog: false,
isItemFreezed: false isItemFreezed: false
}; };
} }
componentDidMount() { componentDidMount() {
this.listDepartGroups(); this.listDepartments();
} }
onFreezedItem = () => { onFreezedItem = () => {
@@ -39,35 +34,16 @@ class DepartmentsList extends React.Component {
this.setState({isItemFreezed: false}); this.setState({isItemFreezed: false});
} }
listDepartGroups = () => { listDepartments = () => {
seafileAPI.sysAdminListAllDepartments().then(res => { seafileAPI.sysAdminListAllDepartments().then(res => {
this.setState({ groups: res.data.data }); this.setState({ groups: res.data.data });
}); });
} }
showDeleteDepartDialog = (group) => {
this.setState({ showDeleteDepartDialog: true, groupID: group.id, groupName: group.name });
}
showSetGroupQuotaDialog = (groupID) => {
this.setState({ showSetGroupQuotaDialog: true, groupID: groupID });
}
toggleAddDepartDialog = () => { toggleAddDepartDialog = () => {
this.setState({ isShowAddDepartDialog: !this.state.isShowAddDepartDialog }); this.setState({ isShowAddDepartDialog: !this.state.isShowAddDepartDialog });
} }
toggleCancel = () => {
this.setState({
showDeleteDepartDialog: false,
showSetGroupQuotaDialog: false,
});
}
onDepartChanged = () => {
this.listDepartGroups();
}
onDepartmentNameChanged = (dept) => { onDepartmentNameChanged = (dept) => {
this.setState({ this.setState({
groups: this.state.groups.map(item => { groups: this.state.groups.map(item => {
@@ -79,6 +55,33 @@ class DepartmentsList extends React.Component {
}); });
} }
onAddNewDepartment = (newDepartment) => {
const { groups } = this.state;
groups.unshift(newDepartment);
this.setState({
groups: groups
});
}
onDeleteDepartment = (id) => {
const { groups } = this.state;
this.setState({
groups: groups.filter((item) => item.id != id)
});
}
onSetDepartmentQuota = (target) => {
const { groups } = this.state;
this.setState({
groups: groups.map((item) => {
if (item.id == target.id) {
item.quota = target.quota;
}
return item;
})
});
}
render() { render() {
const groups = this.state.groups; const groups = this.state.groups;
const topbarChildren = ( const topbarChildren = (
@@ -88,7 +91,7 @@ class DepartmentsList extends React.Component {
{this.state.isShowAddDepartDialog && ( {this.state.isShowAddDepartDialog && (
<ModalPortal> <ModalPortal>
<AddDepartDialog <AddDepartDialog
onDepartChanged={this.onDepartChanged} onAddNewDepartment={this.onAddNewDepartment}
groupID={this.state.groupID} groupID={this.state.groupID}
toggle={this.toggleAddDepartDialog} toggle={this.toggleAddDepartDialog}
/> />
@@ -129,8 +132,8 @@ class DepartmentsList extends React.Component {
onFreezedItem={this.onFreezedItem} onFreezedItem={this.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem} onUnfreezedItem={this.onUnfreezedItem}
onDepartmentNameChanged={this.onDepartmentNameChanged} onDepartmentNameChanged={this.onDepartmentNameChanged}
showDeleteDepartDialog={this.showDeleteDepartDialog} onDeleteDepartment={this.onDeleteDepartment}
showSetGroupQuotaDialog={this.showSetGroupQuotaDialog} onSetDepartmentQuota={this.onSetDepartmentQuota}
/> />
</Fragment> </Fragment>
); );
@@ -141,25 +144,6 @@ class DepartmentsList extends React.Component {
<p className="no-group">{gettext('No departments')}</p> <p className="no-group">{gettext('No departments')}</p>
} }
</div> </div>
{this.state.showDeleteDepartDialog && (
<ModalPortal>
<DeleteDepartDialog
toggle={this.toggleCancel}
groupID={this.state.groupID}
groupName={this.state.groupName}
onDepartChanged={this.onDepartChanged}
/>
</ModalPortal>
)}
{this.state.showSetGroupQuotaDialog && (
<ModalPortal>
<SetGroupQuotaDialog
toggle={this.toggleCancel}
groupID={this.state.groupID}
onDepartChanged={this.onDepartChanged}
/>
</ModalPortal>
)}
</div> </div>
</div> </div>
</Fragment> </Fragment>
@@ -167,4 +151,4 @@ class DepartmentsList extends React.Component {
} }
} }
export default DepartmentsList; export default DepartmentList;

View File

@@ -0,0 +1,180 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import Paginator from '../../../components/paginator';
import { seafileAPI } from '../../../utils/seafile-api';
import { Utils } from '../../../utils/utils.js';
import ModalPortal from '../../../components/modal-portal';
import DeleteMemberDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-delete-member-dialog';
import { gettext, lang } from '../../../utils/constants';
import MemberItem from './member-item';
import Department from './department';
import '../../../css/org-department-item.css';
moment.locale(lang);
const DepartmentMembersPropTypes = {
groupID: PropTypes.string,
};
class DepartmentMembers extends React.Component {
constructor(props) {
super(props);
this.state = {
isItemFreezed: false,
members: [],
membersErrorMsg: '',
currentPageInfo: {
},
currentPage: 1,
perPage: 25,
deletedMember: {},
showDeleteMemberDialog: false
};
}
componentDidMount() {
let urlParams = (new URL(window.location)).searchParams;
const { currentPage, perPage } = this.state;
this.setState({
perPage: parseInt(urlParams.get('per_page') || perPage),
currentPage: parseInt(urlParams.get('page') || currentPage)
}, () => {
const { groupID } = this.props;
this.listMembers(groupID, this.state.currentPage, this.state.perPage);
});
}
componentWillReceiveProps(nextProps) {
if (this.props.groupID !== nextProps.groupID) {
this.listMembers(nextProps.groupID, this.state.currentPage, this.state.perPage);
}
}
listMembers = (groupID, page, perPage) => {
seafileAPI.sysAdminListGroupMembers(groupID, page, perPage).then((res) => {
this.setState({
members: res.data.members,
currentPageInfo: res.data.page_info
});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
this.setState({membersErrorMsg: errMessage});
});
}
getPreviousPageList = () => {
this.listMembers(this.props.groupID, this.state.currentPageInfo.current_page - 1, this.state.perPage);
}
getNextPageList = () => {
this.listMembers(this.props.groupID, this.state.currentPageInfo.current_page + 1, this.state.perPage);
}
resetPerPage = (perPage) => {
this.setState({
perPage: perPage
}, () => {
this.listMembers(this.props.groupID, 1, perPage);
});
}
toggleCancel = () => {
this.setState({
showDeleteMemberDialog: false,
});
}
onMemberChanged = () => {
this.listMembers(this.props.groupID, this.state.currentPageInfo.current_page, this.state.perPage);
}
toggleItemFreezed = (isFreezed) => {
this.setState({ isItemFreezed: isFreezed });
}
showDeleteMemberDialog = (member) => {
this.setState({ showDeleteMemberDialog: true, deletedMember: member });
}
onAddNewMembers = (newMembers) => {
const { members } = this.state;
members.unshift(...newMembers);
this.setState({ members });
}
render() {
const { members, membersErrorMsg } = this.state;
const { groupID } = this.props;
return (
<Fragment>
<Department
groupID={groupID}
currentItem="members"
onAddNewMembers={this.onAddNewMembers}
>
<div className="cur-view-content">
{membersErrorMsg ? <p className="error text-center">{membersErrorMsg}</p> :
members.length == 0 ?
<p className="no-member">{gettext('No members')}</p> :
<Fragment>
<table>
<thead>
<tr>
<th width="5%"></th>
<th width="50%">{gettext('Name')}</th>
<th width="15%">{gettext('Role')}</th>
<th width="30%"></th>
</tr>
</thead>
<tbody>
{members.map((member, index) => {
return (
<Fragment key={index}>
<MemberItem
member={member}
showDeleteMemberDialog={this.showDeleteMemberDialog}
isItemFreezed={this.state.isItemFreezed}
onMemberChanged={this.onMemberChanged}
toggleItemFreezed={this.toggleItemFreezed}
groupID={groupID}
/>
</Fragment>
);
})}
</tbody>
</table>
{this.state.currentPageInfo &&
<Paginator
gotoPreviousPage={this.getPreviousPageList}
gotoNextPage={this.getNextPageList}
currentPage={this.state.currentPageInfo.current_page}
hasNextPage={this.state.currentPageInfo.has_next_page}
curPerPage={this.state.perPage}
resetPerPage={this.resetPerPage}
/>
}
</Fragment>
}
</div>
</Department>
{this.state.showDeleteMemberDialog && (
<ModalPortal>
<DeleteMemberDialog
toggle={this.toggleCancel}
onMemberChanged={this.onMemberChanged}
member={this.state.deletedMember}
groupID={groupID}
/>
</ModalPortal>
)}
</Fragment>
);
}
}
DepartmentMembers.propTypes = DepartmentMembersPropTypes;
export default DepartmentMembers;

View File

@@ -0,0 +1,191 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { Link } from '@gatsbyjs/reach-router';
import { seafileAPI } from '../../../utils/seafile-api';
import { Utils } from '../../../utils/utils.js';
import toaster from '../../../components/toast';
import MainPanelTopbar from '../main-panel-topbar';
import ModalPortal from '../../../components/modal-portal';
import AddDepartmentDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-department-dialog';
import RenameDepartmentDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-rename-department-dialog';
import AddMemberDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-member-dialog';
import AddRepoDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-repo-dialog';
import { siteRoot, gettext, lang } from '../../../utils/constants';
import '../../../css/org-department-item.css';
moment.locale(lang);
const DepartmentDetailPropTypes = {
groupID: PropTypes.string,
currentItem: PropTypes.string.isRequired,
onAddNewDepartment: PropTypes.func,
onAddNewMembers: PropTypes.func,
onAddNewRepo: PropTypes.func,
children: PropTypes.object
};
class Department extends React.Component {
constructor(props) {
super(props);
this.state = {
groupName: '',
ancestorGroups: [],
isShowAddDepartmentDialog: false,
isShowAddMemberDialog: false,
isShowRenameDepartmentDialog: false,
isShowAddRepoDialog: false
};
this.navItems = [
{name: 'subDepartments', urlPart: '/', text: gettext('Sub-departments')},
{name: 'members', urlPart: '/members/', text: gettext('Members')},
{name: 'repos', urlPart: '/libraries/', text: gettext('Libraries')}
];
}
componentDidMount() {
const groupID = this.props.groupID;
this.getDepartmentInfo(groupID);
}
componentWillReceiveProps(nextProps) {
if (this.props.groupID !== nextProps.groupID) {
this.getDepartmentInfo(nextProps.groupID);
}
}
getDepartmentInfo = (groupID) => {
seafileAPI.sysAdminGetDepartmentInfo(groupID, true).then(res => {
this.setState({
ancestorGroups: res.data.ancestor_groups,
groupName: res.data.name,
});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
onDepartmentNameChanged = (dept) => {
this.setState({
groupName: dept.name
});
}
toggleRenameDepartmentDialog = () => {
this.setState({ isShowRenameDepartmentDialog: !this.state.isShowRenameDepartmentDialog });
}
toggleAddRepoDialog = () => {
this.setState({ isShowAddRepoDialog: !this.state.isShowAddRepoDialog });
}
toggleAddMemberDialog = () => {
this.setState({ isShowAddMemberDialog: !this.state.isShowAddMemberDialog });
}
toggleAddDepartmentDialog = () => {
this.setState({ isShowAddDepartmentDialog: !this.state.isShowAddDepartmentDialog});
}
render() {
const { groupID, currentItem } = this.props;
const { groupName } = this.state;
const topBtn = 'btn btn-secondary operation-item';
const topbarChildren = (
<Fragment>
{groupID &&
<Fragment>
<button className={topBtn} title={gettext('Rename Department')} onClick={this.toggleRenameDepartmentDialog}>{gettext('Rename Department')}</button>
{currentItem == 'subDepartments' && <button className={topBtn} title={gettext('New Sub-department')} onClick={this.toggleAddDepartmentDialog}>{gettext('New Sub-department')}</button>}
{currentItem == 'members' && <button className={topBtn} title={gettext('Add Member')} onClick={this.toggleAddMemberDialog}>{gettext('Add Member')}</button>}
{currentItem == 'repos' && <button className={topBtn} onClick={this.toggleAddRepoDialog} title={gettext('New Library')}>{gettext('New Library')}</button>}
</Fragment>
}
{this.state.isShowRenameDepartmentDialog && (
<ModalPortal>
<RenameDepartmentDialog
groupID={groupID}
name={groupName}
toggle={this.toggleRenameDepartmentDialog}
onDepartmentNameChanged={this.onDepartmentNameChanged}
/>
</ModalPortal>
)}
{this.state.isShowAddMemberDialog && (
<ModalPortal>
<AddMemberDialog
toggle={this.toggleAddMemberDialog}
onAddNewMembers={this.props.onAddNewMembers}
groupID={groupID}
/>
</ModalPortal>
)}
{this.state.isShowAddRepoDialog && (
<ModalPortal>
<AddRepoDialog
toggle={this.toggleAddRepoDialog}
onAddNewRepo={this.props.onAddNewRepo}
groupID={groupID}
/>
</ModalPortal>
)}
{this.state.isShowAddDepartmentDialog && (
<ModalPortal>
<AddDepartmentDialog
onAddNewDepartment={this.props.onAddNewDepartment}
parentGroupID={groupID}
toggle={this.toggleAddDepartmentDialog}
/>
</ModalPortal>
)}
</Fragment>
);
return (
<Fragment>
<MainPanelTopbar {...this.props}>
{topbarChildren}
</MainPanelTopbar>
<div className="main-panel-center flex-row h-100">
<div className="cur-view-container o-auto">
<div className="cur-view-path">
<div className="fleft">
<h3 className="sf-heading">
{groupID ?
<Link to={siteRoot + 'sys/departments/'}>{gettext('Departments')}</Link>
: <span>{gettext('Departments')}</span>
}
{this.state.ancestorGroups.map(ancestor => {
let newHref = siteRoot + 'sys/departments/' + ancestor.id + '/';
return <span key={ancestor.id}>{' / '}<Link to={newHref}>{ancestor.name}</Link></span>;
})}
{groupID && <span>{' / '}{groupName}</span>}
</h3>
</div>
</div>
<ul className="nav border-bottom mx-4">
{this.navItems.map((item, index) => {
return (
<li className="nav-item mr-2" key={index}>
<Link to={`${siteRoot}sys/departments/${groupID}${item.urlPart}`} className={`nav-link ${currentItem == item.name ? ' active' : ''}`}>{item.text}</Link>
</li>
);
})}
</ul>
{this.props.children}
</div>
</div>
</Fragment>
);
}
}
Department.propTypes = DepartmentDetailPropTypes;
export default Department;

View File

@@ -5,7 +5,10 @@ import { Link } from '@gatsbyjs/reach-router';
import { Utils } from '../../../utils/utils.js'; import { Utils } from '../../../utils/utils.js';
import { siteRoot, gettext } from '../../../utils/constants'; import { siteRoot, gettext } from '../../../utils/constants';
import OpMenu from '../../../components/dialog/op-menu'; import OpMenu from '../../../components/dialog/op-menu';
import ModalPortal from '../../../components/modal-portal';
import RenameDepartmentDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-rename-department-dialog'; import RenameDepartmentDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-rename-department-dialog';
import DeleteDepartmentDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-delete-department-dialog';
import SetGroupQuotaDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-set-group-quota-dialog';
const GroupItemPropTypes = { const GroupItemPropTypes = {
isItemFreezed: PropTypes.bool.isRequired, isItemFreezed: PropTypes.bool.isRequired,
@@ -13,8 +16,8 @@ const GroupItemPropTypes = {
onUnfreezedItem: PropTypes.func.isRequired, onUnfreezedItem: PropTypes.func.isRequired,
group: PropTypes.object.isRequired, group: PropTypes.object.isRequired,
onDepartmentNameChanged: PropTypes.func.isRequired, onDepartmentNameChanged: PropTypes.func.isRequired,
showSetGroupQuotaDialog: PropTypes.func.isRequired, onDeleteDepartment: PropTypes.func.isRequired,
showDeleteDepartDialog: PropTypes.func.isRequired, onSetDepartmentQuota: PropTypes.func.isRequired
}; };
class GroupItem extends React.Component { class GroupItem extends React.Component {
@@ -24,6 +27,8 @@ class GroupItem extends React.Component {
this.state = { this.state = {
isOpIconShown: false, isOpIconShown: false,
highlight: false, highlight: false,
isSetQuotaDialogOpen: false,
isDeleteDialogOpen: false,
isRenameDialogOpen: false isRenameDialogOpen: false
}; };
} }
@@ -71,13 +76,12 @@ class GroupItem extends React.Component {
} }
onMenuItemClick = (operation) => { onMenuItemClick = (operation) => {
const { group } = this.props;
switch(operation) { switch(operation) {
case 'Rename': case 'Rename':
this.toggleRenameDialog(); this.toggleRenameDialog();
break; break;
case 'Delete': case 'Delete':
this.props.showDeleteDepartDialog(group); this.toggleDeleteDialog();
break; break;
default: default:
break; break;
@@ -90,26 +94,33 @@ class GroupItem extends React.Component {
}); });
} }
toggleDeleteDialog = () => {
this.setState({
isDeleteDialogOpen: !this.state.isDeleteDialogOpen
});
}
toggleSetQuotaDialog = () => {
this.setState({
isSetQuotaDialogOpen: !this.state.isSetQuotaDialogOpen
});
}
render() { render() {
const { group } = this.props; const { group } = this.props;
const { highlight, isOpIconShown, isRenameDialogOpen } = this.state; const { highlight, isOpIconShown, isRenameDialogOpen, isDeleteDialogOpen, isSetQuotaDialogOpen } = this.state;
const newHref = siteRoot+ 'sys/departments/' + group.id + '/'; const newHref = siteRoot+ 'sys/departments/' + group.id + '/';
return ( return (
<Fragment> <Fragment>
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}> <tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
<td><Link to={newHref}>{group.name}</Link></td> <td><Link to={newHref}>{group.name}</Link></td>
<td>{moment(group.created_at).fromNow()}</td> <td>{moment(group.created_at).fromNow()}</td>
{group.org_id === -1 ? <td>
<td onClick={this.props.showSetGroupQuotaDialog.bind(this, group.id)}> {Utils.bytesToSize(group.quota)}{' '}
{Utils.bytesToSize(group.quota)}{' '} <span onClick={this.toggleSetQuotaDialog} title={gettext('Edit')} className={`fa fa-pencil-alt attr-action-icon ${highlight ? '' : 'vh'}`}></span>
<span title="Edit Quota" className={`fa fa-pencil-alt attr-action-icon ${highlight ? '' : 'vh'}`}></span> </td>
</td> <td>
: {isOpIconShown &&
<td>{Utils.bytesToSize(group.quota)}</td>
}
{group.org_id === -1 ?
<td>
{isOpIconShown &&
<OpMenu <OpMenu
operations={['Rename', 'Delete']} operations={['Rename', 'Delete']}
translateOperations={this.translateOperations} translateOperations={this.translateOperations}
@@ -117,12 +128,27 @@ class GroupItem extends React.Component {
onFreezedItem={this.props.onFreezedItem} onFreezedItem={this.props.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem} onUnfreezedItem={this.onUnfreezedItem}
/> />
} }
</td> </td>
:
<td></td>
}
</tr> </tr>
{isDeleteDialogOpen && (
<ModalPortal>
<DeleteDepartmentDialog
group={group}
onDeleteDepartment={this.props.onDeleteDepartment}
toggle={this.toggleDeleteDialog}
/>
</ModalPortal>
)}
{isSetQuotaDialogOpen && (
<ModalPortal>
<SetGroupQuotaDialog
groupID={group.id}
onSetQuota={this.props.onSetDepartmentQuota}
toggle={this.toggleSetQuotaDialog}
/>
</ModalPortal>
)}
{isRenameDialogOpen && ( {isRenameDialogOpen && (
<RenameDepartmentDialog <RenameDepartmentDialog
groupID={group.id} groupID={group.id}

View File

@@ -61,21 +61,17 @@ class MemberItem extends React.Component {
<tr className={highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}> <tr className={highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<td><img src={member.avatar_url} alt="member-header" width="24" className="avatar"/></td> <td><img src={member.avatar_url} alt="member-header" width="24" className="avatar"/></td>
<td><UserLink email={member.email} name={member.name} /></td> <td><UserLink email={member.email} name={member.name} /></td>
{ this.props.orgID == -1 ? <td>
<td> <RoleEditor
<RoleEditor isTextMode={true}
isTextMode={true} isEditIconShow={highlight}
isEditIconShow={highlight} currentRole={member.role}
currentRole={member.role} roles={this.roles}
roles={this.roles} onRoleChanged={this.onChangeUserRole}
onRoleChanged={this.onChangeUserRole} toggleItemFreezed={this.props.toggleItemFreezed}
toggleItemFreezed={this.props.toggleItemFreezed} />
/> </td>
</td> {!this.props.isItemFreezed ?
:
<td>{member.role}</td>
}
{this.props.orgID == -1 && !this.props.isItemFreezed ?
<td className="cursor-pointer text-center" onClick={this.props.showDeleteMemberDialog.bind(this, member)}> <td className="cursor-pointer text-center" onClick={this.props.showDeleteMemberDialog.bind(this, member)}>
<span className={`sf2-icon-x3 action-icon ${highlight ? '' : 'vh'}`} title="Delete"></span> <span className={`sf2-icon-x3 action-icon ${highlight ? '' : 'vh'}`} title="Delete"></span>
</td> : <td></td> </td> : <td></td>

View File

@@ -7,7 +7,7 @@ const { enableSysAdminViewRepo } = window.sysadmin.pageOptions;
const RepoItemPropTypes = { const RepoItemPropTypes = {
repo: PropTypes.object.isRequired, repo: PropTypes.object.isRequired,
showDeleteRepoDialog: PropTypes.func.isRequired, showDeleteRepoDialog: PropTypes.func.isRequired
}; };
class RepoItem extends React.Component { class RepoItem extends React.Component {
@@ -15,7 +15,7 @@ class RepoItem extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
highlight: false, highlight: false
}; };
} }
@@ -28,25 +28,22 @@ class RepoItem extends React.Component {
} }
render() { render() {
const repo = this.props.repo; const { repo } = this.props;
const repoName = repo.name || repo.repo_name;
const highlight = this.state.highlight; const highlight = this.state.highlight;
let iconUrl = Utils.getLibIconUrl(repo); let iconUrl = Utils.getLibIconUrl(repo);
return ( return (
<tr className={highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}> <tr className={highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<td><img src={iconUrl} width="24" alt={gettext('icon')}/></td> <td><img src={iconUrl} width="24" alt={gettext('icon')}/></td>
{ enableSysAdminViewRepo ? { enableSysAdminViewRepo ?
<td><a href={siteRoot + 'sys/libraries/' + repo.repo_id + '/' + repo.name + '/'}>{repo.name}</a></td> <td><a href={`${siteRoot}sys/libraries/${repo.repo_id}/${encodeURIComponent(repoName)}/`}>{repoName}</a></td>
: :
<td>{repo.name}</td> <td>{repoName}</td>
} }
<td>{Utils.bytesToSize(repo.size)}{' '}</td> <td>{Utils.bytesToSize(repo.size)}</td>
{ this.props.orgID == -1 ? <td className="cursor-pointer text-center" onClick={this.props.showDeleteRepoDialog.bind(this, repo)}>
<td className="cursor-pointer text-center" onClick={this.props.showDeleteRepoDialog.bind(this, repo)}> <span className={`sf2-icon-delete action-icon ${highlight ? '' : 'vh'}`} title="Delete"></span>
<span className={`sf2-icon-delete action-icon ${highlight ? '' : 'vh'}`} title="Delete"></span> </td>
</td>
:
<td></td>
}
</tr> </tr>
); );
} }

View File

@@ -0,0 +1,142 @@
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { seafileAPI } from '../../../utils/seafile-api';
import { Utils } from '../../../utils/utils.js';
import toaster from '../../../components/toast';
import { gettext, lang } from '../../../utils/constants';
import GroupItem from './group-item';
import Department from './department';
import '../../../css/org-department-item.css';
moment.locale(lang);
const SubDepartmentsPropTypes = {
groupID: PropTypes.string
};
class SubDepartments extends React.Component {
constructor(props) {
super(props);
this.state = {
isItemFreezed: false,
groups: []
};
}
componentDidMount() {
this.listSubDepartments(this.props.groupID);
}
componentWillReceiveProps(nextProps) {
if (this.props.groupID !== nextProps.groupID) {
this.listSubDepartments(nextProps.groupID);
}
}
listSubDepartments = (groupID) => {
seafileAPI.sysAdminGetDepartmentInfo(groupID, true).then(res => {
this.setState({groups: res.data.groups});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
onSubDepartmentNameChanged = (dept) => {
this.setState({
groups: this.state.groups.map(item => {
if (item.id == dept.id) {
item.name = dept.name;
}
return item;
})
});
}
onFreezedItem = () => {
this.setState({isItemFreezed: true});
}
onUnfreezedItem = () => {
this.setState({isItemFreezed: false});
}
onAddNewDepartment = (newDepartment) => {
const { groups } = this.state;
groups.unshift(newDepartment);
this.setState({
groups: groups
});
}
onDeleteDepartment = (id) => {
const { groups } = this.state;
this.setState({
groups: groups.filter(item => item.id != id)
});
}
onSetDepartmentQuota = (target) => {
const { groups } = this.state;
this.setState({
groups: groups.map((item) => {
if (item.id == target.id) {
item.quota = target.quota;
}
return item;
})
});
}
render() {
const { groups } = this.state;
const { groupID } = this.props;
return (
<Department
groupID={groupID}
currentItem="subDepartments"
onAddNewDepartment={this.onAddNewDepartment}
>
<div className="cur-view-content">
{groups && groups.length > 0 ?
<table>
<thead>
<tr>
<th width="40%">{gettext('Name')}</th>
<th width="25%">{gettext('Created At')}</th>
<th width="20%">{gettext('Quota')}</th>
<th width="15%"></th>
</tr>
</thead>
<tbody>
{groups.map((group, index) => {
return(
<GroupItem
key={group.id}
isItemFreezed={this.state.isItemFreezed}
onFreezedItem={this.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem}
onDepartmentNameChanged={this.onSubDepartmentNameChanged}
group={group}
onDeleteDepartment={this.onDeleteDepartment}
onSetDepartmentQuota={this.onSetDepartmentQuota}
/>
);
})}
</tbody>
</table>
: <p className="no-group">{gettext('No sub-departments')}</p>
}
</div>
</Department>
);
}
}
SubDepartments.propTypes = SubDepartmentsPropTypes;
export default SubDepartments;

View File

@@ -44,8 +44,10 @@ import GroupRepos from './groups/group-repos';
import GroupMembers from './groups/group-members'; import GroupMembers from './groups/group-members';
import Departments from './departments/departments'; import Departments from './departments/departments';
import DepartmentsList from './departments/departments-list'; import DepartmentList from './departments/department-list';
import DepartmentDetail from './departments/department-detail'; import SubDepartments from './departments/sub-departments';
import DepartmentMembers from './departments/department-members';
import DepartmentLibraries from './departments/department-libraries';
import ShareLinks from './links/share-links'; import ShareLinks from './links/share-links';
import UploadLinks from './links/upload-links'; import UploadLinks from './links/upload-links';
@@ -235,8 +237,10 @@ class SysAdmin extends React.Component {
<GroupRepos path={siteRoot + 'sys/groups/:groupID/libraries'} {...commonProps} /> <GroupRepos path={siteRoot + 'sys/groups/:groupID/libraries'} {...commonProps} />
<GroupMembers path={siteRoot + 'sys/groups/:groupID/members'} {...commonProps} /> <GroupMembers path={siteRoot + 'sys/groups/:groupID/members'} {...commonProps} />
<Departments path={siteRoot + 'sys/departments'}> <Departments path={siteRoot + 'sys/departments'}>
<DepartmentsList path='/' {...commonProps} /> <DepartmentList path='/' {...commonProps} />
<DepartmentDetail path='/:groupID' {...commonProps} /> <SubDepartments path='/:groupID' {...commonProps} />
<DepartmentMembers path='/:groupID/members' {...commonProps} />
<DepartmentLibraries path='/:groupID/libraries' {...commonProps} />
</Departments> </Departments>
<ShareLinks path={siteRoot + 'sys/share-links'} {...commonProps} /> <ShareLinks path={siteRoot + 'sys/share-links'} {...commonProps} />
<UploadLinks path={siteRoot + 'sys/upload-links'} {...commonProps} /> <UploadLinks path={siteRoot + 'sys/upload-links'} {...commonProps} />

View File

@@ -760,6 +760,8 @@ urlpatterns = [
url(r'^sys/groups/(?P<group_id>\d+)/members/$', sysadmin_react_fake_view, name="sys_group_members"), url(r'^sys/groups/(?P<group_id>\d+)/members/$', sysadmin_react_fake_view, name="sys_group_members"),
url(r'^sys/departments/$', sysadmin_react_fake_view, name="sys_departments"), url(r'^sys/departments/$', sysadmin_react_fake_view, name="sys_departments"),
url(r'^sys/departments/(?P<group_id>\d+)/$', sysadmin_react_fake_view, name="sys_department"), url(r'^sys/departments/(?P<group_id>\d+)/$', sysadmin_react_fake_view, name="sys_department"),
url(r'^sys/departments/(?P<group_id>\d+)/members/$', sysadmin_react_fake_view, name="sys_department_members"),
url(r'^sys/departments/(?P<group_id>\d+)/libraries/$', sysadmin_react_fake_view, name="sys_department_libraries"),
url(r'^sys/users/$', sysadmin_react_fake_view, name="sys_users"), 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/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/admins/$', sysadmin_react_fake_view, name="sys_users_admin"),