|
|
import React, { useEffect, useState } from 'react'; |
|
|
import { Button, Divider, Form, Grid, Header, Modal, Message } from 'semantic-ui-react'; |
|
|
import { API, removeTrailingSlash, showError } from '../helpers'; |
|
|
|
|
|
const SystemSetting = () => { |
|
|
let [inputs, setInputs] = useState({ |
|
|
PasswordLoginEnabled: '', |
|
|
PasswordRegisterEnabled: '', |
|
|
EmailVerificationEnabled: '', |
|
|
GitHubOAuthEnabled: '', |
|
|
GitHubClientId: '', |
|
|
GitHubClientSecret: '', |
|
|
Notice: '', |
|
|
SMTPServer: '', |
|
|
SMTPPort: '', |
|
|
SMTPAccount: '', |
|
|
SMTPFrom: '', |
|
|
SMTPToken: '', |
|
|
ServerAddress: '', |
|
|
Footer: '', |
|
|
WeChatAuthEnabled: '', |
|
|
WeChatServerAddress: '', |
|
|
WeChatServerToken: '', |
|
|
WeChatAccountQRCodeImageURL: '', |
|
|
TurnstileCheckEnabled: '', |
|
|
TurnstileSiteKey: '', |
|
|
TurnstileSecretKey: '', |
|
|
RegisterEnabled: '', |
|
|
EmailDomainRestrictionEnabled: '', |
|
|
EmailDomainWhitelist: '' |
|
|
}); |
|
|
const [originInputs, setOriginInputs] = useState({}); |
|
|
let [loading, setLoading] = useState(false); |
|
|
const [EmailDomainWhitelist, setEmailDomainWhitelist] = useState([]); |
|
|
const [restrictedDomainInput, setRestrictedDomainInput] = useState(''); |
|
|
const [showPasswordWarningModal, setShowPasswordWarningModal] = useState(false); |
|
|
|
|
|
const getOptions = async () => { |
|
|
const res = await API.get('/api/option/'); |
|
|
const { success, message, data } = res.data; |
|
|
if (success) { |
|
|
let newInputs = {}; |
|
|
data.forEach((item) => { |
|
|
newInputs[item.key] = item.value; |
|
|
}); |
|
|
setInputs({ |
|
|
...newInputs, |
|
|
EmailDomainWhitelist: newInputs.EmailDomainWhitelist.split(',') |
|
|
}); |
|
|
setOriginInputs(newInputs); |
|
|
|
|
|
setEmailDomainWhitelist(newInputs.EmailDomainWhitelist.split(',').map((item) => { |
|
|
return { key: item, text: item, value: item }; |
|
|
})); |
|
|
} else { |
|
|
showError(message); |
|
|
} |
|
|
}; |
|
|
|
|
|
useEffect(() => { |
|
|
getOptions().then(); |
|
|
}, []); |
|
|
|
|
|
const updateOption = async (key, value) => { |
|
|
setLoading(true); |
|
|
switch (key) { |
|
|
case 'PasswordLoginEnabled': |
|
|
case 'PasswordRegisterEnabled': |
|
|
case 'EmailVerificationEnabled': |
|
|
case 'GitHubOAuthEnabled': |
|
|
case 'WeChatAuthEnabled': |
|
|
case 'TurnstileCheckEnabled': |
|
|
case 'EmailDomainRestrictionEnabled': |
|
|
case 'RegisterEnabled': |
|
|
value = inputs[key] === 'true' ? 'false' : 'true'; |
|
|
break; |
|
|
default: |
|
|
break; |
|
|
} |
|
|
const res = await API.put('/api/option/', { |
|
|
key, |
|
|
value |
|
|
}); |
|
|
const { success, message } = res.data; |
|
|
if (success) { |
|
|
if (key === 'EmailDomainWhitelist') { |
|
|
value = value.split(','); |
|
|
} |
|
|
setInputs((inputs) => ({ |
|
|
...inputs, [key]: value |
|
|
})); |
|
|
} else { |
|
|
showError(message); |
|
|
} |
|
|
setLoading(false); |
|
|
}; |
|
|
|
|
|
const handleInputChange = async (e, { name, value }) => { |
|
|
if (name === 'PasswordLoginEnabled' && inputs[name] === 'true') { |
|
|
|
|
|
setShowPasswordWarningModal(true); |
|
|
return; |
|
|
} |
|
|
if ( |
|
|
name === 'Notice' || |
|
|
name.startsWith('SMTP') || |
|
|
name === 'ServerAddress' || |
|
|
name === 'GitHubClientId' || |
|
|
name === 'GitHubClientSecret' || |
|
|
name === 'WeChatServerAddress' || |
|
|
name === 'WeChatServerToken' || |
|
|
name === 'WeChatAccountQRCodeImageURL' || |
|
|
name === 'TurnstileSiteKey' || |
|
|
name === 'TurnstileSecretKey' || |
|
|
name === 'EmailDomainWhitelist' |
|
|
) { |
|
|
setInputs((inputs) => ({ ...inputs, [name]: value })); |
|
|
} else { |
|
|
await updateOption(name, value); |
|
|
} |
|
|
}; |
|
|
|
|
|
const submitServerAddress = async () => { |
|
|
let ServerAddress = removeTrailingSlash(inputs.ServerAddress); |
|
|
await updateOption('ServerAddress', ServerAddress); |
|
|
}; |
|
|
|
|
|
const submitSMTP = async () => { |
|
|
if (originInputs['SMTPServer'] !== inputs.SMTPServer) { |
|
|
await updateOption('SMTPServer', inputs.SMTPServer); |
|
|
} |
|
|
if (originInputs['SMTPAccount'] !== inputs.SMTPAccount) { |
|
|
await updateOption('SMTPAccount', inputs.SMTPAccount); |
|
|
} |
|
|
if (originInputs['SMTPFrom'] !== inputs.SMTPFrom) { |
|
|
await updateOption('SMTPFrom', inputs.SMTPFrom); |
|
|
} |
|
|
if ( |
|
|
originInputs['SMTPPort'] !== inputs.SMTPPort && |
|
|
inputs.SMTPPort !== '' |
|
|
) { |
|
|
await updateOption('SMTPPort', inputs.SMTPPort); |
|
|
} |
|
|
if ( |
|
|
originInputs['SMTPToken'] !== inputs.SMTPToken && |
|
|
inputs.SMTPToken !== '' |
|
|
) { |
|
|
await updateOption('SMTPToken', inputs.SMTPToken); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const submitEmailDomainWhitelist = async () => { |
|
|
if ( |
|
|
originInputs['EmailDomainWhitelist'] !== inputs.EmailDomainWhitelist.join(',') && |
|
|
inputs.SMTPToken !== '' |
|
|
) { |
|
|
await updateOption('EmailDomainWhitelist', inputs.EmailDomainWhitelist.join(',')); |
|
|
} |
|
|
}; |
|
|
|
|
|
const submitWeChat = async () => { |
|
|
if (originInputs['WeChatServerAddress'] !== inputs.WeChatServerAddress) { |
|
|
await updateOption( |
|
|
'WeChatServerAddress', |
|
|
removeTrailingSlash(inputs.WeChatServerAddress) |
|
|
); |
|
|
} |
|
|
if ( |
|
|
originInputs['WeChatAccountQRCodeImageURL'] !== |
|
|
inputs.WeChatAccountQRCodeImageURL |
|
|
) { |
|
|
await updateOption( |
|
|
'WeChatAccountQRCodeImageURL', |
|
|
inputs.WeChatAccountQRCodeImageURL |
|
|
); |
|
|
} |
|
|
if ( |
|
|
originInputs['WeChatServerToken'] !== inputs.WeChatServerToken && |
|
|
inputs.WeChatServerToken !== '' |
|
|
) { |
|
|
await updateOption('WeChatServerToken', inputs.WeChatServerToken); |
|
|
} |
|
|
}; |
|
|
|
|
|
const submitGitHubOAuth = async () => { |
|
|
if (originInputs['GitHubClientId'] !== inputs.GitHubClientId) { |
|
|
await updateOption('GitHubClientId', inputs.GitHubClientId); |
|
|
} |
|
|
if ( |
|
|
originInputs['GitHubClientSecret'] !== inputs.GitHubClientSecret && |
|
|
inputs.GitHubClientSecret !== '' |
|
|
) { |
|
|
await updateOption('GitHubClientSecret', inputs.GitHubClientSecret); |
|
|
} |
|
|
}; |
|
|
|
|
|
const submitTurnstile = async () => { |
|
|
if (originInputs['TurnstileSiteKey'] !== inputs.TurnstileSiteKey) { |
|
|
await updateOption('TurnstileSiteKey', inputs.TurnstileSiteKey); |
|
|
} |
|
|
if ( |
|
|
originInputs['TurnstileSecretKey'] !== inputs.TurnstileSecretKey && |
|
|
inputs.TurnstileSecretKey !== '' |
|
|
) { |
|
|
await updateOption('TurnstileSecretKey', inputs.TurnstileSecretKey); |
|
|
} |
|
|
}; |
|
|
|
|
|
const submitNewRestrictedDomain = () => { |
|
|
const localDomainList = inputs.EmailDomainWhitelist; |
|
|
if (restrictedDomainInput !== '' && !localDomainList.includes(restrictedDomainInput)) { |
|
|
setRestrictedDomainInput(''); |
|
|
setInputs({ |
|
|
...inputs, |
|
|
EmailDomainWhitelist: [...localDomainList, restrictedDomainInput], |
|
|
}); |
|
|
setEmailDomainWhitelist([...EmailDomainWhitelist, { |
|
|
key: restrictedDomainInput, |
|
|
text: restrictedDomainInput, |
|
|
value: restrictedDomainInput, |
|
|
}]); |
|
|
} |
|
|
} |
|
|
|
|
|
return ( |
|
|
<Grid columns={1}> |
|
|
<Grid.Column> |
|
|
<Form loading={loading}> |
|
|
<Header as='h3'>通用设置</Header> |
|
|
<Form.Group widths='equal'> |
|
|
<Form.Input |
|
|
label='服务器地址' |
|
|
placeholder='例如:https://yourdomain.com' |
|
|
value={inputs.ServerAddress} |
|
|
name='ServerAddress' |
|
|
onChange={handleInputChange} |
|
|
/> |
|
|
</Form.Group> |
|
|
<Form.Button onClick={submitServerAddress}> |
|
|
更新服务器地址 |
|
|
</Form.Button> |
|
|
<Divider /> |
|
|
<Header as='h3'>配置登录注册</Header> |
|
|
<Form.Group inline> |
|
|
<Form.Checkbox |
|
|
checked={inputs.PasswordLoginEnabled === 'true'} |
|
|
label='允许通过密码进行登录' |
|
|
name='PasswordLoginEnabled' |
|
|
onChange={handleInputChange} |
|
|
/> |
|
|
{ |
|
|
showPasswordWarningModal && |
|
|
<Modal |
|
|
open={showPasswordWarningModal} |
|
|
onClose={() => setShowPasswordWarningModal(false)} |
|
|
size={'tiny'} |
|
|
style={{ maxWidth: '450px' }} |
|
|
> |
|
|
<Modal.Header>警告</Modal.Header> |
|
|
<Modal.Content> |
|
|
<p>取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?</p> |
|
|
</Modal.Content> |
|
|
<Modal.Actions> |
|
|
<Button onClick={() => setShowPasswordWarningModal(false)}>取消</Button> |
|
|
<Button |
|
|
color='yellow' |
|
|
onClick={async () => { |
|
|
setShowPasswordWarningModal(false); |
|
|
await updateOption('PasswordLoginEnabled', 'false'); |
|
|
}} |
|
|
> |
|
|
确定 |
|
|
</Button> |
|
|
</Modal.Actions> |
|
|
</Modal> |
|
|
} |
|
|
<Form.Checkbox |
|
|
checked={inputs.PasswordRegisterEnabled === 'true'} |
|
|
label='允许通过密码进行注册' |
|
|
name='PasswordRegisterEnabled' |
|
|
onChange={handleInputChange} |
|
|
/> |
|
|
<Form.Checkbox |
|
|
checked={inputs.EmailVerificationEnabled === 'true'} |
|
|
label='通过密码注册时需要进行邮箱验证' |
|
|
name='EmailVerificationEnabled' |
|
|
onChange={handleInputChange} |
|
|
/> |
|
|
<Form.Checkbox |
|
|
checked={inputs.GitHubOAuthEnabled === 'true'} |
|
|
label='允许通过 GitHub 账户登录 & 注册' |
|
|
name='GitHubOAuthEnabled' |
|
|
onChange={handleInputChange} |
|
|
/> |
|
|
<Form.Checkbox |
|
|
checked={inputs.WeChatAuthEnabled === 'true'} |
|
|
label='允许通过微信登录 & 注册' |
|
|
name='WeChatAuthEnabled' |
|
|
onChange={handleInputChange} |
|
|
/> |
|
|
</Form.Group> |
|
|
<Form.Group inline> |
|
|
<Form.Checkbox |
|
|
checked={inputs.RegisterEnabled === 'true'} |
|
|
label='允许新用户注册(此项为否时,新用户将无法以任何方式进行注册)' |
|
|
name='RegisterEnabled' |
|
|
onChange={handleInputChange} |
|
|
/> |
|
|
<Form.Checkbox |
|
|
checked={inputs.TurnstileCheckEnabled === 'true'} |
|
|
label='启用 Turnstile 用户校验' |
|
|
name='TurnstileCheckEnabled' |
|
|
onChange={handleInputChange} |
|
|
/> |
|
|
</Form.Group> |
|
|
<Divider /> |
|
|
<Header as='h3'> |
|
|
配置邮箱域名白名单 |
|
|
<Header.Subheader>用以防止恶意用户利用临时邮箱批量注册</Header.Subheader> |
|
|
</Header> |
|
|
<Form.Group widths={3}> |
|
|
<Form.Checkbox |
|
|
label='启用邮箱域名白名单' |
|
|
name='EmailDomainRestrictionEnabled' |
|
|
onChange={handleInputChange} |
|
|
checked={inputs.EmailDomainRestrictionEnabled === 'true'} |
|
|
/> |
|
|
</Form.Group> |
|
|
<Form.Group widths={2}> |
|
|
<Form.Dropdown |
|
|
label='允许的邮箱域名' |
|
|
placeholder='允许的邮箱域名' |
|
|
name='EmailDomainWhitelist' |
|
|
required |
|
|
fluid |
|
|
multiple |
|
|
selection |
|
|
onChange={handleInputChange} |
|
|
value={inputs.EmailDomainWhitelist} |
|
|
autoComplete='new-password' |
|
|
options={EmailDomainWhitelist} |
|
|
/> |
|
|
<Form.Input |
|
|
label='添加新的允许的邮箱域名' |
|
|
action={ |
|
|
<Button type='button' onClick={() => { |
|
|
submitNewRestrictedDomain(); |
|
|
}}>填入</Button> |
|
|
} |
|
|
onKeyDown={(e) => { |
|
|
if (e.key === 'Enter') { |
|
|
submitNewRestrictedDomain(); |
|
|
} |
|
|
}} |
|
|
autoComplete='new-password' |
|
|
placeholder='输入新的允许的邮箱域名' |
|
|
value={restrictedDomainInput} |
|
|
onChange={(e, { value }) => { |
|
|
setRestrictedDomainInput(value); |
|
|
}} |
|
|
/> |
|
|
</Form.Group> |
|
|
<Form.Button onClick={submitEmailDomainWhitelist}>保存邮箱域名白名单设置</Form.Button> |
|
|
<Divider /> |
|
|
<Header as='h3'> |
|
|
配置 SMTP |
|
|
<Header.Subheader>用以支持系统的邮件发送</Header.Subheader> |
|
|
</Header> |
|
|
<Form.Group widths={3}> |
|
|
<Form.Input |
|
|
label='SMTP 服务器地址' |
|
|
name='SMTPServer' |
|
|
onChange={handleInputChange} |
|
|
autoComplete='new-password' |
|
|
value={inputs.SMTPServer} |
|
|
placeholder='例如:smtp.qq.com' |
|
|
/> |
|
|
<Form.Input |
|
|
label='SMTP 端口' |
|
|
name='SMTPPort' |
|
|
onChange={handleInputChange} |
|
|
autoComplete='new-password' |
|
|
value={inputs.SMTPPort} |
|
|
placeholder='默认: 587' |
|
|
/> |
|
|
<Form.Input |
|
|
label='SMTP 账户' |
|
|
name='SMTPAccount' |
|
|
onChange={handleInputChange} |
|
|
autoComplete='new-password' |
|
|
value={inputs.SMTPAccount} |
|
|
placeholder='通常是邮箱地址' |
|
|
/> |
|
|
</Form.Group> |
|
|
<Form.Group widths={3}> |
|
|
<Form.Input |
|
|
label='SMTP 发送者邮箱' |
|
|
name='SMTPFrom' |
|
|
onChange={handleInputChange} |
|
|
autoComplete='new-password' |
|
|
value={inputs.SMTPFrom} |
|
|
placeholder='通常和邮箱地址保持一致' |
|
|
/> |
|
|
<Form.Input |
|
|
label='SMTP 访问凭证' |
|
|
name='SMTPToken' |
|
|
onChange={handleInputChange} |
|
|
type='password' |
|
|
autoComplete='new-password' |
|
|
checked={inputs.RegisterEnabled === 'true'} |
|
|
placeholder='敏感信息不会发送到前端显示' |
|
|
/> |
|
|
</Form.Group> |
|
|
<Form.Button onClick={submitSMTP}>保存 SMTP 设置</Form.Button> |
|
|
<Divider /> |
|
|
<Header as='h3'> |
|
|
配置 GitHub OAuth App |
|
|
<Header.Subheader> |
|
|
用以支持通过 GitHub 进行登录注册, |
|
|
<a href='https://github.com/settings/developers' target='_blank'> |
|
|
点击此处 |
|
|
</a> |
|
|
管理你的 GitHub OAuth App |
|
|
</Header.Subheader> |
|
|
</Header> |
|
|
<Message> |
|
|
Homepage URL 填 <code>{inputs.ServerAddress}</code> |
|
|
,Authorization callback URL 填{' '} |
|
|
<code>{`${inputs.ServerAddress}/oauth/github`}</code> |
|
|
</Message> |
|
|
<Form.Group widths={3}> |
|
|
<Form.Input |
|
|
label='GitHub Client ID' |
|
|
name='GitHubClientId' |
|
|
onChange={handleInputChange} |
|
|
autoComplete='new-password' |
|
|
value={inputs.GitHubClientId} |
|
|
placeholder='输入你注册的 GitHub OAuth APP 的 ID' |
|
|
/> |
|
|
<Form.Input |
|
|
label='GitHub Client Secret' |
|
|
name='GitHubClientSecret' |
|
|
onChange={handleInputChange} |
|
|
type='password' |
|
|
autoComplete='new-password' |
|
|
value={inputs.GitHubClientSecret} |
|
|
placeholder='敏感信息不会发送到前端显示' |
|
|
/> |
|
|
</Form.Group> |
|
|
<Form.Button onClick={submitGitHubOAuth}> |
|
|
保存 GitHub OAuth 设置 |
|
|
</Form.Button> |
|
|
<Divider /> |
|
|
<Header as='h3'> |
|
|
配置 WeChat Server |
|
|
<Header.Subheader> |
|
|
用以支持通过微信进行登录注册, |
|
|
<a |
|
|
href='https://github.com/songquanpeng/wechat-server' |
|
|
target='_blank' |
|
|
> |
|
|
点击此处 |
|
|
</a> |
|
|
了解 WeChat Server |
|
|
</Header.Subheader> |
|
|
</Header> |
|
|
<Form.Group widths={3}> |
|
|
<Form.Input |
|
|
label='WeChat Server 服务器地址' |
|
|
name='WeChatServerAddress' |
|
|
placeholder='例如:https://yourdomain.com' |
|
|
onChange={handleInputChange} |
|
|
autoComplete='new-password' |
|
|
value={inputs.WeChatServerAddress} |
|
|
/> |
|
|
<Form.Input |
|
|
label='WeChat Server 访问凭证' |
|
|
name='WeChatServerToken' |
|
|
type='password' |
|
|
onChange={handleInputChange} |
|
|
autoComplete='new-password' |
|
|
value={inputs.WeChatServerToken} |
|
|
placeholder='敏感信息不会发送到前端显示' |
|
|
/> |
|
|
<Form.Input |
|
|
label='微信公众号二维码图片链接' |
|
|
name='WeChatAccountQRCodeImageURL' |
|
|
onChange={handleInputChange} |
|
|
autoComplete='new-password' |
|
|
value={inputs.WeChatAccountQRCodeImageURL} |
|
|
placeholder='输入一个图片链接' |
|
|
/> |
|
|
</Form.Group> |
|
|
<Form.Button onClick={submitWeChat}> |
|
|
保存 WeChat Server 设置 |
|
|
</Form.Button> |
|
|
<Divider /> |
|
|
<Header as='h3'> |
|
|
配置 Turnstile |
|
|
<Header.Subheader> |
|
|
用以支持用户校验, |
|
|
<a href='https://dash.cloudflare.com/' target='_blank'> |
|
|
点击此处 |
|
|
</a> |
|
|
管理你的 Turnstile Sites,推荐选择 Invisible Widget Type |
|
|
</Header.Subheader> |
|
|
</Header> |
|
|
<Form.Group widths={3}> |
|
|
<Form.Input |
|
|
label='Turnstile Site Key' |
|
|
name='TurnstileSiteKey' |
|
|
onChange={handleInputChange} |
|
|
autoComplete='new-password' |
|
|
value={inputs.TurnstileSiteKey} |
|
|
placeholder='输入你注册的 Turnstile Site Key' |
|
|
/> |
|
|
<Form.Input |
|
|
label='Turnstile Secret Key' |
|
|
name='TurnstileSecretKey' |
|
|
onChange={handleInputChange} |
|
|
type='password' |
|
|
autoComplete='new-password' |
|
|
value={inputs.TurnstileSecretKey} |
|
|
placeholder='敏感信息不会发送到前端显示' |
|
|
/> |
|
|
</Form.Group> |
|
|
<Form.Button onClick={submitTurnstile}> |
|
|
保存 Turnstile 设置 |
|
|
</Form.Button> |
|
|
</Form> |
|
|
</Grid.Column> |
|
|
</Grid> |
|
|
); |
|
|
}; |
|
|
|
|
|
export default SystemSetting; |
|
|
|