mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-09 02:42:47 +00:00
add-seaTable-integration (#5761)
* add-seaTable-integration * update-request * update-path * update-json * update-UI * update-json * update-text * update-text * remove-space * update-UI * add-hover * add variable control --------- Co-authored-by: 杨顺强 <978987373@qq.com>
This commit is contained in:
@@ -0,0 +1,192 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Modal, ModalHeader, ModalBody } from 'reactstrap';
|
||||||
|
import SeatableAccountSettingList from '../seatable-integration-account-setting-widgets/seatable-account-setting-list.js';
|
||||||
|
import AddSeatableAccountSetting from '../../components/seatable-integration-account-setting-widgets/add-seatable-account-setting.js';
|
||||||
|
import toaster from '../toast';
|
||||||
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
|
import { Utils } from '../../utils/utils';
|
||||||
|
import { gettext, internalFilePath, dirPath } from '../../utils/constants';
|
||||||
|
|
||||||
|
import '../../css/repo-seatable-integration-dialog.css';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
repo: PropTypes.object.isRequired,
|
||||||
|
onSeaTableIntegrationToggle: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
const STATUS = {
|
||||||
|
SEATABLE_ACCOUNT_MANAGE: 'seatable_account_manage',
|
||||||
|
ADD_SETABLE_ACCOUNT: 'add_seatable_account',
|
||||||
|
UPDATE_SEATABLE_ACCOUNT: 'update_seatable_account'
|
||||||
|
};
|
||||||
|
|
||||||
|
class RepoSeaTableIntegrationDialog extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
activeTab: 'SeaTable',
|
||||||
|
seatableSettings: [],
|
||||||
|
baseApiToken: '',
|
||||||
|
isPasswordVisible: false,
|
||||||
|
isShowDialog: false,
|
||||||
|
currentDtableInfo: {},
|
||||||
|
status: STATUS.SEATABLE_ACCOUNT_MANAGE,
|
||||||
|
};
|
||||||
|
this.repo = this.props.repo;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.getSeatableSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
getSeatableSettings = async (status) => {
|
||||||
|
seafileAPI.req.defaults.headers.Authorization = null;
|
||||||
|
const [downloadLinkRes] = await seafileAPI.getFileDownloadLink(this.repo.repo_id, internalFilePath).then(res => [res, null]).catch((err) => [null, err]);
|
||||||
|
if (downloadLinkRes && downloadLinkRes.data) {
|
||||||
|
const fileInfoRes = await seafileAPI.getFileContent(downloadLinkRes.data);
|
||||||
|
if (fileInfoRes?.data && fileInfoRes.data) {
|
||||||
|
this.setState({
|
||||||
|
seatableSettings: fileInfoRes.data
|
||||||
|
});
|
||||||
|
status && this.setState({ status });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
changeTab = (tab) => {
|
||||||
|
if (this.state.activeTab !== tab) {
|
||||||
|
this.setState({activeTab: tab});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
changeStatus = (status) => {
|
||||||
|
this.setState({status});
|
||||||
|
};
|
||||||
|
|
||||||
|
getFile = (detail, fileList) => {
|
||||||
|
const { base_name, seatable_url, seatable_api_token } = detail;
|
||||||
|
let content = [{
|
||||||
|
'base_name': base_name,
|
||||||
|
'seatable_server_url': seatable_url,
|
||||||
|
'base_api_token': seatable_api_token
|
||||||
|
}];
|
||||||
|
|
||||||
|
if (fileList && fileList.length !== 0) {
|
||||||
|
const index = fileList?.findIndex((item) => item.base_api_token === seatable_api_token);
|
||||||
|
if (index !== -1) {
|
||||||
|
fileList[index] = content[0];
|
||||||
|
} else {
|
||||||
|
fileList.push(content[0]);
|
||||||
|
}
|
||||||
|
content = fileList;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = internalFilePath.split('/')[2];
|
||||||
|
const fileContent = JSON.stringify(content);
|
||||||
|
const newFile = new File([fileContent], fileName);
|
||||||
|
return newFile;
|
||||||
|
};
|
||||||
|
|
||||||
|
editSeatableSettingAccount = (baseApiToken) => {
|
||||||
|
const { seatableSettings } = this.state;
|
||||||
|
this.setState({
|
||||||
|
status: STATUS.UPDATE_SEATABLE_ACCOUNT,
|
||||||
|
currentDtableInfo: seatableSettings.find((item) => item.base_api_token === baseApiToken)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
deleteStableAccountSetting = async (setting, status) => {
|
||||||
|
const { base_api_token } = setting;
|
||||||
|
seafileAPI.req.defaults.headers.Authorization = null;
|
||||||
|
const [downloadLinkRes] = await seafileAPI.getFileDownloadLink(this.repo.repo_id, internalFilePath).then(res => [res, null]).catch((err) => [null, err]);
|
||||||
|
if (downloadLinkRes && downloadLinkRes.data) {
|
||||||
|
const fileInfoRes = await seafileAPI.getFileContent(downloadLinkRes.data);
|
||||||
|
if (fileInfoRes?.data) {
|
||||||
|
const fileList = fileInfoRes.data;
|
||||||
|
const index = fileList?.findIndex((item) => item.base_api_token === base_api_token);
|
||||||
|
if (index !== -1) {
|
||||||
|
fileList.splice(index, 1);
|
||||||
|
const fileContent = JSON.stringify(fileList);
|
||||||
|
const fileName = internalFilePath.split('/')[2];
|
||||||
|
const newFile = new File([fileContent], fileName);
|
||||||
|
const updateLink = await seafileAPI.getUpdateLink(this.repo.repo_id, internalFilePath.slice(0, 10));
|
||||||
|
await seafileAPI.updateFile(updateLink.data, internalFilePath, fileName, newFile).catch(err => {toaster.danger(gettext(err.message));});
|
||||||
|
this.getSeatableSettings(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmit = async (detail, status) => {
|
||||||
|
seafileAPI.req.defaults.headers.Authorization = null;
|
||||||
|
const [downloadLinkRes, err] = await seafileAPI.getFileDownloadLink(this.repo.repo_id, internalFilePath).then(res => [res, null]).catch((err) => [null, err]);
|
||||||
|
// Contains configuration files
|
||||||
|
if (downloadLinkRes && downloadLinkRes.data) {
|
||||||
|
const fileInfoRes = await seafileAPI.getFileContent(downloadLinkRes.data);
|
||||||
|
if (fileInfoRes?.data) {
|
||||||
|
const newFile = this.getFile(detail, fileInfoRes.data);
|
||||||
|
const updateLink = await seafileAPI.getUpdateLink(this.repo.repo_id, internalFilePath.slice(0, 10));
|
||||||
|
const fileName = internalFilePath.split('/')[2];
|
||||||
|
await seafileAPI.updateFile(updateLink.data, internalFilePath, fileName, newFile).catch(err => {toaster.danger(gettext(err.message));});
|
||||||
|
this.getSeatableSettings(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No configuration file
|
||||||
|
if (err) {
|
||||||
|
const uploadLink = await seafileAPI.getFileServerUploadLink(this.repo.repo_id, dirPath);
|
||||||
|
const newFile = this.getFile(detail);
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', newFile);
|
||||||
|
formData.append('relative_path', internalFilePath.split('/')[1]);
|
||||||
|
formData.append('parent_dir', dirPath);
|
||||||
|
await seafileAPI.uploadImage(uploadLink.data + '?ret-json=1', formData).catch(err => {toaster.danger(gettext(err.message));});
|
||||||
|
this.getSeatableSettings(status);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { seatableSettings, status, currentDtableInfo } = this.state;
|
||||||
|
const { onSeaTableIntegrationToggle } = this.props;
|
||||||
|
let repo = this.repo;
|
||||||
|
const itemName = '<span class="op-target">' + Utils.HTMLescape(repo.repo_name) + '</span>';
|
||||||
|
const title = gettext('{placeholder} SeaTable integration').replace('{placeholder}', itemName);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} toggle={onSeaTableIntegrationToggle} className="account-dialog">
|
||||||
|
<ModalHeader toggle={onSeaTableIntegrationToggle}>
|
||||||
|
<p dangerouslySetInnerHTML={{__html: title}} className="m-0"></p>
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalBody className="account-dialog-content">
|
||||||
|
<div className="account-dialog-main">
|
||||||
|
{status === STATUS.SEATABLE_ACCOUNT_MANAGE && (
|
||||||
|
<SeatableAccountSettingList
|
||||||
|
seatableSettings={seatableSettings}
|
||||||
|
changeStatus={() => this.changeStatus(STATUS.ADD_SETABLE_ACCOUNT)}
|
||||||
|
editSeatableSettingAccount={this.editSeatableSettingAccount}
|
||||||
|
deleteStableAccountSetting={this.deleteStableAccountSetting}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{status === STATUS.ADD_SETABLE_ACCOUNT && (
|
||||||
|
<AddSeatableAccountSetting
|
||||||
|
changeStatus={() => this.changeStatus(STATUS.SEATABLE_ACCOUNT_MANAGE)}
|
||||||
|
onSubmit={this.onSubmit}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{status === STATUS.UPDATE_SEATABLE_ACCOUNT && (
|
||||||
|
<AddSeatableAccountSetting
|
||||||
|
currentDtableInfo={currentDtableInfo}
|
||||||
|
changeStatus={() => this.changeStatus(STATUS.SEATABLE_ACCOUNT_MANAGE)}
|
||||||
|
onSubmit={this.onSubmit}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RepoSeaTableIntegrationDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default RepoSeaTableIntegrationDialog;
|
@@ -0,0 +1,170 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Alert, Input, FormGroup, Label, InputGroup, InputGroupText } from 'reactstrap';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
|
import { gettext } from '../../utils/constants';
|
||||||
|
|
||||||
|
class AddSeatableAccountSetting extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
t: PropTypes.func,
|
||||||
|
changeStatus: PropTypes.func,
|
||||||
|
onSubmit: PropTypes.func,
|
||||||
|
currentDtableInfo: PropTypes.object,
|
||||||
|
addSeatableAccountSetting: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
const { currentDtableInfo } = props;
|
||||||
|
this.state = {
|
||||||
|
errMessage: '',
|
||||||
|
base_name: currentDtableInfo?.base_name || '',
|
||||||
|
seatable_url: currentDtableInfo?.seatable_url || 'https://dev.seatable.cn/',
|
||||||
|
seatable_api_token: currentDtableInfo?.base_api_token || '',
|
||||||
|
successMessage: null,
|
||||||
|
stage: 'toCheck', // toCheck: need to check -> toSubmit: need to submit
|
||||||
|
passwordType: 'password'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeBaseName = (event) => {
|
||||||
|
let value = event.target.value;
|
||||||
|
if (value === this.state.base_name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
base_name: value,
|
||||||
|
errMessage: '',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onChangeSeatableUrl = (event) => {
|
||||||
|
let value = event.target.value;
|
||||||
|
if (value === this.state.seatable_url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
seatable_url: value,
|
||||||
|
successMessage: null,
|
||||||
|
stage: 'toCheck',
|
||||||
|
errMessage: '',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onChangeSeatableApiToken = (event) => {
|
||||||
|
let value = event.target.value;
|
||||||
|
if (value === this.state.seatable_api_token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
seatable_api_token: value,
|
||||||
|
successMessage: null,
|
||||||
|
stage: 'toCheck',
|
||||||
|
errMessage: '',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
addSeatableAccountSetting = () => {
|
||||||
|
const { t } = this.props;
|
||||||
|
let { base_name, seatable_url, seatable_api_token } = this.state;
|
||||||
|
base_name = base_name.trim();
|
||||||
|
seatable_url = seatable_url.trim();
|
||||||
|
seatable_api_token = seatable_api_token.trim();
|
||||||
|
let errMessage = '';
|
||||||
|
if (!base_name) {
|
||||||
|
errMessage = gettext('Base name is required');
|
||||||
|
}
|
||||||
|
else if (!seatable_url) {
|
||||||
|
errMessage = gettext('URL is required');
|
||||||
|
}
|
||||||
|
else if (!seatable_api_token) {
|
||||||
|
errMessage = gettext('SeaTable API token is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({errMessage});
|
||||||
|
if (errMessage) return;
|
||||||
|
let detail = {
|
||||||
|
base_name,
|
||||||
|
seatable_url,
|
||||||
|
seatable_api_token
|
||||||
|
};
|
||||||
|
this.props.onSubmit(detail, 'seatable_account_manage');
|
||||||
|
};
|
||||||
|
|
||||||
|
testSeatableAPIToken = async () => {
|
||||||
|
const { seatable_url, seatable_api_token } = this.state;
|
||||||
|
seafileAPI.req.defaults.headers.Authorization = `Token ${seatable_api_token}`;
|
||||||
|
const [res, err] = await seafileAPI.req.get(`${seatable_url}api/v2.1/dtable/app-access-token/`).then(res => [res, null]).catch((err) => [null, err]);
|
||||||
|
if (res) {
|
||||||
|
this.setState({
|
||||||
|
successMessage: res.data,
|
||||||
|
stage: 'toSubmit',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
this.setState({
|
||||||
|
errMessage: gettext('URL or SeaTable API token is invalid'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
togglePasswordShow = () => {
|
||||||
|
if (this.state.passwordType === 'password') {
|
||||||
|
this.setState({passwordType: 'text'});
|
||||||
|
} else {
|
||||||
|
this.setState({passwordType: 'password'});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { errMessage, stage, successMessage, base_name, seatable_url, seatable_api_token, passwordType } = this.state;
|
||||||
|
return (
|
||||||
|
<div className="add-account">
|
||||||
|
<div className="add-account-header d-flex align-items-center justify-content-between">
|
||||||
|
<span>
|
||||||
|
<span className="back-btn d-inline-flex align-items-center justify-content-center" onClick={this.props.changeStatus}>
|
||||||
|
<i className="link-icon icon-left sf3-font sf3-font-arrow" style={{transform: 'rotate(180deg)', color: '#999'}}></i>
|
||||||
|
</span>
|
||||||
|
<span className="add-account-header-text">{gettext('Add SeaTable Integration')}</span>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={stage === 'toCheck'? this.testSeatableAPIToken : this.addSeatableAccountSetting}
|
||||||
|
type="button"
|
||||||
|
className="btn btn-secondary add-account-btn"
|
||||||
|
>{stage === 'toCheck' ? gettext('Check') : gettext('Submit')}</button>
|
||||||
|
</div>
|
||||||
|
<div className="base-account">
|
||||||
|
<div className="account-name-desc">
|
||||||
|
<FormGroup>
|
||||||
|
<Label>{gettext('Base name')}</Label>
|
||||||
|
<Input value={base_name} onChange={this.onChangeBaseName}/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<Label>{gettext('SeaTable server URL')}</Label>
|
||||||
|
<Input value={seatable_url} onChange={this.onChangeSeatableUrl}/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup className="base-account-password">
|
||||||
|
<Label>{gettext('SeaTable API token')}</Label>
|
||||||
|
<InputGroup>
|
||||||
|
<Input value={seatable_api_token} type={passwordType} onChange={this.onChangeSeatableApiToken}/>
|
||||||
|
<InputGroupText>
|
||||||
|
<i className={`fas ${passwordType === 'password' ? 'fa-eye-slash' : 'fa-eye'} cursor-pointer`} onClick={this.togglePasswordShow} />
|
||||||
|
</InputGroupText>
|
||||||
|
</InputGroup>
|
||||||
|
</FormGroup>
|
||||||
|
</div>
|
||||||
|
{errMessage && <Alert color="danger">{errMessage}</Alert>}
|
||||||
|
{successMessage && (
|
||||||
|
<Alert color="success">
|
||||||
|
<span className="dtable-font dtable-icon-check-circle mr-2"></span>
|
||||||
|
{gettext('Successfully connected to SeaTable')}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddSeatableAccountSetting;
|
@@ -0,0 +1,32 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
|
||||||
|
import { gettext } from '../../utils/constants';
|
||||||
|
|
||||||
|
class DeleteSeatablesDialog extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
t: PropTypes.func,
|
||||||
|
accountName: PropTypes.string,
|
||||||
|
onDeleteSeatables: PropTypes.func,
|
||||||
|
closeDialog: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { accountName, closeDialog } = this.props;
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} toggle={closeDialog}>
|
||||||
|
<ModalHeader toggle={closeDialog}>{gettext('Delete SeaTable base')}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<div className="pb-6">{gettext('Are you sure to delete SeaTable')}{' '}{accountName}?</div>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="secondary" onClick={closeDialog}>{gettext('Cancel')}</Button>
|
||||||
|
<Button color="primary" onClick={this.props.onDeleteSeatables}>{gettext('Delete')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeleteSeatablesDialog;
|
@@ -0,0 +1,78 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import DeleteSeatablesDialog from './delete-seatables-dialog';
|
||||||
|
import { gettext } from '../../utils/constants';
|
||||||
|
|
||||||
|
class SeatableAccountItem extends Component {
|
||||||
|
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isShowDialog: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
t: PropTypes.func,
|
||||||
|
editSeatableSettingAccount: PropTypes.func,
|
||||||
|
deleteStableAccountSetting: PropTypes.func,
|
||||||
|
setting: PropTypes.object,
|
||||||
|
index: PropTypes.number,
|
||||||
|
};
|
||||||
|
|
||||||
|
openDialog = () => {
|
||||||
|
this.setState({isShowDialog: true});
|
||||||
|
};
|
||||||
|
|
||||||
|
closeDialog = () => {
|
||||||
|
this.setState({isShowDialog: false});
|
||||||
|
};
|
||||||
|
|
||||||
|
onDeleteSeatables = () => {
|
||||||
|
const { setting } = this.props;
|
||||||
|
this.props.deleteStableAccountSetting(setting, 'seatable_account_manage');
|
||||||
|
this.closeDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { isShowDialog } = this.state;
|
||||||
|
const { setting, t, index } = this.props;
|
||||||
|
const { base_api_token, base_name, seatable_server_url } = setting;
|
||||||
|
return (
|
||||||
|
<tr key={`account-${base_api_token}`}>
|
||||||
|
<td width='30%' className="text-truncate" title={base_name} aria-label={base_name}>{base_name}</td>
|
||||||
|
<td id={`abc-${index}`} width='55%' className="text-truncate" title={seatable_server_url} aria-label={seatable_server_url}>
|
||||||
|
{seatable_server_url}
|
||||||
|
</td>
|
||||||
|
<td width='15%'>
|
||||||
|
<span
|
||||||
|
className="account-operation-btn"
|
||||||
|
onClick={this.props.editSeatableSettingAccount.bind(this, base_api_token)}
|
||||||
|
title={gettext('Edit')}
|
||||||
|
aria-label={gettext('Edit')}
|
||||||
|
>
|
||||||
|
<i className="sf2-icon-edit" style={{color: '#999'}}></i>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="account-operation-btn"
|
||||||
|
onClick={this.openDialog}
|
||||||
|
title={gettext('Delete')}
|
||||||
|
aria-label={gettext('Delete')}
|
||||||
|
>
|
||||||
|
<i className="sf2-icon-delete" style={{color: '#999'}}></i>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
{isShowDialog &&
|
||||||
|
<DeleteSeatablesDialog
|
||||||
|
t={t}
|
||||||
|
accountName={base_name}
|
||||||
|
onDeleteSeatables={this.onDeleteSeatables}
|
||||||
|
closeDialog={this.closeDialog}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SeatableAccountItem;
|
@@ -0,0 +1,74 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Button } from 'reactstrap';
|
||||||
|
import { gettext, mediaUrl } from '../../utils/constants';
|
||||||
|
import SeatableAccountItem from './seatable-account-setting-item';
|
||||||
|
|
||||||
|
class SeatableAccountSettingList extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
accounts: PropTypes.array,
|
||||||
|
changeStatus: PropTypes.func,
|
||||||
|
editSeatableSettingAccount: PropTypes.func,
|
||||||
|
seatableSettings: PropTypes.array,
|
||||||
|
deleteStableAccountSetting: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
renderContent = () => {
|
||||||
|
const { seatableSettings } = this.props;
|
||||||
|
if (!Array.isArray(seatableSettings) || seatableSettings.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="no-accounts d-flex flex-column align-items-center justify-content-center">
|
||||||
|
<img src={`${mediaUrl}img/no-items-tip.png`} alt={gettext('No SeaTable libraries')} />
|
||||||
|
<p>{gettext('No Seafile libraries')}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<table className="accounts-list-header">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width='30%'>{gettext('Base name')}</th>
|
||||||
|
<th width='55%'>{gettext('SeaTable server URL')}</th>
|
||||||
|
<th width='15%'> </th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
<div className="accounts-list-body">
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
{seatableSettings.map((setting, index) => {
|
||||||
|
return (
|
||||||
|
<SeatableAccountItem
|
||||||
|
key={setting.base_api_token}
|
||||||
|
index={index}
|
||||||
|
setting={setting}
|
||||||
|
editSeatableSettingAccount={this.props.editSeatableSettingAccount}
|
||||||
|
deleteStableAccountSetting={this.props.deleteStableAccountSetting}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="accounts-manage">
|
||||||
|
<div className="accounts-manage-header d-flex align-items-center justify-content-between">
|
||||||
|
<span>{gettext('SeaTable')}</span>
|
||||||
|
<Button color="primary" size="sm" outline={true} onClick={this.props.changeStatus}>{gettext('Add')}</Button>
|
||||||
|
</div>
|
||||||
|
<div className="accounts-list mt-2">
|
||||||
|
{this.renderContent()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SeatableAccountSettingList;
|
274
frontend/src/css/repo-seatable-integration-dialog.css
Normal file
274
frontend/src/css/repo-seatable-integration-dialog.css
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
/* common */
|
||||||
|
.account-dialog .nav .nav-item {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog .nav .nav-item .nav-link {
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
color: #8a948f;
|
||||||
|
font-weight: normal;
|
||||||
|
transition: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog .nav .nav-item .nav-link.active {
|
||||||
|
color: #ff8000;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 0.125rem solid #ff8000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog .nav-pills .nav-item .nav-link {
|
||||||
|
padding: .3125rem 1rem .3125rem 8px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog .nav-pills .nav-item .nav-link:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog .nav-pills .nav-item .nav-link.active {
|
||||||
|
background-color: #ff8000;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog table {
|
||||||
|
width: 100%;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog .accounts-list {
|
||||||
|
max-height: 400px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog .accounts-list table thead tr {
|
||||||
|
border-bottom: 1px solid #efefef;
|
||||||
|
height: 2.1875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog .accounts-list table tbody tr {
|
||||||
|
border-bottom: 1px solid #efefef;
|
||||||
|
height: 2.5625rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog table th {
|
||||||
|
padding: 0.3125rem 0.1875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog table td {
|
||||||
|
padding: 3px 0.1875rem;
|
||||||
|
color: #333;
|
||||||
|
font-size: 14px;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog table td a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog .ellipsis {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog .select-module {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog .select-module.select-module-icon {
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog .select-module.select-module-name {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog .op-target {
|
||||||
|
color: #ea7500;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* custom */
|
||||||
|
.account-dialog {
|
||||||
|
max-width: 600px;
|
||||||
|
height: calc(100% - 56px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog .account-dialog-content {
|
||||||
|
padding: 0;
|
||||||
|
min-height: 27rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog-content .account-dialog-side {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 25%;
|
||||||
|
padding: 12px 8px;
|
||||||
|
border-right: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog-content .account-dialog-main {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.5rem 1.5rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog-content .account-dialog-main .tab-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog-content .account-dialog-main .tab-pane {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-dialog .wechat-input-content {
|
||||||
|
height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accounts-manage .accounts-manage-header {
|
||||||
|
border-bottom: 1px solid #efefef;
|
||||||
|
padding: .375rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accounts-manage .accounts-manage-header button {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accounts-manage .no-accounts {
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accounts-manage .no-accounts img {
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accounts-manage .no-accounts p {
|
||||||
|
margin-top: 1rem;
|
||||||
|
color: #afafaf;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accounts-manage .accounts-list-body {
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accounts-manage th,
|
||||||
|
.accounts-manage td {
|
||||||
|
padding: .5rem .1875rem;
|
||||||
|
border-color: #efefef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accounts-manage th {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accounts-manage .accounts-list-body tr:hover {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accounts-manage .accounts-list-body .account-operation-btn {
|
||||||
|
display: none;
|
||||||
|
margin-right: .2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accounts-manage .accounts-list-body tr:hover .account-operation-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accounts-manage .account-operation-btn .dtable-font {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accounts-manage .account-operation-btn:hover .dtable-font {
|
||||||
|
color: #5a5a5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-account,
|
||||||
|
.edit-account {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-account .add-account-header,
|
||||||
|
.edit-account .edit-account-header {
|
||||||
|
padding: .375rem 0;
|
||||||
|
border-bottom: 1px solid #efefef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-account .back-btn,
|
||||||
|
.edit-account .back-btn {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-account .back-btn .dtable-icon-return,
|
||||||
|
.edit-account .back-btn .dtable-icon-return {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-account .back-btn:hover .dtable-icon-return,
|
||||||
|
.edit-account .back-btn:hover .dtable-icon-return {
|
||||||
|
color: #5a5a5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-account .add-account-header-text,
|
||||||
|
.edit-account .edit-account-header-text {
|
||||||
|
margin-left: .25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-account .add-account-btn,
|
||||||
|
.edit-account .edit-account-btn {
|
||||||
|
margin: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
border-color: #ff8000;
|
||||||
|
color: #ff8000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-account .account-loading-tip {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-account .add-account-btn:hover,
|
||||||
|
.edit-account .edit-account-btn:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-account {
|
||||||
|
height: calc(100% - 38px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-account input[type="checkbox"] {
|
||||||
|
margin-top: -2px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-account .account-name-desc {
|
||||||
|
margin-top: .375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-account .base-account-password .input-group .form-control {
|
||||||
|
height: 38px;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-account .base-account-password .input-group .input-group-text {
|
||||||
|
height: 38px;
|
||||||
|
border-radius: 0 3px 3px 0;
|
||||||
|
}
|
||||||
|
|
@@ -19,6 +19,7 @@ import LibSubFolderPermissionDialog from '../../components/dialog/lib-sub-folder
|
|||||||
import Rename from '../../components/rename';
|
import Rename from '../../components/rename';
|
||||||
import MylibRepoMenu from './mylib-repo-menu';
|
import MylibRepoMenu from './mylib-repo-menu';
|
||||||
import RepoAPITokenDialog from '../../components/dialog/repo-api-token-dialog';
|
import RepoAPITokenDialog from '../../components/dialog/repo-api-token-dialog';
|
||||||
|
import RepoSeaTableIntegrationDialog from '../../components/dialog/repo-seatable-integration-dialog';
|
||||||
import RepoShareAdminDialog from '../../components/dialog/repo-share-admin-dialog';
|
import RepoShareAdminDialog from '../../components/dialog/repo-share-admin-dialog';
|
||||||
import LibOldFilesAutoDelDialog from '../../components/dialog/lib-old-files-auto-del-dialog';
|
import LibOldFilesAutoDelDialog from '../../components/dialog/lib-old-files-auto-del-dialog';
|
||||||
import RepoMonitoredIcon from '../../components/repo-monitored-icon';
|
import RepoMonitoredIcon from '../../components/repo-monitored-icon';
|
||||||
@@ -52,6 +53,7 @@ class MylibRepoListItem extends React.Component {
|
|||||||
isLabelRepoStateDialogOpen: false,
|
isLabelRepoStateDialogOpen: false,
|
||||||
isFolderPermissionDialogShow: false,
|
isFolderPermissionDialogShow: false,
|
||||||
isAPITokenDialogShow: false,
|
isAPITokenDialogShow: false,
|
||||||
|
isSeaTableIntegrationShow: false,
|
||||||
isRepoShareAdminDialogOpen: false,
|
isRepoShareAdminDialogOpen: false,
|
||||||
isRepoDeleted: false,
|
isRepoDeleted: false,
|
||||||
isOldFilesAutoDelDialogOpen: false,
|
isOldFilesAutoDelDialogOpen: false,
|
||||||
@@ -132,6 +134,9 @@ class MylibRepoListItem extends React.Component {
|
|||||||
case 'Old Files Auto Delete':
|
case 'Old Files Auto Delete':
|
||||||
this.toggleOldFilesAutoDelDialog();
|
this.toggleOldFilesAutoDelDialog();
|
||||||
break;
|
break;
|
||||||
|
case 'SeaTable integration':
|
||||||
|
this.onSeaTableIntegrationToggle();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -240,6 +245,10 @@ class MylibRepoListItem extends React.Component {
|
|||||||
this.setState({isAPITokenDialogShow: !this.state.isAPITokenDialogShow});
|
this.setState({isAPITokenDialogShow: !this.state.isAPITokenDialogShow});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onSeaTableIntegrationToggle = () => {
|
||||||
|
this.setState({isSeaTableIntegrationShow: !this.state.isSeaTableIntegrationShow});
|
||||||
|
};
|
||||||
|
|
||||||
toggleRepoShareAdminDialog = () => {
|
toggleRepoShareAdminDialog = () => {
|
||||||
this.setState({isRepoShareAdminDialogOpen: !this.state.isRepoShareAdminDialogOpen});
|
this.setState({isRepoShareAdminDialogOpen: !this.state.isRepoShareAdminDialogOpen});
|
||||||
};
|
};
|
||||||
@@ -509,6 +518,15 @@ class MylibRepoListItem extends React.Component {
|
|||||||
</ModalPortal>
|
</ModalPortal>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{this.state.isSeaTableIntegrationShow && (
|
||||||
|
<ModalPortal>
|
||||||
|
<RepoSeaTableIntegrationDialog
|
||||||
|
repo={repo}
|
||||||
|
onSeaTableIntegrationToggle={this.onSeaTableIntegrationToggle}
|
||||||
|
/>
|
||||||
|
</ModalPortal>
|
||||||
|
)}
|
||||||
|
|
||||||
{this.state.isRepoShareAdminDialogOpen && (
|
{this.state.isRepoShareAdminDialogOpen && (
|
||||||
<ModalPortal>
|
<ModalPortal>
|
||||||
<RepoShareAdminDialog
|
<RepoShareAdminDialog
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap';
|
import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap';
|
||||||
import { gettext, isPro, folderPermEnabled, enableRepoSnapshotLabel, enableResetEncryptedRepoPassword, isEmailConfigured, enableRepoAutoDel } from '../../utils/constants';
|
import { gettext, isPro, folderPermEnabled, enableRepoSnapshotLabel, enableResetEncryptedRepoPassword, isEmailConfigured, enableRepoAutoDel, enableSeaTableIntegration } from '../../utils/constants';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
@@ -123,6 +123,10 @@ class MylibRepoMenu extends React.Component {
|
|||||||
if (enableRepoAutoDel) {
|
if (enableRepoAutoDel) {
|
||||||
operations.push('Old Files Auto Delete');
|
operations.push('Old Files Auto Delete');
|
||||||
}
|
}
|
||||||
|
console.log(enableSeaTableIntegration);
|
||||||
|
if (enableSeaTableIntegration) {
|
||||||
|
operations.push('SeaTable integration');
|
||||||
|
}
|
||||||
return operations;
|
return operations;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -180,6 +184,9 @@ class MylibRepoMenu extends React.Component {
|
|||||||
case 'Advanced':
|
case 'Advanced':
|
||||||
translateResult = gettext('Advanced');
|
translateResult = gettext('Advanced');
|
||||||
break;
|
break;
|
||||||
|
case 'SeaTable integration':
|
||||||
|
translateResult = gettext('SeaTable integration');
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,8 @@ export const defaultContentForSDoc = {
|
|||||||
export const dirPath = '/';
|
export const dirPath = '/';
|
||||||
export const gettext = window.gettext;
|
export const gettext = window.gettext;
|
||||||
|
|
||||||
|
export const internalFilePath = '/_Internal/seatable-integration.json';
|
||||||
|
|
||||||
export const siteRoot = window.app.config.siteRoot;
|
export const siteRoot = window.app.config.siteRoot;
|
||||||
export const loginUrl = window.app.config.loginUrl;
|
export const loginUrl = window.app.config.loginUrl;
|
||||||
export const avatarInfo = window.app.config.avatarInfo;
|
export const avatarInfo = window.app.config.avatarInfo;
|
||||||
@@ -105,6 +107,7 @@ export const showLogoutIcon = window.app.pageOptions.showLogoutIcon;
|
|||||||
export const additionalShareDialogNote = window.app.pageOptions.additionalShareDialogNote;
|
export const additionalShareDialogNote = window.app.pageOptions.additionalShareDialogNote;
|
||||||
export const additionalAppBottomLinks = window.app.pageOptions.additionalAppBottomLinks;
|
export const additionalAppBottomLinks = window.app.pageOptions.additionalAppBottomLinks;
|
||||||
export const additionalAboutDialogLinks = window.app.pageOptions.additionalAboutDialogLinks;
|
export const additionalAboutDialogLinks = window.app.pageOptions.additionalAboutDialogLinks;
|
||||||
|
export const enableSeaTableIntegration = window.app.pageOptions.enableSeaTableIntegration;
|
||||||
|
|
||||||
// wiki
|
// wiki
|
||||||
export const slug = window.wiki ? window.wiki.config.slug : '';
|
export const slug = window.wiki ? window.wiki.config.slug : '';
|
||||||
|
@@ -24,7 +24,8 @@ from seahub.settings import SEAFILE_VERSION, SITE_DESCRIPTION, \
|
|||||||
MEDIA_ROOT, SHOW_LOGOUT_ICON, CUSTOM_LOGO_PATH, CUSTOM_FAVICON_PATH, \
|
MEDIA_ROOT, SHOW_LOGOUT_ICON, CUSTOM_LOGO_PATH, CUSTOM_FAVICON_PATH, \
|
||||||
ENABLE_SEAFILE_DOCS, LOGIN_BG_IMAGE_PATH, \
|
ENABLE_SEAFILE_DOCS, LOGIN_BG_IMAGE_PATH, \
|
||||||
CUSTOM_LOGIN_BG_PATH, ENABLE_SHARE_LINK_REPORT_ABUSE, \
|
CUSTOM_LOGIN_BG_PATH, ENABLE_SHARE_LINK_REPORT_ABUSE, \
|
||||||
PRIVACY_POLICY_LINK, TERMS_OF_SERVICE_LINK, ENABLE_SEADOC, ENABLE_SEAFILE_AI
|
PRIVACY_POLICY_LINK, TERMS_OF_SERVICE_LINK, ENABLE_SEADOC, ENABLE_SEAFILE_AI, \
|
||||||
|
ENABLE_SEATABLE_INTEGRATION
|
||||||
|
|
||||||
from seahub.organizations.models import OrgAdminSettings
|
from seahub.organizations.models import OrgAdminSettings
|
||||||
from seahub.organizations.settings import ORG_ENABLE_ADMIN_CUSTOM_LOGO
|
from seahub.organizations.settings import ORG_ENABLE_ADMIN_CUSTOM_LOGO
|
||||||
@@ -170,6 +171,7 @@ def base(request):
|
|||||||
'enable_repo_auto_del': ENABLE_REPO_AUTO_DEL,
|
'enable_repo_auto_del': ENABLE_REPO_AUTO_DEL,
|
||||||
'enable_seadoc': ENABLE_SEADOC,
|
'enable_seadoc': ENABLE_SEADOC,
|
||||||
'enable_seafile_ai': ENABLE_SEAFILE_AI,
|
'enable_seafile_ai': ENABLE_SEAFILE_AI,
|
||||||
|
'enable_seatable_integration': ENABLE_SEATABLE_INTEGRATION
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.user.is_staff:
|
if request.user.is_staff:
|
||||||
|
@@ -479,6 +479,9 @@ ENABLE_FILE_COMMENT = True
|
|||||||
# Enable seafile docs
|
# Enable seafile docs
|
||||||
ENABLE_SEAFILE_DOCS = False
|
ENABLE_SEAFILE_DOCS = False
|
||||||
|
|
||||||
|
# enable integration seatbale
|
||||||
|
ENABLE_SEATABLE_INTEGRATION = False
|
||||||
|
|
||||||
# File preview
|
# File preview
|
||||||
FILE_PREVIEW_MAX_SIZE = 30 * 1024 * 1024
|
FILE_PREVIEW_MAX_SIZE = 30 * 1024 * 1024
|
||||||
FILE_ENCODING_LIST = ['auto', 'utf-8', 'gbk', 'ISO-8859-1', 'ISO-8859-5']
|
FILE_ENCODING_LIST = ['auto', 'utf-8', 'gbk', 'ISO-8859-1', 'ISO-8859-5']
|
||||||
|
@@ -147,6 +147,7 @@
|
|||||||
enableSeadoc: {% if enable_seadoc %} true {% else %} false {% endif %},
|
enableSeadoc: {% if enable_seadoc %} true {% else %} false {% endif %},
|
||||||
enableSeafileAI: {% if enable_seafile_ai %} true {% else %} false {% endif %},
|
enableSeafileAI: {% if enable_seafile_ai %} true {% else %} false {% endif %},
|
||||||
canSetExProps: {% if can_set_ex_props %} true {% else %} false {% endif %},
|
canSetExProps: {% if can_set_ex_props %} true {% else %} false {% endif %},
|
||||||
|
enableSeaTableIntegration: {% if enable_seatable_integration %} true {% else %} false {% endif %},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
Reference in New Issue
Block a user