mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-06 01:23:56 +00:00
sysadmin reconstruct department pages (#4156)
* sysadmin reconstruct department pages * optimize code
This commit is contained in:
parent
36a734d9b4
commit
58a2bff06e
@ -0,0 +1,101 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Input, Form, FormGroup, Label } from 'reactstrap';
|
||||||
|
import { gettext, orgID } from '../../../utils/constants';
|
||||||
|
import { seafileAPI } from '../../../utils/seafile-api';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
groupID: PropTypes.string,
|
||||||
|
parentGroupID: PropTypes.string,
|
||||||
|
toggle: PropTypes.func.isRequired,
|
||||||
|
onDepartChanged: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class AddDepartDialog extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
departName: '',
|
||||||
|
errMessage: '',
|
||||||
|
};
|
||||||
|
this.newInput = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.newInput.focus();
|
||||||
|
this.newInput.setSelectionRange(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit = () => {
|
||||||
|
let isValid = this.validateName();
|
||||||
|
if (isValid) {
|
||||||
|
let parentGroup = -1;
|
||||||
|
if (this.props.parentGroupID) {
|
||||||
|
parentGroup = this.props.parentGroupID;
|
||||||
|
}
|
||||||
|
seafileAPI.sysAdminAddNewDepartment(parentGroup, this.state.departName.trim()).then((res) => {
|
||||||
|
this.props.toggle();
|
||||||
|
this.props.onDepartChanged();
|
||||||
|
}).catch(error => {
|
||||||
|
let errorMsg = gettext(error.response.data.error_msg);
|
||||||
|
this.setState({ errMessage: errorMsg });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateName = () => {
|
||||||
|
let errMessage = '';
|
||||||
|
const name = this.state.departName.trim();
|
||||||
|
if (!name.length) {
|
||||||
|
errMessage = gettext('Name is required');
|
||||||
|
this.setState({ errMessage: errMessage });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = (e) => {
|
||||||
|
this.setState({
|
||||||
|
departName: e.target.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyPress = (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
this.handleSubmit();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let header = this.props.parentGroupID ? gettext('New Sub-department') : gettext('New Department');
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} toggle={this.props.toggle}>
|
||||||
|
<ModalHeader toggle={this.props.toggle}>{header}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<Form>
|
||||||
|
<FormGroup>
|
||||||
|
<Label for="departName">{gettext('Name')}</Label>
|
||||||
|
<Input
|
||||||
|
id="departName"
|
||||||
|
onKeyPress={this.handleKeyPress}
|
||||||
|
value={this.state.departName}
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddDepartDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default AddDepartDialog;
|
@ -0,0 +1,75 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
|
||||||
|
import { gettext, orgID } from '../../../utils/constants';
|
||||||
|
import { seafileAPI } from '../../../utils/seafile-api';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
import toaster from '../../toast';
|
||||||
|
import UserSelect from '../../user-select.js';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
toggle: PropTypes.func.isRequired,
|
||||||
|
groupID: PropTypes.string.isRequired,
|
||||||
|
onMemberChanged: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class AddMemberDialog extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
selectedOption: null,
|
||||||
|
errMessage: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSelectChange = (option) => {
|
||||||
|
this.setState({ selectedOption: option });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit = () => {
|
||||||
|
if (!this.state.selectedOption) return;
|
||||||
|
const emails = this.state.selectedOption.map(item => item.email);
|
||||||
|
this.refs.orgSelect.clearSelect();
|
||||||
|
this.setState({ errMessage: [] });
|
||||||
|
seafileAPI.sysAdminAddGroupMember(this.props.groupID, emails).then((res) => {
|
||||||
|
this.setState({ selectedOption: null });
|
||||||
|
if (res.data.failed.length > 0) {
|
||||||
|
this.setState({ errMessage: res.data.failed[0].error_msg });
|
||||||
|
}
|
||||||
|
if (res.data.success.length > 0) {
|
||||||
|
this.props.onMemberChanged();
|
||||||
|
this.props.toggle();
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} toggle={this.props.toggle}>
|
||||||
|
<ModalHeader toggle={this.props.toggle}>{gettext('Add Member')}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<UserSelect
|
||||||
|
placeholder={gettext('Select users...')}
|
||||||
|
onSelectChange={this.handleSelectChange}
|
||||||
|
ref="orgSelect"
|
||||||
|
isMulti={true}
|
||||||
|
className='org-add-member-select'
|
||||||
|
/>
|
||||||
|
{ this.state.errMessage && <p className="error">{this.state.errMessage}</p> }
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
|
||||||
|
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddMemberDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default AddMemberDialog;
|
@ -0,0 +1,96 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
toggle: PropTypes.func.isRequired,
|
||||||
|
groupID: PropTypes.string.isRequired,
|
||||||
|
onRepoChanged: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class AddRepoDialog extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
repoName: '',
|
||||||
|
errMessage: '',
|
||||||
|
};
|
||||||
|
this.newInput = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.newInput.focus();
|
||||||
|
this.newInput.setSelectionRange(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit = () => {
|
||||||
|
let isValid = this.validateName();
|
||||||
|
if (isValid) {
|
||||||
|
seafileAPI.sysAdminAddRepoInDepartment(this.props.groupID, this.state.repoName.trim()).then((res) => {
|
||||||
|
this.props.toggle();
|
||||||
|
this.props.onRepoChanged();
|
||||||
|
}).catch(error => {
|
||||||
|
let errorMsg = Utils.getErrorMsg(error);
|
||||||
|
this.setState({ errMessage: errorMsg });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateName = () => {
|
||||||
|
let errMessage = '';
|
||||||
|
const name = this.state.repoName.trim();
|
||||||
|
if (!name.length) {
|
||||||
|
errMessage = gettext('Name is required');
|
||||||
|
this.setState({ errMessage: errMessage });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = (e) => {
|
||||||
|
this.setState({
|
||||||
|
repoName: e.target.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyPress = (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
this.handleSubmit();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} toggle={this.props.toggle}>
|
||||||
|
<ModalHeader toggle={this.props.toggle}>{gettext('New Library')}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<Form>
|
||||||
|
<FormGroup>
|
||||||
|
<Label for="repoName">{gettext('Name')}</Label>
|
||||||
|
<Input
|
||||||
|
id="repoName"
|
||||||
|
onKeyPress={this.handleKeyPress}
|
||||||
|
value={this.state.repoName}
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddRepoDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default AddRepoDialog;
|
@ -0,0 +1,56 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
import { seafileAPI } from '../../../utils/seafile-api';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
groupName: PropTypes.string,
|
||||||
|
groupID: PropTypes.number.isRequired,
|
||||||
|
toggle: PropTypes.func.isRequired,
|
||||||
|
onDepartChanged: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class DeleteDepartDialog extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
errMessage: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteDepart = () => {
|
||||||
|
seafileAPI.sysAdminDeleteDepartment(this.props.groupID).then((res) => {
|
||||||
|
if (res.data.success) {
|
||||||
|
this.props.onDepartChanged();
|
||||||
|
this.props.toggle();
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
this.setState({ errMessage: 'There are sub-departments in this department.' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
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>');
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} toggle={this.props.toggle}>
|
||||||
|
<ModalHeader toggle={this.props.toggle}>{gettext('Delete Department')}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<div dangerouslySetInnerHTML={{__html: tipMessage}}></div>
|
||||||
|
{ this.state.errMessage && <p className="error">{this.state.errMessage}</p> }
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
{!this.state.errMessage && <Button color="primary" onClick={this.deleteDepart}>{gettext('Delete')}</Button>}
|
||||||
|
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteDepartDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default DeleteDepartDialog;
|
@ -0,0 +1,55 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
import { seafileAPI } from '../../../utils/seafile-api';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
import toaster from '../../toast';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
member: PropTypes.object.isRequired,
|
||||||
|
groupID: PropTypes.string.isRequired,
|
||||||
|
toggle: PropTypes.func.isRequired,
|
||||||
|
onMemberChanged: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class DeleteMemberDialog extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteMember = () => {
|
||||||
|
const userEmail = this.props.member.email;
|
||||||
|
seafileAPI.sysAdminDeleteGroupMember(this.props.groupID, userEmail).then((res) => {
|
||||||
|
if (res.data.success) {
|
||||||
|
this.props.onMemberChanged();
|
||||||
|
this.props.toggle();
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let tipMessage = gettext('Are you sure you want to delete {placeholder} ?');
|
||||||
|
tipMessage = tipMessage.replace('{placeholder}', '<span class="op-target">' + Utils.HTMLescape(this.props.member.name) + '</span>');
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} toggle={this.props.toggle}>
|
||||||
|
<ModalHeader toggle={this.props.toggle}>{gettext('Delete Member')}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<div dangerouslySetInnerHTML={{__html: tipMessage}}></div>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="primary" onClick={this.deleteMember}>{gettext('Delete')}</Button>
|
||||||
|
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteMemberDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default DeleteMemberDialog;
|
@ -0,0 +1,54 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
import { seafileAPI } from '../../../utils/seafile-api';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
import toaster from '../../toast';
|
||||||
|
|
||||||
|
class DeleteRepoDialog extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRepo = () => {
|
||||||
|
seafileAPI.sysAdminDeleteRepoInDepartment(this.props.groupID, this.props.repo.repo_id).then((res) => {
|
||||||
|
if (res.data.success) {
|
||||||
|
this.props.onRepoChanged();
|
||||||
|
this.props.toggle();
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
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>');
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} toggle={this.props.toggle}>
|
||||||
|
<ModalHeader toggle={this.props.toggle}>{gettext('Delete Library')}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<div dangerouslySetInnerHTML={{__html: tipMessage}}></div>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="primary" onClick={this.deleteRepo}>{gettext('Delete')}</Button>
|
||||||
|
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
repo: PropTypes.object.isRequired,
|
||||||
|
toggle: PropTypes.func.isRequired,
|
||||||
|
groupID: PropTypes.string.isRequired,
|
||||||
|
onRepoChanged: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
DeleteRepoDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default DeleteRepoDialog;
|
@ -0,0 +1,92 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Input, InputGroupAddon, InputGroup } from 'reactstrap';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
import { seafileAPI } from '../../../utils/seafile-api';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
import toaster from '../../toast';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
toggle: PropTypes.func.isRequired,
|
||||||
|
groupID: PropTypes.number.isRequired,
|
||||||
|
onDepartChanged: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class SetGroupQuotaDialog extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
quota: '',
|
||||||
|
errMessage: '',
|
||||||
|
};
|
||||||
|
this.newInput = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.newInput.focus();
|
||||||
|
this.newInput.setSelectionRange(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
setGroupQuota = () => {
|
||||||
|
const numberReg = /^[1-9]\d*$/im;
|
||||||
|
let quota = this.state.quota;
|
||||||
|
if ((quota.length && numberReg.test(quota)) || quota == -2) {
|
||||||
|
this.setState({ errMessage: '' });
|
||||||
|
let newQuota = this.state.quota == -2 ? this.state.quota : this.state.quota * 1000000;
|
||||||
|
seafileAPI.sysAdminUpdateDepartmentQuota(this.props.groupID, newQuota).then((res) => {
|
||||||
|
this.props.toggle();
|
||||||
|
this.props.onDepartChanged();
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const err = gettext('Quota is invalid.');
|
||||||
|
this.setState({ errMessage: err });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = (e) => {
|
||||||
|
const quota = e.target.value.trim();
|
||||||
|
this.setState({ quota: quota });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyPress = (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
this.setGroupQuota();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} toggle={this.props.toggle}>
|
||||||
|
<ModalHeader toggle={this.props.toggle}>{gettext('Set Quota')}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<InputGroup>
|
||||||
|
<Input
|
||||||
|
onKeyPress={this.handleKeyPress}
|
||||||
|
value={this.state.quota}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
innerRef={input => {this.newInput = input;}}
|
||||||
|
/>
|
||||||
|
<InputGroupAddon addonType="append">{'MB'}</InputGroupAddon>
|
||||||
|
</InputGroup>
|
||||||
|
<p className="tip">
|
||||||
|
<br/><span>{gettext('An integer that is greater than 0 or equal to -2.')}</span><br/>
|
||||||
|
<span>{gettext('Tip: -2 means no limit.')}</span>
|
||||||
|
</p>
|
||||||
|
{ this.state.errMessage && <p className="error">{this.state.errMessage}</p> }
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="primary" onClick={this.setGroupQuota}>{gettext('Submit')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SetGroupQuotaDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default SetGroupQuotaDialog;
|
372
frontend/src/pages/sys-admin/departments/department-detail.js
Normal file
372
frontend/src/pages/sys-admin/departments/department-detail.js
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Link } from '@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 AddDepartDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-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 = {
|
||||||
|
groupName: '',
|
||||||
|
isItemFreezed: false,
|
||||||
|
ancestorGroups: [],
|
||||||
|
members: [],
|
||||||
|
deletedMember: {},
|
||||||
|
isShowAddMemberDialog: false,
|
||||||
|
showDeleteMemberDialog: false,
|
||||||
|
repos: [],
|
||||||
|
deletedRepo: {},
|
||||||
|
isShowAddRepoDialog: false,
|
||||||
|
showDeleteRepoDialog: false,
|
||||||
|
groups: [],
|
||||||
|
subGroupID: '',
|
||||||
|
subGroupName: '',
|
||||||
|
isShowAddDepartDialog: false,
|
||||||
|
showDeleteDepartDialog: false,
|
||||||
|
showSetGroupQuotaDialog: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const groupID = this.props.groupID;
|
||||||
|
this.listGroupRepo(groupID);
|
||||||
|
this.listMembers(groupID);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (this.props.groupID !== nextProps.groupID) {
|
||||||
|
this.listGroupRepo(nextProps.groupID);
|
||||||
|
this.listMembers(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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
listMembers = (groupID) => {
|
||||||
|
seafileAPI.sysAdminGetDepartmentInfo(groupID, true).then(res => {
|
||||||
|
this.setState({
|
||||||
|
members: res.data.members,
|
||||||
|
groups: res.data.groups,
|
||||||
|
ancestorGroups: res.data.ancestor_groups,
|
||||||
|
groupName: res.data.name,
|
||||||
|
});
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
onRepoChanged = () => {
|
||||||
|
this.listGroupRepo(this.props.groupID);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMemberChanged = () => {
|
||||||
|
this.listMembers(this.props.groupID);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleItemFreezed = (isFreezed) => {
|
||||||
|
this.setState({ isItemFreezed: isFreezed });
|
||||||
|
}
|
||||||
|
|
||||||
|
showDeleteMemberDialog = (member) => {
|
||||||
|
this.setState({ showDeleteMemberDialog: true, deletedMember: member });
|
||||||
|
}
|
||||||
|
|
||||||
|
showDeleteRepoDialog = (repo) => {
|
||||||
|
this.setState({ showDeleteRepoDialog: true, deletedRepo: repo });
|
||||||
|
}
|
||||||
|
|
||||||
|
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, repos, groups } = this.state;
|
||||||
|
const groupID = this.props.groupID;
|
||||||
|
const topBtn = 'btn btn-secondary operation-item';
|
||||||
|
const topbarChildren = (
|
||||||
|
<Fragment>
|
||||||
|
{groupID &&
|
||||||
|
<Fragment>
|
||||||
|
<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.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 children={topbarChildren} />
|
||||||
|
<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
|
||||||
|
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">
|
||||||
|
{(members && members.length === 1 && members[0].role === 'Owner') ?
|
||||||
|
<p className="no-member">{gettext('No members')}</p> :
|
||||||
|
<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>
|
||||||
|
}
|
||||||
|
</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} 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;
|
145
frontend/src/pages/sys-admin/departments/departments-list.js
Normal file
145
frontend/src/pages/sys-admin/departments/departments-list.js
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { seafileAPI } from '../../../utils/seafile-api';
|
||||||
|
import MainPanelTopbar from '../main-panel-topbar';
|
||||||
|
import ModalPortal from '../../../components/modal-portal';
|
||||||
|
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 GroupItem from './group-item';
|
||||||
|
import '../../../css/org-department-item.css';
|
||||||
|
|
||||||
|
moment.locale(lang);
|
||||||
|
|
||||||
|
class DepartmentsList extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
groups: null,
|
||||||
|
groupID: '',
|
||||||
|
groupName: '',
|
||||||
|
showDeleteDepartDialog: false,
|
||||||
|
showSetGroupQuotaDialog: false,
|
||||||
|
isShowAddDepartDialog: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.listDepartGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
listDepartGroups = () => {
|
||||||
|
seafileAPI.sysAdminListAllDepartments().then(res => {
|
||||||
|
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 = () => {
|
||||||
|
this.setState({ isShowAddDepartDialog: !this.state.isShowAddDepartDialog});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleCancel = () => {
|
||||||
|
this.setState({
|
||||||
|
showDeleteDepartDialog: false,
|
||||||
|
showSetGroupQuotaDialog: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onDepartChanged = () => {
|
||||||
|
this.listDepartGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const groups = this.state.groups;
|
||||||
|
const topbarChildren = (
|
||||||
|
<Fragment>
|
||||||
|
<button className='btn btn-secondary operation-item' title={gettext('New Department')} onClick={this.toggleAddDepartDialog}>{gettext('New Department')}
|
||||||
|
</button>
|
||||||
|
{this.state.isShowAddDepartDialog && (
|
||||||
|
<ModalPortal>
|
||||||
|
<AddDepartDialog
|
||||||
|
onDepartChanged={this.onDepartChanged}
|
||||||
|
groupID={this.state.groupID}
|
||||||
|
toggle={this.toggleAddDepartDialog}
|
||||||
|
/>
|
||||||
|
</ModalPortal>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<MainPanelTopbar children={topbarChildren}/>
|
||||||
|
<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">{gettext('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
|
||||||
|
group={group}
|
||||||
|
showDeleteDepartDialog={this.showDeleteDepartDialog}
|
||||||
|
showSetGroupQuotaDialog={this.showSetGroupQuotaDialog}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
:
|
||||||
|
<p className="no-group">{gettext('No departments')}</p>
|
||||||
|
}
|
||||||
|
</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>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DepartmentsList;
|
19
frontend/src/pages/sys-admin/departments/departments.js
Normal file
19
frontend/src/pages/sys-admin/departments/departments.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import '../../../css/org-department-item.css';
|
||||||
|
|
||||||
|
class Departments extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="h-100 org-departments">
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Departments;
|
53
frontend/src/pages/sys-admin/departments/group-item.js
Normal file
53
frontend/src/pages/sys-admin/departments/group-item.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Link } from '@reach/router';
|
||||||
|
import { Utils } from '../../../utils/utils.js';
|
||||||
|
import { siteRoot } from '../../../utils/constants';
|
||||||
|
|
||||||
|
const GroupItemPropTypes = {
|
||||||
|
group: PropTypes.object.isRequired,
|
||||||
|
showSetGroupQuotaDialog: PropTypes.func.isRequired,
|
||||||
|
showDeleteDepartDialog: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class GroupItem extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
highlight: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseEnter = () => {
|
||||||
|
this.setState({ highlight: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseLeave = () => {
|
||||||
|
this.setState({ highlight: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const group = this.props.group;
|
||||||
|
const highlight = this.state.highlight;
|
||||||
|
const newHref = siteRoot+ 'sys/departments/' + group.id + '/';
|
||||||
|
return (
|
||||||
|
<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>
|
||||||
|
<td onClick={this.props.showSetGroupQuotaDialog.bind(this, group.id)}>
|
||||||
|
{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>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupItem.propTypes = GroupItemPropTypes;
|
||||||
|
|
||||||
|
export default GroupItem;
|
87
frontend/src/pages/sys-admin/departments/member-item.js
Normal file
87
frontend/src/pages/sys-admin/departments/member-item.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { seafileAPI } from '../../../utils/seafile-api';
|
||||||
|
import { Utils } from '../../../utils/utils.js';
|
||||||
|
import toaster from '../../../components/toast';
|
||||||
|
import RoleEditor from '../../../components/select-editor/role-editor';
|
||||||
|
import { serviceURL } from '../../../utils/constants';
|
||||||
|
|
||||||
|
const MemberItemPropTypes = {
|
||||||
|
groupID: PropTypes.string.isRequired,
|
||||||
|
member: PropTypes.object.isRequired,
|
||||||
|
isItemFreezed: PropTypes.bool.isRequired,
|
||||||
|
onMemberChanged: PropTypes.func.isRequired,
|
||||||
|
showDeleteMemberDialog: PropTypes.func.isRequired,
|
||||||
|
toggleItemFreezed: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class MemberItem extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
highlight: false,
|
||||||
|
showRoleMenu: false,
|
||||||
|
};
|
||||||
|
this.roles = ['Admin', 'Member'];
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseEnter = () => {
|
||||||
|
if (this.props.isItemFreezed) return;
|
||||||
|
this.setState({ highlight: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseLeave = () => {
|
||||||
|
if (this.props.isItemFreezed) return;
|
||||||
|
this.setState({ highlight: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMemberRoleMenu = () => {
|
||||||
|
this.setState({ showRoleMenu: !this.state.showRoleMenu });
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeUserRole = (role) => {
|
||||||
|
let isAdmin = role === 'Admin' ? true : false;
|
||||||
|
seafileAPI.sysAdminUpdateGroupMemberRole(this.props.groupID, this.props.member.email, isAdmin).then((res) => {
|
||||||
|
this.props.onMemberChanged();
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
highlight: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const member = this.props.member;
|
||||||
|
const highlight = this.state.highlight;
|
||||||
|
let memberLink = serviceURL + '/useradmin/info/' + member.email + '/';
|
||||||
|
if (member.role === 'Owner') return null;
|
||||||
|
return (
|
||||||
|
<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><a href={memberLink}>{member.name}</a></td>
|
||||||
|
<td>
|
||||||
|
<RoleEditor
|
||||||
|
isTextMode={true}
|
||||||
|
isEditIconShow={highlight}
|
||||||
|
currentRole={member.role}
|
||||||
|
roles={this.roles}
|
||||||
|
onRoleChanged={this.onChangeUserRole}
|
||||||
|
toggleItemFreezed={this.props.toggleItemFreezed}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
{!this.props.isItemFreezed ?
|
||||||
|
<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>
|
||||||
|
</td> : <td></td>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MemberItem.propTypes = MemberItemPropTypes;
|
||||||
|
|
||||||
|
export default MemberItem;
|
47
frontend/src/pages/sys-admin/departments/repo-item.js
Normal file
47
frontend/src/pages/sys-admin/departments/repo-item.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Utils } from '../../../utils/utils.js';
|
||||||
|
import { siteRoot, gettext } from '../../../utils/constants';
|
||||||
|
|
||||||
|
const RepoItemPropTypes = {
|
||||||
|
repo: PropTypes.object.isRequired,
|
||||||
|
showDeleteRepoDialog: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class RepoItem extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
highlight: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseEnter = () => {
|
||||||
|
this.setState({ highlight: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseLeave = () => {
|
||||||
|
this.setState({ highlight: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const repo = this.props.repo;
|
||||||
|
const highlight = this.state.highlight;
|
||||||
|
let iconUrl = Utils.getLibIconUrl(repo);
|
||||||
|
return (
|
||||||
|
<tr className={highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||||
|
<td><img src={iconUrl} width="24" alt={gettext('icon')}/></td>
|
||||||
|
<td><a href={siteRoot + 'sys/libraries/' + repo.repo_id + '/' + repo.name + '/'}>{repo.name}</a></td>
|
||||||
|
<td>{Utils.bytesToSize(repo.size)}{' '}</td>
|
||||||
|
<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>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RepoItem.propTypes = RepoItemPropTypes;
|
||||||
|
|
||||||
|
export default RepoItem;
|
@ -20,6 +20,10 @@ import Groups from './groups/groups';
|
|||||||
import GroupRepos from './groups/group-repos';
|
import GroupRepos from './groups/group-repos';
|
||||||
import GroupMembers from './groups/group-members';
|
import GroupMembers from './groups/group-members';
|
||||||
|
|
||||||
|
import Departments from './departments/departments';
|
||||||
|
import DepartmentsList from './departments/departments-list';
|
||||||
|
import DepartmentDetail from './departments/department-detail';
|
||||||
|
|
||||||
import ShareLinks from './links/share-links';
|
import ShareLinks from './links/share-links';
|
||||||
import UploadLinks from './links/upload-links';
|
import UploadLinks from './links/upload-links';
|
||||||
|
|
||||||
@ -115,6 +119,10 @@ class SysAdmin extends React.Component {
|
|||||||
<Groups path={siteRoot + 'sys/groups'} />
|
<Groups path={siteRoot + 'sys/groups'} />
|
||||||
<GroupRepos path={siteRoot + 'sys/groups/:groupID/libraries'} />
|
<GroupRepos path={siteRoot + 'sys/groups/:groupID/libraries'} />
|
||||||
<GroupMembers path={siteRoot + 'sys/groups/:groupID/members'} />
|
<GroupMembers path={siteRoot + 'sys/groups/:groupID/members'} />
|
||||||
|
<Departments path={siteRoot + 'sys/departments'}>
|
||||||
|
<DepartmentsList path='/'/>
|
||||||
|
<DepartmentDetail path='/:groupID'/>
|
||||||
|
</Departments>
|
||||||
<ShareLinks path={siteRoot + 'sys/share-links'} />
|
<ShareLinks path={siteRoot + 'sys/share-links'} />
|
||||||
<UploadLinks path={siteRoot + 'sys/upload-links'} />
|
<UploadLinks path={siteRoot + 'sys/upload-links'} />
|
||||||
<FileScanRecords
|
<FileScanRecords
|
||||||
|
@ -109,10 +109,14 @@ class SidePanel extends React.Component {
|
|||||||
}
|
}
|
||||||
{isPro && canManageGroup &&
|
{isPro && canManageGroup &&
|
||||||
<li className="nav-item">
|
<li className="nav-item">
|
||||||
<a className='nav-link ellipsis' href={siteRoot + 'sysadmin/#address-book/'}>
|
<Link
|
||||||
<span className="sf2-icon-organization" aria-hidden="true"></span>
|
className={`nav-link ellipsis ${this.getActiveClass('departments')}`}
|
||||||
|
to={siteRoot + 'sys/departments/'}
|
||||||
|
onClick={() => this.props.tabItemClick('departments')}
|
||||||
|
>
|
||||||
|
<span className="sf2-icon-group" aria-hidden="true"></span>
|
||||||
<span className="nav-text">{gettext('Departments')}</span>
|
<span className="nav-text">{gettext('Departments')}</span>
|
||||||
</a>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
{multiTenancy && isDefaultAdmin &&
|
{multiTenancy && isDefaultAdmin &&
|
||||||
|
@ -666,6 +666,8 @@ urlpatterns = [
|
|||||||
url(r'^sys/groups/$', sysadmin_react_fake_view, name="sys_groups"),
|
url(r'^sys/groups/$', sysadmin_react_fake_view, name="sys_groups"),
|
||||||
url(r'^sys/groups/(?P<group_id>\d+)/libraries/$', sysadmin_react_fake_view, name="sys_group_libraries"),
|
url(r'^sys/groups/(?P<group_id>\d+)/libraries/$', sysadmin_react_fake_view, name="sys_group_libraries"),
|
||||||
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/(?P<group_id>\d+)/$', sysadmin_react_fake_view, name="sys_department"),
|
||||||
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/users-all/$', sysadmin_react_fake_view, name="sys_users_all"),
|
url(r'^sys/users-all/$', sysadmin_react_fake_view, name="sys_users_all"),
|
||||||
url(r'^sys/users-admin/$', sysadmin_react_fake_view, name="sys_users_admin"),
|
url(r'^sys/users-admin/$', sysadmin_react_fake_view, name="sys_users_admin"),
|
||||||
|
Loading…
Reference in New Issue
Block a user