mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-03 07:55:36 +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 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 Notifications from './notifications/notifications';
|
||||
import FileScanRecords from './file-scan-records';
|
||||
@@ -64,6 +70,10 @@ class SysAdmin extends React.Component {
|
||||
tab: 'groups',
|
||||
urlPartList: ['groups/']
|
||||
},
|
||||
{
|
||||
tab: 'organizations',
|
||||
urlPartList: ['organizations/']
|
||||
},
|
||||
];
|
||||
const tmpTab = this.getCurrentTabForPageList(pageList);
|
||||
currentTab = tmpTab ? tmpTab : currentTab;
|
||||
@@ -125,6 +135,11 @@ class SysAdmin extends React.Component {
|
||||
</Departments>
|
||||
<ShareLinks path={siteRoot + 'sys/share-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
|
||||
path={siteRoot + 'sys/file-scan-records'}
|
||||
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 &&
|
||||
<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="nav-text">{gettext('Organizations')}</span>
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
}
|
||||
{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 canViewAdminLog = window.sysadmin ? window.sysadmin.pageOptions.admin_permissions.can_view_admin_log : '';
|
||||
export const enableWorkWeixin = window.sysadmin ? window.sysadmin.pageOptions.enable_work_weixin : '';
|
||||
|
||||
|
@@ -44,11 +44,6 @@ try:
|
||||
except ImportError:
|
||||
MULTI_TENANCY = False
|
||||
|
||||
try:
|
||||
from seahub_extra.organizations.settings import ORG_TRIAL_DAYS
|
||||
except ImportError:
|
||||
ORG_TRIAL_DAYS = 0
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
|
||||
|
@@ -18,11 +18,15 @@
|
||||
enable_work_weixin: {% if enable_work_weixin %} true {% else %} false {% endif %},
|
||||
enableSysAdminViewRepo: {% if enable_sys_admin_view_repo %} true {% else %} false {% endif %},
|
||||
trashReposExpireDays: {{ trash_repos_expire_days }},
|
||||
is_email_configured: {% if is_email_configured %} true {% else %} false {% endif %},
|
||||
send_email_on_resetting_user_passwd: {% if send_email_on_resetting_user_passwd %} true {% else %} false {% endif %},
|
||||
send_email_on_adding_system_member: {% if send_email_on_adding_system_member %} true {% else %} false {% endif %},
|
||||
enable_two_factor_auth: {% if enable_two_factor_auth %} true {% else %} false {% endif %},
|
||||
available_roles: '{{ available_roles | escapejs }}' ,
|
||||
availableRoles: (function() {
|
||||
var list = [];
|
||||
{% for role in available_roles %}
|
||||
list.push('{{role|escapejs}}');
|
||||
{% endfor %}
|
||||
return list;
|
||||
})(),
|
||||
available_admin_roles: '{{ available_admin_roles | escapejs }}',
|
||||
admin_permissions: {
|
||||
"can_view_system_info": {% if user.admin_permissions.can_view_system_info %} 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-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/organizations/$', sysadmin_react_fake_view, name="sys_organizations_all"),
|
||||
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+)/groups/$', sysadmin_org_react_fake_view, name="sys_organization_groups"),
|
||||
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+)/settings/$', sysadmin_org_react_fake_view, name="sys_organization_settings"),
|
||||
url(r'^sys/organizations/$', sysadmin_react_fake_view, name="sys_organizations"),
|
||||
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+)/users/$', sysadmin_react_fake_view, name="sys_organization_users"),
|
||||
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+)/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/upload-links/$', sysadmin_react_fake_view, name="sys_upload_links"),
|
||||
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,
|
||||
'multi_tenancy': MULTI_TENANCY,
|
||||
'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,
|
||||
'sysadmin_extra_enabled': ENABLE_SYSADMIN_EXTRA,
|
||||
'enable_guest_invitation': ENABLE_GUEST_INVITATION,
|
||||
@@ -165,15 +163,6 @@ def sysadmin_react_fake_view(request, **kwargs):
|
||||
'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
|
||||
@sys_staff_required
|
||||
def sys_statistic_file(request):
|
||||
|
Reference in New Issue
Block a user