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')}
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+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 = (
+
+
+
+
+ {gettext('Name')} |
+ {gettext('Creator')} |
+ {gettext('Created At')} |
+ {/* Operations */} |
+
+
+
+ {items.map((item, index) => {
+ return ( );
+ })}
+
+
+
+ );
+ 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 = (
+
+
+
+
+ |
+ {gettext('Name')} |
+ {gettext('ID')} |
+ {gettext('Owner')} |
+ {/* Operations */} |
+
+
+
+ {items.map((item, index) => {
+ return ( );
+ })}
+
+
+
+ );
+ 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 (
+
+
+  |
+ {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 = (
+
+
+
+
+ {gettext('Name')} |
+ {gettext('Status')} |
+ {gettext('Space Used')} |
+ {gettext('Created At')}{' / '}{gettext('Last Login')} |
+ {/* Operations */} |
+
+
+
+ {items.map((item, index) => {
+ return ( );
+ })}
+
+
+
+ );
+ 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 = (
+
+
+
+
+ {gettext('Name')} |
+ {gettext('Creator')} |
+ {gettext('Role')} |
+ {gettext('Space Used')} |
+ {gettext('Created At')} |
+ {/* Operations */} |
+
+
+
+ {items.map((item, index) => {
+ return ( );
+ })}
+
+
+
+ );
+ 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):