chore: 初代air主题将使用default主题的运营设置界面、系统设置界面、其他设置界面

This commit is contained in:
Apple\Apple 2024-03-16 10:02:58 +08:00
parent 6b6cb4cee7
commit 1202e7699b
3 changed files with 409 additions and 634 deletions

View File

@ -11,11 +11,10 @@ const OperationSetting = () => {
QuotaRemindThreshold: 0, QuotaRemindThreshold: 0,
PreConsumedQuota: 0, PreConsumedQuota: 0,
ModelRatio: '', ModelRatio: '',
ModelPrice: '', CompletionRatio: '',
GroupRatio: '', GroupRatio: '',
TopUpLink: '', TopUpLink: '',
ChatLink: '', ChatLink: '',
ChatLink2: '', // 添加的新状态变量
QuotaPerUnit: 0, QuotaPerUnit: 0,
AutomaticDisableChannelEnabled: '', AutomaticDisableChannelEnabled: '',
AutomaticEnableChannelEnabled: '', AutomaticEnableChannelEnabled: '',
@ -23,32 +22,25 @@ const OperationSetting = () => {
LogConsumeEnabled: '', LogConsumeEnabled: '',
DisplayInCurrencyEnabled: '', DisplayInCurrencyEnabled: '',
DisplayTokenStatEnabled: '', DisplayTokenStatEnabled: '',
MjNotifyEnabled: '', ApproximateTokenEnabled: '',
DrawingEnabled: '',
DataExportEnabled: '',
DataExportDefaultTime: 'hour',
DataExportInterval: 5,
DefaultCollapseSidebar: '', // 默认折叠侧边栏
RetryTimes: 0 RetryTimes: 0
}); });
const [originInputs, setOriginInputs] = useState({}); const [originInputs, setOriginInputs] = useState({});
let [loading, setLoading] = useState(false); let [loading, setLoading] = useState(false);
let [historyTimestamp, setHistoryTimestamp] = useState(timestamp2string(now.getTime() / 1000 - 30 * 24 * 3600)); // a month ago let [historyTimestamp, setHistoryTimestamp] = useState(timestamp2string(now.getTime() / 1000 - 30 * 24 * 3600)); // a month ago
// 精确时间选项(小时,天,周)
const timeOptions = [
{ key: 'hour', text: '小时', value: 'hour' },
{ key: 'day', text: '天', value: 'day' },
{ key: 'week', text: '周', value: 'week' }
];
const getOptions = async () => { const getOptions = async () => {
const res = await API.get('/api/option/'); const res = await API.get('/api/option/');
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
let newInputs = {}; let newInputs = {};
data.forEach((item) => { data.forEach((item) => {
if (item.key === 'ModelRatio' || item.key === 'GroupRatio' || item.key === 'ModelPrice') { if (item.key === 'ModelRatio' || item.key === 'GroupRatio' || item.key === 'CompletionRatio') {
item.value = JSON.stringify(JSON.parse(item.value), null, 2); item.value = JSON.stringify(JSON.parse(item.value), null, 2);
} }
if (item.value === '{}') {
item.value = '';
}
newInputs[item.key] = item.value; newInputs[item.key] = item.value;
}); });
setInputs(newInputs); setInputs(newInputs);
@ -67,10 +59,6 @@ const OperationSetting = () => {
if (key.endsWith('Enabled')) { if (key.endsWith('Enabled')) {
value = inputs[key] === 'true' ? 'false' : 'true'; value = inputs[key] === 'true' ? 'false' : 'true';
} }
if (key === 'DefaultCollapseSidebar') {
value = inputs[key] === 'true' ? 'false' : 'true';
}
console.log(key, value);
const res = await API.put('/api/option/', { const res = await API.put('/api/option/', {
key, key,
value value
@ -85,12 +73,7 @@ const OperationSetting = () => {
}; };
const handleInputChange = async (e, { name, value }) => { const handleInputChange = async (e, { name, value }) => {
if (name.endsWith('Enabled') || name === 'DataExportInterval' || name === 'DataExportDefaultTime' || name === 'DefaultCollapseSidebar') { if (name.endsWith('Enabled')) {
if (name === 'DataExportDefaultTime') {
localStorage.setItem('data_export_default_time', value);
} else if (name === 'MjNotifyEnabled') {
localStorage.setItem('mj_notify_enabled', value);
}
await updateOption(name, value); await updateOption(name, value);
} else { } else {
setInputs((inputs) => ({ ...inputs, [name]: value })); setInputs((inputs) => ({ ...inputs, [name]: value }));
@ -122,12 +105,12 @@ const OperationSetting = () => {
} }
await updateOption('GroupRatio', inputs.GroupRatio); await updateOption('GroupRatio', inputs.GroupRatio);
} }
if (originInputs['ModelPrice'] !== inputs.ModelPrice) { if (originInputs['CompletionRatio'] !== inputs.CompletionRatio) {
if (!verifyJSON(inputs.ModelPrice)) { if (!verifyJSON(inputs.CompletionRatio)) {
showError('模型固定价格不是合法的 JSON 字符串'); showError('补全倍率不是合法的 JSON 字符串');
return; return;
} }
await updateOption('ModelPrice', inputs.ModelPrice); await updateOption('CompletionRatio', inputs.CompletionRatio);
} }
break; break;
case 'quota': case 'quota':
@ -151,9 +134,6 @@ const OperationSetting = () => {
if (originInputs['ChatLink'] !== inputs.ChatLink) { if (originInputs['ChatLink'] !== inputs.ChatLink) {
await updateOption('ChatLink', inputs.ChatLink); await updateOption('ChatLink', inputs.ChatLink);
} }
if (originInputs['ChatLink2'] !== inputs.ChatLink2) {
await updateOption('ChatLink2', inputs.ChatLink2);
}
if (originInputs['QuotaPerUnit'] !== inputs.QuotaPerUnit) { if (originInputs['QuotaPerUnit'] !== inputs.QuotaPerUnit) {
await updateOption('QuotaPerUnit', inputs.QuotaPerUnit); await updateOption('QuotaPerUnit', inputs.QuotaPerUnit);
} }
@ -174,80 +154,72 @@ const OperationSetting = () => {
} }
showError('日志清理失败:' + message); showError('日志清理失败:' + message);
}; };
return ( return (
<Grid columns={1}> <Grid columns={1}>
<Grid.Column> <Grid.Column>
<Form loading={loading}> <Form loading={loading}>
<Header as="h3"> <Header as='h3'>
通用设置 通用设置
</Header> </Header>
<Form.Group widths={4}> <Form.Group widths={4}>
<Form.Input <Form.Input
label="充值链接" label='充值链接'
name="TopUpLink" name='TopUpLink'
onChange={handleInputChange} onChange={handleInputChange}
autoComplete="new-password" autoComplete='new-password'
value={inputs.TopUpLink} value={inputs.TopUpLink}
type="link" type='link'
placeholder="例如发卡网站的购买链接" placeholder='例如发卡网站的购买链接'
/> />
<Form.Input <Form.Input
label="默认聊天页面链接" label='聊天页面链接'
name="ChatLink" name='ChatLink'
onChange={handleInputChange} onChange={handleInputChange}
autoComplete="new-password" autoComplete='new-password'
value={inputs.ChatLink} value={inputs.ChatLink}
type="link" type='link'
placeholder="例如 ChatGPT Next Web 的部署地址" placeholder='例如 ChatGPT Next Web 的部署地址'
/> />
<Form.Input <Form.Input
label="聊天页面2链接" label='单位美元额度'
name="ChatLink2" name='QuotaPerUnit'
onChange={handleInputChange} onChange={handleInputChange}
autoComplete="new-password" autoComplete='new-password'
value={inputs.ChatLink2}
type="link"
placeholder="例如 ChatGPT Web & Midjourney 的部署地址"
/>
<Form.Input
label="单位美元额度"
name="QuotaPerUnit"
onChange={handleInputChange}
autoComplete="new-password"
value={inputs.QuotaPerUnit} value={inputs.QuotaPerUnit}
type="number" type='number'
step="0.01" step='0.01'
placeholder="一单位货币能兑换的额度" placeholder='一单位货币能兑换的额度'
/> />
<Form.Input <Form.Input
label="失败重试次数" label='失败重试次数'
name="RetryTimes" name='RetryTimes'
type={'number'} type={'number'}
step="1" step='1'
min="0" min='0'
onChange={handleInputChange} onChange={handleInputChange}
autoComplete="new-password" autoComplete='new-password'
value={inputs.RetryTimes} value={inputs.RetryTimes}
placeholder="失败重试次数" placeholder='失败重试次数'
/> />
</Form.Group> </Form.Group>
<Form.Group inline> <Form.Group inline>
<Form.Checkbox <Form.Checkbox
checked={inputs.DisplayInCurrencyEnabled === 'true'} checked={inputs.DisplayInCurrencyEnabled === 'true'}
label="以货币形式显示额度" label='以货币形式显示额度'
name="DisplayInCurrencyEnabled" name='DisplayInCurrencyEnabled'
onChange={handleInputChange} onChange={handleInputChange}
/> />
<Form.Checkbox <Form.Checkbox
checked={inputs.DisplayTokenStatEnabled === 'true'} checked={inputs.DisplayTokenStatEnabled === 'true'}
label="Billing 相关 API 显示令牌额度而非用户额度" label='Billing 相关 API 显示令牌额度而非用户额度'
name="DisplayTokenStatEnabled" name='DisplayTokenStatEnabled'
onChange={handleInputChange} onChange={handleInputChange}
/> />
<Form.Checkbox <Form.Checkbox
checked={inputs.DefaultCollapseSidebar === 'true'} checked={inputs.ApproximateTokenEnabled === 'true'}
label="默认折叠侧边栏" label='使用近似的方式估算 token 数以减少计算量'
name="DefaultCollapseSidebar" name='ApproximateTokenEnabled'
onChange={handleInputChange} onChange={handleInputChange}
/> />
</Form.Group> </Form.Group>
@ -255,38 +227,20 @@ const OperationSetting = () => {
submitConfig('general').then(); submitConfig('general').then();
}}>保存通用设置</Form.Button> }}>保存通用设置</Form.Button>
<Divider /> <Divider />
<Header as="h3"> <Header as='h3'>
绘图设置
</Header>
<Form.Group inline>
<Form.Checkbox
checked={inputs.DrawingEnabled === 'true'}
label="启用绘图功能"
name="DrawingEnabled"
onChange={handleInputChange}
/>
<Form.Checkbox
checked={inputs.MjNotifyEnabled === 'true'}
label="允许回调会泄露服务器ip地址"
name="MjNotifyEnabled"
onChange={handleInputChange}
/>
</Form.Group>
<Divider />
<Header as="h3">
日志设置 日志设置
</Header> </Header>
<Form.Group inline> <Form.Group inline>
<Form.Checkbox <Form.Checkbox
checked={inputs.LogConsumeEnabled === 'true'} checked={inputs.LogConsumeEnabled === 'true'}
label="启用额度消费日志记录" label='启用额度消费日志记录'
name="LogConsumeEnabled" name='LogConsumeEnabled'
onChange={handleInputChange} onChange={handleInputChange}
/> />
</Form.Group> </Form.Group>
<Form.Group widths={4}> <Form.Group widths={4}>
<Form.Input label="目标时间" value={historyTimestamp} type="datetime-local" <Form.Input label='目标时间' value={historyTimestamp} type='datetime-local'
name="history_timestamp" name='history_timestamp'
onChange={(e, { name, value }) => { onChange={(e, { name, value }) => {
setHistoryTimestamp(value); setHistoryTimestamp(value);
}} /> }} />
@ -295,74 +249,42 @@ const OperationSetting = () => {
deleteHistoryLogs().then(); deleteHistoryLogs().then();
}}>清理历史日志</Form.Button> }}>清理历史日志</Form.Button>
<Divider /> <Divider />
<Header as="h3"> <Header as='h3'>
数据看板
</Header>
<Form.Checkbox
checked={inputs.DataExportEnabled === 'true'}
label="启用数据看板(实验性)"
name="DataExportEnabled"
onChange={handleInputChange}
/>
<Form.Group>
<Form.Input
label="数据看板更新间隔(分钟,设置过短会影响数据库性能)"
name="DataExportInterval"
type={'number'}
step="1"
min="1"
onChange={handleInputChange}
autoComplete="new-password"
value={inputs.DataExportInterval}
placeholder="数据看板更新间隔(分钟,设置过短会影响数据库性能)"
/>
<Form.Select
label="数据看板默认时间粒度(仅修改展示粒度,统计精确到小时)"
options={timeOptions}
name="DataExportDefaultTime"
onChange={handleInputChange}
autoComplete="new-password"
value={inputs.DataExportDefaultTime}
placeholder="数据看板默认时间粒度"
/>
</Form.Group>
<Divider />
<Header as="h3">
监控设置 监控设置
</Header> </Header>
<Form.Group widths={3}> <Form.Group widths={3}>
<Form.Input <Form.Input
label="最长响应时间" label='最长响应时间'
name="ChannelDisableThreshold" name='ChannelDisableThreshold'
onChange={handleInputChange} onChange={handleInputChange}
autoComplete="new-password" autoComplete='new-password'
value={inputs.ChannelDisableThreshold} value={inputs.ChannelDisableThreshold}
type="number" type='number'
min="0" min='0'
placeholder="单位秒,当运行通道全部测试时,超过此时间将自动禁用通道" placeholder='单位秒,当运行通道全部测试时,超过此时间将自动禁用通道'
/> />
<Form.Input <Form.Input
label="额度提醒阈值" label='额度提醒阈值'
name="QuotaRemindThreshold" name='QuotaRemindThreshold'
onChange={handleInputChange} onChange={handleInputChange}
autoComplete="new-password" autoComplete='new-password'
value={inputs.QuotaRemindThreshold} value={inputs.QuotaRemindThreshold}
type="number" type='number'
min="0" min='0'
placeholder="低于此额度时将发送邮件提醒用户" placeholder='低于此额度时将发送邮件提醒用户'
/> />
</Form.Group> </Form.Group>
<Form.Group inline> <Form.Group inline>
<Form.Checkbox <Form.Checkbox
checked={inputs.AutomaticDisableChannelEnabled === 'true'} checked={inputs.AutomaticDisableChannelEnabled === 'true'}
label="失败时自动禁用通道" label='失败时自动禁用通道'
name="AutomaticDisableChannelEnabled" name='AutomaticDisableChannelEnabled'
onChange={handleInputChange} onChange={handleInputChange}
/> />
<Form.Checkbox <Form.Checkbox
checked={inputs.AutomaticEnableChannelEnabled === 'true'} checked={inputs.AutomaticEnableChannelEnabled === 'true'}
label="成功时自动启用通道" label='成功时自动启用通道'
name="AutomaticEnableChannelEnabled" name='AutomaticEnableChannelEnabled'
onChange={handleInputChange} onChange={handleInputChange}
/> />
</Form.Group> </Form.Group>
@ -370,89 +292,89 @@ const OperationSetting = () => {
submitConfig('monitor').then(); submitConfig('monitor').then();
}}>保存监控设置</Form.Button> }}>保存监控设置</Form.Button>
<Divider /> <Divider />
<Header as="h3"> <Header as='h3'>
额度设置 额度设置
</Header> </Header>
<Form.Group widths={4}> <Form.Group widths={4}>
<Form.Input <Form.Input
label="新用户初始额度" label='新用户初始额度'
name="QuotaForNewUser" name='QuotaForNewUser'
onChange={handleInputChange} onChange={handleInputChange}
autoComplete="new-password" autoComplete='new-password'
value={inputs.QuotaForNewUser} value={inputs.QuotaForNewUser}
type="number" type='number'
min="0" min='0'
placeholder="例如100" placeholder='例如100'
/> />
<Form.Input <Form.Input
label="请求预扣费额度" label='请求预扣费额度'
name="PreConsumedQuota" name='PreConsumedQuota'
onChange={handleInputChange} onChange={handleInputChange}
autoComplete="new-password" autoComplete='new-password'
value={inputs.PreConsumedQuota} value={inputs.PreConsumedQuota}
type="number" type='number'
min="0" min='0'
placeholder="请求结束后多退少补" placeholder='请求结束后多退少补'
/> />
<Form.Input <Form.Input
label="邀请新用户奖励额度" label='邀请新用户奖励额度'
name="QuotaForInviter" name='QuotaForInviter'
onChange={handleInputChange} onChange={handleInputChange}
autoComplete="new-password" autoComplete='new-password'
value={inputs.QuotaForInviter} value={inputs.QuotaForInviter}
type="number" type='number'
min="0" min='0'
placeholder="例如2000" placeholder='例如2000'
/> />
<Form.Input <Form.Input
label="新用户使用邀请码奖励额度" label='新用户使用邀请码奖励额度'
name="QuotaForInvitee" name='QuotaForInvitee'
onChange={handleInputChange} onChange={handleInputChange}
autoComplete="new-password" autoComplete='new-password'
value={inputs.QuotaForInvitee} value={inputs.QuotaForInvitee}
type="number" type='number'
min="0" min='0'
placeholder="例如1000" placeholder='例如1000'
/> />
</Form.Group> </Form.Group>
<Form.Button onClick={() => { <Form.Button onClick={() => {
submitConfig('quota').then(); submitConfig('quota').then();
}}>保存额度设置</Form.Button> }}>保存额度设置</Form.Button>
<Divider /> <Divider />
<Header as="h3"> <Header as='h3'>
倍率设置 倍率设置
</Header> </Header>
<Form.Group widths="equal"> <Form.Group widths='equal'>
<Form.TextArea <Form.TextArea
label="模型固定价格(一次调用消耗多少刀,优先级大于模型倍率)" label='模型倍率'
name="ModelPrice" name='ModelRatio'
onChange={handleInputChange} onChange={handleInputChange}
style={{ minHeight: 250, fontFamily: 'JetBrains Mono, Consolas' }} style={{ minHeight: 250, fontFamily: 'JetBrains Mono, Consolas' }}
autoComplete="new-password" autoComplete='new-password'
value={inputs.ModelPrice}
placeholder='为一个 JSON 文本,键为模型名称,值为一次调用消耗多少刀,比如 "gpt-4-gizmo-*": 0.1一次消耗0.1刀'
/>
</Form.Group>
<Form.Group widths="equal">
<Form.TextArea
label="模型倍率"
name="ModelRatio"
onChange={handleInputChange}
style={{ minHeight: 250, fontFamily: 'JetBrains Mono, Consolas' }}
autoComplete="new-password"
value={inputs.ModelRatio} value={inputs.ModelRatio}
placeholder="为一个 JSON 文本,键为模型名称,值为倍率" placeholder='为一个 JSON 文本,键为模型名称,值为倍率'
/> />
</Form.Group> </Form.Group>
<Form.Group widths="equal"> <Form.Group widths='equal'>
<Form.TextArea <Form.TextArea
label="分组倍率" label='补全倍率'
name="GroupRatio" name='CompletionRatio'
onChange={handleInputChange} onChange={handleInputChange}
style={{ minHeight: 250, fontFamily: 'JetBrains Mono, Consolas' }} style={{ minHeight: 250, fontFamily: 'JetBrains Mono, Consolas' }}
autoComplete="new-password" autoComplete='new-password'
value={inputs.CompletionRatio}
placeholder='为一个 JSON 文本,键为模型名称,值为倍率,此处的倍率设置是模型补全倍率相较于提示倍率的比例,使用该设置可强制覆盖 One API 的内部比例'
/>
</Form.Group>
<Form.Group widths='equal'>
<Form.TextArea
label='分组倍率'
name='GroupRatio'
onChange={handleInputChange}
style={{ minHeight: 250, fontFamily: 'JetBrains Mono, Consolas' }}
autoComplete='new-password'
value={inputs.GroupRatio} value={inputs.GroupRatio}
placeholder="为一个 JSON 文本,键为分组名称,值为倍率" placeholder='为一个 JSON 文本,键为分组名称,值为倍率'
/> />
</Form.Group> </Form.Group>
<Form.Button onClick={() => { <Form.Button onClick={() => {
@ -461,8 +383,7 @@ const OperationSetting = () => {
</Form> </Form>
</Grid.Column> </Grid.Column>
</Grid> </Grid>
) );
;
}; };
export default OperationSetting; export default OperationSetting;

View File

@ -1,16 +1,18 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Banner, Button, Col, Form, Row } from '@douyinfe/semi-ui'; import { Button, Divider, Form, Grid, Header, Message, Modal } from 'semantic-ui-react';
import { API, showError, showSuccess } from '../helpers'; import { API, showError, showSuccess } from '../helpers';
import { marked } from 'marked'; import { marked } from 'marked';
import { Link } from 'react-router-dom';
const OtherSetting = () => { const OtherSetting = () => {
let [inputs, setInputs] = useState({ let [inputs, setInputs] = useState({
Footer: '',
Notice: '', Notice: '',
About: '',
SystemName: '', SystemName: '',
Logo: '', Logo: '',
Footer: '', HomePageContent: '',
About: '', Theme: ''
HomePageContent: ''
}); });
let [loading, setLoading] = useState(false); let [loading, setLoading] = useState(false);
const [showUpdateModal, setShowUpdateModal] = useState(false); const [showUpdateModal, setShowUpdateModal] = useState(false);
@ -19,6 +21,25 @@ const OtherSetting = () => {
content: '' content: ''
}); });
const getOptions = async () => {
const res = await API.get('/api/option/');
const { success, message, data } = res.data;
if (success) {
let newInputs = {};
data.forEach((item) => {
if (item.key in inputs) {
newInputs[item.key] = item.value;
}
});
setInputs(newInputs);
} else {
showError(message);
}
};
useEffect(() => {
getOptions().then();
}, []);
const updateOption = async (key, value) => { const updateOption = async (key, value) => {
setLoading(true); setLoading(true);
@ -35,103 +56,37 @@ const OtherSetting = () => {
setLoading(false); setLoading(false);
}; };
const [loadingInput, setLoadingInput] = useState({ const handleInputChange = async (e, { name, value }) => {
Notice: false,
SystemName: false,
Logo: false,
HomePageContent: false,
About: false,
Footer: false
});
const handleInputChange = async (value, e) => {
const name = e.target.id;
setInputs((inputs) => ({ ...inputs, [name]: value })); setInputs((inputs) => ({ ...inputs, [name]: value }));
}; };
// 通用设置
const formAPISettingGeneral = useRef();
// 通用设置 - Notice
const submitNotice = async () => { const submitNotice = async () => {
try { await updateOption('Notice', inputs.Notice);
setLoadingInput((loadingInput) => ({ ...loadingInput, Notice: true }));
await updateOption('Notice', inputs.Notice);
showSuccess('公告已更新');
} catch (error) {
console.error('公告更新失败', error);
showError('公告更新失败');
} finally {
setLoadingInput((loadingInput) => ({ ...loadingInput, Notice: false }));
}
};
// 个性化设置
const formAPIPersonalization = useRef();
// 个性化设置 - SystemName
const submitSystemName = async () => {
try {
setLoadingInput((loadingInput) => ({ ...loadingInput, SystemName: true }));
await updateOption('SystemName', inputs.SystemName);
showSuccess('系统名称已更新');
} catch (error) {
console.error('系统名称更新失败', error);
showError('系统名称更新失败');
} finally {
setLoadingInput((loadingInput) => ({ ...loadingInput, SystemName: false }));
}
}; };
// 个性化设置 - Logo
const submitLogo = async () => {
try {
setLoadingInput((loadingInput) => ({ ...loadingInput, Logo: true }));
await updateOption('Logo', inputs.Logo);
showSuccess('Logo 已更新');
} catch (error) {
console.error('Logo 更新失败', error);
showError('Logo 更新失败');
} finally {
setLoadingInput((loadingInput) => ({ ...loadingInput, Logo: false }));
}
};
// 个性化设置 - 首页内容
const submitOption = async (key) => {
try {
setLoadingInput((loadingInput) => ({ ...loadingInput, HomePageContent: true }));
await updateOption(key, inputs[key]);
showSuccess('首页内容已更新');
} catch (error) {
console.error('首页内容更新失败', error);
showError('首页内容更新失败');
} finally {
setLoadingInput((loadingInput) => ({ ...loadingInput, HomePageContent: false }));
}
};
// 个性化设置 - 关于
const submitAbout = async () => {
try {
setLoadingInput((loadingInput) => ({ ...loadingInput, About: true }));
await updateOption('About', inputs.About);
showSuccess('关于内容已更新');
} catch (error) {
console.error('关于内容更新失败', error);
showError('关于内容更新失败');
} finally {
setLoadingInput((loadingInput) => ({ ...loadingInput, About: false }));
}
};
// 个性化设置 - 页脚
const submitFooter = async () => { const submitFooter = async () => {
try { await updateOption('Footer', inputs.Footer);
setLoadingInput((loadingInput) => ({ ...loadingInput, Footer: true }));
await updateOption('Footer', inputs.Footer);
showSuccess('页脚内容已更新');
} catch (error) {
console.error('页脚内容更新失败', error);
showError('页脚内容更新失败');
} finally {
setLoadingInput((loadingInput) => ({ ...loadingInput, Footer: false }));
}
}; };
const submitSystemName = async () => {
await updateOption('SystemName', inputs.SystemName);
};
const submitTheme = async () => {
await updateOption('Theme', inputs.Theme);
};
const submitLogo = async () => {
await updateOption('Logo', inputs.Logo);
};
const submitAbout = async () => {
await updateOption('About', inputs.About);
};
const submitOption = async (key) => {
await updateOption(key, inputs[key]);
};
const openGitHubRelease = () => { const openGitHubRelease = () => {
window.location = window.location =
@ -153,125 +108,117 @@ const OtherSetting = () => {
setShowUpdateModal(true); setShowUpdateModal(true);
} }
}; };
const getOptions = async () => {
const res = await API.get('/api/option/');
const { success, message, data } = res.data;
if (success) {
let newInputs = {};
data.forEach((item) => {
if (item.key in inputs) {
newInputs[item.key] = item.value;
}
});
setInputs(newInputs);
formAPISettingGeneral.current.setValues(newInputs);
formAPIPersonalization.current.setValues(newInputs);
} else {
showError(message);
}
};
useEffect(() => {
getOptions();
}, []);
return ( return (
<Row> <Grid columns={1}>
<Col span={24}> <Grid.Column>
{/* 通用设置 */} <Form loading={loading}>
<Form values={inputs} getFormApi={formAPI => formAPISettingGeneral.current = formAPI} <Header as='h3'>通用设置</Header>
style={{ marginBottom: 15 }}> <Form.Button onClick={checkUpdate}>检查更新</Form.Button>
<Form.Section text={'通用设置'}> <Form.Group widths='equal'>
<Form.TextArea <Form.TextArea
label={'公告'} label='公告'
placeholder={'在此输入新的公告内容,支持 Markdown & HTML 代码'} placeholder='在此输入新的公告内容,支持 Markdown & HTML 代码'
field={'Notice'} value={inputs.Notice}
name='Notice'
onChange={handleInputChange} onChange={handleInputChange}
style={{ fontFamily: 'JetBrains Mono, Consolas' }} style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
autosize={{ minRows: 6, maxRows: 12 }}
/> />
<Button onClick={submitNotice} loading={loadingInput['Notice']}>设置公告</Button> </Form.Group>
</Form.Section> <Form.Button onClick={submitNotice}>保存公告</Form.Button>
<Divider />
<Header as='h3'>个性化设置</Header>
<Form.Group widths='equal'>
<Form.Input
label='系统名称'
placeholder='在此输入系统名称'
value={inputs.SystemName}
name='SystemName'
onChange={handleInputChange}
/>
</Form.Group>
<Form.Button onClick={submitSystemName}>设置系统名称</Form.Button>
<Form.Group widths='equal'>
<Form.Input
label={<label>主题名称<Link
to='https://github.com/songquanpeng/one-api/blob/main/web/README.md'>当前可用主题</Link></label>}
placeholder='请输入主题名称'
value={inputs.Theme}
name='Theme'
onChange={handleInputChange}
/>
</Form.Group>
<Form.Button onClick={submitTheme}>设置主题重启生效</Form.Button>
<Form.Group widths='equal'>
<Form.Input
label='Logo 图片地址'
placeholder='在此输入 Logo 图片地址'
value={inputs.Logo}
name='Logo'
type='url'
onChange={handleInputChange}
/>
</Form.Group>
<Form.Button onClick={submitLogo}>设置 Logo</Form.Button>
<Form.Group widths='equal'>
<Form.TextArea
label='首页内容'
placeholder='在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页。'
value={inputs.HomePageContent}
name='HomePageContent'
onChange={handleInputChange}
style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
/>
</Form.Group>
<Form.Button onClick={() => submitOption('HomePageContent')}>保存首页内容</Form.Button>
<Form.Group widths='equal'>
<Form.TextArea
label='关于'
placeholder='在此输入新的关于内容,支持 Markdown & HTML 代码。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为关于页面。'
value={inputs.About}
name='About'
onChange={handleInputChange}
style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
/>
</Form.Group>
<Form.Button onClick={submitAbout}>保存关于</Form.Button>
<Message>移除 One API
的版权标识必须首先获得授权项目维护需要花费大量精力如果本项目对你有意义请主动支持本项目</Message>
<Form.Group widths='equal'>
<Form.Input
label='页脚'
placeholder='在此输入新的页脚,留空则使用默认页脚,支持 HTML 代码'
value={inputs.Footer}
name='Footer'
onChange={handleInputChange}
/>
</Form.Group>
<Form.Button onClick={submitFooter}>设置页脚</Form.Button>
</Form> </Form>
{/* 个性化设置 */} </Grid.Column>
<Form values={inputs} getFormApi={formAPI => formAPIPersonalization.current = formAPI} <Modal
style={{ marginBottom: 15 }}> onClose={() => setShowUpdateModal(false)}
<Form.Section text={'个性化设置'}> onOpen={() => setShowUpdateModal(true)}
<Form.Input open={showUpdateModal}
label={'系统名称'} >
placeholder={'在此输入系统名称'} <Modal.Header>新版本{updateData.tag_name}</Modal.Header>
field={'SystemName'} <Modal.Content>
onChange={handleInputChange} <Modal.Description>
/> <div dangerouslySetInnerHTML={{ __html: updateData.content }}></div>
<Button onClick={submitSystemName} loading={loadingInput['SystemName']}>设置系统名称</Button> </Modal.Description>
<Form.Input </Modal.Content>
label={'Logo 图片地址'} <Modal.Actions>
placeholder={'在此输入 Logo 图片地址'} <Button onClick={() => setShowUpdateModal(false)}>关闭</Button>
field={'Logo'} <Button
onChange={handleInputChange} content='详情'
/> onClick={() => {
<Button onClick={submitLogo} loading={loadingInput['Logo']}>设置 Logo</Button> setShowUpdateModal(false);
<Form.TextArea openGitHubRelease();
label={'首页内容'} }}
placeholder={'在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页。'} />
field={'HomePageContent'} </Modal.Actions>
onChange={handleInputChange} </Modal>
style={{ fontFamily: 'JetBrains Mono, Consolas' }} </Grid>
autosize={{ minRows: 6, maxRows: 12 }}
/>
<Button onClick={() => submitOption('HomePageContent')}
loading={loadingInput['HomePageContent']}>设置首页内容</Button>
<Form.TextArea
label={'关于'}
placeholder={'在此输入新的关于内容,支持 Markdown & HTML 代码。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为关于页面。'}
field={'About'}
onChange={handleInputChange}
style={{ fontFamily: 'JetBrains Mono, Consolas' }}
autosize={{ minRows: 6, maxRows: 12 }}
/>
<Button onClick={submitAbout} loading={loadingInput['About']}>设置关于</Button>
{/* */}
<Banner
fullMode={false}
type="info"
description="移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目。"
closeIcon={null}
style={{ marginTop: 15 }}
/>
<Form.Input
label={'页脚'}
placeholder={'在此输入新的页脚,留空则使用默认页脚,支持 HTML 代码'}
field={'Footer'}
onChange={handleInputChange}
/>
<Button onClick={submitFooter} loading={loadingInput['Footer']}>设置页脚</Button>
</Form.Section>
</Form>
</Col>
{/*<Modal*/}
{/* onClose={() => setShowUpdateModal(false)}*/}
{/* onOpen={() => setShowUpdateModal(true)}*/}
{/* open={showUpdateModal}*/}
{/*>*/}
{/* <Modal.Header>新版本:{updateData.tag_name}</Modal.Header>*/}
{/* <Modal.Content>*/}
{/* <Modal.Description>*/}
{/* <div dangerouslySetInnerHTML={{ __html: updateData.content }}></div>*/}
{/* </Modal.Description>*/}
{/* </Modal.Content>*/}
{/* <Modal.Actions>*/}
{/* <Button onClick={() => setShowUpdateModal(false)}>关闭</Button>*/}
{/* <Button*/}
{/* content='详情'*/}
{/* onClick={() => {*/}
{/* setShowUpdateModal(false);*/}
{/* openGitHubRelease();*/}
{/* }}*/}
{/* />*/}
{/* </Modal.Actions>*/}
{/*</Modal>*/}
</Row>
); );
}; };

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button, Divider, Form, Grid, Header, Message, Modal } from 'semantic-ui-react'; import { Button, Divider, Form, Grid, Header, Modal, Message } from 'semantic-ui-react';
import { API, removeTrailingSlash, showError, verifyJSON } from '../helpers'; import { API, removeTrailingSlash, showError } from '../helpers';
const SystemSetting = () => { const SystemSetting = () => {
let [inputs, setInputs] = useState({ let [inputs, setInputs] = useState({
@ -17,28 +17,19 @@ const SystemSetting = () => {
SMTPFrom: '', SMTPFrom: '',
SMTPToken: '', SMTPToken: '',
ServerAddress: '', ServerAddress: '',
EpayId: '',
EpayKey: '',
Price: 7.3,
MinTopUp: 1,
TopupGroupRatio: '',
PayAddress: '',
CustomCallbackAddress: '',
Footer: '', Footer: '',
WeChatAuthEnabled: '', WeChatAuthEnabled: '',
WeChatServerAddress: '', WeChatServerAddress: '',
WeChatServerToken: '', WeChatServerToken: '',
WeChatAccountQRCodeImageURL: '', WeChatAccountQRCodeImageURL: '',
MessagePusherAddress: '',
MessagePusherToken: '',
TurnstileCheckEnabled: '', TurnstileCheckEnabled: '',
TurnstileSiteKey: '', TurnstileSiteKey: '',
TurnstileSecretKey: '', TurnstileSecretKey: '',
RegisterEnabled: '', RegisterEnabled: '',
EmailDomainRestrictionEnabled: '', EmailDomainRestrictionEnabled: '',
EmailDomainWhitelist: '', EmailDomainWhitelist: ''
// telegram login
TelegramOAuthEnabled: '',
TelegramBotToken: '',
TelegramBotName: ''
}); });
const [originInputs, setOriginInputs] = useState({}); const [originInputs, setOriginInputs] = useState({});
let [loading, setLoading] = useState(false); let [loading, setLoading] = useState(false);
@ -52,9 +43,6 @@ const SystemSetting = () => {
if (success) { if (success) {
let newInputs = {}; let newInputs = {};
data.forEach((item) => { data.forEach((item) => {
if (item.key === 'TopupGroupRatio') {
item.value = JSON.stringify(JSON.parse(item.value), null, 2);
}
newInputs[item.key] = item.value; newInputs[item.key] = item.value;
}); });
setInputs({ setInputs({
@ -83,7 +71,6 @@ const SystemSetting = () => {
case 'EmailVerificationEnabled': case 'EmailVerificationEnabled':
case 'GitHubOAuthEnabled': case 'GitHubOAuthEnabled':
case 'WeChatAuthEnabled': case 'WeChatAuthEnabled':
case 'TelegramOAuthEnabled':
case 'TurnstileCheckEnabled': case 'TurnstileCheckEnabled':
case 'EmailDomainRestrictionEnabled': case 'EmailDomainRestrictionEnabled':
case 'RegisterEnabled': case 'RegisterEnabled':
@ -101,9 +88,6 @@ const SystemSetting = () => {
if (key === 'EmailDomainWhitelist') { if (key === 'EmailDomainWhitelist') {
value = value.split(','); value = value.split(',');
} }
if (key === 'Price') {
value = parseFloat(value);
}
setInputs((inputs) => ({ setInputs((inputs) => ({
...inputs, [key]: value ...inputs, [key]: value
})); }));
@ -123,10 +107,6 @@ const SystemSetting = () => {
name === 'Notice' || name === 'Notice' ||
name.startsWith('SMTP') || name.startsWith('SMTP') ||
name === 'ServerAddress' || name === 'ServerAddress' ||
name === 'EpayId' ||
name === 'EpayKey' ||
name === 'Price' ||
name === 'PayAddress' ||
name === 'GitHubClientId' || name === 'GitHubClientId' ||
name === 'GitHubClientSecret' || name === 'GitHubClientSecret' ||
name === 'WeChatServerAddress' || name === 'WeChatServerAddress' ||
@ -134,10 +114,7 @@ const SystemSetting = () => {
name === 'WeChatAccountQRCodeImageURL' || name === 'WeChatAccountQRCodeImageURL' ||
name === 'TurnstileSiteKey' || name === 'TurnstileSiteKey' ||
name === 'TurnstileSecretKey' || name === 'TurnstileSecretKey' ||
name === 'EmailDomainWhitelist' || name === 'EmailDomainWhitelist'
name === 'TopupGroupRatio' ||
name === 'TelegramBotToken' ||
name === 'TelegramBotName'
) { ) {
setInputs((inputs) => ({ ...inputs, [name]: value })); setInputs((inputs) => ({ ...inputs, [name]: value }));
} else { } else {
@ -150,29 +127,6 @@ const SystemSetting = () => {
await updateOption('ServerAddress', ServerAddress); await updateOption('ServerAddress', ServerAddress);
}; };
const submitPayAddress = async () => {
if (inputs.ServerAddress === '') {
showError('请先填写服务器地址');
return;
}
if (originInputs['TopupGroupRatio'] !== inputs.TopupGroupRatio) {
if (!verifyJSON(inputs.TopupGroupRatio)) {
showError('充值分组倍率不是合法的 JSON 字符串');
return;
}
await updateOption('TopupGroupRatio', inputs.TopupGroupRatio);
}
let PayAddress = removeTrailingSlash(inputs.PayAddress);
await updateOption('PayAddress', PayAddress);
if (inputs.EpayId !== '') {
await updateOption('EpayId', inputs.EpayId);
}
if (inputs.EpayKey !== '') {
await updateOption('EpayKey', inputs.EpayKey);
}
await updateOption('Price', '' + inputs.Price);
};
const submitSMTP = async () => { const submitSMTP = async () => {
if (originInputs['SMTPServer'] !== inputs.SMTPServer) { if (originInputs['SMTPServer'] !== inputs.SMTPServer) {
await updateOption('SMTPServer', inputs.SMTPServer); await updateOption('SMTPServer', inputs.SMTPServer);
@ -231,6 +185,21 @@ const SystemSetting = () => {
} }
}; };
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 () => { const submitGitHubOAuth = async () => {
if (originInputs['GitHubClientId'] !== inputs.GitHubClientId) { if (originInputs['GitHubClientId'] !== inputs.GitHubClientId) {
await updateOption('GitHubClientId', inputs.GitHubClientId); await updateOption('GitHubClientId', inputs.GitHubClientId);
@ -243,12 +212,6 @@ const SystemSetting = () => {
} }
}; };
const submitTelegramSettings = async () => {
// await updateOption('TelegramOAuthEnabled', inputs.TelegramOAuthEnabled);
await updateOption('TelegramBotToken', inputs.TelegramBotToken);
await updateOption('TelegramBotName', inputs.TelegramBotName);
};
const submitTurnstile = async () => { const submitTurnstile = async () => {
if (originInputs['TurnstileSiteKey'] !== inputs.TurnstileSiteKey) { if (originInputs['TurnstileSiteKey'] !== inputs.TurnstileSiteKey) {
await updateOption('TurnstileSiteKey', inputs.TurnstileSiteKey); await updateOption('TurnstileSiteKey', inputs.TurnstileSiteKey);
@ -267,27 +230,27 @@ const SystemSetting = () => {
setRestrictedDomainInput(''); setRestrictedDomainInput('');
setInputs({ setInputs({
...inputs, ...inputs,
EmailDomainWhitelist: [...localDomainList, restrictedDomainInput] EmailDomainWhitelist: [...localDomainList, restrictedDomainInput],
}); });
setEmailDomainWhitelist([...EmailDomainWhitelist, { setEmailDomainWhitelist([...EmailDomainWhitelist, {
key: restrictedDomainInput, key: restrictedDomainInput,
text: restrictedDomainInput, text: restrictedDomainInput,
value: restrictedDomainInput value: restrictedDomainInput,
}]); }]);
} }
}; }
return ( return (
<Grid columns={1}> <Grid columns={1}>
<Grid.Column> <Grid.Column>
<Form loading={loading}> <Form loading={loading}>
<Header as="h3">通用设置</Header> <Header as='h3'>通用设置</Header>
<Form.Group widths="equal"> <Form.Group widths='equal'>
<Form.Input <Form.Input
label="服务器地址" label='服务器地址'
placeholder="例如https://yourdomain.com" placeholder='例如https://yourdomain.com'
value={inputs.ServerAddress} value={inputs.ServerAddress}
name="ServerAddress" name='ServerAddress'
onChange={handleInputChange} onChange={handleInputChange}
/> />
</Form.Group> </Form.Group>
@ -295,77 +258,12 @@ const SystemSetting = () => {
更新服务器地址 更新服务器地址
</Form.Button> </Form.Button>
<Divider /> <Divider />
<Header as="h3">支付设置当前仅支持易支付接口默认使用上方服务器地址作为回调地址</Header> <Header as='h3'>配置登录注册</Header>
<Form.Group widths="equal">
<Form.Input
label="支付地址,不填写则不启用在线支付"
placeholder="例如https://yourdomain.com"
value={inputs.PayAddress}
name="PayAddress"
onChange={handleInputChange}
/>
<Form.Input
label="易支付商户ID"
placeholder="例如0001"
value={inputs.EpayId}
name="EpayId"
onChange={handleInputChange}
/>
<Form.Input
label="易支付商户密钥"
placeholder="例如dejhfueqhujasjmndbjkqaw"
value={inputs.EpayKey}
name="EpayKey"
onChange={handleInputChange}
/>
</Form.Group>
<Form.Group widths="equal">
<Form.Input
label="回调地址,不填写则使用上方服务器地址作为回调地址"
placeholder="例如https://yourdomain.com"
value={inputs.CustomCallbackAddress}
name="CustomCallbackAddress"
onChange={handleInputChange}
/>
<Form.Input
label="充值价格x元/美金)"
placeholder="例如7就是7元/美金"
value={inputs.Price}
name="Price"
min={0}
onChange={handleInputChange}
/>
<Form.Input
label="最低充值数量"
placeholder="例如2就是最低充值2$"
value={inputs.MinTopUp}
name="MinTopUp"
min={1}
onChange={handleInputChange}
/>
</Form.Group>
<Form.Group widths="equal">
<Form.TextArea
label="充值分组倍率"
name="TopupGroupRatio"
onChange={handleInputChange}
style={{ minHeight: 250, fontFamily: 'JetBrains Mono, Consolas' }}
autoComplete="new-password"
value={inputs.TopupGroupRatio}
placeholder="为一个 JSON 文本,键为组名称,值为倍率"
/>
</Form.Group>
<Form.Button onClick={submitPayAddress}>
更新支付设置
</Form.Button>
<Divider />
<Header as="h3">配置登录注册</Header>
<Form.Group inline> <Form.Group inline>
<Form.Checkbox <Form.Checkbox
checked={inputs.PasswordLoginEnabled === 'true'} checked={inputs.PasswordLoginEnabled === 'true'}
label="允许通过密码进行登录" label='允许通过密码进行登录'
name="PasswordLoginEnabled" name='PasswordLoginEnabled'
onChange={handleInputChange} onChange={handleInputChange}
/> />
{ {
@ -383,7 +281,7 @@ const SystemSetting = () => {
<Modal.Actions> <Modal.Actions>
<Button onClick={() => setShowPasswordWarningModal(false)}>取消</Button> <Button onClick={() => setShowPasswordWarningModal(false)}>取消</Button>
<Button <Button
color="yellow" color='yellow'
onClick={async () => { onClick={async () => {
setShowPasswordWarningModal(false); setShowPasswordWarningModal(false);
await updateOption('PasswordLoginEnabled', 'false'); await updateOption('PasswordLoginEnabled', 'false');
@ -396,80 +294,74 @@ const SystemSetting = () => {
} }
<Form.Checkbox <Form.Checkbox
checked={inputs.PasswordRegisterEnabled === 'true'} checked={inputs.PasswordRegisterEnabled === 'true'}
label="允许通过密码进行注册" label='允许通过密码进行注册'
name="PasswordRegisterEnabled" name='PasswordRegisterEnabled'
onChange={handleInputChange} onChange={handleInputChange}
/> />
<Form.Checkbox <Form.Checkbox
checked={inputs.EmailVerificationEnabled === 'true'} checked={inputs.EmailVerificationEnabled === 'true'}
label="通过密码注册时需要进行邮箱验证" label='通过密码注册时需要进行邮箱验证'
name="EmailVerificationEnabled" name='EmailVerificationEnabled'
onChange={handleInputChange} onChange={handleInputChange}
/> />
<Form.Checkbox <Form.Checkbox
checked={inputs.GitHubOAuthEnabled === 'true'} checked={inputs.GitHubOAuthEnabled === 'true'}
label="允许通过 GitHub 账户登录 & 注册" label='允许通过 GitHub 账户登录 & 注册'
name="GitHubOAuthEnabled" name='GitHubOAuthEnabled'
onChange={handleInputChange} onChange={handleInputChange}
/> />
<Form.Checkbox <Form.Checkbox
checked={inputs.WeChatAuthEnabled === 'true'} checked={inputs.WeChatAuthEnabled === 'true'}
label="允许通过微信登录 & 注册" label='允许通过微信登录 & 注册'
name="WeChatAuthEnabled" name='WeChatAuthEnabled'
onChange={handleInputChange}
/>
<Form.Checkbox
checked={inputs.TelegramOAuthEnabled === 'true'}
label="允许通过 Telegram 进行登录"
name="TelegramOAuthEnabled"
onChange={handleInputChange} onChange={handleInputChange}
/> />
</Form.Group> </Form.Group>
<Form.Group inline> <Form.Group inline>
<Form.Checkbox <Form.Checkbox
checked={inputs.RegisterEnabled === 'true'} checked={inputs.RegisterEnabled === 'true'}
label="允许新用户注册(此项为否时,新用户将无法以任何方式进行注册)" label='允许新用户注册(此项为否时,新用户将无法以任何方式进行注册)'
name="RegisterEnabled" name='RegisterEnabled'
onChange={handleInputChange} onChange={handleInputChange}
/> />
<Form.Checkbox <Form.Checkbox
checked={inputs.TurnstileCheckEnabled === 'true'} checked={inputs.TurnstileCheckEnabled === 'true'}
label="启用 Turnstile 用户校验" label='启用 Turnstile 用户校验'
name="TurnstileCheckEnabled" name='TurnstileCheckEnabled'
onChange={handleInputChange} onChange={handleInputChange}
/> />
</Form.Group> </Form.Group>
<Divider /> <Divider />
<Header as="h3"> <Header as='h3'>
配置邮箱域名白名单 配置邮箱域名白名单
<Header.Subheader>用以防止恶意用户利用临时邮箱批量注册</Header.Subheader> <Header.Subheader>用以防止恶意用户利用临时邮箱批量注册</Header.Subheader>
</Header> </Header>
<Form.Group widths={3}> <Form.Group widths={3}>
<Form.Checkbox <Form.Checkbox
label="启用邮箱域名白名单" label='启用邮箱域名白名单'
name="EmailDomainRestrictionEnabled" name='EmailDomainRestrictionEnabled'
onChange={handleInputChange} onChange={handleInputChange}
checked={inputs.EmailDomainRestrictionEnabled === 'true'} checked={inputs.EmailDomainRestrictionEnabled === 'true'}
/> />
</Form.Group> </Form.Group>
<Form.Group widths={2}> <Form.Group widths={2}>
<Form.Dropdown <Form.Dropdown
label="允许的邮箱域名" label='允许的邮箱域名'
placeholder="允许的邮箱域名" placeholder='允许的邮箱域名'
name="EmailDomainWhitelist" name='EmailDomainWhitelist'
required required
fluid fluid
multiple multiple
selection selection
onChange={handleInputChange} onChange={handleInputChange}
value={inputs.EmailDomainWhitelist} value={inputs.EmailDomainWhitelist}
autoComplete="new-password" autoComplete='new-password'
options={EmailDomainWhitelist} options={EmailDomainWhitelist}
/> />
<Form.Input <Form.Input
label="添加新的允许的邮箱域名" label='添加新的允许的邮箱域名'
action={ action={
<Button type="button" onClick={() => { <Button type='button' onClick={() => {
submitNewRestrictedDomain(); submitNewRestrictedDomain();
}}>填入</Button> }}>填入</Button>
} }
@ -478,8 +370,8 @@ const SystemSetting = () => {
submitNewRestrictedDomain(); submitNewRestrictedDomain();
} }
}} }}
autoComplete="new-password" autoComplete='new-password'
placeholder="输入新的允许的邮箱域名" placeholder='输入新的允许的邮箱域名'
value={restrictedDomainInput} value={restrictedDomainInput}
onChange={(e, { value }) => { onChange={(e, { value }) => {
setRestrictedDomainInput(value); setRestrictedDomainInput(value);
@ -488,62 +380,62 @@ const SystemSetting = () => {
</Form.Group> </Form.Group>
<Form.Button onClick={submitEmailDomainWhitelist}>保存邮箱域名白名单设置</Form.Button> <Form.Button onClick={submitEmailDomainWhitelist}>保存邮箱域名白名单设置</Form.Button>
<Divider /> <Divider />
<Header as="h3"> <Header as='h3'>
配置 SMTP 配置 SMTP
<Header.Subheader>用以支持系统的邮件发送</Header.Subheader> <Header.Subheader>用以支持系统的邮件发送</Header.Subheader>
</Header> </Header>
<Form.Group widths={3}> <Form.Group widths={3}>
<Form.Input <Form.Input
label="SMTP 服务器地址" label='SMTP 服务器地址'
name="SMTPServer" name='SMTPServer'
onChange={handleInputChange} onChange={handleInputChange}
autoComplete="new-password" autoComplete='new-password'
value={inputs.SMTPServer} value={inputs.SMTPServer}
placeholder="例如smtp.qq.com" placeholder='例如smtp.qq.com'
/> />
<Form.Input <Form.Input
label="SMTP 端口" label='SMTP 端口'
name="SMTPPort" name='SMTPPort'
onChange={handleInputChange} onChange={handleInputChange}
autoComplete="new-password" autoComplete='new-password'
value={inputs.SMTPPort} value={inputs.SMTPPort}
placeholder="默认: 587" placeholder='默认: 587'
/> />
<Form.Input <Form.Input
label="SMTP 账户" label='SMTP 账户'
name="SMTPAccount" name='SMTPAccount'
onChange={handleInputChange} onChange={handleInputChange}
autoComplete="new-password" autoComplete='new-password'
value={inputs.SMTPAccount} value={inputs.SMTPAccount}
placeholder="通常是邮箱地址" placeholder='通常是邮箱地址'
/> />
</Form.Group> </Form.Group>
<Form.Group widths={3}> <Form.Group widths={3}>
<Form.Input <Form.Input
label="SMTP 发送者邮箱" label='SMTP 发送者邮箱'
name="SMTPFrom" name='SMTPFrom'
onChange={handleInputChange} onChange={handleInputChange}
autoComplete="new-password" autoComplete='new-password'
value={inputs.SMTPFrom} value={inputs.SMTPFrom}
placeholder="通常和邮箱地址保持一致" placeholder='通常和邮箱地址保持一致'
/> />
<Form.Input <Form.Input
label="SMTP 访问凭证" label='SMTP 访问凭证'
name="SMTPToken" name='SMTPToken'
onChange={handleInputChange} onChange={handleInputChange}
type="password" type='password'
autoComplete="new-password" autoComplete='new-password'
checked={inputs.RegisterEnabled === 'true'} checked={inputs.RegisterEnabled === 'true'}
placeholder="敏感信息不会发送到前端显示" placeholder='敏感信息不会发送到前端显示'
/> />
</Form.Group> </Form.Group>
<Form.Button onClick={submitSMTP}>保存 SMTP 设置</Form.Button> <Form.Button onClick={submitSMTP}>保存 SMTP 设置</Form.Button>
<Divider /> <Divider />
<Header as="h3"> <Header as='h3'>
配置 GitHub OAuth App 配置 GitHub OAuth App
<Header.Subheader> <Header.Subheader>
用以支持通过 GitHub 进行登录注册 用以支持通过 GitHub 进行登录注册
<a href="https://github.com/settings/developers" target="_blank" rel="noreferrer"> <a href='https://github.com/settings/developers' target='_blank'>
点击此处 点击此处
</a> </a>
管理你的 GitHub OAuth App 管理你的 GitHub OAuth App
@ -556,34 +448,34 @@ const SystemSetting = () => {
</Message> </Message>
<Form.Group widths={3}> <Form.Group widths={3}>
<Form.Input <Form.Input
label="GitHub Client ID" label='GitHub Client ID'
name="GitHubClientId" name='GitHubClientId'
onChange={handleInputChange} onChange={handleInputChange}
autoComplete="new-password" autoComplete='new-password'
value={inputs.GitHubClientId} value={inputs.GitHubClientId}
placeholder="输入你注册的 GitHub OAuth APP 的 ID" placeholder='输入你注册的 GitHub OAuth APP 的 ID'
/> />
<Form.Input <Form.Input
label="GitHub Client Secret" label='GitHub Client Secret'
name="GitHubClientSecret" name='GitHubClientSecret'
onChange={handleInputChange} onChange={handleInputChange}
type="password" type='password'
autoComplete="new-password" autoComplete='new-password'
value={inputs.GitHubClientSecret} value={inputs.GitHubClientSecret}
placeholder="敏感信息不会发送到前端显示" placeholder='敏感信息不会发送到前端显示'
/> />
</Form.Group> </Form.Group>
<Form.Button onClick={submitGitHubOAuth}> <Form.Button onClick={submitGitHubOAuth}>
保存 GitHub OAuth 设置 保存 GitHub OAuth 设置
</Form.Button> </Form.Button>
<Divider /> <Divider />
<Header as="h3"> <Header as='h3'>
配置 WeChat Server 配置 WeChat Server
<Header.Subheader> <Header.Subheader>
用以支持通过微信进行登录注册 用以支持通过微信进行登录注册
<a <a
href="https://github.com/songquanpeng/wechat-server" href='https://github.com/songquanpeng/wechat-server'
target="_blank" rel="noreferrer" target='_blank'
> >
点击此处 点击此处
</a> </a>
@ -592,61 +484,76 @@ const SystemSetting = () => {
</Header> </Header>
<Form.Group widths={3}> <Form.Group widths={3}>
<Form.Input <Form.Input
label="WeChat Server 服务器地址" label='WeChat Server 服务器地址'
name="WeChatServerAddress" name='WeChatServerAddress'
placeholder="例如https://yourdomain.com" placeholder='例如https://yourdomain.com'
onChange={handleInputChange} onChange={handleInputChange}
autoComplete="new-password" autoComplete='new-password'
value={inputs.WeChatServerAddress} value={inputs.WeChatServerAddress}
/> />
<Form.Input <Form.Input
label="WeChat Server 访问凭证" label='WeChat Server 访问凭证'
name="WeChatServerToken" name='WeChatServerToken'
type="password" type='password'
onChange={handleInputChange} onChange={handleInputChange}
autoComplete="new-password" autoComplete='new-password'
value={inputs.WeChatServerToken} value={inputs.WeChatServerToken}
placeholder="敏感信息不会发送到前端显示" placeholder='敏感信息不会发送到前端显示'
/> />
<Form.Input <Form.Input
label="微信公众号二维码图片链接" label='微信公众号二维码图片链接'
name="WeChatAccountQRCodeImageURL" name='WeChatAccountQRCodeImageURL'
onChange={handleInputChange} onChange={handleInputChange}
autoComplete="new-password" autoComplete='new-password'
value={inputs.WeChatAccountQRCodeImageURL} value={inputs.WeChatAccountQRCodeImageURL}
placeholder="输入一个图片链接" placeholder='输入一个图片链接'
/> />
</Form.Group> </Form.Group>
<Form.Button onClick={submitWeChat}> <Form.Button onClick={submitWeChat}>
保存 WeChat Server 设置 保存 WeChat Server 设置
</Form.Button> </Form.Button>
<Divider /> <Divider />
<Header as="h3">配置 Telegram 登录</Header> <Header as='h3'>
<Form.Group inline> 配置 Message Pusher
<Header.Subheader>
用以推送报警信息
<a
href='https://github.com/songquanpeng/message-pusher'
target='_blank'
>
点击此处
</a>
了解 Message Pusher
</Header.Subheader>
</Header>
<Form.Group widths={3}>
<Form.Input <Form.Input
label="Telegram Bot Token" label='Message Pusher 推送地址'
name="TelegramBotToken" name='MessagePusherAddress'
placeholder='例如https://msgpusher.com/push/your_username'
onChange={handleInputChange} onChange={handleInputChange}
value={inputs.TelegramBotToken} autoComplete='new-password'
placeholder="输入你的 Telegram Bot Token" value={inputs.MessagePusherAddress}
/> />
<Form.Input <Form.Input
label="Telegram Bot 名称" label='Message Pusher 访问凭证'
name="TelegramBotName" name='MessagePusherToken'
type='password'
onChange={handleInputChange} onChange={handleInputChange}
value={inputs.TelegramBotName} autoComplete='new-password'
placeholder="输入你的 Telegram Bot 名称" value={inputs.MessagePusherToken}
placeholder='敏感信息不会发送到前端显示'
/> />
</Form.Group> </Form.Group>
<Form.Button onClick={submitTelegramSettings}> <Form.Button onClick={submitMessagePusher}>
保存 Telegram 登录设置 保存 Message Pusher 设置
</Form.Button> </Form.Button>
<Divider /> <Divider />
<Header as="h3"> <Header as='h3'>
配置 Turnstile 配置 Turnstile
<Header.Subheader> <Header.Subheader>
用以支持用户校验 用以支持用户校验
<a href="https://dash.cloudflare.com/" target="_blank" rel="noreferrer"> <a href='https://dash.cloudflare.com/' target='_blank'>
点击此处 点击此处
</a> </a>
管理你的 Turnstile Sites推荐选择 Invisible Widget Type 管理你的 Turnstile Sites推荐选择 Invisible Widget Type
@ -654,21 +561,21 @@ const SystemSetting = () => {
</Header> </Header>
<Form.Group widths={3}> <Form.Group widths={3}>
<Form.Input <Form.Input
label="Turnstile Site Key" label='Turnstile Site Key'
name="TurnstileSiteKey" name='TurnstileSiteKey'
onChange={handleInputChange} onChange={handleInputChange}
autoComplete="new-password" autoComplete='new-password'
value={inputs.TurnstileSiteKey} value={inputs.TurnstileSiteKey}
placeholder="输入你注册的 Turnstile Site Key" placeholder='输入你注册的 Turnstile Site Key'
/> />
<Form.Input <Form.Input
label="Turnstile Secret Key" label='Turnstile Secret Key'
name="TurnstileSecretKey" name='TurnstileSecretKey'
onChange={handleInputChange} onChange={handleInputChange}
type="password" type='password'
autoComplete="new-password" autoComplete='new-password'
value={inputs.TurnstileSecretKey} value={inputs.TurnstileSecretKey}
placeholder="敏感信息不会发送到前端显示" placeholder='敏感信息不会发送到前端显示'
/> />
</Form.Group> </Form.Group>
<Form.Button onClick={submitTurnstile}> <Form.Button onClick={submitTurnstile}>