Files
one-api/web/default/src/components/SystemSetting.js

645 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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: '',
LarkClientId: '',
LarkClientSecret: '',
Notice: '',
SMTPServer: '',
SMTPPort: '',
SMTPAccount: '',
SMTPFrom: '',
SMTPToken: '',
ServerAddress: '',
Footer: '',
WeChatAuthEnabled: '',
WeChatServerAddress: '',
WeChatServerToken: '',
WeChatAccountQRCodeImageURL: '',
MessagePusherAddress: '',
MessagePusherToken: '',
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') {
// block disabling password login
setShowPasswordWarningModal(true);
return;
}
if (
name === 'Notice' ||
name.startsWith('SMTP') ||
name === 'ServerAddress' ||
name === 'GitHubClientId' ||
name === 'GitHubClientSecret' ||
name === 'LarkClientId' ||
name === 'LarkClientSecret' ||
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 submitMessagePusher = async () => {
if (originInputs['MessagePusherAddress'] !== inputs.MessagePusherAddress) {
await updateOption(
'MessagePusherAddress',
removeTrailingSlash(inputs.MessagePusherAddress)
);
}
if (
originInputs['MessagePusherToken'] !== inputs.MessagePusherToken &&
inputs.MessagePusherToken !== ''
) {
await updateOption('MessagePusherToken', inputs.MessagePusherToken);
}
};
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 submitLarkOAuth = async () => {
if (originInputs['LarkClientId'] !== inputs.LarkClientId) {
await updateOption('LarkClientId', inputs.LarkClientId);
}
if (
originInputs['LarkClientSecret'] !== inputs.LarkClientSecret &&
inputs.LarkClientSecret !== ''
) {
await updateOption('LarkClientSecret', inputs.LarkClientSecret);
}
};
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'>General Settings</Header>
<Form.Group widths='equal'>
<Form.Input
label='Server Address'
placeholder='For examplehttps://yourdomain.com'
value={inputs.ServerAddress}
name='ServerAddress'
onChange={handleInputChange}
/>
</Form.Group>
<Form.Button onClick={submitServerAddress}>
Update Server Address
</Form.Button>
<Divider />
<Header as='h3'>Configure Login/Registration</Header>
<Form.Group inline>
<Form.Checkbox
checked={inputs.PasswordLoginEnabled === 'true'}
label='Allow login via password'
name='PasswordLoginEnabled'
onChange={handleInputChange}
/>
{
showPasswordWarningModal &&
<Modal
open={showPasswordWarningModal}
onClose={() => setShowPasswordWarningModal(false)}
size={'tiny'}
style={{ maxWidth: '450px' }}
>
<Modal.Header>警告</Modal.Header>
<Modal.Content>
<p>Canceling password login will cause all users (including administrators) who have not bound other login methods to be unable to log in via password, confirm cancel?</p>
</Modal.Content>
<Modal.Actions>
<Button onClick={() => setShowPasswordWarningModal(false)}>Cancel</Button>
<Button
color='yellow'
onClick={async () => {
setShowPasswordWarningModal(false);
await updateOption('PasswordLoginEnabled', 'false');
}}
>
确定
</Button>
</Modal.Actions>
</Modal>
}
<Form.Checkbox
checked={inputs.PasswordRegisterEnabled === 'true'}
label='Allow registration via password'
name='PasswordRegisterEnabled'
onChange={handleInputChange}
/>
<Form.Checkbox
checked={inputs.EmailVerificationEnabled === 'true'}
label='Email verification is required when registering via password'
name='EmailVerificationEnabled'
onChange={handleInputChange}
/>
<Form.Checkbox
checked={inputs.GitHubOAuthEnabled === 'true'}
label='Allow login & registration via GitHub account'
name='GitHubOAuthEnabled'
onChange={handleInputChange}
/>
<Form.Checkbox
checked={inputs.WeChatAuthEnabled === 'true'}
label='Allow login & registration via WeChat'
name='WeChatAuthEnabled'
onChange={handleInputChange}
/>
</Form.Group>
<Form.Group inline>
<Form.Checkbox
checked={inputs.RegisterEnabled === 'true'}
label='Allow new user registration (if this option is off, new users will not be able to register in any way'
name='RegisterEnabled'
onChange={handleInputChange}
/>
<Form.Checkbox
checked={inputs.TurnstileCheckEnabled === 'true'}
label='Enable Turnstile user verification'
name='TurnstileCheckEnabled'
onChange={handleInputChange}
/>
</Form.Group>
<Divider />
<Header as='h3'>
配置邮箱域名白名单
<Header.Subheader>用以防止恶意Users利用临时邮箱批量Sign up</Header.Subheader>
</Header>
<Form.Group widths={3}>
<Form.Checkbox
label='Enable邮箱域名白名单'
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='Enter新的允许的邮箱域名'
value={restrictedDomainInput}
onChange={(e, { value }) => {
setRestrictedDomainInput(value);
}}
/>
</Form.Group>
<Form.Button onClick={submitEmailDomainWhitelist}>保存邮箱域名白名单Settings</Form.Button>
<Divider />
<Header as='h3'>
Configure SMTP
<Header.Subheader>To support the system email sending</Header.Subheader>
</Header>
<Form.Group widths={3}>
<Form.Input
label='SMTP Server Address'
name='SMTPServer'
onChange={handleInputChange}
autoComplete='new-password'
value={inputs.SMTPServer}
placeholder='For example: smtp.qq.com'
/>
<Form.Input
label='SMTP Port'
name='SMTPPort'
onChange={handleInputChange}
autoComplete='new-password'
value={inputs.SMTPPort}
placeholder='Default: 587'
/>
<Form.Input
label='SMTP Account'
name='SMTPAccount'
onChange={handleInputChange}
autoComplete='new-password'
value={inputs.SMTPAccount}
placeholder='Usually an email address'
/>
</Form.Group>
<Form.Group widths={3}>
<Form.Input
label='SMTP Sender email'
name='SMTPFrom'
onChange={handleInputChange}
autoComplete='new-password'
value={inputs.SMTPFrom}
placeholder='Usually consistent with the email address'
/>
<Form.Input
label='SMTP Access Credential'
name='SMTPToken'
onChange={handleInputChange}
type='password'
autoComplete='new-password'
checked={inputs.RegisterEnabled === 'true'}
placeholder='Sensitive information will not be displayed in the frontend'
/>
</Form.Group>
<Form.Button onClick={submitSMTP}>Save SMTP Settings</Form.Button>
<Divider />
<Header as='h3'>
Configure GitHub OAuth App
<Header.Subheader>
To support login & registration via GitHub
<a href='https://github.com/settings/developers' target='_blank'>
Click here
</a>
Manage your GitHub OAuth App
</Header.Subheader>
</Header>
<Message>
Fill in the Homepage URL <code>{inputs.ServerAddress}</code>
Fill in the 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='Enter your registered GitHub OAuth APP ID'
/>
<Form.Input
label='GitHub Client Secret'
name='GitHubClientSecret'
onChange={handleInputChange}
type='password'
autoComplete='new-password'
value={inputs.GitHubClientSecret}
placeholder='Sensitive information will not be displayed in the frontend'
/>
</Form.Group>
<Form.Button onClick={submitGitHubOAuth}>
Save GitHub OAuth Settings
</Form.Button>
<Divider />
<Header as='h3'>
配置飞书授权Log in
<Header.Subheader>
用以支持通过飞书进行Log inSign up
<a href='https://open.feishu.cn/app' target='_blank'>
Click here
</a>
Management你的飞书应用
</Header.Subheader>
</Header>
<Message>
主页链接填 <code>{inputs.ServerAddress}</code>
重定向 URL {' '}
<code>{`${inputs.ServerAddress}/oauth/lark`}</code>
</Message>
<Form.Group widths={3}>
<Form.Input
label='App ID'
name='LarkClientId'
onChange={handleInputChange}
autoComplete='new-password'
value={inputs.LarkClientId}
placeholder='Enter App ID'
/>
<Form.Input
label='App Secret'
name='LarkClientSecret'
onChange={handleInputChange}
type='password'
autoComplete='new-password'
value={inputs.LarkClientSecret}
placeholder='Sensitive information will not be displayed in the frontend'
/>
</Form.Group>
<Form.Button onClick={submitLarkOAuth}>
保存飞书 OAuth Settings
</Form.Button>
<Divider />
<Header as='h3'>
Configure WeChat Server
<Header.Subheader>
To support login & registration via WeChat
<a
href='https://github.com/songquanpeng/wechat-server'
target='_blank'
>
Click here
</a>
Learn about WeChat Server
</Header.Subheader>
</Header>
<Form.Group widths={3}>
<Form.Input
label='WeChat Server Server Address'
name='WeChatServerAddress'
placeholder='For examplehttps://yourdomain.com'
onChange={handleInputChange}
autoComplete='new-password'
value={inputs.WeChatServerAddress}
/>
<Form.Input
label='WeChat Server Access Credential'
name='WeChatServerToken'
type='password'
onChange={handleInputChange}
autoComplete='new-password'
value={inputs.WeChatServerToken}
placeholder='Sensitive information will not be displayed in the frontend'
/>
<Form.Input
label='WeChat Public Account QR Code Image Link'
name='WeChatAccountQRCodeImageURL'
onChange={handleInputChange}
autoComplete='new-password'
value={inputs.WeChatAccountQRCodeImageURL}
placeholder='Enter an image link'
/>
</Form.Group>
<Form.Button onClick={submitWeChat}>
Save WeChat Server Settings
</Form.Button>
<Divider />
<Header as='h3'>
配置 Message Pusher
<Header.Subheader>
用以推送报警信息
<a
href='https://github.com/songquanpeng/message-pusher'
target='_blank'
>
Click here
</a>
了解 Message Pusher
</Header.Subheader>
</Header>
<Form.Group widths={3}>
<Form.Input
label='Message Pusher 推送地址'
name='MessagePusherAddress'
placeholder='For examplehttps://msgpusher.com/push/your_username'
onChange={handleInputChange}
autoComplete='new-password'
value={inputs.MessagePusherAddress}
/>
<Form.Input
label='Message Pusher 访问凭证'
name='MessagePusherToken'
type='password'
onChange={handleInputChange}
autoComplete='new-password'
value={inputs.MessagePusherToken}
placeholder='Sensitive information will not be displayed in the frontend'
/>
</Form.Group>
<Form.Button onClick={submitMessagePusher}>
保存 Message Pusher Settings
</Form.Button>
<Divider />
<Header as='h3'>
Configure Turnstile
<Header.Subheader>
To support user verification
<a href='https://dash.cloudflare.com/' target='_blank'>
Click here
</a>
Manage your Turnstile Sites, recommend selecting 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='Enter your registered Turnstile Site Key'
/>
<Form.Input
label='Turnstile Secret Key'
name='TurnstileSecretKey'
onChange={handleInputChange}
type='password'
autoComplete='new-password'
value={inputs.TurnstileSecretKey}
placeholder='Sensitive information will not be displayed in the frontend'
/>
</Form.Group>
<Form.Button onClick={submitTurnstile}>
Save Turnstile Settings
</Form.Button>
</Form>
</Grid.Column>
</Grid>
);
};
export default SystemSetting;