chore: 初代air主题将使用default主题的运营设置界面、系统设置界面、其他设置界面
This commit is contained in:
parent
6b6cb4cee7
commit
1202e7699b
@ -11,11 +11,10 @@ const OperationSetting = () => {
|
||||
QuotaRemindThreshold: 0,
|
||||
PreConsumedQuota: 0,
|
||||
ModelRatio: '',
|
||||
ModelPrice: '',
|
||||
CompletionRatio: '',
|
||||
GroupRatio: '',
|
||||
TopUpLink: '',
|
||||
ChatLink: '',
|
||||
ChatLink2: '', // 添加的新状态变量
|
||||
QuotaPerUnit: 0,
|
||||
AutomaticDisableChannelEnabled: '',
|
||||
AutomaticEnableChannelEnabled: '',
|
||||
@ -23,32 +22,25 @@ const OperationSetting = () => {
|
||||
LogConsumeEnabled: '',
|
||||
DisplayInCurrencyEnabled: '',
|
||||
DisplayTokenStatEnabled: '',
|
||||
MjNotifyEnabled: '',
|
||||
DrawingEnabled: '',
|
||||
DataExportEnabled: '',
|
||||
DataExportDefaultTime: 'hour',
|
||||
DataExportInterval: 5,
|
||||
DefaultCollapseSidebar: '', // 默认折叠侧边栏
|
||||
ApproximateTokenEnabled: '',
|
||||
RetryTimes: 0
|
||||
});
|
||||
const [originInputs, setOriginInputs] = useState({});
|
||||
let [loading, setLoading] = useState(false);
|
||||
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 res = await API.get('/api/option/');
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
let newInputs = {};
|
||||
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);
|
||||
}
|
||||
if (item.value === '{}') {
|
||||
item.value = '';
|
||||
}
|
||||
newInputs[item.key] = item.value;
|
||||
});
|
||||
setInputs(newInputs);
|
||||
@ -67,10 +59,6 @@ const OperationSetting = () => {
|
||||
if (key.endsWith('Enabled')) {
|
||||
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/', {
|
||||
key,
|
||||
value
|
||||
@ -85,12 +73,7 @@ const OperationSetting = () => {
|
||||
};
|
||||
|
||||
const handleInputChange = async (e, { name, value }) => {
|
||||
if (name.endsWith('Enabled') || name === 'DataExportInterval' || name === 'DataExportDefaultTime' || name === 'DefaultCollapseSidebar') {
|
||||
if (name === 'DataExportDefaultTime') {
|
||||
localStorage.setItem('data_export_default_time', value);
|
||||
} else if (name === 'MjNotifyEnabled') {
|
||||
localStorage.setItem('mj_notify_enabled', value);
|
||||
}
|
||||
if (name.endsWith('Enabled')) {
|
||||
await updateOption(name, value);
|
||||
} else {
|
||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||
@ -122,12 +105,12 @@ const OperationSetting = () => {
|
||||
}
|
||||
await updateOption('GroupRatio', inputs.GroupRatio);
|
||||
}
|
||||
if (originInputs['ModelPrice'] !== inputs.ModelPrice) {
|
||||
if (!verifyJSON(inputs.ModelPrice)) {
|
||||
showError('模型固定价格不是合法的 JSON 字符串');
|
||||
if (originInputs['CompletionRatio'] !== inputs.CompletionRatio) {
|
||||
if (!verifyJSON(inputs.CompletionRatio)) {
|
||||
showError('补全倍率不是合法的 JSON 字符串');
|
||||
return;
|
||||
}
|
||||
await updateOption('ModelPrice', inputs.ModelPrice);
|
||||
await updateOption('CompletionRatio', inputs.CompletionRatio);
|
||||
}
|
||||
break;
|
||||
case 'quota':
|
||||
@ -151,9 +134,6 @@ const OperationSetting = () => {
|
||||
if (originInputs['ChatLink'] !== inputs.ChatLink) {
|
||||
await updateOption('ChatLink', inputs.ChatLink);
|
||||
}
|
||||
if (originInputs['ChatLink2'] !== inputs.ChatLink2) {
|
||||
await updateOption('ChatLink2', inputs.ChatLink2);
|
||||
}
|
||||
if (originInputs['QuotaPerUnit'] !== inputs.QuotaPerUnit) {
|
||||
await updateOption('QuotaPerUnit', inputs.QuotaPerUnit);
|
||||
}
|
||||
@ -174,80 +154,72 @@ const OperationSetting = () => {
|
||||
}
|
||||
showError('日志清理失败:' + message);
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid columns={1}>
|
||||
<Grid.Column>
|
||||
<Form loading={loading}>
|
||||
<Header as="h3">
|
||||
<Header as='h3'>
|
||||
通用设置
|
||||
</Header>
|
||||
<Form.Group widths={4}>
|
||||
<Form.Input
|
||||
label="充值链接"
|
||||
name="TopUpLink"
|
||||
label='充值链接'
|
||||
name='TopUpLink'
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
value={inputs.TopUpLink}
|
||||
type="link"
|
||||
placeholder="例如发卡网站的购买链接"
|
||||
type='link'
|
||||
placeholder='例如发卡网站的购买链接'
|
||||
/>
|
||||
<Form.Input
|
||||
label="默认聊天页面链接"
|
||||
name="ChatLink"
|
||||
label='聊天页面链接'
|
||||
name='ChatLink'
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
value={inputs.ChatLink}
|
||||
type="link"
|
||||
placeholder="例如 ChatGPT Next Web 的部署地址"
|
||||
type='link'
|
||||
placeholder='例如 ChatGPT Next Web 的部署地址'
|
||||
/>
|
||||
<Form.Input
|
||||
label="聊天页面2链接"
|
||||
name="ChatLink2"
|
||||
label='单位美元额度'
|
||||
name='QuotaPerUnit'
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
value={inputs.ChatLink2}
|
||||
type="link"
|
||||
placeholder="例如 ChatGPT Web & Midjourney 的部署地址"
|
||||
/>
|
||||
<Form.Input
|
||||
label="单位美元额度"
|
||||
name="QuotaPerUnit"
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
value={inputs.QuotaPerUnit}
|
||||
type="number"
|
||||
step="0.01"
|
||||
placeholder="一单位货币能兑换的额度"
|
||||
type='number'
|
||||
step='0.01'
|
||||
placeholder='一单位货币能兑换的额度'
|
||||
/>
|
||||
<Form.Input
|
||||
label="失败重试次数"
|
||||
name="RetryTimes"
|
||||
label='失败重试次数'
|
||||
name='RetryTimes'
|
||||
type={'number'}
|
||||
step="1"
|
||||
min="0"
|
||||
step='1'
|
||||
min='0'
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
value={inputs.RetryTimes}
|
||||
placeholder="失败重试次数"
|
||||
placeholder='失败重试次数'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group inline>
|
||||
<Form.Checkbox
|
||||
checked={inputs.DisplayInCurrencyEnabled === 'true'}
|
||||
label="以货币形式显示额度"
|
||||
name="DisplayInCurrencyEnabled"
|
||||
label='以货币形式显示额度'
|
||||
name='DisplayInCurrencyEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.DisplayTokenStatEnabled === 'true'}
|
||||
label="Billing 相关 API 显示令牌额度而非用户额度"
|
||||
name="DisplayTokenStatEnabled"
|
||||
label='Billing 相关 API 显示令牌额度而非用户额度'
|
||||
name='DisplayTokenStatEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.DefaultCollapseSidebar === 'true'}
|
||||
label="默认折叠侧边栏"
|
||||
name="DefaultCollapseSidebar"
|
||||
checked={inputs.ApproximateTokenEnabled === 'true'}
|
||||
label='使用近似的方式估算 token 数以减少计算量'
|
||||
name='ApproximateTokenEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
@ -255,38 +227,20 @@ const OperationSetting = () => {
|
||||
submitConfig('general').then();
|
||||
}}>保存通用设置</Form.Button>
|
||||
<Divider />
|
||||
<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 as='h3'>
|
||||
日志设置
|
||||
</Header>
|
||||
<Form.Group inline>
|
||||
<Form.Checkbox
|
||||
checked={inputs.LogConsumeEnabled === 'true'}
|
||||
label="启用额度消费日志记录"
|
||||
name="LogConsumeEnabled"
|
||||
label='启用额度消费日志记录'
|
||||
name='LogConsumeEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group widths={4}>
|
||||
<Form.Input label="目标时间" value={historyTimestamp} type="datetime-local"
|
||||
name="history_timestamp"
|
||||
<Form.Input label='目标时间' value={historyTimestamp} type='datetime-local'
|
||||
name='history_timestamp'
|
||||
onChange={(e, { name, value }) => {
|
||||
setHistoryTimestamp(value);
|
||||
}} />
|
||||
@ -295,74 +249,42 @@ const OperationSetting = () => {
|
||||
deleteHistoryLogs().then();
|
||||
}}>清理历史日志</Form.Button>
|
||||
<Divider />
|
||||
<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 as='h3'>
|
||||
监控设置
|
||||
</Header>
|
||||
<Form.Group widths={3}>
|
||||
<Form.Input
|
||||
label="最长响应时间"
|
||||
name="ChannelDisableThreshold"
|
||||
label='最长响应时间'
|
||||
name='ChannelDisableThreshold'
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
value={inputs.ChannelDisableThreshold}
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder="单位秒,当运行通道全部测试时,超过此时间将自动禁用通道"
|
||||
type='number'
|
||||
min='0'
|
||||
placeholder='单位秒,当运行通道全部测试时,超过此时间将自动禁用通道'
|
||||
/>
|
||||
<Form.Input
|
||||
label="额度提醒阈值"
|
||||
name="QuotaRemindThreshold"
|
||||
label='额度提醒阈值'
|
||||
name='QuotaRemindThreshold'
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
value={inputs.QuotaRemindThreshold}
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder="低于此额度时将发送邮件提醒用户"
|
||||
type='number'
|
||||
min='0'
|
||||
placeholder='低于此额度时将发送邮件提醒用户'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group inline>
|
||||
<Form.Checkbox
|
||||
checked={inputs.AutomaticDisableChannelEnabled === 'true'}
|
||||
label="失败时自动禁用通道"
|
||||
name="AutomaticDisableChannelEnabled"
|
||||
label='失败时自动禁用通道'
|
||||
name='AutomaticDisableChannelEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.AutomaticEnableChannelEnabled === 'true'}
|
||||
label="成功时自动启用通道"
|
||||
name="AutomaticEnableChannelEnabled"
|
||||
label='成功时自动启用通道'
|
||||
name='AutomaticEnableChannelEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
@ -370,89 +292,89 @@ const OperationSetting = () => {
|
||||
submitConfig('monitor').then();
|
||||
}}>保存监控设置</Form.Button>
|
||||
<Divider />
|
||||
<Header as="h3">
|
||||
<Header as='h3'>
|
||||
额度设置
|
||||
</Header>
|
||||
<Form.Group widths={4}>
|
||||
<Form.Input
|
||||
label="新用户初始额度"
|
||||
name="QuotaForNewUser"
|
||||
label='新用户初始额度'
|
||||
name='QuotaForNewUser'
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
value={inputs.QuotaForNewUser}
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder="例如:100"
|
||||
type='number'
|
||||
min='0'
|
||||
placeholder='例如:100'
|
||||
/>
|
||||
<Form.Input
|
||||
label="请求预扣费额度"
|
||||
name="PreConsumedQuota"
|
||||
label='请求预扣费额度'
|
||||
name='PreConsumedQuota'
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
value={inputs.PreConsumedQuota}
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder="请求结束后多退少补"
|
||||
type='number'
|
||||
min='0'
|
||||
placeholder='请求结束后多退少补'
|
||||
/>
|
||||
<Form.Input
|
||||
label="邀请新用户奖励额度"
|
||||
name="QuotaForInviter"
|
||||
label='邀请新用户奖励额度'
|
||||
name='QuotaForInviter'
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
value={inputs.QuotaForInviter}
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder="例如:2000"
|
||||
type='number'
|
||||
min='0'
|
||||
placeholder='例如:2000'
|
||||
/>
|
||||
<Form.Input
|
||||
label="新用户使用邀请码奖励额度"
|
||||
name="QuotaForInvitee"
|
||||
label='新用户使用邀请码奖励额度'
|
||||
name='QuotaForInvitee'
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
value={inputs.QuotaForInvitee}
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder="例如:1000"
|
||||
type='number'
|
||||
min='0'
|
||||
placeholder='例如:1000'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={() => {
|
||||
submitConfig('quota').then();
|
||||
}}>保存额度设置</Form.Button>
|
||||
<Divider />
|
||||
<Header as="h3">
|
||||
<Header as='h3'>
|
||||
倍率设置
|
||||
</Header>
|
||||
<Form.Group widths="equal">
|
||||
<Form.Group widths='equal'>
|
||||
<Form.TextArea
|
||||
label="模型固定价格(一次调用消耗多少刀,优先级大于模型倍率)"
|
||||
name="ModelPrice"
|
||||
label='模型倍率'
|
||||
name='ModelRatio'
|
||||
onChange={handleInputChange}
|
||||
style={{ minHeight: 250, fontFamily: 'JetBrains Mono, Consolas' }}
|
||||
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"
|
||||
autoComplete='new-password'
|
||||
value={inputs.ModelRatio}
|
||||
placeholder="为一个 JSON 文本,键为模型名称,值为倍率"
|
||||
placeholder='为一个 JSON 文本,键为模型名称,值为倍率'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group widths="equal">
|
||||
<Form.Group widths='equal'>
|
||||
<Form.TextArea
|
||||
label="分组倍率"
|
||||
name="GroupRatio"
|
||||
label='补全倍率'
|
||||
name='CompletionRatio'
|
||||
onChange={handleInputChange}
|
||||
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}
|
||||
placeholder="为一个 JSON 文本,键为分组名称,值为倍率"
|
||||
placeholder='为一个 JSON 文本,键为分组名称,值为倍率'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={() => {
|
||||
@ -461,8 +383,7 @@ const OperationSetting = () => {
|
||||
</Form>
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
)
|
||||
;
|
||||
);
|
||||
};
|
||||
|
||||
export default OperationSetting;
|
||||
|
@ -1,16 +1,18 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Banner, Button, Col, Form, Row } from '@douyinfe/semi-ui';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Divider, Form, Grid, Header, Message, Modal } from 'semantic-ui-react';
|
||||
import { API, showError, showSuccess } from '../helpers';
|
||||
import { marked } from 'marked';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const OtherSetting = () => {
|
||||
let [inputs, setInputs] = useState({
|
||||
Footer: '',
|
||||
Notice: '',
|
||||
About: '',
|
||||
SystemName: '',
|
||||
Logo: '',
|
||||
Footer: '',
|
||||
About: '',
|
||||
HomePageContent: ''
|
||||
HomePageContent: '',
|
||||
Theme: ''
|
||||
});
|
||||
let [loading, setLoading] = useState(false);
|
||||
const [showUpdateModal, setShowUpdateModal] = useState(false);
|
||||
@ -19,6 +21,25 @@ const OtherSetting = () => {
|
||||
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) => {
|
||||
setLoading(true);
|
||||
@ -35,103 +56,37 @@ const OtherSetting = () => {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const [loadingInput, setLoadingInput] = useState({
|
||||
Notice: false,
|
||||
SystemName: false,
|
||||
Logo: false,
|
||||
HomePageContent: false,
|
||||
About: false,
|
||||
Footer: false
|
||||
});
|
||||
const handleInputChange = async (value, e) => {
|
||||
const name = e.target.id;
|
||||
const handleInputChange = async (e, { name, value }) => {
|
||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||
};
|
||||
|
||||
// 通用设置
|
||||
const formAPISettingGeneral = useRef();
|
||||
// 通用设置 - Notice
|
||||
const submitNotice = async () => {
|
||||
try {
|
||||
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 }));
|
||||
}
|
||||
await updateOption('Notice', inputs.Notice);
|
||||
};
|
||||
|
||||
// 个性化设置 - 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 () => {
|
||||
try {
|
||||
setLoadingInput((loadingInput) => ({ ...loadingInput, Footer: true }));
|
||||
await updateOption('Footer', inputs.Footer);
|
||||
showSuccess('页脚内容已更新');
|
||||
} catch (error) {
|
||||
console.error('页脚内容更新失败', error);
|
||||
showError('页脚内容更新失败');
|
||||
} finally {
|
||||
setLoadingInput((loadingInput) => ({ ...loadingInput, Footer: false }));
|
||||
}
|
||||
await updateOption('Footer', inputs.Footer);
|
||||
};
|
||||
|
||||
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 = () => {
|
||||
window.location =
|
||||
@ -153,125 +108,117 @@ const OtherSetting = () => {
|
||||
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 (
|
||||
<Row>
|
||||
<Col span={24}>
|
||||
{/* 通用设置 */}
|
||||
<Form values={inputs} getFormApi={formAPI => formAPISettingGeneral.current = formAPI}
|
||||
style={{ marginBottom: 15 }}>
|
||||
<Form.Section text={'通用设置'}>
|
||||
<Grid columns={1}>
|
||||
<Grid.Column>
|
||||
<Form loading={loading}>
|
||||
<Header as='h3'>通用设置</Header>
|
||||
<Form.Button onClick={checkUpdate}>检查更新</Form.Button>
|
||||
<Form.Group widths='equal'>
|
||||
<Form.TextArea
|
||||
label={'公告'}
|
||||
placeholder={'在此输入新的公告内容,支持 Markdown & HTML 代码'}
|
||||
field={'Notice'}
|
||||
label='公告'
|
||||
placeholder='在此输入新的公告内容,支持 Markdown & HTML 代码'
|
||||
value={inputs.Notice}
|
||||
name='Notice'
|
||||
onChange={handleInputChange}
|
||||
style={{ fontFamily: 'JetBrains Mono, Consolas' }}
|
||||
autosize={{ minRows: 6, maxRows: 12 }}
|
||||
style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
|
||||
/>
|
||||
<Button onClick={submitNotice} loading={loadingInput['Notice']}>设置公告</Button>
|
||||
</Form.Section>
|
||||
</Form.Group>
|
||||
<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 values={inputs} getFormApi={formAPI => formAPIPersonalization.current = formAPI}
|
||||
style={{ marginBottom: 15 }}>
|
||||
<Form.Section text={'个性化设置'}>
|
||||
<Form.Input
|
||||
label={'系统名称'}
|
||||
placeholder={'在此输入系统名称'}
|
||||
field={'SystemName'}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Button onClick={submitSystemName} loading={loadingInput['SystemName']}>设置系统名称</Button>
|
||||
<Form.Input
|
||||
label={'Logo 图片地址'}
|
||||
placeholder={'在此输入 Logo 图片地址'}
|
||||
field={'Logo'}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Button onClick={submitLogo} loading={loadingInput['Logo']}>设置 Logo</Button>
|
||||
<Form.TextArea
|
||||
label={'首页内容'}
|
||||
placeholder={'在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页。'}
|
||||
field={'HomePageContent'}
|
||||
onChange={handleInputChange}
|
||||
style={{ fontFamily: 'JetBrains Mono, Consolas' }}
|
||||
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>
|
||||
</Grid.Column>
|
||||
<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>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Divider, Form, Grid, Header, Message, Modal } from 'semantic-ui-react';
|
||||
import { API, removeTrailingSlash, showError, verifyJSON } from '../helpers';
|
||||
import { Button, Divider, Form, Grid, Header, Modal, Message } from 'semantic-ui-react';
|
||||
import { API, removeTrailingSlash, showError } from '../helpers';
|
||||
|
||||
const SystemSetting = () => {
|
||||
let [inputs, setInputs] = useState({
|
||||
@ -17,28 +17,19 @@ const SystemSetting = () => {
|
||||
SMTPFrom: '',
|
||||
SMTPToken: '',
|
||||
ServerAddress: '',
|
||||
EpayId: '',
|
||||
EpayKey: '',
|
||||
Price: 7.3,
|
||||
MinTopUp: 1,
|
||||
TopupGroupRatio: '',
|
||||
PayAddress: '',
|
||||
CustomCallbackAddress: '',
|
||||
Footer: '',
|
||||
WeChatAuthEnabled: '',
|
||||
WeChatServerAddress: '',
|
||||
WeChatServerToken: '',
|
||||
WeChatAccountQRCodeImageURL: '',
|
||||
MessagePusherAddress: '',
|
||||
MessagePusherToken: '',
|
||||
TurnstileCheckEnabled: '',
|
||||
TurnstileSiteKey: '',
|
||||
TurnstileSecretKey: '',
|
||||
RegisterEnabled: '',
|
||||
EmailDomainRestrictionEnabled: '',
|
||||
EmailDomainWhitelist: '',
|
||||
// telegram login
|
||||
TelegramOAuthEnabled: '',
|
||||
TelegramBotToken: '',
|
||||
TelegramBotName: ''
|
||||
EmailDomainWhitelist: ''
|
||||
});
|
||||
const [originInputs, setOriginInputs] = useState({});
|
||||
let [loading, setLoading] = useState(false);
|
||||
@ -52,9 +43,6 @@ const SystemSetting = () => {
|
||||
if (success) {
|
||||
let newInputs = {};
|
||||
data.forEach((item) => {
|
||||
if (item.key === 'TopupGroupRatio') {
|
||||
item.value = JSON.stringify(JSON.parse(item.value), null, 2);
|
||||
}
|
||||
newInputs[item.key] = item.value;
|
||||
});
|
||||
setInputs({
|
||||
@ -83,7 +71,6 @@ const SystemSetting = () => {
|
||||
case 'EmailVerificationEnabled':
|
||||
case 'GitHubOAuthEnabled':
|
||||
case 'WeChatAuthEnabled':
|
||||
case 'TelegramOAuthEnabled':
|
||||
case 'TurnstileCheckEnabled':
|
||||
case 'EmailDomainRestrictionEnabled':
|
||||
case 'RegisterEnabled':
|
||||
@ -101,9 +88,6 @@ const SystemSetting = () => {
|
||||
if (key === 'EmailDomainWhitelist') {
|
||||
value = value.split(',');
|
||||
}
|
||||
if (key === 'Price') {
|
||||
value = parseFloat(value);
|
||||
}
|
||||
setInputs((inputs) => ({
|
||||
...inputs, [key]: value
|
||||
}));
|
||||
@ -123,10 +107,6 @@ const SystemSetting = () => {
|
||||
name === 'Notice' ||
|
||||
name.startsWith('SMTP') ||
|
||||
name === 'ServerAddress' ||
|
||||
name === 'EpayId' ||
|
||||
name === 'EpayKey' ||
|
||||
name === 'Price' ||
|
||||
name === 'PayAddress' ||
|
||||
name === 'GitHubClientId' ||
|
||||
name === 'GitHubClientSecret' ||
|
||||
name === 'WeChatServerAddress' ||
|
||||
@ -134,10 +114,7 @@ const SystemSetting = () => {
|
||||
name === 'WeChatAccountQRCodeImageURL' ||
|
||||
name === 'TurnstileSiteKey' ||
|
||||
name === 'TurnstileSecretKey' ||
|
||||
name === 'EmailDomainWhitelist' ||
|
||||
name === 'TopupGroupRatio' ||
|
||||
name === 'TelegramBotToken' ||
|
||||
name === 'TelegramBotName'
|
||||
name === 'EmailDomainWhitelist'
|
||||
) {
|
||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||
} else {
|
||||
@ -150,29 +127,6 @@ const SystemSetting = () => {
|
||||
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 () => {
|
||||
if (originInputs['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 () => {
|
||||
if (originInputs['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 () => {
|
||||
if (originInputs['TurnstileSiteKey'] !== inputs.TurnstileSiteKey) {
|
||||
await updateOption('TurnstileSiteKey', inputs.TurnstileSiteKey);
|
||||
@ -267,27 +230,27 @@ const SystemSetting = () => {
|
||||
setRestrictedDomainInput('');
|
||||
setInputs({
|
||||
...inputs,
|
||||
EmailDomainWhitelist: [...localDomainList, restrictedDomainInput]
|
||||
EmailDomainWhitelist: [...localDomainList, restrictedDomainInput],
|
||||
});
|
||||
setEmailDomainWhitelist([...EmailDomainWhitelist, {
|
||||
key: restrictedDomainInput,
|
||||
text: restrictedDomainInput,
|
||||
value: restrictedDomainInput
|
||||
value: restrictedDomainInput,
|
||||
}]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid columns={1}>
|
||||
<Grid.Column>
|
||||
<Form loading={loading}>
|
||||
<Header as="h3">通用设置</Header>
|
||||
<Form.Group widths="equal">
|
||||
<Header as='h3'>通用设置</Header>
|
||||
<Form.Group widths='equal'>
|
||||
<Form.Input
|
||||
label="服务器地址"
|
||||
placeholder="例如:https://yourdomain.com"
|
||||
label='服务器地址'
|
||||
placeholder='例如:https://yourdomain.com'
|
||||
value={inputs.ServerAddress}
|
||||
name="ServerAddress"
|
||||
name='ServerAddress'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
@ -295,77 +258,12 @@ const SystemSetting = () => {
|
||||
更新服务器地址
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<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>
|
||||
<Header as='h3'>配置登录注册</Header>
|
||||
<Form.Group inline>
|
||||
<Form.Checkbox
|
||||
checked={inputs.PasswordLoginEnabled === 'true'}
|
||||
label="允许通过密码进行登录"
|
||||
name="PasswordLoginEnabled"
|
||||
label='允许通过密码进行登录'
|
||||
name='PasswordLoginEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
{
|
||||
@ -383,7 +281,7 @@ const SystemSetting = () => {
|
||||
<Modal.Actions>
|
||||
<Button onClick={() => setShowPasswordWarningModal(false)}>取消</Button>
|
||||
<Button
|
||||
color="yellow"
|
||||
color='yellow'
|
||||
onClick={async () => {
|
||||
setShowPasswordWarningModal(false);
|
||||
await updateOption('PasswordLoginEnabled', 'false');
|
||||
@ -396,80 +294,74 @@ const SystemSetting = () => {
|
||||
}
|
||||
<Form.Checkbox
|
||||
checked={inputs.PasswordRegisterEnabled === 'true'}
|
||||
label="允许通过密码进行注册"
|
||||
name="PasswordRegisterEnabled"
|
||||
label='允许通过密码进行注册'
|
||||
name='PasswordRegisterEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.EmailVerificationEnabled === 'true'}
|
||||
label="通过密码注册时需要进行邮箱验证"
|
||||
name="EmailVerificationEnabled"
|
||||
label='通过密码注册时需要进行邮箱验证'
|
||||
name='EmailVerificationEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.GitHubOAuthEnabled === 'true'}
|
||||
label="允许通过 GitHub 账户登录 & 注册"
|
||||
name="GitHubOAuthEnabled"
|
||||
label='允许通过 GitHub 账户登录 & 注册'
|
||||
name='GitHubOAuthEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.WeChatAuthEnabled === 'true'}
|
||||
label="允许通过微信登录 & 注册"
|
||||
name="WeChatAuthEnabled"
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.TelegramOAuthEnabled === 'true'}
|
||||
label="允许通过 Telegram 进行登录"
|
||||
name="TelegramOAuthEnabled"
|
||||
label='允许通过微信登录 & 注册'
|
||||
name='WeChatAuthEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group inline>
|
||||
<Form.Checkbox
|
||||
checked={inputs.RegisterEnabled === 'true'}
|
||||
label="允许新用户注册(此项为否时,新用户将无法以任何方式进行注册)"
|
||||
name="RegisterEnabled"
|
||||
label='允许新用户注册(此项为否时,新用户将无法以任何方式进行注册)'
|
||||
name='RegisterEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.TurnstileCheckEnabled === 'true'}
|
||||
label="启用 Turnstile 用户校验"
|
||||
name="TurnstileCheckEnabled"
|
||||
label='启用 Turnstile 用户校验'
|
||||
name='TurnstileCheckEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Divider />
|
||||
<Header as="h3">
|
||||
<Header as='h3'>
|
||||
配置邮箱域名白名单
|
||||
<Header.Subheader>用以防止恶意用户利用临时邮箱批量注册</Header.Subheader>
|
||||
</Header>
|
||||
<Form.Group widths={3}>
|
||||
<Form.Checkbox
|
||||
label="启用邮箱域名白名单"
|
||||
name="EmailDomainRestrictionEnabled"
|
||||
label='启用邮箱域名白名单'
|
||||
name='EmailDomainRestrictionEnabled'
|
||||
onChange={handleInputChange}
|
||||
checked={inputs.EmailDomainRestrictionEnabled === 'true'}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group widths={2}>
|
||||
<Form.Dropdown
|
||||
label="允许的邮箱域名"
|
||||
placeholder="允许的邮箱域名"
|
||||
name="EmailDomainWhitelist"
|
||||
label='允许的邮箱域名'
|
||||
placeholder='允许的邮箱域名'
|
||||
name='EmailDomainWhitelist'
|
||||
required
|
||||
fluid
|
||||
multiple
|
||||
selection
|
||||
onChange={handleInputChange}
|
||||
value={inputs.EmailDomainWhitelist}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
options={EmailDomainWhitelist}
|
||||
/>
|
||||
<Form.Input
|
||||
label="添加新的允许的邮箱域名"
|
||||
label='添加新的允许的邮箱域名'
|
||||
action={
|
||||
<Button type="button" onClick={() => {
|
||||
<Button type='button' onClick={() => {
|
||||
submitNewRestrictedDomain();
|
||||
}}>填入</Button>
|
||||
}
|
||||
@ -478,8 +370,8 @@ const SystemSetting = () => {
|
||||
submitNewRestrictedDomain();
|
||||
}
|
||||
}}
|
||||
autoComplete="new-password"
|
||||
placeholder="输入新的允许的邮箱域名"
|
||||
autoComplete='new-password'
|
||||
placeholder='输入新的允许的邮箱域名'
|
||||
value={restrictedDomainInput}
|
||||
onChange={(e, { value }) => {
|
||||
setRestrictedDomainInput(value);
|
||||
@ -488,62 +380,62 @@ const SystemSetting = () => {
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitEmailDomainWhitelist}>保存邮箱域名白名单设置</Form.Button>
|
||||
<Divider />
|
||||
<Header as="h3">
|
||||
<Header as='h3'>
|
||||
配置 SMTP
|
||||
<Header.Subheader>用以支持系统的邮件发送</Header.Subheader>
|
||||
</Header>
|
||||
<Form.Group widths={3}>
|
||||
<Form.Input
|
||||
label="SMTP 服务器地址"
|
||||
name="SMTPServer"
|
||||
label='SMTP 服务器地址'
|
||||
name='SMTPServer'
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
value={inputs.SMTPServer}
|
||||
placeholder="例如:smtp.qq.com"
|
||||
placeholder='例如:smtp.qq.com'
|
||||
/>
|
||||
<Form.Input
|
||||
label="SMTP 端口"
|
||||
name="SMTPPort"
|
||||
label='SMTP 端口'
|
||||
name='SMTPPort'
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
value={inputs.SMTPPort}
|
||||
placeholder="默认: 587"
|
||||
placeholder='默认: 587'
|
||||
/>
|
||||
<Form.Input
|
||||
label="SMTP 账户"
|
||||
name="SMTPAccount"
|
||||
label='SMTP 账户'
|
||||
name='SMTPAccount'
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
value={inputs.SMTPAccount}
|
||||
placeholder="通常是邮箱地址"
|
||||
placeholder='通常是邮箱地址'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group widths={3}>
|
||||
<Form.Input
|
||||
label="SMTP 发送者邮箱"
|
||||
name="SMTPFrom"
|
||||
label='SMTP 发送者邮箱'
|
||||
name='SMTPFrom'
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
value={inputs.SMTPFrom}
|
||||
placeholder="通常和邮箱地址保持一致"
|
||||
placeholder='通常和邮箱地址保持一致'
|
||||
/>
|
||||
<Form.Input
|
||||
label="SMTP 访问凭证"
|
||||
name="SMTPToken"
|
||||
label='SMTP 访问凭证'
|
||||
name='SMTPToken'
|
||||
onChange={handleInputChange}
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
type='password'
|
||||
autoComplete='new-password'
|
||||
checked={inputs.RegisterEnabled === 'true'}
|
||||
placeholder="敏感信息不会发送到前端显示"
|
||||
placeholder='敏感信息不会发送到前端显示'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitSMTP}>保存 SMTP 设置</Form.Button>
|
||||
<Divider />
|
||||
<Header as="h3">
|
||||
<Header as='h3'>
|
||||
配置 GitHub OAuth App
|
||||
<Header.Subheader>
|
||||
用以支持通过 GitHub 进行登录注册,
|
||||
<a href="https://github.com/settings/developers" target="_blank" rel="noreferrer">
|
||||
<a href='https://github.com/settings/developers' target='_blank'>
|
||||
点击此处
|
||||
</a>
|
||||
管理你的 GitHub OAuth App
|
||||
@ -556,34 +448,34 @@ const SystemSetting = () => {
|
||||
</Message>
|
||||
<Form.Group widths={3}>
|
||||
<Form.Input
|
||||
label="GitHub Client ID"
|
||||
name="GitHubClientId"
|
||||
label='GitHub Client ID'
|
||||
name='GitHubClientId'
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
value={inputs.GitHubClientId}
|
||||
placeholder="输入你注册的 GitHub OAuth APP 的 ID"
|
||||
placeholder='输入你注册的 GitHub OAuth APP 的 ID'
|
||||
/>
|
||||
<Form.Input
|
||||
label="GitHub Client Secret"
|
||||
name="GitHubClientSecret"
|
||||
label='GitHub Client Secret'
|
||||
name='GitHubClientSecret'
|
||||
onChange={handleInputChange}
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
type='password'
|
||||
autoComplete='new-password'
|
||||
value={inputs.GitHubClientSecret}
|
||||
placeholder="敏感信息不会发送到前端显示"
|
||||
placeholder='敏感信息不会发送到前端显示'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitGitHubOAuth}>
|
||||
保存 GitHub OAuth 设置
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as="h3">
|
||||
<Header as='h3'>
|
||||
配置 WeChat Server
|
||||
<Header.Subheader>
|
||||
用以支持通过微信进行登录注册,
|
||||
<a
|
||||
href="https://github.com/songquanpeng/wechat-server"
|
||||
target="_blank" rel="noreferrer"
|
||||
href='https://github.com/songquanpeng/wechat-server'
|
||||
target='_blank'
|
||||
>
|
||||
点击此处
|
||||
</a>
|
||||
@ -592,61 +484,76 @@ const SystemSetting = () => {
|
||||
</Header>
|
||||
<Form.Group widths={3}>
|
||||
<Form.Input
|
||||
label="WeChat Server 服务器地址"
|
||||
name="WeChatServerAddress"
|
||||
placeholder="例如:https://yourdomain.com"
|
||||
label='WeChat Server 服务器地址'
|
||||
name='WeChatServerAddress'
|
||||
placeholder='例如:https://yourdomain.com'
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
value={inputs.WeChatServerAddress}
|
||||
/>
|
||||
<Form.Input
|
||||
label="WeChat Server 访问凭证"
|
||||
name="WeChatServerToken"
|
||||
type="password"
|
||||
label='WeChat Server 访问凭证'
|
||||
name='WeChatServerToken'
|
||||
type='password'
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
value={inputs.WeChatServerToken}
|
||||
placeholder="敏感信息不会发送到前端显示"
|
||||
placeholder='敏感信息不会发送到前端显示'
|
||||
/>
|
||||
<Form.Input
|
||||
label="微信公众号二维码图片链接"
|
||||
name="WeChatAccountQRCodeImageURL"
|
||||
label='微信公众号二维码图片链接'
|
||||
name='WeChatAccountQRCodeImageURL'
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
value={inputs.WeChatAccountQRCodeImageURL}
|
||||
placeholder="输入一个图片链接"
|
||||
placeholder='输入一个图片链接'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitWeChat}>
|
||||
保存 WeChat Server 设置
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as="h3">配置 Telegram 登录</Header>
|
||||
<Form.Group inline>
|
||||
<Header as='h3'>
|
||||
配置 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
|
||||
label="Telegram Bot Token"
|
||||
name="TelegramBotToken"
|
||||
label='Message Pusher 推送地址'
|
||||
name='MessagePusherAddress'
|
||||
placeholder='例如:https://msgpusher.com/push/your_username'
|
||||
onChange={handleInputChange}
|
||||
value={inputs.TelegramBotToken}
|
||||
placeholder="输入你的 Telegram Bot Token"
|
||||
autoComplete='new-password'
|
||||
value={inputs.MessagePusherAddress}
|
||||
/>
|
||||
<Form.Input
|
||||
label="Telegram Bot 名称"
|
||||
name="TelegramBotName"
|
||||
label='Message Pusher 访问凭证'
|
||||
name='MessagePusherToken'
|
||||
type='password'
|
||||
onChange={handleInputChange}
|
||||
value={inputs.TelegramBotName}
|
||||
placeholder="输入你的 Telegram Bot 名称"
|
||||
autoComplete='new-password'
|
||||
value={inputs.MessagePusherToken}
|
||||
placeholder='敏感信息不会发送到前端显示'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitTelegramSettings}>
|
||||
保存 Telegram 登录设置
|
||||
<Form.Button onClick={submitMessagePusher}>
|
||||
保存 Message Pusher 设置
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as="h3">
|
||||
<Header as='h3'>
|
||||
配置 Turnstile
|
||||
<Header.Subheader>
|
||||
用以支持用户校验,
|
||||
<a href="https://dash.cloudflare.com/" target="_blank" rel="noreferrer">
|
||||
<a href='https://dash.cloudflare.com/' target='_blank'>
|
||||
点击此处
|
||||
</a>
|
||||
管理你的 Turnstile Sites,推荐选择 Invisible Widget Type
|
||||
@ -654,21 +561,21 @@ const SystemSetting = () => {
|
||||
</Header>
|
||||
<Form.Group widths={3}>
|
||||
<Form.Input
|
||||
label="Turnstile Site Key"
|
||||
name="TurnstileSiteKey"
|
||||
label='Turnstile Site Key'
|
||||
name='TurnstileSiteKey'
|
||||
onChange={handleInputChange}
|
||||
autoComplete="new-password"
|
||||
autoComplete='new-password'
|
||||
value={inputs.TurnstileSiteKey}
|
||||
placeholder="输入你注册的 Turnstile Site Key"
|
||||
placeholder='输入你注册的 Turnstile Site Key'
|
||||
/>
|
||||
<Form.Input
|
||||
label="Turnstile Secret Key"
|
||||
name="TurnstileSecretKey"
|
||||
label='Turnstile Secret Key'
|
||||
name='TurnstileSecretKey'
|
||||
onChange={handleInputChange}
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
type='password'
|
||||
autoComplete='new-password'
|
||||
value={inputs.TurnstileSecretKey}
|
||||
placeholder="敏感信息不会发送到前端显示"
|
||||
placeholder='敏感信息不会发送到前端显示'
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitTurnstile}>
|
||||
|
Loading…
Reference in New Issue
Block a user