diff --git a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-add-org-dialog.js b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-add-org-dialog.js new file mode 100644 index 0000000000..eee5479fe8 --- /dev/null +++ b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-add-org-dialog.js @@ -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 ( + + {gettext('Add Organization')} + +
+ + + + + + + + + + + + + + + + +
+ {errorMsg && {errorMsg}} +
+ + + + +
+ ); + } +} + +SysAdminAddOrgDialog.propTypes = propTypes; + +export default SysAdminAddOrgDialog; diff --git a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-add-user-dialog.js b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-add-user-dialog.js new file mode 100644 index 0000000000..914e72a7a9 --- /dev/null +++ b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-add-user-dialog.js @@ -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 ( + + {gettext('Add Member')} + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ {errorMsg && {errorMsg}} +
+ + + + +
+ ); + } +} + +SysAdminAddUserDialog.propTypes = propTypes; + +export default SysAdminAddUserDialog; diff --git a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-set-org-max-user-number-dialog.js b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-set-org-max-user-number-dialog.js new file mode 100644 index 0000000000..2e5cb5b6bc --- /dev/null +++ b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-set-org-max-user-number-dialog.js @@ -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 ( + + {gettext('Set max number of members')} + +
+ + + +
+
+ + + + +
+ ); + } +} + +SysAdminSetOrgMaxUserNumberDialog.propTypes = propTypes; + +export default SysAdminSetOrgMaxUserNumberDialog; diff --git a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-set-org-name-dialog.js b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-set-org-name-dialog.js new file mode 100644 index 0000000000..cf1dd95942 --- /dev/null +++ b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-set-org-name-dialog.js @@ -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 ( + + {gettext('Set Name')} + +
+ + + +
+
+ + + + +
+ ); + } +} + +SysAdminSetOrgNameDialog.propTypes = propTypes; + +export default SysAdminSetOrgNameDialog; diff --git a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-set-org-quota-dialog.js b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-set-org-quota-dialog.js new file mode 100644 index 0000000000..c7a3d3dccb --- /dev/null +++ b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-set-org-quota-dialog.js @@ -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 ( + + {gettext('Set Quota')} + +
+ + + + + MB + + +

+ {gettext('An integer that is greater than or equal to 0.')} +
+ {gettext('Tip: 0 means default limit')} +

+
+
+
+ + + + +
+ ); + } +} + +SysAdminOrgSetQuotaDialog.propTypes = propTypes; + +export default SysAdminOrgSetQuotaDialog; diff --git a/frontend/src/components/select-editor/sysadmin-user-role-editor.js b/frontend/src/components/select-editor/sysadmin-user-role-editor.js new file mode 100644 index 0000000000..8b1cff839a --- /dev/null +++ b/frontend/src/components/select-editor/sysadmin-user-role-editor.js @@ -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 ( + + ); + } +} + +SysAdminUserRoleEditor.propTypes = propTypes; + +export default SysAdminUserRoleEditor; diff --git a/frontend/src/components/select-editor/sysadmin-user-status-editor.js b/frontend/src/components/select-editor/sysadmin-user-status-editor.js new file mode 100644 index 0000000000..88ad57d502 --- /dev/null +++ b/frontend/src/components/select-editor/sysadmin-user-status-editor.js @@ -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 ( + + ); + } +} + +SysAdminUserStatusEditor.propTypes = propTypes; + +export default SysAdminUserStatusEditor; diff --git a/frontend/src/pages/sys-admin/index.js b/frontend/src/pages/sys-admin/index.js index e417af7556..0161dc3363 100644 --- a/frontend/src/pages/sys-admin/index.js +++ b/frontend/src/pages/sys-admin/index.js @@ -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 { + + + + + ; + } else if (errorMsg) { + return

{errorMsg}

; + } else { + const emptyTip = ( + +

{gettext('No groups')}

+
+ ); + const table = ( + + + + + + + + + + + + {items.map((item, index) => { + return (); + })} + +
{gettext('Name')}{gettext('Creator')}{gettext('Created At')}{/* Operations */}
+
+ ); + 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 = '' + Utils.HTMLescape(item.group_name) + ''; + 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 ( + + + {item.group_name} + {item.creator_name} + {moment(item.created_at).format('YYYY-MM-DD hh:mm:ss')} + + + + + {isDeleteDialogOpen && + + } + + ); + } +} + +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 ( + + +
+
+ +
+ +
+
+
+
+ ); + } +} + +export default OrgGroups; diff --git a/frontend/src/pages/sys-admin/orgs/org-info.js b/frontend/src/pages/sys-admin/orgs/org-info.js new file mode 100644 index 0000000000..edb8652df6 --- /dev/null +++ b/frontend/src/pages/sys-admin/orgs/org-info.js @@ -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 ( + + + ); + } + + render() { + const { loading, errorMsg, orgInfo } = this.props; + if (loading) { + return ; + } else if (errorMsg) { + return

{errorMsg}

; + } else { + const { org_name, users_count, max_user_number, groups_count, quota, quota_usage } = this.props.orgInfo; + const { isSetQuotaDialogOpen, isSetNameDialogOpen, isSetMaxUserNumberDialogOpen } = this.state; + return ( + +
+
{gettext('Name')}
+
+ {org_name} + {this.showEditIcon(this.toggleSetNameDialog)} +
+ +
{gettext('Number of members')}
+
{users_count}
+ + {max_user_number && + +
{gettext('Max number of members')}
+
+ {max_user_number} + {this.showEditIcon(this.toggleSetMaxUserNumberDialog)} +
+
+ } + +
{gettext('Number of groups')}
+
{groups_count}
+ +
{gettext('Space Used')}
+
+ {`${Utils.bytesToSize(quota_usage)} / ${quota > 0 ? Utils.bytesToSize(quota) : '--'}`} + {this.showEditIcon(this.toggleSetQuotaDialog)} +
+
+ {isSetQuotaDialogOpen && + + } + {isSetNameDialogOpen && + + } + {isSetMaxUserNumberDialogOpen && + + } +
+ ); + } + } +} + +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 ( + + +
+
+ +
+ +
+
+
+
+ ); + } +} + +export default OrgInfo; diff --git a/frontend/src/pages/sys-admin/orgs/org-nav.js b/frontend/src/pages/sys-admin/orgs/org-nav.js new file mode 100644 index 0000000000..c4d2fd71ac --- /dev/null +++ b/frontend/src/pages/sys-admin/orgs/org-nav.js @@ -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 ( +
+
+

{gettext('Organizations')} / {orgName}

+
+
    + {this.navItems.map((item, index) => { + return ( +
  • + {item.text} +
  • + ); + })} +
+
+ ); + } +} + +Nav.propTypes = propTypes; + +export default Nav; diff --git a/frontend/src/pages/sys-admin/orgs/org-repos.js b/frontend/src/pages/sys-admin/orgs/org-repos.js new file mode 100644 index 0000000000..c847e27d4c --- /dev/null +++ b/frontend/src/pages/sys-admin/orgs/org-repos.js @@ -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 ; + } else if (errorMsg) { + return

{errorMsg}

; + } else { + const emptyTip = ( + +

{gettext('No libraries')}

+
+ ); + const table = ( + + + + + + + + + + + + + {items.map((item, index) => { + return (); + })} + +
{gettext('Name')}{gettext('ID')}{gettext('Owner')}{/* Operations */}
+
+ ); + 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 = '' + Utils.HTMLescape(item.repo_name) + ''; + const deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', itemName); + + return ( + + + {iconTitle} + {item.repo_name} + {item.repo_id} + + {item.owner_email ? + {item.owner_name} : + '--' + } + + + + + + {isDeleteDialogOpen && + + } + + ); + } +} + +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 ( + + +
+
+ +
+ +
+
+
+
+ ); + } +} + +export default OrgRepos; diff --git a/frontend/src/pages/sys-admin/orgs/org-users.js b/frontend/src/pages/sys-admin/orgs/org-users.js new file mode 100644 index 0000000000..e94d7ea12f --- /dev/null +++ b/frontend/src/pages/sys-admin/orgs/org-users.js @@ -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 ; + } else if (errorMsg) { + return

{errorMsg}

; + } else { + const emptyTip = ( + +

{gettext('No members')}

+
+ ); + const table = ( + + + + + + + + + + + + + {items.map((item, index) => { + return (); + })} + +
{gettext('Name')}{gettext('Status')}{gettext('Space Used')}{gettext('Created At')}{' / '}{gettext('Last Login')}{/* Operations */}
+
+ ); + 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 = '' + Utils.HTMLescape(item.name) + ''; + 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 ( + + + {item.name} + + + + {`${Utils.bytesToSize(item.quota_usage)} / ${item.quota_total > 0 ? Utils.bytesToSize(item.quota_total) : '--'}`} + + {moment(item.ctime).format('YYYY-MM-DD hh:mm:ss')}{' / '}{item.last_login ? moment(item.last_login).fromNow() : '--'} + + + {(isOpIconShown && item.email != username) && + + } + + + {isDeleteDialogOpen && + + } + {isResetPasswordDialogOpen && + + } + + ); + } +} + +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 ( + + + + +
+
+ +
+ +
+
+
+ {isAddUserDialogOpen && + + } +
+ ); + } +} + +export default OrgUsers; diff --git a/frontend/src/pages/sys-admin/orgs/orgs.js b/frontend/src/pages/sys-admin/orgs/orgs.js new file mode 100644 index 0000000000..1eead22ed5 --- /dev/null +++ b/frontend/src/pages/sys-admin/orgs/orgs.js @@ -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 ; + } else if (errorMsg) { + return

{errorMsg}

; + } else { + const emptyTip = ( + +

{gettext('No organizations')}

+
+ ); + const table = ( + + + + + + + + + + + + + + {items.map((item, index) => { + return (); + })} + +
{gettext('Name')}{gettext('Creator')}{gettext('Role')}{gettext('Space Used')}{gettext('Created At')}{/* Operations */}
+
+ ); + 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 = '' + Utils.HTMLescape(item.org_name) + ''; + const deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', orgName); + + return ( + + + {item.org_name} + {item.creator_name} + + + + {`${Utils.bytesToSize(item.quota_usage)} / ${item.quota > 0 ? Utils.bytesToSize(item.quota) : '--'}`} + {moment(item.ctime).format('YYYY-MM-DD hh:mm:ss')} + + + + + {isDeleteDialogOpen && + + } + + ); + } +} + +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 ( + + + + +
+
+
+

{gettext('Organizations')}

+
+
+ +
+
+
+ {isAddOrgDialogOpen && + + } +
+ ); + } +} + +export default Orgs; diff --git a/frontend/src/pages/sys-admin/orgs/user-op-menu.js b/frontend/src/pages/sys-admin/orgs/user-op-menu.js new file mode 100644 index 0000000000..14adbb3ac6 --- /dev/null +++ b/frontend/src/pages/sys-admin/orgs/user-op-menu.js @@ -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 ( + + + + {operations.map((item, index )=> { + return ({this.translateOperations(item)}); + })} + + + ); + } +} + +OpMenu.propTypes = propTypes; + +export default OpMenu; diff --git a/frontend/src/pages/sys-admin/side-panel.js b/frontend/src/pages/sys-admin/side-panel.js index 503cb00a6b..54bd225bbf 100644 --- a/frontend/src/pages/sys-admin/side-panel.js +++ b/frontend/src/pages/sys-admin/side-panel.js @@ -121,10 +121,14 @@ class SidePanel extends React.Component { } {multiTenancy && isDefaultAdmin &&
  • - + this.props.tabItemClick('organizations')} + > {gettext('Organizations')} - +
  • } {multiInstitution && isDefaultAdmin && diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js index e33a600de3..ccc1e308b7 100644 --- a/frontend/src/utils/constants.js +++ b/frontend/src/utils/constants.js @@ -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 : ''; - diff --git a/seahub/api2/endpoints/admin/organizations.py b/seahub/api2/endpoints/admin/organizations.py index 3f1b0da7cf..6a728cbded 100644 --- a/seahub/api2/endpoints/admin/organizations.py +++ b/seahub/api2/endpoints/admin/organizations.py @@ -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 diff --git a/seahub/templates/sysadmin/sysadmin_react_app.html b/seahub/templates/sysadmin/sysadmin_react_app.html index 78acca0352..4204884ee9 100644 --- a/seahub/templates/sysadmin/sysadmin_react_app.html +++ b/seahub/templates/sysadmin/sysadmin_react_app.html @@ -18,12 +18,16 @@ 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 }}' , - available_admin_roles: '{{ available_admin_roles | escapejs }}' , + 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 %}, + 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 %}, "can_view_statistic": {% if user.admin_permissions.can_view_statistic %} true {% else %} false {% endif %}, diff --git a/seahub/urls.py b/seahub/urls.py index f316f62ca1..b475541eba 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -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[^/]+)/$', sysadmin_react_fake_view, name="sys_users"), - url(r'^sys/organizations/$', sysadmin_react_fake_view, name="sys_organizations_all"), - url(r'^sys/organizations/(?P\d+)/users/$', sysadmin_org_react_fake_view, name="sys_organization_users"), - url(r'^sys/organizations/(?P\d+)/groups/$', sysadmin_org_react_fake_view, name="sys_organization_groups"), - url(r'^sys/organizations/(?P\d+)/libraries/$', sysadmin_org_react_fake_view, name="sys_organization_repos"), - url(r'^sys/organizations/(?P\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\d+)/info/$', sysadmin_react_fake_view, name="sys_organization_info"), + url(r'^sys/organizations/(?P\d+)/users/$', sysadmin_react_fake_view, name="sys_organization_users"), + url(r'^sys/organizations/(?P\d+)/groups/$', sysadmin_react_fake_view, name="sys_organization_groups"), + url(r'^sys/organizations/(?P\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"), diff --git a/seahub/views/sysadmin.py b/seahub/views/sysadmin.py index dd52a5fab9..2834c9414a 100644 --- a/seahub/views/sysadmin.py +++ b/seahub/views/sysadmin.py @@ -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):