mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-03 16:10:26 +00:00
sysadmin reconstrcut orgs frontend (#4074)
* sysadmin reconstruct orgs page * [system admin] orgs: refactored it
This commit is contained in:
@@ -0,0 +1,127 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Alert, Modal, ModalHeader, ModalBody, ModalFooter, Button, Form, FormGroup, Label, Input } from 'reactstrap';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
toggleDialog: PropTypes.func.isRequired,
|
||||||
|
addOrg: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class SysAdminAddOrgDialog extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
passwordAgain: '',
|
||||||
|
errorMsg: '',
|
||||||
|
isSubmitBtnActive: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSubmitBtnActive = () => {
|
||||||
|
const { name, email, password, passwordAgain } = this.state;
|
||||||
|
let btnActive = true;
|
||||||
|
if (name !='' &&
|
||||||
|
email != '' &&
|
||||||
|
password != '' &&
|
||||||
|
passwordAgain != '') {
|
||||||
|
btnActive = true;
|
||||||
|
} else {
|
||||||
|
btnActive = false;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
isSubmitBtnActive: btnActive
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle = () => {
|
||||||
|
this.props.toggleDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPassword = (e) => {
|
||||||
|
let passwd = e.target.value.trim();
|
||||||
|
this.setState({
|
||||||
|
password: passwd
|
||||||
|
}, this.checkSubmitBtnActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPasswordAgain = (e) => {
|
||||||
|
let passwd = e.target.value.trim();
|
||||||
|
this.setState({
|
||||||
|
passwordAgain: passwd
|
||||||
|
}, this.checkSubmitBtnActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
inputEmail = (e) => {
|
||||||
|
let email = e.target.value.trim();
|
||||||
|
this.setState({
|
||||||
|
email: email
|
||||||
|
}, this.checkSubmitBtnActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
inputName = (e) => {
|
||||||
|
let name = e.target.value.trim();
|
||||||
|
this.setState({
|
||||||
|
name: name
|
||||||
|
}, this.checkSubmitBtnActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit = () => {
|
||||||
|
let { name, email, password, passwordAgain } = this.state;
|
||||||
|
if (password != passwordAgain) {
|
||||||
|
this.setState({errorMsg: gettext('Passwords do not match.')});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
orgName: name,
|
||||||
|
ownerEmail: email,
|
||||||
|
password: password
|
||||||
|
};
|
||||||
|
this.props.addOrg(data);
|
||||||
|
this.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { errorMsg, password, passwordAgain, email, name, isSubmitBtnActive } = this.state;
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} toggle={this.toggle}>
|
||||||
|
<ModalHeader toggle={this.toggle}>{gettext('Add Organization')}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<Form autoComplete="off">
|
||||||
|
<FormGroup>
|
||||||
|
<Label>{gettext('Name')}</Label>
|
||||||
|
<Input value={name} onChange={this.inputName} />
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<Label>
|
||||||
|
{gettext('Owner')}
|
||||||
|
<span className="small text-secondary ml-1 fas fa-question-circle" title={gettext('Owner can use admin panel in an organization, must be a new account.')}></span>
|
||||||
|
</Label>
|
||||||
|
<Input value={email} onChange={this.inputEmail} />
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<Label>{gettext('Password')}</Label>
|
||||||
|
<Input type="password" value={password} onChange={this.inputPassword} />
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<Label>{gettext('Password again')}</Label>
|
||||||
|
<Input type="password" value={passwordAgain} onChange={this.inputPasswordAgain} />
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
{errorMsg && <Alert color="danger">{errorMsg}</Alert>}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
|
||||||
|
<Button color="primary" onClick={this.handleSubmit} disabled={!isSubmitBtnActive}>{gettext('Submit')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SysAdminAddOrgDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default SysAdminAddOrgDialog;
|
@@ -0,0 +1,149 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Alert, Modal, ModalHeader, ModalBody, ModalFooter, Button, Form, FormGroup, Label, Input, InputGroup, InputGroupAddon } from 'reactstrap';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
import SysAdminUserRoleEditor from '../../../components/select-editor/sysadmin-user-role-editor';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
toggleDialog: PropTypes.func.isRequired,
|
||||||
|
addUser: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class SysAdminAddUserDialog extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
errorMsg: '',
|
||||||
|
isPasswordVisible: false,
|
||||||
|
password: '',
|
||||||
|
passwordAgain: '',
|
||||||
|
email: '',
|
||||||
|
name: '',
|
||||||
|
isSubmitBtnActive: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSubmitBtnActive = () => {
|
||||||
|
const { email, password, passwordAgain } = this.state;
|
||||||
|
let btnActive = true;
|
||||||
|
if (email != '' &&
|
||||||
|
password != '' &&
|
||||||
|
passwordAgain != '') {
|
||||||
|
btnActive = true;
|
||||||
|
} else {
|
||||||
|
btnActive = false;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
isSubmitBtnActive: btnActive
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle = () => {
|
||||||
|
this.props.toggleDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
togglePasswordVisible = () => {
|
||||||
|
this.setState({isPasswordVisible: !this.state.isPasswordVisible});
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPassword = (e) => {
|
||||||
|
let passwd = e.target.value.trim();
|
||||||
|
this.setState({
|
||||||
|
password: passwd,
|
||||||
|
errorMsg: ''
|
||||||
|
}, this.checkSubmitBtnActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPasswordAgain = (e) => {
|
||||||
|
let passwd = e.target.value.trim();
|
||||||
|
this.setState({
|
||||||
|
passwordAgain: passwd,
|
||||||
|
errorMsg: ''
|
||||||
|
}, this.checkSubmitBtnActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
generatePassword = () => {
|
||||||
|
let val = Utils.generatePassword(8);
|
||||||
|
this.setState({
|
||||||
|
password: val,
|
||||||
|
passwordAgain: val
|
||||||
|
}, this.checkSubmitBtnActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
inputEmail = (e) => {
|
||||||
|
let email = e.target.value.trim();
|
||||||
|
this.setState({
|
||||||
|
email: email
|
||||||
|
}, this.checkSubmitBtnActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
inputName = (e) => {
|
||||||
|
let name = e.target.value.trim();
|
||||||
|
this.setState({
|
||||||
|
name: name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit = () => {
|
||||||
|
const { email, password, passwordAgain, name } = this.state;
|
||||||
|
if (password != passwordAgain) {
|
||||||
|
this.setState({errorMsg: gettext('Passwords do not match.')});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
email: email,
|
||||||
|
name: name,
|
||||||
|
password: password
|
||||||
|
};
|
||||||
|
this.props.addUser(data);
|
||||||
|
this.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { errorMsg, isPasswordVisible, password, passwordAgain, email, name,
|
||||||
|
isSubmitBtnActive
|
||||||
|
} = this.state;
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} toggle={this.toggle}>
|
||||||
|
<ModalHeader toggle={this.toggle}>{gettext('Add Member')}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<Form autoComplete="off">
|
||||||
|
<FormGroup>
|
||||||
|
<Label>{gettext('Email')}</Label>
|
||||||
|
<Input value={email} onChange={this.inputEmail} />
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<Label>{gettext('Name(optional)')}</Label>
|
||||||
|
<Input autoComplete="new-password" value={name} onChange={this.inputName} />
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<Label>{gettext('Password')}</Label>
|
||||||
|
<InputGroup>
|
||||||
|
<Input autoComplete="new-password" type={isPasswordVisible ? 'text' : 'password'} value={password || ''} onChange={this.inputPassword} />
|
||||||
|
<InputGroupAddon addonType="append">
|
||||||
|
<Button className="mt-0" onClick={this.togglePasswordVisible}><i className={`link-operation-icon fas ${this.state.isPasswordVisible ? 'fa-eye': 'fa-eye-slash'}`}></i></Button>
|
||||||
|
<Button className="mt-0" onClick={this.generatePassword}><i className="link-operation-icon fas fa-magic"></i></Button>
|
||||||
|
</InputGroupAddon>
|
||||||
|
</InputGroup>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<Label>{gettext('Password again')}</Label>
|
||||||
|
<Input type={isPasswordVisible ? 'text' : 'password'} value={passwordAgain || ''} onChange={this.inputPasswordAgain} />
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
{errorMsg && <Alert color="danger">{errorMsg}</Alert>}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
|
||||||
|
<Button color="primary" onClick={this.handleSubmit} disabled={!isSubmitBtnActive}>{gettext('Submit')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SysAdminAddUserDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default SysAdminAddUserDialog;
|
@@ -0,0 +1,75 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Alert, Modal, ModalHeader, ModalBody, ModalFooter, Button, Form, FormGroup, Input, InputGroup, InputGroupAddon, InputGroupText } from 'reactstrap';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
toggle: PropTypes.func.isRequired,
|
||||||
|
updateValue: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class SysAdminSetOrgMaxUserNumberDialog extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
value: this.props.value,
|
||||||
|
isSubmitBtnActive: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle = () => {
|
||||||
|
this.props.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputChange = (e) => {
|
||||||
|
const value = e.target.value.trim();
|
||||||
|
this.setState({
|
||||||
|
value: value,
|
||||||
|
isSubmitBtnActive: value != ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyPress = (e) => {
|
||||||
|
if (e.key == 'Enter') {
|
||||||
|
this.handleSubmit();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit = () => {
|
||||||
|
this.props.updateValue(this.state.value);
|
||||||
|
this.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { value, isSubmitBtnActive } = this.state;
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} toggle={this.toggle}>
|
||||||
|
<ModalHeader toggle={this.toggle}>{gettext('Set max number of members')}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<Form>
|
||||||
|
<FormGroup>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
value={value}
|
||||||
|
onKeyPress={this.handleKeyPress}
|
||||||
|
onChange={this.handleInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
|
||||||
|
<Button color="primary" onClick={this.handleSubmit} disabled={!isSubmitBtnActive}>{gettext('Submit')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SysAdminSetOrgMaxUserNumberDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default SysAdminSetOrgMaxUserNumberDialog;
|
@@ -0,0 +1,75 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Alert, Modal, ModalHeader, ModalBody, ModalFooter, Button, Form, FormGroup, Input, InputGroup, InputGroupAddon, InputGroupText } from 'reactstrap';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
toggle: PropTypes.func.isRequired,
|
||||||
|
updateName: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class SysAdminSetOrgNameDialog extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
name: this.props.name,
|
||||||
|
isSubmitBtnActive: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle = () => {
|
||||||
|
this.props.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputChange = (e) => {
|
||||||
|
const value = e.target.value.trim();
|
||||||
|
this.setState({
|
||||||
|
name: value,
|
||||||
|
isSubmitBtnActive: value != ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyPress = (e) => {
|
||||||
|
if (e.key == 'Enter') {
|
||||||
|
this.handleSubmit();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit = () => {
|
||||||
|
this.props.updateName(this.state.name);
|
||||||
|
this.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { name, isSubmitBtnActive } = this.state;
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} toggle={this.toggle}>
|
||||||
|
<ModalHeader toggle={this.toggle}>{gettext('Set Name')}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<Form>
|
||||||
|
<FormGroup>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
value={name}
|
||||||
|
onKeyPress={this.handleKeyPress}
|
||||||
|
onChange={this.handleInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
|
||||||
|
<Button color="primary" onClick={this.handleSubmit} disabled={!isSubmitBtnActive}>{gettext('Submit')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SysAdminSetOrgNameDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default SysAdminSetOrgNameDialog;
|
@@ -0,0 +1,85 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Alert, Modal, ModalHeader, ModalBody, ModalFooter, Button, Form, FormGroup, Input, InputGroup, InputGroupAddon, InputGroupText } from 'reactstrap';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
toggle: PropTypes.func.isRequired,
|
||||||
|
updateQuota: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class SysAdminOrgSetQuotaDialog extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
quota: '',
|
||||||
|
isSubmitBtnActive: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle = () => {
|
||||||
|
this.props.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleQuotaChange = (e) => {
|
||||||
|
const value = e.target.value.trim();
|
||||||
|
this.setState({
|
||||||
|
quota: value,
|
||||||
|
isSubmitBtnActive: value != ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyPress = (e) => {
|
||||||
|
if (e.key == 'Enter') {
|
||||||
|
this.handleSubmit();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit = () => {
|
||||||
|
this.props.updateQuota(this.state.quota);
|
||||||
|
this.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { quota, isSubmitBtnActive } = this.state;
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} toggle={this.toggle}>
|
||||||
|
<ModalHeader toggle={this.toggle}>{gettext('Set Quota')}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<Form>
|
||||||
|
<FormGroup>
|
||||||
|
<InputGroup>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
value={quota}
|
||||||
|
onKeyPress={this.handleKeyPress}
|
||||||
|
onChange={this.handleQuotaChange}
|
||||||
|
/>
|
||||||
|
<InputGroupAddon addonType="append">
|
||||||
|
<InputGroupText>MB</InputGroupText>
|
||||||
|
</InputGroupAddon>
|
||||||
|
</InputGroup>
|
||||||
|
<p className="small text-secondary mt-2 mb-2">
|
||||||
|
{gettext('An integer that is greater than or equal to 0.')}
|
||||||
|
<br />
|
||||||
|
{gettext('Tip: 0 means default limit')}
|
||||||
|
</p>
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
|
||||||
|
<Button color="primary" onClick={this.handleSubmit} disabled={!isSubmitBtnActive}>{gettext('Submit')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SysAdminOrgSetQuotaDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default SysAdminOrgSetQuotaDialog;
|
@@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { gettext } from '../../utils/constants';
|
||||||
|
import SelectEditor from './select-editor';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
isTextMode: PropTypes.bool.isRequired,
|
||||||
|
isEditIconShow: PropTypes.bool.isRequired,
|
||||||
|
roleOptions: PropTypes.array.isRequired,
|
||||||
|
currentRole: PropTypes.string.isRequired,
|
||||||
|
onRoleChanged: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class SysAdminUserRoleEditor extends React.Component {
|
||||||
|
|
||||||
|
translateRoles = (role) => {
|
||||||
|
switch (role) {
|
||||||
|
case 'default':
|
||||||
|
return gettext('Default');
|
||||||
|
case 'guest':
|
||||||
|
return gettext('Guest');
|
||||||
|
default:
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<SelectEditor
|
||||||
|
isTextMode={this.props.isTextMode}
|
||||||
|
isEditIconShow={this.props.isEditIconShow}
|
||||||
|
options={this.props.roleOptions}
|
||||||
|
currentOption={this.props.currentRole}
|
||||||
|
onOptionChanged={this.props.onRoleChanged}
|
||||||
|
translateOption={this.translateRoles}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SysAdminUserRoleEditor.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default SysAdminUserRoleEditor;
|
@@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { gettext } from '../../utils/constants';
|
||||||
|
import SelectEditor from './select-editor';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
isTextMode: PropTypes.bool.isRequired,
|
||||||
|
isEditIconShow: PropTypes.bool.isRequired,
|
||||||
|
statusOptions: PropTypes.array.isRequired,
|
||||||
|
currentStatus: PropTypes.string.isRequired,
|
||||||
|
onStatusChanged: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class SysAdminUserStatusEditor extends React.Component {
|
||||||
|
|
||||||
|
translateStatus = (status) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'active':
|
||||||
|
return gettext('Active');
|
||||||
|
case 'inactive':
|
||||||
|
return gettext('Inactive');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<SelectEditor
|
||||||
|
isTextMode={this.props.isTextMode}
|
||||||
|
isEditIconShow={this.props.isEditIconShow}
|
||||||
|
options={this.props.statusOptions}
|
||||||
|
currentOption={this.props.currentStatus}
|
||||||
|
onOptionChanged={this.props.onStatusChanged}
|
||||||
|
translateOption={this.translateStatus}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SysAdminUserStatusEditor.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default SysAdminUserStatusEditor;
|
@@ -27,6 +27,12 @@ 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';
|
||||||
|
|
||||||
|
import Orgs from './orgs/orgs';
|
||||||
|
import OrgInfo from './orgs/org-info';
|
||||||
|
import OrgUsers from './orgs/org-users';
|
||||||
|
import OrgGroups from './orgs/org-groups';
|
||||||
|
import OrgRepos from './orgs/org-repos';
|
||||||
|
|
||||||
import WebSettings from './web-settings/web-settings';
|
import WebSettings from './web-settings/web-settings';
|
||||||
import Notifications from './notifications/notifications';
|
import Notifications from './notifications/notifications';
|
||||||
import FileScanRecords from './file-scan-records';
|
import FileScanRecords from './file-scan-records';
|
||||||
@@ -64,6 +70,10 @@ class SysAdmin extends React.Component {
|
|||||||
tab: 'groups',
|
tab: 'groups',
|
||||||
urlPartList: ['groups/']
|
urlPartList: ['groups/']
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
tab: 'organizations',
|
||||||
|
urlPartList: ['organizations/']
|
||||||
|
},
|
||||||
];
|
];
|
||||||
const tmpTab = this.getCurrentTabForPageList(pageList);
|
const tmpTab = this.getCurrentTabForPageList(pageList);
|
||||||
currentTab = tmpTab ? tmpTab : currentTab;
|
currentTab = tmpTab ? tmpTab : currentTab;
|
||||||
@@ -125,6 +135,11 @@ class SysAdmin extends React.Component {
|
|||||||
</Departments>
|
</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'} />
|
||||||
|
<Orgs path={siteRoot + 'sys/organizations'} />
|
||||||
|
<OrgInfo path={siteRoot + 'sys/organizations/:orgID/info'} />
|
||||||
|
<OrgUsers path={siteRoot + 'sys/organizations/:orgID/users'} />
|
||||||
|
<OrgGroups path={siteRoot + 'sys/organizations/:orgID/groups'} />
|
||||||
|
<OrgRepos path={siteRoot + 'sys/organizations/:orgID/libraries'} />
|
||||||
<FileScanRecords
|
<FileScanRecords
|
||||||
path={siteRoot + 'sys/file-scan-records'}
|
path={siteRoot + 'sys/file-scan-records'}
|
||||||
currentTab={currentTab}
|
currentTab={currentTab}
|
||||||
|
209
frontend/src/pages/sys-admin/orgs/org-groups.js
Normal file
209
frontend/src/pages/sys-admin/orgs/org-groups.js
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
import { seafileAPI } from '../../../utils/seafile-api';
|
||||||
|
import { siteRoot, loginUrl, gettext } from '../../../utils/constants';
|
||||||
|
import toaster from '../../../components/toast';
|
||||||
|
import EmptyTip from '../../../components/empty-tip';
|
||||||
|
import Loading from '../../../components/loading';
|
||||||
|
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
|
||||||
|
import MainPanelTopbar from '../main-panel-topbar';
|
||||||
|
import OrgNav from './org-nav';
|
||||||
|
|
||||||
|
class Content extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { loading, errorMsg, items } = this.props;
|
||||||
|
if (loading) {
|
||||||
|
return <Loading />;
|
||||||
|
} else if (errorMsg) {
|
||||||
|
return <p className="error text-center mt-4">{errorMsg}</p>;
|
||||||
|
} else {
|
||||||
|
const emptyTip = (
|
||||||
|
<EmptyTip>
|
||||||
|
<h2>{gettext('No groups')}</h2>
|
||||||
|
</EmptyTip>
|
||||||
|
);
|
||||||
|
const table = (
|
||||||
|
<Fragment>
|
||||||
|
<table className="table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="30%">{gettext('Name')}</th>
|
||||||
|
<th width="30%">{gettext('Creator')}</th>
|
||||||
|
<th width="30%">{gettext('Created At')}</th>
|
||||||
|
<th width="10%">{/* Operations */}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{items.map((item, index) => {
|
||||||
|
return (<Item
|
||||||
|
key={index}
|
||||||
|
item={item}
|
||||||
|
deleteGroup={this.props.deleteGroup}
|
||||||
|
/>);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
return items.length ? table : emptyTip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Item extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isOpIconShown: false,
|
||||||
|
isDeleteDialogOpen: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseEnter = () => {
|
||||||
|
this.setState({isOpIconShown: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseLeave = () => {
|
||||||
|
this.setState({isOpIconShown: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDeleteDialog = (e) => {
|
||||||
|
if (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
this.setState({isDeleteDialogOpen: !this.state.isDeleteDialogOpen});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteGroup = () => {
|
||||||
|
this.toggleDeleteDialog();
|
||||||
|
this.props.deleteGroup(this.props.item.group_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { item } = this.props;
|
||||||
|
const { isOpIconShown, isDeleteDialogOpen } = this.state;
|
||||||
|
|
||||||
|
const itemName = '<span class="op-target">' + Utils.HTMLescape(item.group_name) + '</span>';
|
||||||
|
const deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', itemName);
|
||||||
|
|
||||||
|
const groupUrl = item.parent_group_id == 0 ?
|
||||||
|
`${siteRoot}sys/groups/${item.group_id}/libraries/` :
|
||||||
|
`${siteRoot}sysadmin/#address-book/groups/${item.group_id}/`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<tr onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||||
|
<td><a href={groupUrl}>{item.group_name}</a></td>
|
||||||
|
<td><a href={`${siteRoot}useradmin/info/${encodeURIComponent(item.creator_email)}/`}>{item.creator_name}</a></td>
|
||||||
|
<td>{moment(item.created_at).format('YYYY-MM-DD hh:mm:ss')}</td>
|
||||||
|
<td>
|
||||||
|
<a href="#" className={`action-icon sf2-icon-delete ${isOpIconShown ? '' : 'invisible'}`} title={gettext('Delete')} onClick={this.toggleDeleteDialog}></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{isDeleteDialogOpen &&
|
||||||
|
<CommonOperationConfirmationDialog
|
||||||
|
title={gettext('Delete Group')}
|
||||||
|
message={deleteDialogMsg}
|
||||||
|
executeOperation={this.deleteGroup}
|
||||||
|
confirmBtnText={gettext('Delete')}
|
||||||
|
toggleDialog={this.toggleDeleteDialog}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrgGroups extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
loading: true,
|
||||||
|
errorMsg: '',
|
||||||
|
orgName: '',
|
||||||
|
groupList: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
seafileAPI.sysAdminGetOrgInfo(this.props.orgID).then((res) => {
|
||||||
|
this.setState({
|
||||||
|
orgName: res.data.org_name
|
||||||
|
});
|
||||||
|
});
|
||||||
|
seafileAPI.sysAdminListAllOrgGroups(this.props.orgID).then((res) => {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
groupList: res.data.group_list
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.response) {
|
||||||
|
if (error.response.status == 403) {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorMsg: gettext('Permission denied')
|
||||||
|
});
|
||||||
|
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorMsg: gettext('Error')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorMsg: gettext('Please check the network.')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteGroup = (groupID) => {
|
||||||
|
seafileAPI.sysAdminDismissGroupByID(groupID).then(res => {
|
||||||
|
let newGroupList = this.state.groupList.filter(item => {
|
||||||
|
return item.group_id != groupID;
|
||||||
|
});
|
||||||
|
this.setState({groupList: newGroupList});
|
||||||
|
toaster.success(gettext('Successfully deleted 1 item.'));
|
||||||
|
}).catch((error) => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<MainPanelTopbar />
|
||||||
|
<div className="main-panel-center flex-row">
|
||||||
|
<div className="cur-view-container">
|
||||||
|
<OrgNav
|
||||||
|
currentItem="groups"
|
||||||
|
orgID={this.props.orgID}
|
||||||
|
orgName={this.state.orgName}
|
||||||
|
/>
|
||||||
|
<div className="cur-view-content">
|
||||||
|
<Content
|
||||||
|
loading={this.state.loading}
|
||||||
|
errorMsg={this.state.errorMsg}
|
||||||
|
items={this.state.groupList}
|
||||||
|
deleteGroup={this.deleteGroup}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrgGroups;
|
220
frontend/src/pages/sys-admin/orgs/org-info.js
Normal file
220
frontend/src/pages/sys-admin/orgs/org-info.js
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
import { seafileAPI } from '../../../utils/seafile-api';
|
||||||
|
import { loginUrl, gettext } from '../../../utils/constants';
|
||||||
|
import toaster from '../../../components/toast';
|
||||||
|
import Loading from '../../../components/loading';
|
||||||
|
import SysAdminSetOrgQuotaDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-set-org-quota-dialog';
|
||||||
|
import SysAdminSetOrgNameDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-set-org-name-dialog';
|
||||||
|
import SysAdminSetOrgMaxUserNumberDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-set-org-max-user-number-dialog';
|
||||||
|
import MainPanelTopbar from '../main-panel-topbar';
|
||||||
|
import OrgNav from './org-nav';
|
||||||
|
|
||||||
|
class Content extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isSetQuotaDialogOpen: false,
|
||||||
|
isSetNameDialogOpen: false,
|
||||||
|
isSetMaxUserNumberDialogOpen: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSetQuotaDialog = () => {
|
||||||
|
this.setState({isSetQuotaDialogOpen: !this.state.isSetQuotaDialogOpen});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSetNameDialog = () => {
|
||||||
|
this.setState({isSetNameDialogOpen: !this.state.isSetNameDialogOpen});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSetMaxUserNumberDialog = () => {
|
||||||
|
this.setState({isSetMaxUserNumberDialogOpen: !this.state.isSetMaxUserNumberDialogOpen});
|
||||||
|
}
|
||||||
|
|
||||||
|
showEditIcon = (action) => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
title={gettext('Edit')}
|
||||||
|
className="fa fa-pencil-alt attr-action-icon"
|
||||||
|
onClick={action}>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { loading, errorMsg, orgInfo } = this.props;
|
||||||
|
if (loading) {
|
||||||
|
return <Loading />;
|
||||||
|
} else if (errorMsg) {
|
||||||
|
return <p className="error text-center">{errorMsg}</p>;
|
||||||
|
} else {
|
||||||
|
const { org_name, users_count, max_user_number, groups_count, quota, quota_usage } = this.props.orgInfo;
|
||||||
|
const { isSetQuotaDialogOpen, isSetNameDialogOpen, isSetMaxUserNumberDialogOpen } = this.state;
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<dl className="m-0">
|
||||||
|
<dt className="info-item-heading">{gettext('Name')}</dt>
|
||||||
|
<dd className="info-item-content">
|
||||||
|
{org_name}
|
||||||
|
{this.showEditIcon(this.toggleSetNameDialog)}
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt className="info-item-heading">{gettext('Number of members')}</dt>
|
||||||
|
<dd className="info-item-content">{users_count}</dd>
|
||||||
|
|
||||||
|
{max_user_number &&
|
||||||
|
<Fragment>
|
||||||
|
<dt className="info-item-heading">{gettext('Max number of members')}</dt>
|
||||||
|
<dd className="info-item-content">
|
||||||
|
{max_user_number}
|
||||||
|
{this.showEditIcon(this.toggleSetMaxUserNumberDialog)}
|
||||||
|
</dd>
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
|
||||||
|
<dt className="info-item-heading">{gettext('Number of groups')}</dt>
|
||||||
|
<dd className="info-item-content">{groups_count}</dd>
|
||||||
|
|
||||||
|
<dt className="info-item-heading">{gettext('Space Used')}</dt>
|
||||||
|
<dd className="info-item-content">
|
||||||
|
{`${Utils.bytesToSize(quota_usage)} / ${quota > 0 ? Utils.bytesToSize(quota) : '--'}`}
|
||||||
|
{this.showEditIcon(this.toggleSetQuotaDialog)}
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
{isSetQuotaDialogOpen &&
|
||||||
|
<SysAdminSetOrgQuotaDialog
|
||||||
|
updateQuota={this.props.updateQuota}
|
||||||
|
toggle={this.toggleSetQuotaDialog}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{isSetNameDialogOpen &&
|
||||||
|
<SysAdminSetOrgNameDialog
|
||||||
|
name={org_name}
|
||||||
|
updateName={this.props.updateName}
|
||||||
|
toggle={this.toggleSetNameDialog}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{isSetMaxUserNumberDialogOpen &&
|
||||||
|
<SysAdminSetOrgMaxUserNumberDialog
|
||||||
|
value={max_user_number}
|
||||||
|
updateValue={this.props.updateMaxUserNumber}
|
||||||
|
toggle={this.toggleSetMaxUserNumberDialog}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrgInfo extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
loading: true,
|
||||||
|
errorMsg: '',
|
||||||
|
orgInfo: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
seafileAPI.sysAdminGetOrgInfo(this.props.orgID).then((res) => {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
orgInfo: res.data
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.response) {
|
||||||
|
if (error.response.status == 403) {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorMsg: gettext('Permission denied')
|
||||||
|
});
|
||||||
|
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorMsg: gettext('Error')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorMsg: gettext('Please check the network.')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateQuota = (quota) => {
|
||||||
|
const data = {quota: quota};
|
||||||
|
seafileAPI.sysAdminUpdateOrgInfo(this.props.orgID, data).then(res => {
|
||||||
|
const newOrgInfo = Object.assign(this.state.orgInfo, {
|
||||||
|
quota: res.data.quota
|
||||||
|
});
|
||||||
|
this.setState({orgInfo: newOrgInfo});
|
||||||
|
toaster.success(gettext('Successfully set quota.'));
|
||||||
|
}).catch((error) => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateName = (orgName) => {
|
||||||
|
const data = {orgName: orgName};
|
||||||
|
seafileAPI.sysAdminUpdateOrgInfo(this.props.orgID, data).then(res => {
|
||||||
|
const newOrgInfo = Object.assign(this.state.orgInfo, {
|
||||||
|
org_name: res.data.org_name
|
||||||
|
});
|
||||||
|
this.setState({orgInfo: newOrgInfo});
|
||||||
|
toaster.success(gettext('Successfully set name.'));
|
||||||
|
}).catch((error) => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMaxUserNumber = (newValue) => {
|
||||||
|
const data = {maxUserNumber: newValue};
|
||||||
|
seafileAPI.sysAdminUpdateOrgInfo(this.props.orgID, data).then(res => {
|
||||||
|
const newOrgInfo = Object.assign(this.state.orgInfo, {
|
||||||
|
max_user_number: res.data.max_user_number
|
||||||
|
});
|
||||||
|
this.setState({orgInfo: newOrgInfo});
|
||||||
|
toaster.success(gettext('Successfully set max number of members.'));
|
||||||
|
}).catch((error) => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { orgInfo } = this.state;
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<MainPanelTopbar />
|
||||||
|
<div className="main-panel-center flex-row">
|
||||||
|
<div className="cur-view-container">
|
||||||
|
<OrgNav currentItem="info" orgID={this.props.orgID} orgName={orgInfo.org_name} />
|
||||||
|
<div className="cur-view-content">
|
||||||
|
<Content
|
||||||
|
orgID={this.props.orgID}
|
||||||
|
loading={this.state.loading}
|
||||||
|
errorMsg={this.state.errorMsg}
|
||||||
|
orgInfo={this.state.orgInfo}
|
||||||
|
updateQuota={this.updateQuota}
|
||||||
|
updateName={this.updateName}
|
||||||
|
updateMaxUserNumber={this.updateMaxUserNumber}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrgInfo;
|
47
frontend/src/pages/sys-admin/orgs/org-nav.js
Normal file
47
frontend/src/pages/sys-admin/orgs/org-nav.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Link } from '@reach/router';
|
||||||
|
import { siteRoot, gettext } from '../../../utils/constants';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
currentItem: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class Nav extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.navItems = [
|
||||||
|
{name: 'info', urlPart: 'info', text: gettext('Info')},
|
||||||
|
{name: 'users', urlPart: 'users', text: gettext('Members')},
|
||||||
|
{name: 'groups', urlPart: 'groups', text: gettext('Groups')},
|
||||||
|
{name: 'repos', urlPart: 'libraries', text: gettext('Libraries')},
|
||||||
|
//{name: 'traffic', urlPart: 'traffic', text: gettext('traffic')},
|
||||||
|
//{name: 'settings', urlPart: 'settings', text: gettext('Settings')}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { currentItem, orgID, orgName } = this.props;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="cur-view-path">
|
||||||
|
<h3 className="sf-heading"><Link to={`${siteRoot}sys/organizations/`}>{gettext('Organizations')}</Link> / {orgName}</h3>
|
||||||
|
</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/organizations/${orgID}/${item.urlPart}/`} className={`nav-link ${currentItem == item.name ? ' active' : ''}`}>{item.text}</Link>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Nav.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default Nav;
|
213
frontend/src/pages/sys-admin/orgs/org-repos.js
Normal file
213
frontend/src/pages/sys-admin/orgs/org-repos.js
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
import { seafileAPI } from '../../../utils/seafile-api';
|
||||||
|
import { siteRoot, loginUrl, gettext } from '../../../utils/constants';
|
||||||
|
import toaster from '../../../components/toast';
|
||||||
|
import EmptyTip from '../../../components/empty-tip';
|
||||||
|
import Loading from '../../../components/loading';
|
||||||
|
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
|
||||||
|
import MainPanelTopbar from '../main-panel-topbar';
|
||||||
|
import OrgNav from './org-nav';
|
||||||
|
|
||||||
|
class Content extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { loading, errorMsg, items } = this.props;
|
||||||
|
if (loading) {
|
||||||
|
return <Loading />;
|
||||||
|
} else if (errorMsg) {
|
||||||
|
return <p className="error text-center mt-4">{errorMsg}</p>;
|
||||||
|
} else {
|
||||||
|
const emptyTip = (
|
||||||
|
<EmptyTip>
|
||||||
|
<h2>{gettext('No libraries')}</h2>
|
||||||
|
</EmptyTip>
|
||||||
|
);
|
||||||
|
const table = (
|
||||||
|
<Fragment>
|
||||||
|
<table className="table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="5%"></th>
|
||||||
|
<th width="30%">{gettext('Name')}</th>
|
||||||
|
<th width="35%">{gettext('ID')}</th>
|
||||||
|
<th width="20%">{gettext('Owner')}</th>
|
||||||
|
<th width="10%">{/* Operations */}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{items.map((item, index) => {
|
||||||
|
return (<Item
|
||||||
|
key={index}
|
||||||
|
item={item}
|
||||||
|
deleteRepo={this.props.deleteRepo}
|
||||||
|
/>);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
return items.length ? table : emptyTip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Item extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isOpIconShown: false,
|
||||||
|
isDeleteDialogOpen: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseEnter = () => {
|
||||||
|
this.setState({isOpIconShown: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseLeave = () => {
|
||||||
|
this.setState({isOpIconShown: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDeleteDialog = (e) => {
|
||||||
|
if (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
this.setState({isDeleteDialogOpen: !this.state.isDeleteDialogOpen});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRepo = () => {
|
||||||
|
this.props.deleteRepo(this.props.item.repo_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { item } = this.props;
|
||||||
|
const { isOpIconShown, isDeleteDialogOpen } = this.state;
|
||||||
|
|
||||||
|
const iconUrl = Utils.getLibIconUrl(item);
|
||||||
|
const iconTitle = Utils.getLibIconTitle(item);
|
||||||
|
|
||||||
|
const itemName = '<span class="op-target">' + Utils.HTMLescape(item.repo_name) + '</span>';
|
||||||
|
const deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', itemName);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<tr onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||||
|
<td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
|
||||||
|
<td>{item.repo_name}</td>
|
||||||
|
<td>{item.repo_id}</td>
|
||||||
|
<td>
|
||||||
|
{item.owner_email ?
|
||||||
|
<a href={`${siteRoot}useradmin/info/${encodeURIComponent(item.owner_email)}/`}>{item.owner_name}</a> :
|
||||||
|
'--'
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="#" className={`action-icon sf2-icon-delete ${isOpIconShown ? '' : 'invisible'}`} title={gettext('Delete')} onClick={this.toggleDeleteDialog}></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{isDeleteDialogOpen &&
|
||||||
|
<CommonOperationConfirmationDialog
|
||||||
|
title={gettext('Delete Library')}
|
||||||
|
message={deleteDialogMsg}
|
||||||
|
executeOperation={this.deleteRepo}
|
||||||
|
confirmBtnText={gettext('Delete')}
|
||||||
|
toggleDialog={this.toggleDeleteDialog}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrgRepos extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
loading: true,
|
||||||
|
errorMsg: '',
|
||||||
|
orgName: '',
|
||||||
|
repoList: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
seafileAPI.sysAdminGetOrgInfo(this.props.orgID).then((res) => {
|
||||||
|
this.setState({
|
||||||
|
orgName: res.data.org_name
|
||||||
|
});
|
||||||
|
});
|
||||||
|
seafileAPI.sysAdminListAllOrgRepos(this.props.orgID).then((res) => {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
repoList: res.data.repo_list
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.response) {
|
||||||
|
if (error.response.status == 403) {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorMsg: gettext('Permission denied')
|
||||||
|
});
|
||||||
|
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorMsg: gettext('Error')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorMsg: gettext('Please check the network.')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRepo = (repoID) => {
|
||||||
|
seafileAPI.sysAdminDeleteRepo(repoID).then(res => {
|
||||||
|
let newRepoList = this.state.repoList.filter(item => {
|
||||||
|
return item.repo_id != repoID;
|
||||||
|
});
|
||||||
|
this.setState({repoList: newRepoList});
|
||||||
|
toaster.success(gettext('Successfully deleted 1 item.'));
|
||||||
|
}).catch((error) => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<MainPanelTopbar />
|
||||||
|
<div className="main-panel-center flex-row">
|
||||||
|
<div className="cur-view-container">
|
||||||
|
<OrgNav
|
||||||
|
currentItem="repos"
|
||||||
|
orgID={this.props.orgID}
|
||||||
|
orgName={this.state.orgName}
|
||||||
|
/>
|
||||||
|
<div className="cur-view-content">
|
||||||
|
<Content
|
||||||
|
loading={this.state.loading}
|
||||||
|
errorMsg={this.state.errorMsg}
|
||||||
|
items={this.state.repoList}
|
||||||
|
deleteRepo={this.deleteRepo}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrgRepos;
|
348
frontend/src/pages/sys-admin/orgs/org-users.js
Normal file
348
frontend/src/pages/sys-admin/orgs/org-users.js
Normal file
@@ -0,0 +1,348 @@
|
|||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import { Button } from 'reactstrap';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
import { seafileAPI } from '../../../utils/seafile-api';
|
||||||
|
import { siteRoot, loginUrl, gettext, username } from '../../../utils/constants';
|
||||||
|
import toaster from '../../../components/toast';
|
||||||
|
import EmptyTip from '../../../components/empty-tip';
|
||||||
|
import Loading from '../../../components/loading';
|
||||||
|
import SysAdminUserStatusEditor from '../../../components/select-editor/sysadmin-user-status-editor';
|
||||||
|
import SysAdminAddUserDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-user-dialog';
|
||||||
|
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
|
||||||
|
import MainPanelTopbar from '../main-panel-topbar';
|
||||||
|
import OrgNav from './org-nav';
|
||||||
|
import OpMenu from './user-op-menu';
|
||||||
|
|
||||||
|
class Content extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isItemFreezed: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onFreezedItem = () => {
|
||||||
|
this.setState({isItemFreezed: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnfreezedItem = () => {
|
||||||
|
this.setState({isItemFreezed: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { loading, errorMsg, items } = this.props;
|
||||||
|
if (loading) {
|
||||||
|
return <Loading />;
|
||||||
|
} else if (errorMsg) {
|
||||||
|
return <p className="error text-center mt-4">{errorMsg}</p>;
|
||||||
|
} else {
|
||||||
|
const emptyTip = (
|
||||||
|
<EmptyTip>
|
||||||
|
<h2>{gettext('No members')}</h2>
|
||||||
|
</EmptyTip>
|
||||||
|
);
|
||||||
|
const table = (
|
||||||
|
<Fragment>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="25%">{gettext('Name')}</th>
|
||||||
|
<th width="20%">{gettext('Status')}</th>
|
||||||
|
<th width="20%">{gettext('Space Used')}</th>
|
||||||
|
<th width="30%">{gettext('Created At')}{' / '}{gettext('Last Login')}</th>
|
||||||
|
<th width="5%">{/* Operations */}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{items.map((item, index) => {
|
||||||
|
return (<Item
|
||||||
|
key={index}
|
||||||
|
item={item}
|
||||||
|
isItemFreezed={this.state.isItemFreezed}
|
||||||
|
onFreezedItem={this.onFreezedItem}
|
||||||
|
onUnfreezedItem={this.onUnfreezedItem}
|
||||||
|
updateStatus={this.props.updateStatus}
|
||||||
|
deleteUser={this.props.deleteUser}
|
||||||
|
/>);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
return items.length ? table : emptyTip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Item extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isOpIconShown: false,
|
||||||
|
highlight: false,
|
||||||
|
isDeleteDialogOpen: false,
|
||||||
|
isResetPasswordDialogOpen: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseEnter = () => {
|
||||||
|
if (!this.props.isItemFreezed) {
|
||||||
|
this.setState({
|
||||||
|
isOpIconShown: true,
|
||||||
|
highlight: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseLeave = () => {
|
||||||
|
if (!this.props.isItemFreezed) {
|
||||||
|
this.setState({
|
||||||
|
isOpIconShown: false,
|
||||||
|
highlight: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnfreezedItem = () => {
|
||||||
|
this.setState({
|
||||||
|
highlight: false,
|
||||||
|
isOpIconShow: false
|
||||||
|
});
|
||||||
|
this.props.onUnfreezedItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMenuItemClick = (operation) => {
|
||||||
|
switch(operation) {
|
||||||
|
case 'Delete':
|
||||||
|
this.toggleDeleteDialog();
|
||||||
|
break;
|
||||||
|
case 'Reset Password':
|
||||||
|
this.toggleResetPasswordDialog();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDeleteDialog = (e) => {
|
||||||
|
if (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
this.setState({isDeleteDialogOpen: !this.state.isDeleteDialogOpen});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleResetPasswordDialog = (e) => {
|
||||||
|
if (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
this.setState({isResetPasswordDialogOpen: !this.state.isResetPasswordDialogOpen});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatus= (statusValue) => {
|
||||||
|
this.props.updateStatus(this.props.item.email, statusValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteUser = () => {
|
||||||
|
const { item } = this.props;
|
||||||
|
this.props.deleteUser(item.org_id, item.email);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetPassword = () => {
|
||||||
|
seafileAPI.sysAdminResetUserPassword(this.props.item.email).then(res => {
|
||||||
|
toaster.success(res.data.reset_tip);
|
||||||
|
}).catch((error) => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { item } = this.props;
|
||||||
|
const { isOpIconShown, isDeleteDialogOpen, isResetPasswordDialogOpen } = this.state;
|
||||||
|
|
||||||
|
const itemName = '<span class="op-target">' + Utils.HTMLescape(item.name) + '</span>';
|
||||||
|
let deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', itemName);
|
||||||
|
let resetPasswordDialogMsg = gettext('Are you sure you want to reset the password of {placeholder} ?').replace('{placeholder}', itemName);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||||
|
<td><a href={`${siteRoot}useradmin/info/${encodeURIComponent(item.email)}/`}>{item.name}</a></td>
|
||||||
|
<td>
|
||||||
|
<SysAdminUserStatusEditor
|
||||||
|
isTextMode={true}
|
||||||
|
isEditIconShow={isOpIconShown}
|
||||||
|
currentStatus={item.active ? 'active' : 'inactive'}
|
||||||
|
statusOptions={['active', 'inactive']}
|
||||||
|
onStatusChanged={this.updateStatus}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>{`${Utils.bytesToSize(item.quota_usage)} / ${item.quota_total > 0 ? Utils.bytesToSize(item.quota_total) : '--'}`}</td>
|
||||||
|
<td>
|
||||||
|
{moment(item.ctime).format('YYYY-MM-DD hh:mm:ss')}{' / '}{item.last_login ? moment(item.last_login).fromNow() : '--'}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{(isOpIconShown && item.email != username) &&
|
||||||
|
<OpMenu
|
||||||
|
onMenuItemClick={this.onMenuItemClick}
|
||||||
|
onFreezedItem={this.props.onFreezedItem}
|
||||||
|
onUnfreezedItem={this.onUnfreezedItem}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{isDeleteDialogOpen &&
|
||||||
|
<CommonOperationConfirmationDialog
|
||||||
|
title={gettext('Delete Member')}
|
||||||
|
message={deleteDialogMsg}
|
||||||
|
executeOperation={this.deleteUser}
|
||||||
|
confirmBtnText={gettext('Delete')}
|
||||||
|
toggleDialog={this.toggleDeleteDialog}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{isResetPasswordDialogOpen &&
|
||||||
|
<CommonOperationConfirmationDialog
|
||||||
|
title={gettext('Reset Password')}
|
||||||
|
message={resetPasswordDialogMsg}
|
||||||
|
executeOperation={this.resetPassword}
|
||||||
|
confirmBtnText={gettext('Reset')}
|
||||||
|
toggleDialog={this.toggleResetPasswordDialog}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrgUsers extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
loading: true,
|
||||||
|
errorMsg: '',
|
||||||
|
orgName: '',
|
||||||
|
userList: [],
|
||||||
|
isAddUserDialogOpen: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
seafileAPI.sysAdminGetOrgInfo(this.props.orgID).then((res) => {
|
||||||
|
this.setState({
|
||||||
|
orgName: res.data.org_name
|
||||||
|
});
|
||||||
|
});
|
||||||
|
seafileAPI.sysAdminListAllOrgUsers(this.props.orgID).then((res) => {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
userList: res.data.users
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.response) {
|
||||||
|
if (error.response.status == 403) {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorMsg: gettext('Permission denied')
|
||||||
|
});
|
||||||
|
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorMsg: gettext('Error')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorMsg: gettext('Please check the network.')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAddUserDialog = () => {
|
||||||
|
this.setState({isAddUserDialogOpen: !this.state.isAddUserDialogOpen});
|
||||||
|
}
|
||||||
|
|
||||||
|
addUser = (newUserInfo) => {
|
||||||
|
const { email, name, password } = newUserInfo;
|
||||||
|
seafileAPI.sysAdminAddOrgUser(this.props.orgID, email, name, password).then(res => {
|
||||||
|
let userList = this.state.userList;
|
||||||
|
userList.unshift(res.data);
|
||||||
|
this.setState({userList: userList});
|
||||||
|
}).catch((error) => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteUser = (orgID, email) => {
|
||||||
|
seafileAPI.sysAdminDeleteOrgUser(orgID, email).then(res => {
|
||||||
|
let newUserList = this.state.userList.filter(item => {
|
||||||
|
return item.email != email;
|
||||||
|
});
|
||||||
|
this.setState({userList: newUserList});
|
||||||
|
toaster.success(gettext('Successfully deleted 1 item.'));
|
||||||
|
}).catch((error) => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatus = (email, statusValue) => {
|
||||||
|
const isActive = statusValue == 'active';
|
||||||
|
seafileAPI.sysAdminUpdateOrgUserInfo(this.props.orgID, email, 'active', isActive).then(res => {
|
||||||
|
let newUserList = this.state.userList.map(item => {
|
||||||
|
if (item.email == email) {
|
||||||
|
item.active = res.data.active;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
this.setState({userList: newUserList});
|
||||||
|
}).catch((error) => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { isAddUserDialogOpen, orgName } = this.state;
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<MainPanelTopbar>
|
||||||
|
<Button className="btn btn-secondary operation-item" onClick={this.toggleAddUserDialog}>{gettext('Add Member')}</Button>
|
||||||
|
</MainPanelTopbar>
|
||||||
|
<div className="main-panel-center flex-row">
|
||||||
|
<div className="cur-view-container">
|
||||||
|
<OrgNav
|
||||||
|
currentItem="users"
|
||||||
|
orgID={this.props.orgID}
|
||||||
|
orgName={orgName}
|
||||||
|
/>
|
||||||
|
<div className="cur-view-content">
|
||||||
|
<Content
|
||||||
|
loading={this.state.loading}
|
||||||
|
errorMsg={this.state.errorMsg}
|
||||||
|
items={this.state.userList}
|
||||||
|
updateStatus={this.updateStatus}
|
||||||
|
deleteUser={this.deleteUser}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isAddUserDialogOpen &&
|
||||||
|
<SysAdminAddUserDialog
|
||||||
|
addUser={this.addUser}
|
||||||
|
toggleDialog={this.toggleAddUserDialog}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrgUsers;
|
261
frontend/src/pages/sys-admin/orgs/orgs.js
Normal file
261
frontend/src/pages/sys-admin/orgs/orgs.js
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import { Button } from 'reactstrap';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
import { seafileAPI } from '../../../utils/seafile-api';
|
||||||
|
import { siteRoot, loginUrl, gettext } from '../../../utils/constants';
|
||||||
|
import toaster from '../../../components/toast';
|
||||||
|
import EmptyTip from '../../../components/empty-tip';
|
||||||
|
import Loading from '../../../components/loading';
|
||||||
|
import SysAdminUserRoleEditor from '../../../components/select-editor/sysadmin-user-role-editor';
|
||||||
|
import SysAdminAddOrgDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-org-dialog';
|
||||||
|
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
|
||||||
|
import MainPanelTopbar from '../main-panel-topbar';
|
||||||
|
|
||||||
|
const { availableRoles } = window.sysadmin.pageOptions;
|
||||||
|
|
||||||
|
class Content extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { loading, errorMsg, items } = this.props;
|
||||||
|
if (loading) {
|
||||||
|
return <Loading />;
|
||||||
|
} else if (errorMsg) {
|
||||||
|
return <p className="error text-center mt-4">{errorMsg}</p>;
|
||||||
|
} else {
|
||||||
|
const emptyTip = (
|
||||||
|
<EmptyTip>
|
||||||
|
<h2>{gettext('No organizations')}</h2>
|
||||||
|
</EmptyTip>
|
||||||
|
);
|
||||||
|
const table = (
|
||||||
|
<Fragment>
|
||||||
|
<table className="table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="20%">{gettext('Name')}</th>
|
||||||
|
<th width="20%">{gettext('Creator')}</th>
|
||||||
|
<th width="20%">{gettext('Role')}</th>
|
||||||
|
<th width="15%">{gettext('Space Used')}</th>
|
||||||
|
<th width="20%">{gettext('Created At')}</th>
|
||||||
|
<th width="5%">{/* Operations */}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{items.map((item, index) => {
|
||||||
|
return (<Item
|
||||||
|
key={index}
|
||||||
|
item={item}
|
||||||
|
updateRole={this.props.updateRole}
|
||||||
|
deleteOrg={this.props.deleteOrg}
|
||||||
|
/>);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
return items.length ? table : emptyTip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Item extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isOpIconShown: false,
|
||||||
|
isDeleteDialogOpen: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseEnter = () => {
|
||||||
|
this.setState({isOpIconShown: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseLeave = () => {
|
||||||
|
this.setState({isOpIconShown: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDeleteDialog = (e) => {
|
||||||
|
if (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
this.setState({isDeleteDialogOpen: !this.state.isDeleteDialogOpen});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRole = (role) => {
|
||||||
|
this.props.updateRole(this.props.item.org_id, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteOrg = () => {
|
||||||
|
this.props.deleteOrg(this.props.item.org_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { item } = this.props;
|
||||||
|
const { isOpIconShown, isDeleteDialogOpen } = this.state;
|
||||||
|
|
||||||
|
const orgName = '<span class="op-target">' + Utils.HTMLescape(item.org_name) + '</span>';
|
||||||
|
const deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', orgName);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<tr onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||||
|
<td><a href={`${siteRoot}sys/organizations/${item.org_id}/info/`}>{item.org_name}</a></td>
|
||||||
|
<td><a href={`${siteRoot}useradmin/info/${encodeURIComponent(item.creator_email)}/`}>{item.creator_name}</a></td>
|
||||||
|
<td>
|
||||||
|
<SysAdminUserRoleEditor
|
||||||
|
isTextMode={true}
|
||||||
|
isEditIconShow={isOpIconShown}
|
||||||
|
currentRole={item.role}
|
||||||
|
roleOptions={availableRoles}
|
||||||
|
onRoleChanged={this.updateRole}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>{`${Utils.bytesToSize(item.quota_usage)} / ${item.quota > 0 ? Utils.bytesToSize(item.quota) : '--'}`}</td>
|
||||||
|
<td>{moment(item.ctime).format('YYYY-MM-DD hh:mm:ss')}</td>
|
||||||
|
<td>
|
||||||
|
<a href="#" className={`action-icon sf2-icon-delete ${isOpIconShown ? '' : 'invisible'}`} title={gettext('Delete')} onClick={this.toggleDeleteDialog}></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{isDeleteDialogOpen &&
|
||||||
|
<CommonOperationConfirmationDialog
|
||||||
|
title={gettext('Delete Organization')}
|
||||||
|
message={deleteDialogMsg}
|
||||||
|
executeOperation={this.deleteOrg}
|
||||||
|
confirmBtnText={gettext('Delete')}
|
||||||
|
toggleDialog={this.toggleDeleteDialog}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Orgs extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
loading: true,
|
||||||
|
errorMsg: '',
|
||||||
|
orgList: [],
|
||||||
|
isAddOrgDialogOpen: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
seafileAPI.sysAdminListAllOrgs().then((res) => {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
orgList: res.data.organizations
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.response) {
|
||||||
|
if (error.response.status == 403) {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorMsg: gettext('Permission denied')
|
||||||
|
});
|
||||||
|
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorMsg: gettext('Error')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorMsg: gettext('Please check the network.')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAddOrgDialog = () => {
|
||||||
|
this.setState({isAddOrgDialogOpen: !this.state.isAddOrgDialogOpen});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRole = (orgID, role) => {
|
||||||
|
let orgInfo = {};
|
||||||
|
orgInfo.role = role;
|
||||||
|
seafileAPI.sysAdminUpdateOrgInfo(orgID, orgInfo).then(res => {
|
||||||
|
let newOrgList = this.state.orgList.map(org => {
|
||||||
|
if (org.org_id == orgID) {
|
||||||
|
org.role = role;
|
||||||
|
}
|
||||||
|
return org;
|
||||||
|
});
|
||||||
|
this.setState({orgList: newOrgList});
|
||||||
|
}).catch((error) => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addOrg = (data) => {
|
||||||
|
const { orgName, ownerEmail, password } = data;
|
||||||
|
seafileAPI.sysAdminAddOrg(orgName, ownerEmail, password).then(res => {
|
||||||
|
let orgList = this.state.orgList;
|
||||||
|
orgList.unshift(res.data);
|
||||||
|
this.setState({orgList: orgList});
|
||||||
|
}).catch((error) => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteOrg = (orgID) => {
|
||||||
|
seafileAPI.sysAdminDeleteOrg(orgID).then(res => {
|
||||||
|
let orgList = this.state.orgList.filter(org => {
|
||||||
|
return org.org_id != orgID;
|
||||||
|
});
|
||||||
|
this.setState({orgList: orgList});
|
||||||
|
toaster.success(gettext('Successfully deleted 1 item.'));
|
||||||
|
}).catch((error) => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { isAddOrgDialogOpen } = this.state;
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<MainPanelTopbar>
|
||||||
|
<Button className="btn btn-secondary operation-item" onClick={this.toggleAddOrgDialog}>{gettext('Add Organization')}</Button>
|
||||||
|
</MainPanelTopbar>
|
||||||
|
<div className="main-panel-center flex-row">
|
||||||
|
<div className="cur-view-container">
|
||||||
|
<div className="cur-view-path">
|
||||||
|
<h3 className="sf-heading">{gettext('Organizations')}</h3>
|
||||||
|
</div>
|
||||||
|
<div className="cur-view-content">
|
||||||
|
<Content
|
||||||
|
loading={this.state.loading}
|
||||||
|
errorMsg={this.state.errorMsg}
|
||||||
|
items={this.state.orgList}
|
||||||
|
updateRole={this.updateRole}
|
||||||
|
deleteOrg={this.deleteOrg}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isAddOrgDialogOpen &&
|
||||||
|
<SysAdminAddOrgDialog
|
||||||
|
addOrg={this.addOrg}
|
||||||
|
toggleDialog={this.toggleAddOrgDialog}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Orgs;
|
81
frontend/src/pages/sys-admin/orgs/user-op-menu.js
Normal file
81
frontend/src/pages/sys-admin/orgs/user-op-menu.js
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
onFreezedItem: PropTypes.func.isRequired,
|
||||||
|
onUnfreezedItem: PropTypes.func.isRequired,
|
||||||
|
onMenuItemClick: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class OpMenu extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isItemMenuShow: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onMenuItemClick = (e) => {
|
||||||
|
let operation = Utils.getEventData(e, 'op');
|
||||||
|
this.props.onMenuItemClick(operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDropdownToggleClick = (e) => {
|
||||||
|
this.toggleOperationMenu(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleOperationMenu = (e) => {
|
||||||
|
this.setState(
|
||||||
|
{isItemMenuShow: !this.state.isItemMenuShow},
|
||||||
|
() => {
|
||||||
|
if (this.state.isItemMenuShow) {
|
||||||
|
this.props.onFreezedItem();
|
||||||
|
} else {
|
||||||
|
this.props.onUnfreezedItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
translateOperations = (item) => {
|
||||||
|
let translateResult = '';
|
||||||
|
switch(item) {
|
||||||
|
case 'Delete':
|
||||||
|
translateResult = gettext('Delete');
|
||||||
|
break;
|
||||||
|
case 'Reset Password':
|
||||||
|
translateResult = gettext('Reset Password');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return translateResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const operations = ['Delete', 'Reset Password'];
|
||||||
|
return (
|
||||||
|
<Dropdown isOpen={this.state.isItemMenuShow} toggle={this.toggleOperationMenu}>
|
||||||
|
<DropdownToggle
|
||||||
|
tag="i"
|
||||||
|
className="sf-dropdown-toggle fa fa-ellipsis-v"
|
||||||
|
title={gettext('More Operations')}
|
||||||
|
data-toggle="dropdown"
|
||||||
|
aria-expanded={this.state.isItemMenuShow}
|
||||||
|
/>
|
||||||
|
<DropdownMenu className="mt-2 mr-2">
|
||||||
|
{operations.map((item, index )=> {
|
||||||
|
return (<DropdownItem key={index} data-op={item} onClick={this.onMenuItemClick}>{this.translateOperations(item)}</DropdownItem>);
|
||||||
|
})}
|
||||||
|
</DropdownMenu>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpMenu.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default OpMenu;
|
@@ -121,10 +121,14 @@ class SidePanel extends React.Component {
|
|||||||
}
|
}
|
||||||
{multiTenancy && isDefaultAdmin &&
|
{multiTenancy && isDefaultAdmin &&
|
||||||
<li className="nav-item">
|
<li className="nav-item">
|
||||||
<a className='nav-link ellipsis' href={siteRoot + 'sys/orgadmin/'}>
|
<Link
|
||||||
|
className={`nav-link ellipsis ${this.getActiveClass('organizations')}`}
|
||||||
|
to={siteRoot + 'sys/organizations/'}
|
||||||
|
onClick={() => this.props.tabItemClick('organizations')}
|
||||||
|
>
|
||||||
<span className="sf2-icon-organization" aria-hidden="true"></span>
|
<span className="sf2-icon-organization" aria-hidden="true"></span>
|
||||||
<span className="nav-text">{gettext('Organizations')}</span>
|
<span className="nav-text">{gettext('Organizations')}</span>
|
||||||
</a>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
{multiInstitution && isDefaultAdmin &&
|
{multiInstitution && isDefaultAdmin &&
|
||||||
|
@@ -125,4 +125,3 @@ export const canManageGroup = window.sysadmin ? window.sysadmin.pageOptions.admi
|
|||||||
export const canViewUserLog = window.sysadmin ? window.sysadmin.pageOptions.admin_permissions.can_view_user_log : '';
|
export const canViewUserLog = window.sysadmin ? window.sysadmin.pageOptions.admin_permissions.can_view_user_log : '';
|
||||||
export const canViewAdminLog = window.sysadmin ? window.sysadmin.pageOptions.admin_permissions.can_view_admin_log : '';
|
export const canViewAdminLog = window.sysadmin ? window.sysadmin.pageOptions.admin_permissions.can_view_admin_log : '';
|
||||||
export const enableWorkWeixin = window.sysadmin ? window.sysadmin.pageOptions.enable_work_weixin : '';
|
export const enableWorkWeixin = window.sysadmin ? window.sysadmin.pageOptions.enable_work_weixin : '';
|
||||||
|
|
||||||
|
@@ -44,11 +44,6 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
MULTI_TENANCY = False
|
MULTI_TENANCY = False
|
||||||
|
|
||||||
try:
|
|
||||||
from seahub_extra.organizations.settings import ORG_TRIAL_DAYS
|
|
||||||
except ImportError:
|
|
||||||
ORG_TRIAL_DAYS = 0
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def get_org_info(org):
|
def get_org_info(org):
|
||||||
@@ -86,11 +81,6 @@ def get_org_detailed_info(org):
|
|||||||
groups = ccnet_api.get_org_groups(org_id, -1, -1)
|
groups = ccnet_api.get_org_groups(org_id, -1, -1)
|
||||||
org_info['groups_count'] = len(groups)
|
org_info['groups_count'] = len(groups)
|
||||||
|
|
||||||
if ORG_TRIAL_DAYS > 0:
|
|
||||||
org_info['expiration'] = datetime.datetime.fromtimestamp(org.ctime / 1e6) + timedelta(days=ORG_TRIAL_DAYS)
|
|
||||||
else:
|
|
||||||
org_info['expiration'] = ''
|
|
||||||
|
|
||||||
return org_info
|
return org_info
|
||||||
|
|
||||||
|
|
||||||
|
@@ -18,12 +18,16 @@
|
|||||||
enable_work_weixin: {% if enable_work_weixin %} true {% else %} false {% endif %},
|
enable_work_weixin: {% if enable_work_weixin %} true {% else %} false {% endif %},
|
||||||
enableSysAdminViewRepo: {% if enable_sys_admin_view_repo %} true {% else %} false {% endif %},
|
enableSysAdminViewRepo: {% if enable_sys_admin_view_repo %} true {% else %} false {% endif %},
|
||||||
trashReposExpireDays: {{ trash_repos_expire_days }},
|
trashReposExpireDays: {{ trash_repos_expire_days }},
|
||||||
is_email_configured: {% if is_email_configured %} true {% else %} false {% endif %},
|
send_email_on_adding_system_member: {% if send_email_on_adding_system_member %} true {% else %} false {% endif %},
|
||||||
send_email_on_resetting_user_passwd: {% if send_email_on_resetting_user_passwd %} true {% else %} false {% endif %},
|
enable_two_factor_auth: {% if enable_two_factor_auth %} true {% else %} false {% endif %},
|
||||||
send_email_on_adding_system_member: {% if send_email_on_adding_system_member %} true {% else %} false {% endif %},
|
availableRoles: (function() {
|
||||||
enable_two_factor_auth: {% if enable_two_factor_auth %} true {% else %} false {% endif %},
|
var list = [];
|
||||||
available_roles: '{{ available_roles | escapejs }}' ,
|
{% for role in available_roles %}
|
||||||
available_admin_roles: '{{ available_admin_roles | escapejs }}' ,
|
list.push('{{role|escapejs}}');
|
||||||
|
{% endfor %}
|
||||||
|
return list;
|
||||||
|
})(),
|
||||||
|
available_admin_roles: '{{ available_admin_roles | escapejs }}',
|
||||||
admin_permissions: {
|
admin_permissions: {
|
||||||
"can_view_system_info": {% if user.admin_permissions.can_view_system_info %} true {% else %} false {% endif %},
|
"can_view_system_info": {% if user.admin_permissions.can_view_system_info %} true {% else %} false {% endif %},
|
||||||
"can_view_statistic": {% if user.admin_permissions.can_view_statistic %} true {% else %} false {% endif %},
|
"can_view_statistic": {% if user.admin_permissions.can_view_statistic %} true {% else %} false {% endif %},
|
||||||
|
@@ -672,11 +672,11 @@ urlpatterns = [
|
|||||||
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"),
|
||||||
url(r'^sys/user-info/(?P<email>[^/]+)/$', sysadmin_react_fake_view, name="sys_users"),
|
url(r'^sys/user-info/(?P<email>[^/]+)/$', sysadmin_react_fake_view, name="sys_users"),
|
||||||
url(r'^sys/organizations/$', sysadmin_react_fake_view, name="sys_organizations_all"),
|
url(r'^sys/organizations/$', sysadmin_react_fake_view, name="sys_organizations"),
|
||||||
url(r'^sys/organizations/(?P<org_id>\d+)/users/$', sysadmin_org_react_fake_view, name="sys_organization_users"),
|
url(r'^sys/organizations/(?P<org_id>\d+)/info/$', sysadmin_react_fake_view, name="sys_organization_info"),
|
||||||
url(r'^sys/organizations/(?P<org_id>\d+)/groups/$', sysadmin_org_react_fake_view, name="sys_organization_groups"),
|
url(r'^sys/organizations/(?P<org_id>\d+)/users/$', sysadmin_react_fake_view, name="sys_organization_users"),
|
||||||
url(r'^sys/organizations/(?P<org_id>\d+)/libraries/$', sysadmin_org_react_fake_view, name="sys_organization_repos"),
|
url(r'^sys/organizations/(?P<org_id>\d+)/groups/$', sysadmin_react_fake_view, name="sys_organization_groups"),
|
||||||
url(r'^sys/organizations/(?P<org_id>\d+)/settings/$', sysadmin_org_react_fake_view, name="sys_organization_settings"),
|
url(r'^sys/organizations/(?P<org_id>\d+)/libraries/$', sysadmin_react_fake_view, name="sys_organization_repos"),
|
||||||
url(r'^sys/share-links/$', sysadmin_react_fake_view, name="sys_share_links"),
|
url(r'^sys/share-links/$', sysadmin_react_fake_view, name="sys_share_links"),
|
||||||
url(r'^sys/upload-links/$', sysadmin_react_fake_view, name="sys_upload_links"),
|
url(r'^sys/upload-links/$', sysadmin_react_fake_view, name="sys_upload_links"),
|
||||||
url(r'^sys/work-weixin/$', sysadmin_react_fake_view, name="sys_work_weixin"),
|
url(r'^sys/work-weixin/$', sysadmin_react_fake_view, name="sys_work_weixin"),
|
||||||
|
@@ -150,8 +150,6 @@ def sysadmin_react_fake_view(request, **kwargs):
|
|||||||
'constance_enabled': dj_settings.CONSTANCE_ENABLED,
|
'constance_enabled': dj_settings.CONSTANCE_ENABLED,
|
||||||
'multi_tenancy': MULTI_TENANCY,
|
'multi_tenancy': MULTI_TENANCY,
|
||||||
'multi_institution': getattr(dj_settings, 'MULTI_INSTITUTION', False),
|
'multi_institution': getattr(dj_settings, 'MULTI_INSTITUTION', False),
|
||||||
'is_email_configured': IS_EMAIL_CONFIGURED,
|
|
||||||
'send_email_on_resetting_user_passwd': SEND_EMAIL_ON_RESETTING_USER_PASSWD,
|
|
||||||
'send_email_on_adding_system_member': SEND_EMAIL_ON_ADDING_SYSTEM_MEMBER,
|
'send_email_on_adding_system_member': SEND_EMAIL_ON_ADDING_SYSTEM_MEMBER,
|
||||||
'sysadmin_extra_enabled': ENABLE_SYSADMIN_EXTRA,
|
'sysadmin_extra_enabled': ENABLE_SYSADMIN_EXTRA,
|
||||||
'enable_guest_invitation': ENABLE_GUEST_INVITATION,
|
'enable_guest_invitation': ENABLE_GUEST_INVITATION,
|
||||||
@@ -165,15 +163,6 @@ def sysadmin_react_fake_view(request, **kwargs):
|
|||||||
'available_admin_roles': get_available_admin_roles()
|
'available_admin_roles': get_available_admin_roles()
|
||||||
})
|
})
|
||||||
|
|
||||||
@login_required
|
|
||||||
@sys_staff_required
|
|
||||||
def sysadmin_org_react_fake_view(request, **kwargs):
|
|
||||||
return render(request, 'sysadmin/sysadmin_org_react_app.html', {
|
|
||||||
'org_id': kwargs['org_id'],
|
|
||||||
'is_email_configured': IS_EMAIL_CONFIGURED,
|
|
||||||
'send_email_on_resetting_user_passwd': SEND_EMAIL_ON_RESETTING_USER_PASSWD,
|
|
||||||
})
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@sys_staff_required
|
@sys_staff_required
|
||||||
def sys_statistic_file(request):
|
def sys_statistic_file(request):
|
||||||
|
Reference in New Issue
Block a user