1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-18 08:16:07 +00:00
Co-authored-by: lian <lian@seafile.com>
This commit is contained in:
lian
2020-03-19 20:15:26 +08:00
committed by GitHub
parent 8ef7d18c15
commit 2de4d4846e
33 changed files with 4756 additions and 7 deletions

View File

@@ -0,0 +1,45 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap';
import { gettext } from '../../utils/constants';
const propTypes = {
formActionURL: PropTypes.string.isRequired,
csrfToken: PropTypes.string.isRequired,
toggle: PropTypes.func.isRequired
};
class ConfirmDisconnectDingtalk extends Component {
constructor(props) {
super(props);
this.form = React.createRef();
}
disconnect = () => {
this.form.current.submit();
}
render() {
const {formActionURL, csrfToken, toggle} = this.props;
return (
<Modal centered={true} isOpen={true} toggle={toggle}>
<ModalHeader toggle={toggle}>{gettext('Disconnect')}</ModalHeader>
<ModalBody>
<p>{gettext('Are you sure you want to disconnect?')}</p>
<form ref={this.form} className="d-none" method="post" action={formActionURL}>
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
</form>
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={toggle}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.disconnect}>{gettext('Disconnect')}</Button>
</ModalFooter>
</Modal>
);
}
}
ConfirmDisconnectDingtalk.propTypes = propTypes;
export default ConfirmDisconnectDingtalk;

View File

@@ -0,0 +1,54 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
import Loading from '../loading';
const propTypes = {
importDepartmentDialogToggle: PropTypes.func.isRequired,
onImportDepartmentSubmit: PropTypes.func.isRequired,
departmentsCount: PropTypes.number.isRequired,
membersCount: PropTypes.number.isRequired,
departmentName: PropTypes.string.isRequired,
};
class ImportDingtalkDepartmentDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading : false,
};
}
toggle = () => {
this.props.importDepartmentDialogToggle(null);
};
handleSubmit = () => {
this.props.onImportDepartmentSubmit();
this.setState({ isLoading : true });
};
render() {
const { departmentsCount, membersCount, departmentName } = this.props;
return (
<Modal isOpen={true} toggle={this.toggle}>
<ModalHeader toggle={this.toggle}>
<span>{'导入部门 '}</span><span className="op-target" title={departmentName}>{departmentName}</span>
</ModalHeader>
<ModalBody>
<p>{'将要导入 '}<strong>{departmentsCount}</strong>{' '}<strong>{membersCount}</strong>{' '}</p>
{this.state.isLoading && <Loading/>}
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.toggle}>{'取消'}</Button>
<Button color="primary" onClick={this.handleSubmit}>{'导入'}</Button>
</ModalFooter>
</Modal>
);
}
}
ImportDingtalkDepartmentDialog.propTypes = propTypes;
export default ImportDingtalkDepartmentDialog;

View File

@@ -0,0 +1,60 @@
import React from 'react';
import { gettext, siteRoot } from '../../utils/constants';
import ModalPortal from '../modal-portal';
import ConfirmDisconnectDingtalk from '../dialog/confirm-disconnect-dingtalk';
const {
csrfToken,
langCode,
socialConnectedDingtalk,
socialNextPage
} = window.app.pageOptions;
class SocialLoginDintalk extends React.Component {
constructor(props) {
super(props);
this.state = {
isConfirmDialogOpen: false
};
}
confirmDisconnect = (e) => {
e.preventDefault();
this.setState({
isConfirmDialogOpen: true
});
}
toggleDialog = () => {
this.setState({
isConfirmDialogOpen: !this.state.isConfirmDialogOpen
});
}
render() {
return (
<React.Fragment>
<div className="setting-item" id="social-auth">
<h3 className="setting-item-heading">{gettext('Social Login')}</h3>
<p className="mb-2">{langCode == 'zh-cn' ? '钉钉': 'Dingtalk'}</p>
{socialConnectedDingtalk ?
<a href="#" className="btn btn-outline-primary" onClick={this.confirmDisconnect}>{gettext('Disconnect')}</a> :
<a href={`${siteRoot}dingtalk/connect/?next=${encodeURIComponent(socialNextPage)}`} className="btn btn-outline-primary">{gettext('Connect')}</a>
}
</div>
{this.state.isConfirmDialogOpen && (
<ModalPortal>
<ConfirmDisconnectDingtalk
formActionURL={`${siteRoot}dingtalk/disconnect/?next=${encodeURIComponent(socialNextPage)}`}
csrfToken={csrfToken}
toggle={this.toggleDialog}
/>
</ModalPortal>
)}
</React.Fragment>
);
}
}
export default SocialLoginDintalk;

View File

@@ -0,0 +1,87 @@
.cur-view-content {
position: relative;
}
.dir-content-main {
position: absolute;
right: 0;
height: 100%;
width: 75%;
overflow-y: hidden;
padding-right: 1rem;
}
.dir-content-main:hover {
overflow-y: auto;
}
.dir-content-main table td {
line-height: 2rem;
}
.dir-content-main .empty-tip {
box-shadow: none;
}
.dir-content-main .empty-tip img {
width: 140px;
height: 140px;
}
.dir-content-nav {
position: absolute;
overflow: hidden;
width: 24%;
}
.dir-content-nav:hover {
overflow: auto;
}
.dir-content-resize {
position: absolute;
left: 25%;
height: 100%;
width: 1%;
border-left: 1px solid #eee;
}
.department-children {
padding-left: 1rem;
position: relative;
}
.tree-node-inner {
position: relative;
display: flex;
padding-right: 1rem;
}
.tree-node-inner i {
position: absolute;
top: 20%;
left: 0.3rem;
color: silver;
}
.tree-node-inner-hover {
background-color: #FFEFB2;
border-radius: 0.25rem;
cursor: pointer;
}
.tree-node-hight-light {
color: #fff;
border-radius: 4px;
background-color: #feac74 !important;
}
.tree-node-hight-light i {
color: #fff;
}
.tree-node-hight-light .attr-action-icon,
.tree-node-hight-light .attr-action-icon:focus,
.tree-node-hight-light .attr-action-icon:hover {
color: #fff !important;
}
.tree-node-icon {
padding-right: 1.5rem;
}
.tree-node-text {
width: calc(100% - 2.5rem);
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 24px;
}
.tree-view {
padding: 12px 12px 12px 0;
flex: 1 1;
}

View File

@@ -0,0 +1,355 @@
import React, { Component, Fragment } from 'react';
import { Button } from 'reactstrap';
import _ from 'lodash';
import { seafileAPI } from '../../utils/seafile-api';
import { gettext, siteRoot, isPro } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import toaster from '../../components/toast';
import Account from '../../components/common/account';
import { DingtalkDepartmentMembersList, DingtalkDepartmentsTreePanel } from './dingtalk';
import ImportDingtalkDepartmentDialog from '../../components/dialog/import-dingtalk-department-dialog';
import '../../css/work-weixin-departments.css';
import '../../css/dingtalk-departments.css';
class DingtalkDepartments extends Component {
constructor(props) {
super(props);
this.state = {
isTreeLoading: true,
isMembersListLoading: true,
departmentsTree: [],
checkedDepartmentId: 0,
membersTempObj: {},
membersList: [],
newUsersTempObj: {},
isCheckedAll: false,
canCheckUserIds: [],
isImportDepartmentDialogShow: false,
importDepartment: null,
importDepartmentChildrenCount: 0,
importDepartmentMembersCount: 0,
};
}
getDepartmentsTree = (list) => {
let childIds = [];
let parentIds = [];
for (let i = 0; i < list.length; i++) {
if (childIds.indexOf(list[i].id) === -1) {
childIds.push(list[i].id);
}
if (parentIds.indexOf(list[i].parentid) === -1) {
parentIds.push(list[i].parentid);
}
}
let intersection = parentIds.filter((v) => {
return childIds.indexOf(v) !== -1;
});
let rootIds = parentIds.concat(intersection).filter((v) => {
return parentIds.indexOf(v) === -1 || intersection.indexOf(v) === -1;
});
let cloneData = _.cloneDeep(list);
return cloneData.filter(father => {
let branchArr = cloneData.filter(child => father.id === child.parentid);
branchArr.length > 0 ? father.children = branchArr : '';
return rootIds.indexOf(father.parentid) !== -1;
});
};
getDingtalkDepartmentsList = (departmentID) => {
seafileAPI.adminListDingtalkDepartments(departmentID).then((res) => {
if (!departmentID) {
let departmentsTree = this.getDepartmentsTree(res.data.department);
this.setState({
isTreeLoading: false,
departmentsTree: departmentsTree,
});
} else {
this.setState({
importDepartmentChildrenCount: res.data.department.length,
importDepartmentMembersCount: this.state.membersTempObj[departmentID].length,
});
}
}).catch((error) => {
this.handleError(error);
this.setState({
isTreeLoading: false,
isMembersListLoading: false,
});
if (error.response && error.response.status === 403) {
window.location = siteRoot + 'sys/useradmin/';
}
});
};
getDingtalkDepartmentMembersList = (department_id) => {
this.setState({
isMembersListLoading: true,
});
seafileAPI.adminListDingtalkDepartmentMembers(department_id).then((res) => {
let membersTempObj = this.state.membersTempObj;
membersTempObj[department_id] = res.data.userlist;
let canCheckUserIds = this.getCanCheckUserIds(res.data.userlist);
this.setState({
membersTempObj: membersTempObj,
membersList: res.data.userlist,
isMembersListLoading: false,
canCheckUserIds: canCheckUserIds,
});
}).catch((error) => {
this.setState({isMembersListLoading: false});
this.handleError(error);
});
}
getCanCheckUserIds = (membersList) => {
let userIds = [];
membersList.forEach((member) => {
if (!member.email) userIds.push(member.userid);
});
return userIds;
};
onChangeDepartment = (department_id) => {
this.setState({
newUsersTempObj: {},
isCheckedAll: false,
checkedDepartmentId: department_id,
});
if (!(department_id in this.state.membersTempObj)) {
this.getDingtalkDepartmentMembersList(department_id);
} else {
let canCheckUserIds = this.getCanCheckUserIds(this.state.membersTempObj[department_id]);
this.setState({
membersList: this.state.membersTempObj[department_id],
canCheckUserIds: canCheckUserIds,
});
}
};
onUserChecked = (user) => {
if (this.state.canCheckUserIds.indexOf(user.userid) !== -1) {
let newUsersTempObj = this.state.newUsersTempObj;
if (user.userid in newUsersTempObj) {
delete newUsersTempObj[user.userid];
if (this.state.isCheckedAll) {
this.setState({ isCheckedAll: false });
}
} else {
newUsersTempObj[user.userid] = user;
if (Object.keys(newUsersTempObj).length === this.state.canCheckUserIds.length) {
this.setState({ isCheckedAll: true });
}
}
this.setState({ newUsersTempObj: newUsersTempObj });
}
};
onAllUsersChecked = () => {
this.setState({
isCheckedAll: !this.state.isCheckedAll,
}, () => {
if (this.state.isCheckedAll) {
let newUsersTempObj = {};
let newUsersTempList = this.state.membersList.filter(user => {
return this.state.canCheckUserIds.indexOf(user.userid) !== -1;
});
for (let i = 0; i < newUsersTempList.length; i++) {
newUsersTempObj[newUsersTempList[i].userid] = newUsersTempList[i];
}
this.setState({ newUsersTempObj: newUsersTempObj });
} else {
this.setState({ newUsersTempObj: {} });
}
});
};
onSubmit = () => {
const { newUsersTempObj } = this.state;
if (JSON.stringify(newUsersTempObj) === '{}') return;
let userList = [];
for (let i in newUsersTempObj) {
userList.push(newUsersTempObj[i]);
}
if (userList.length === 0) {
toaster.danger('未选择成员', {duration: 3});
return;
}
seafileAPI.adminAddDingtalkUsersBatch(userList).then((res) => {
this.setState({
newUsersTempObj: {},
isCheckedAll: false,
});
if (res.data.success) {
this.handleSubmitSuccess(res.data.success);
}
if (res.data.failed) {
const fails= res.data.failed;
for (let i = 0; i < fails.length; i++) {
toaster.danger(fails[i].name + ' ' + fails[i].error_msg, {duration: 3});
}
}
}).catch((error) => {
this.handleError(error);
});
};
handleSubmitSuccess = (success) => {
let { membersTempObj, membersList, canCheckUserIds } = this.state;
for (let i = 0; i < success.length; i++) {
let { userid, name, email } = success[i];
toaster.success(name + ' 成功导入', {duration: 1});
// refresh all temp
if (canCheckUserIds.indexOf(userid) !== -1) {
canCheckUserIds.splice(canCheckUserIds.indexOf(userid), 1);
}
for (let j = 0; j < membersList.length; j++) {
if (membersList[j].userid === userid) {
membersList[j].email = email;
break;
}
}
for (let departmentId in membersTempObj) {
for (let k = 0; k < membersTempObj[departmentId].length; k++) {
if (membersTempObj[departmentId][k].userid === userid) {
membersTempObj[departmentId][k].email = email;
break;
}
}
}
}
this.setState({
membersTempObj: membersTempObj,
membersList: membersList,
canCheckUserIds: canCheckUserIds,
});
}
importDepartmentDialogToggle = (importDepartment) => {
this.setState({
isImportDepartmentDialogShow: !this.state.isImportDepartmentDialogShow,
importDepartment: importDepartment,
}, () => {
if (importDepartment) {
this.getDingtalkDepartmentsList(importDepartment.id);
}
});
};
onImportDepartmentSubmit = () => {
let importDepartment = this.state.importDepartment;
if (!importDepartment) return;
seafileAPI.adminImportDingtalkDepartment(importDepartment.id).then((res) => {
this.setState({
isMembersListLoading: true,
checkedDepartmentId: importDepartment.id,
membersTempObj: {},
membersList: [],
newUsersTempObj: {},
isCheckedAll: false,
canCheckUserIds: [],
});
this.getDingtalkDepartmentMembersList(importDepartment.id);
this.importDepartmentDialogToggle(null);
if (res.data.success) {
this.handleImportDepartmentSubmitSuccess(res.data.success);
}
if (res.data.failed) {
this.handleImportDepartmentSubmitFailed(res.data.failed);
}
}).catch((error) => {
this.handleError(error);
});
};
handleImportDepartmentSubmitSuccess = (successes) => {
for (let i = 0, len = successes.length; i < len; i++) {
let success = successes[i];
let successMsg = success.type === 'department' ? '部门 ' + success.department_name + ' 导入成功' : success.api_user_name + ' 导入成功' ;
toaster.success(successMsg, { duration: 3 });
}
};
handleImportDepartmentSubmitFailed = (fails) => {
for (let i = 0, len = fails.length; i < len; i++) {
let fail = fails[i];
let failName = fail.type === 'department' ? fail.department_name : fail.api_user_name;
toaster.danger(failName + ' ' + fail.msg, { duration: 3} );
}
};
handleError = (error) => {
const errorMsg = Utils.getErrorMsg(error);
toaster.danger(errorMsg);
}
componentDidMount() {
this.getDingtalkDepartmentsList(null);
}
renderNav() {
const btnClass = 'btn btn-secondary operation-item ';
return (
<div className="main-panel-north border-left-show">
<div className="cur-view-toolbar">
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu"></span>
<Button className={btnClass + 'my-1 d-md-none'} onClick={this.onSubmit}>{'导入用户'}</Button>
<Button className={btnClass + 'hidden-md-up'} onClick={this.onSubmit}>{'导入用户'}</Button>
</div>
<div className="common-toolbar">
<Account isAdminPanel={true}/>
</div>
</div>
);
}
render() {
const { isImportDepartmentDialogShow, isTreeLoading, importDepartment, importDepartmentChildrenCount, importDepartmentMembersCount } = this.state;
let canImportDepartment = !!(isPro && isImportDepartmentDialogShow && !isTreeLoading && importDepartment);
return (
<Fragment>
{this.renderNav()}
<div className="main-panel-center">
<div className="cur-view-container">
<div className="cur-view-path">
<h3 className="sf-heading">{'钉钉集成'}</h3>
</div>
<div className="cur-view-content d-flex flex-row">
<DingtalkDepartmentsTreePanel
departmentsTree={this.state.departmentsTree}
isTreeLoading={this.state.isTreeLoading}
onChangeDepartment={this.onChangeDepartment}
checkedDepartmentId={this.state.checkedDepartmentId}
importDepartmentDialogToggle={this.importDepartmentDialogToggle}
/>
<div className="dir-content-resize"></div>
<DingtalkDepartmentMembersList
isMembersListLoading={this.state.isMembersListLoading}
membersList={this.state.membersList}
checkedDepartmentId={this.state.checkedDepartmentId}
newUsersTempObj={this.state.newUsersTempObj}
onUserChecked={this.onUserChecked}
onAllUsersChecked={this.onAllUsersChecked}
isCheckedAll={this.state.isCheckedAll}
canCheckUserIds={this.state.canCheckUserIds}
/>
</div>
</div>
</div>
{canImportDepartment &&
<ImportDingtalkDepartmentDialog
importDepartmentDialogToggle={this.importDepartmentDialogToggle}
onImportDepartmentSubmit={this.onImportDepartmentSubmit}
departmentsCount={importDepartmentChildrenCount}
membersCount={importDepartmentMembersCount}
departmentName={importDepartment.name}
/>
}
</Fragment>
);
}
}
export default DingtalkDepartments;

View File

@@ -0,0 +1,84 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Table } from 'reactstrap';
import { siteRoot } from '../../../utils/constants';
import Loading from '../../../components/loading';
const DingtalkDepartmentMembersListPropTypes = {
isMembersListLoading: PropTypes.bool.isRequired,
membersList: PropTypes.array.isRequired,
newUsersTempObj: PropTypes.object.isRequired,
checkedDepartmentId: PropTypes.number.isRequired,
onUserChecked: PropTypes.func.isRequired,
onAllUsersChecked: PropTypes.func.isRequired,
isCheckedAll: PropTypes.bool.isRequired,
canCheckUserIds: PropTypes.array.isRequired,
};
class DingtalkDepartmentMembersList extends Component {
constructor(props) {
super(props);
}
render() {
const { newUsersTempObj, checkedDepartmentId, isMembersListLoading, canCheckUserIds } = this.props;
const membersList = this.props.membersList.map((member, index) => {
let avatar = member.avatar;
if (member.avatar.length > 0) {
avatar = member.avatar;
} else {
avatar = siteRoot + 'media/avatars/default.png';
}
return (
<tr key={checkedDepartmentId.toString() + member.userid}>
<td>
{!member.email &&
<input type="checkbox" className="vam" onChange={() => this.props.onUserChecked(member)}
checked={(member.userid in newUsersTempObj) ? 'checked' : ''}></input>}
</td>
<td><img className="avatar" src={avatar} alt=""></img></td>
<td>{member.name}</td>
<td>{member.mobile}</td>
<td>{member.contact_email}</td>
<td>{member.email && <i className="sf2-icon-tick"></i>}</td>
</tr>
);
});
return (
<div className="dir-content-main">
{isMembersListLoading && <Loading/>}
{!isMembersListLoading && this.props.membersList.length > 0 &&
<Table hover>
<thead>
<tr>
<th width="5%">
{canCheckUserIds.length > 0 &&
<input type="checkbox" className="vam" checked={this.props.isCheckedAll}
onChange={() => this.props.onAllUsersChecked()}></input>}
</th>
<th width="10%"></th>
<th width="20%">{'名称'}</th>
<th width="20%">{'手机号'}</th>
<th width="30%">{'邮箱'}</th>
<th width="15%">{'已添加'}</th>
</tr>
</thead>
<tbody>{membersList}</tbody>
</Table>
}
{!isMembersListLoading && this.props.membersList.length === 0 &&
<div className="message empty-tip text-center">
<img src={`${siteRoot}media/img/member-list-empty-2x.png`} alt=""/>
<h4>{'成员列表为空'}</h4>
</div>
}
</div>
);
}
}
DingtalkDepartmentMembersList.propTypes = DingtalkDepartmentMembersListPropTypes;
export default DingtalkDepartmentMembersList;

View File

@@ -0,0 +1,147 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Dropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
import { gettext, isPro } from '../../../utils/constants';
const DingtalkDepartmentsTreeNodePropTypes = {
index: PropTypes.number,
department: PropTypes.object.isRequired,
isChildrenShow: PropTypes.bool.isRequired,
onChangeDepartment: PropTypes.func.isRequired,
checkedDepartmentId: PropTypes.number.isRequired,
importDepartmentDialogToggle: PropTypes.func.isRequired,
};
class DingtalkDepartmentsTreeNode extends Component {
constructor(props) {
super(props);
this.state = {
isChildrenShow: false,
dropdownOpen: false,
active: false,
};
}
toggleChildren = (e) => {
e.preventDefault();
e.stopPropagation();
this.setState({
isChildrenShow: !this.state.isChildrenShow,
});
};
dropdownToggle = (e) => {
e.stopPropagation();
this.setState({ dropdownOpen: !this.state.dropdownOpen });
};
onMouseEnter = () => {
this.setState({ active: true });
};
onMouseLeave = () => {
if (this.state.dropdownOpen) return;
this.setState({ active: false });
};
importDepartmentDialogToggle = (depart) => {
this.setState({ active: false });
this.props.importDepartmentDialogToggle(depart);
}
componentDidMount() {
if (this.props.index === 0) {
this.setState({ isChildrenShow: true });
this.props.onChangeDepartment(this.props.department.id);
}
}
renderTreeNodes = (departmentsTree) => {
if (departmentsTree.length > 0) {
return departmentsTree.map((department) => {
return (
<DingtalkDepartmentsTreeNode
key={department.id}
department={department}
isChildrenShow={this.state.isChildrenShow}
onChangeDepartment={this.props.onChangeDepartment}
checkedDepartmentId={this.props.checkedDepartmentId}
importDepartmentDialogToggle={this.importDepartmentDialogToggle}
/>
);
});
}
};
changeDept = (departmentID) => {
const { department, checkedDepartmentId } = this.props;
this.props.onChangeDepartment(departmentID);
if (checkedDepartmentId === department.id && !this.state.isChildrenShow) {
this.setState({ isChildrenShow: true });
}
}
render() {
const { isChildrenShow, department, checkedDepartmentId } = this.props;
let toggleClass = classNames({
'folder-toggle-icon fa fa-caret-down': department.children && this.state.isChildrenShow,
'folder-toggle-icon fa fa-caret-right': department.children && !this.state.isChildrenShow,
});
let nodeInnerClass = classNames({
'tree-node-inner': true,
'tree-node-inner-hover': this.state.active,
'tree-node-hight-light': checkedDepartmentId === department.id
});
return (
<Fragment>
{isChildrenShow &&
<div
className={nodeInnerClass}
onClick={() => this.changeDept(department.id)}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
>
<span className="tree-node-icon" onClick={(e) => this.toggleChildren(e)}>
<i className={toggleClass}></i>
</span>
<span className="tree-node-text">{department.name}</span>
{isPro &&
<Dropdown
isOpen={this.state.dropdownOpen}
toggle={(e) => this.dropdownToggle(e)}
direction="down"
style={this.state.active ? {} : { opacity: 0 }}
>
<DropdownToggle
tag='i'
className='fa fa-ellipsis-v cursor-pointer attr-action-icon'
title={gettext('More Operations')}
data-toggle="dropdown"
aria-expanded={this.state.dropdownOpen}
>
</DropdownToggle>
<DropdownMenu className="drop-list" right={true}>
<DropdownItem
onClick={this.importDepartmentDialogToggle.bind(this, department)}
id={department.id}
>{'导入部门'}</DropdownItem>
</DropdownMenu>
</Dropdown>
}
</div>
}
{this.state.isChildrenShow &&
<div className="department-children">
{department.children && this.renderTreeNodes(department.children)}
</div>
}
</Fragment>
);
}
}
DingtalkDepartmentsTreeNode.propTypes = DingtalkDepartmentsTreeNodePropTypes;
export default DingtalkDepartmentsTreeNode;

View File

@@ -0,0 +1,51 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Loading from '../../../components/loading';
import DingtalkDepartmentsTreeNode from './dingtalk-departments-tree-node';
const DingtalkDepartmentsTreePanelPropTypes = {
isTreeLoading: PropTypes.bool.isRequired,
departmentsTree: PropTypes.array.isRequired,
onChangeDepartment: PropTypes.func.isRequired,
checkedDepartmentId: PropTypes.number.isRequired,
importDepartmentDialogToggle: PropTypes.func.isRequired,
};
class DingtalkDepartmentsTreePanel extends Component {
constructor(props) {
super(props);
}
render() {
const { departmentsTree } = this.props;
return (
<div className="dir-content-nav">
<div className="tree-view tree">
{this.props.isTreeLoading ?
<Loading/> :
<div className="tree-node">
{departmentsTree.length > 0 && departmentsTree.map((department, index) => {
return (
<DingtalkDepartmentsTreeNode
key={department.id}
index={index}
department={department}
isChildrenShow={true}
onChangeDepartment={this.props.onChangeDepartment}
checkedDepartmentId={this.props.checkedDepartmentId}
importDepartmentDialogToggle={this.props.importDepartmentDialogToggle}
/>
);
})}
</div>
}
</div>
</div>
);
}
}
DingtalkDepartmentsTreePanel.propTypes = DingtalkDepartmentsTreePanelPropTypes;
export default DingtalkDepartmentsTreePanel;

View File

@@ -0,0 +1,5 @@
import DingtalkDepartmentMembersList from './dingtalk-department-members-list';
import DingtalkDepartmentsTreePanel from './dingtalk-departments-tree-panel';
import DingtalkDepartmentsTreeNode from './dingtalk-departments-tree-node';
export { DingtalkDepartmentMembersList, DingtalkDepartmentsTreePanel, DingtalkDepartmentsTreeNode };

View File

@@ -68,6 +68,7 @@ import Notifications from './notifications/notifications';
import FileScanRecords from './file-scan-records';
import VirusScanRecords from './virus-scan-records';
import WorkWeixinDepartments from './work-weixin-departments';
import DingtalkDepartments from './dingtalk-departments';
import Invitations from './invitations/invitations';
import StatisticFile from './statistic/statistic-file';
@@ -258,6 +259,11 @@ class SysAdmin extends React.Component {
currentTab={currentTab}
tabItemClick={this.tabItemClick}
/>
<DingtalkDepartments
path={siteRoot + 'sys/dingtalk'}
currentTab={currentTab}
tabItemClick={this.tabItemClick}
/>
<AbuseReports path={siteRoot + 'sys/abuse-reports'} />
</Router>
</MainPanel>

View File

@@ -5,7 +5,7 @@ import Logo from '../../components/logo';
import { gettext, siteRoot, isPro, otherPermission, canViewSystemInfo, canViewStatistic,
canConfigSystem, canManageLibrary, canManageUser, canManageGroup, canViewUserLog,
canViewAdminLog, constanceEnabled, multiTenancy, multiInstitution, sysadminExtraEnabled,
enableGuestInvitation, enableTermsAndConditions, enableFileScan, enableWorkWeixin,
enableGuestInvitation, enableTermsAndConditions, enableFileScan, enableWorkWeixin, enableDingtalk,
enableShareLinkReportAbuse } from '../../utils/constants';
const propTypes = {
@@ -259,6 +259,20 @@ class SidePanel extends React.Component {
</Link>
</li>
}
{otherPermission && enableDingtalk &&
<li className="nav-item">
<Link
className={`nav-link ellipsis ${this.getActiveClass('dingtalk')}`}
to={siteRoot + 'sys/dingtalk/'}
onClick={() => this.props.tabItemClick('dingtalk')}
>
<span className="sf3-font-enterprise-dingtalk sf3-font" aria-hidden="true"></span>
<span className="nav-text">{'钉钉集成'}</span>
</Link>
</li>
}
{otherPermission && enableShareLinkReportAbuse &&
<li className="nav-item">
<Link

3074
frontend/src/seafile-api.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@ import ListInAddressBook from './components/user-settings/list-in-address-book';
import EmailNotice from './components/user-settings/email-notice';
import TwoFactorAuthentication from './components/user-settings/two-factor-auth';
import SocialLogin from './components/user-settings/social-login';
import SocialLoginDingtalk from './components/user-settings/social-login-dingtalk';
import DeleteAccount from './components/user-settings/delete-account';
import './css/toolbar.css';
@@ -22,12 +23,13 @@ import './css/search.css';
import './css/user-settings.css';
const {
const {
canUpdatePassword, passwordOperationText,
enableAddressBook,
enableWebdavSecret,
twoFactorAuthEnabled,
enableWechatWork,
enableDingtalk,
enableDeleteAccount
} = window.app.pageOptions;
@@ -44,6 +46,7 @@ class Settings extends React.Component {
{show: isPro, href: '#email-notice', text: gettext('Email Notification')},
{show: twoFactorAuthEnabled, href: '#two-factor-auth', text: gettext('Two-Factor Authentication')},
{show: enableWechatWork, href: '#social-auth', text: gettext('Social Login')},
{show: enableDingtalk, href: '#social-auth', text: gettext('Social Login')},
{show: enableDeleteAccount, href: '#del-account', text: gettext('Delete Account')},
];
@@ -62,7 +65,7 @@ class Settings extends React.Component {
toaster.danger(errMessage);
});
}
updateUserInfo = (data) => {
seafileAPI.updateUserInfo(data).then((res) => {
this.setState({
@@ -127,12 +130,13 @@ class Settings extends React.Component {
</div>
}
{enableWebdavSecret && <WebdavPassword />}
{enableAddressBook && this.state.userInfo &&
{enableAddressBook && this.state.userInfo &&
<ListInAddressBook userInfo={this.state.userInfo} updateUserInfo={this.updateUserInfo} />}
<LanguageSetting />
{isPro && <EmailNotice />}
{twoFactorAuthEnabled && <TwoFactorAuthentication />}
{enableWechatWork && <SocialLogin />}
{enableDingtalk && <SocialLoginDingtalk />}
{enableDeleteAccount && <DeleteAccount />}
</div>
</div>

View File

@@ -128,6 +128,7 @@ export const canViewUserLog = window.sysadmin ? window.sysadmin.pageOptions.admi
export const canViewAdminLog = window.sysadmin ? window.sysadmin.pageOptions.admin_permissions.can_view_admin_log : '';
export const otherPermission = window.sysadmin ? window.sysadmin.pageOptions.admin_permissions.other_permission : '';
export const enableWorkWeixin = window.sysadmin ? window.sysadmin.pageOptions.enable_work_weixin : '';
export const enableDingtalk = window.sysadmin ? window.sysadmin.pageOptions.enable_dingtalk : '';
export const enableSysAdminViewRepo = window.sysadmin ? window.sysadmin.pageOptions.enableSysAdminViewRepo : '';
export const haveLDAP = window.sysadmin ? window.sysadmin.pageOptions.haveLDAP : '';
export const enableShareLinkReportAbuse = window.sysadmin ? window.sysadmin.pageOptions.enable_share_link_report_abuse : '';

View File

@@ -1,5 +1,5 @@
import cookie from 'react-cookies';
import { SeafileAPI } from 'seafile-js';
import { SeafileAPI } from '../seafile-api';
import { siteRoot } from './constants';
let seafileAPI = new SeafileAPI();