mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-09-17 09:16:36 +08:00
feat: i18n support
This commit is contained in:
parent
e7ea7c866f
commit
d0965050a9
@ -667,6 +667,64 @@
|
|||||||
"save": "Save General Settings"
|
"save": "Save General Settings"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"other": {
|
||||||
|
"notice": {
|
||||||
|
"title": "Notice Settings",
|
||||||
|
"content": "Notice Content",
|
||||||
|
"content_placeholder": "Enter new notice content here, supports Markdown & HTML code",
|
||||||
|
"buttons": {
|
||||||
|
"save": "Save Notice"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"system": {
|
||||||
|
"title": "System Settings",
|
||||||
|
"name": "System Name",
|
||||||
|
"name_placeholder": "Please enter system name",
|
||||||
|
"logo": "Logo Image URL",
|
||||||
|
"logo_placeholder": "Enter Logo image URL here",
|
||||||
|
"theme": {
|
||||||
|
"title": "Theme Name",
|
||||||
|
"link": "Available Themes",
|
||||||
|
"placeholder": "Please enter theme name"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"save_name": "Set System Name",
|
||||||
|
"save_logo": "Set Logo",
|
||||||
|
"save_theme": "Set Theme (Restart Required)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"title": "Content Settings",
|
||||||
|
"homepage": {
|
||||||
|
"title": "Homepage Content",
|
||||||
|
"placeholder": "Enter homepage content here, supports Markdown & HTML code. Status information will not be shown after setting. If a link is entered, it will be used as the src attribute of an iframe, allowing you to set any webpage as homepage."
|
||||||
|
},
|
||||||
|
"about": {
|
||||||
|
"title": "About System",
|
||||||
|
"description": "You can set about content in settings page, supports HTML & Markdown",
|
||||||
|
"repository": "Project Repository:",
|
||||||
|
"loading_failed": "Failed to load about content..."
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"title": "Footer",
|
||||||
|
"placeholder": "Enter new footer here, leave empty to use default footer, supports HTML code"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"save_homepage": "Save Homepage Content",
|
||||||
|
"save_about": "Save About",
|
||||||
|
"save_footer": "Set Footer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"copyright": {
|
||||||
|
"notice": "Removing One API's copyright notice requires authorization. Project maintenance requires significant effort, if this project is meaningful to you, please actively support it."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"built_by": "built by",
|
||||||
|
"built_by_name": "JustSong",
|
||||||
|
"license": ", source code is licensed under the",
|
||||||
|
"mit": "MIT License"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -667,6 +667,68 @@
|
|||||||
"save": "保存通用设置"
|
"save": "保存通用设置"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"other": {
|
||||||
|
"notice": {
|
||||||
|
"title": "公告设置",
|
||||||
|
"content": "公告内容",
|
||||||
|
"content_placeholder": "在此输入新的公告内容,支持 Markdown & HTML 代码",
|
||||||
|
"buttons": {
|
||||||
|
"save": "保存公告"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"system": {
|
||||||
|
"title": "系统设置",
|
||||||
|
"name": "系统名称",
|
||||||
|
"name_placeholder": "请输入系统名称",
|
||||||
|
"logo": "Logo 图片地址",
|
||||||
|
"logo_placeholder": "在此输入 Logo 图片地址",
|
||||||
|
"theme": {
|
||||||
|
"title": "主题名称",
|
||||||
|
"link": "当前可用主题",
|
||||||
|
"placeholder": "请输入主题名称"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"save_name": "设置系统名称",
|
||||||
|
"save_logo": "设置 Logo",
|
||||||
|
"save_theme": "设置主题(重启生效)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"title": "内容设置",
|
||||||
|
"homepage": {
|
||||||
|
"title": "首页内容",
|
||||||
|
"placeholder": "在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页。"
|
||||||
|
},
|
||||||
|
"about": {
|
||||||
|
"title": "关于",
|
||||||
|
"placeholder": "在此输入新的关于内容,支持 Markdown & HTML 代码。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为关于页面。"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"title": "页脚",
|
||||||
|
"placeholder": "在此输入新的页脚,留空则使用默认页脚,支持 HTML 代码"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"save_homepage": "保存首页内容",
|
||||||
|
"save_about": "保存关于",
|
||||||
|
"save_footer": "设置页脚"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"copyright": {
|
||||||
|
"notice": "移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目。"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"about": {
|
||||||
|
"title": "关于系统",
|
||||||
|
"description": "可在设置页面设置关于内容,支持 HTML & Markdown",
|
||||||
|
"repository": "项目仓库地址:",
|
||||||
|
"loading_failed": "加载关于内容失败..."
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"built_by": "由",
|
||||||
|
"built_by_name": "JustSong",
|
||||||
|
"license": "构建,源代码遵循",
|
||||||
|
"mit": "MIT 协议"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Container, Segment } from 'semantic-ui-react';
|
import { Container, Segment } from 'semantic-ui-react';
|
||||||
import { getFooterHTML, getSystemName } from '../helpers';
|
import { getFooterHTML, getSystemName } from '../helpers';
|
||||||
|
|
||||||
const Footer = () => {
|
const Footer = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const systemName = getSystemName();
|
const systemName = getSystemName();
|
||||||
const [footer, setFooter] = useState(getFooterHTML());
|
const [footer, setFooter] = useState(getFooterHTML());
|
||||||
let remainCheckTimes = 5;
|
let remainCheckTimes = 5;
|
||||||
@ -40,13 +41,13 @@ const Footer = () => {
|
|||||||
<a href='https://github.com/songquanpeng/one-api' target='_blank'>
|
<a href='https://github.com/songquanpeng/one-api' target='_blank'>
|
||||||
{systemName} {process.env.REACT_APP_VERSION}{' '}
|
{systemName} {process.env.REACT_APP_VERSION}{' '}
|
||||||
</a>
|
</a>
|
||||||
由{' '}
|
{t('footer.built_by')}{' '}
|
||||||
<a href='https://github.com/songquanpeng' target='_blank'>
|
<a href='https://github.com/songquanpeng' target='_blank'>
|
||||||
JustSong
|
{t('footer.built_by_name')}
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
构建,源代码遵循{' '}
|
{t('footer.license')}{' '}
|
||||||
<a href='https://opensource.org/licenses/mit-license.php'>
|
<a href='https://opensource.org/licenses/mit-license.php'>
|
||||||
MIT 协议
|
{t('footer.mit')}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Divider, Form, Grid, Header } from 'semantic-ui-react';
|
import { Divider, Form, Grid, Header } from 'semantic-ui-react';
|
||||||
import { API, showError, showSuccess, timestamp2string, verifyJSON } from '../helpers';
|
import {
|
||||||
|
API,
|
||||||
|
showError,
|
||||||
|
showSuccess,
|
||||||
|
timestamp2string,
|
||||||
|
verifyJSON,
|
||||||
|
} from '../helpers';
|
||||||
|
|
||||||
const OperationSetting = () => {
|
const OperationSetting = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -25,11 +31,13 @@ const OperationSetting = () => {
|
|||||||
DisplayInCurrencyEnabled: '',
|
DisplayInCurrencyEnabled: '',
|
||||||
DisplayTokenStatEnabled: '',
|
DisplayTokenStatEnabled: '',
|
||||||
ApproximateTokenEnabled: '',
|
ApproximateTokenEnabled: '',
|
||||||
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 getOptions = async () => {
|
const getOptions = async () => {
|
||||||
const res = await API.get('/api/option/');
|
const res = await API.get('/api/option/');
|
||||||
@ -37,7 +45,11 @@ const OperationSetting = () => {
|
|||||||
if (success) {
|
if (success) {
|
||||||
let newInputs = {};
|
let newInputs = {};
|
||||||
data.forEach((item) => {
|
data.forEach((item) => {
|
||||||
if (item.key === 'ModelRatio' || item.key === 'GroupRatio' || item.key === 'CompletionRatio') {
|
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 === '{}') {
|
if (item.value === '{}') {
|
||||||
@ -63,7 +75,7 @@ const OperationSetting = () => {
|
|||||||
}
|
}
|
||||||
const res = await API.put('/api/option/', {
|
const res = await API.put('/api/option/', {
|
||||||
key,
|
key,
|
||||||
value
|
value,
|
||||||
});
|
});
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -85,11 +97,22 @@ const OperationSetting = () => {
|
|||||||
const submitConfig = async (group) => {
|
const submitConfig = async (group) => {
|
||||||
switch (group) {
|
switch (group) {
|
||||||
case 'monitor':
|
case 'monitor':
|
||||||
if (originInputs['ChannelDisableThreshold'] !== inputs.ChannelDisableThreshold) {
|
if (
|
||||||
await updateOption('ChannelDisableThreshold', inputs.ChannelDisableThreshold);
|
originInputs['ChannelDisableThreshold'] !==
|
||||||
|
inputs.ChannelDisableThreshold
|
||||||
|
) {
|
||||||
|
await updateOption(
|
||||||
|
'ChannelDisableThreshold',
|
||||||
|
inputs.ChannelDisableThreshold
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (originInputs['QuotaRemindThreshold'] !== inputs.QuotaRemindThreshold) {
|
if (
|
||||||
await updateOption('QuotaRemindThreshold', inputs.QuotaRemindThreshold);
|
originInputs['QuotaRemindThreshold'] !== inputs.QuotaRemindThreshold
|
||||||
|
) {
|
||||||
|
await updateOption(
|
||||||
|
'QuotaRemindThreshold',
|
||||||
|
inputs.QuotaRemindThreshold
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'ratio':
|
case 'ratio':
|
||||||
@ -148,7 +171,9 @@ const OperationSetting = () => {
|
|||||||
|
|
||||||
const deleteHistoryLogs = async () => {
|
const deleteHistoryLogs = async () => {
|
||||||
console.log(inputs);
|
console.log(inputs);
|
||||||
const res = await API.delete(`/api/log/?target_timestamp=${Date.parse(historyTimestamp) / 1000}`);
|
const res = await API.delete(
|
||||||
|
`/api/log/?target_timestamp=${Date.parse(historyTimestamp) / 1000}`
|
||||||
|
);
|
||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
showSuccess(`${data} 条日志已清理!`);
|
showSuccess(`${data} 条日志已清理!`);
|
||||||
@ -161,9 +186,7 @@ const OperationSetting = () => {
|
|||||||
<Grid columns={1}>
|
<Grid columns={1}>
|
||||||
<Grid.Column>
|
<Grid.Column>
|
||||||
<Form loading={loading}>
|
<Form loading={loading}>
|
||||||
<Header as='h3'>
|
<Header as='h3'>{t('setting.operation.quota.title')}</Header>
|
||||||
{t('setting.operation.quota.title')}
|
|
||||||
</Header>
|
|
||||||
<Form.Group widths='equal'>
|
<Form.Group widths='equal'>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label={t('setting.operation.quota.new_user')}
|
label={t('setting.operation.quota.new_user')}
|
||||||
@ -193,7 +216,9 @@ const OperationSetting = () => {
|
|||||||
value={inputs.QuotaForInviter}
|
value={inputs.QuotaForInviter}
|
||||||
type='number'
|
type='number'
|
||||||
min='0'
|
min='0'
|
||||||
placeholder={t('setting.operation.quota.inviter_reward_placeholder')}
|
placeholder={t(
|
||||||
|
'setting.operation.quota.inviter_reward_placeholder'
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label={t('setting.operation.quota.invitee_reward')}
|
label={t('setting.operation.quota.invitee_reward')}
|
||||||
@ -203,18 +228,20 @@ const OperationSetting = () => {
|
|||||||
value={inputs.QuotaForInvitee}
|
value={inputs.QuotaForInvitee}
|
||||||
type='number'
|
type='number'
|
||||||
min='0'
|
min='0'
|
||||||
placeholder={t('setting.operation.quota.invitee_reward_placeholder')}
|
placeholder={t(
|
||||||
|
'setting.operation.quota.invitee_reward_placeholder'
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={() => {
|
<Form.Button
|
||||||
submitConfig('quota').then();
|
onClick={() => {
|
||||||
}}>
|
submitConfig('quota').then();
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t('setting.operation.quota.buttons.save')}
|
{t('setting.operation.quota.buttons.save')}
|
||||||
</Form.Button>
|
</Form.Button>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Header as='h3'>
|
<Header as='h3'>{t('setting.operation.ratio.title')}</Header>
|
||||||
{t('setting.operation.ratio.title')}
|
|
||||||
</Header>
|
|
||||||
<Form.Group widths='equal'>
|
<Form.Group widths='equal'>
|
||||||
<Form.TextArea
|
<Form.TextArea
|
||||||
label={t('setting.operation.ratio.model.title')}
|
label={t('setting.operation.ratio.model.title')}
|
||||||
@ -248,9 +275,11 @@ const OperationSetting = () => {
|
|||||||
placeholder={t('setting.operation.ratio.group.placeholder')}
|
placeholder={t('setting.operation.ratio.group.placeholder')}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={() => {
|
<Form.Button
|
||||||
submitConfig('ratio').then();
|
onClick={() => {
|
||||||
}}>
|
submitConfig('ratio').then();
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t('setting.operation.ratio.buttons.save')}
|
{t('setting.operation.ratio.buttons.save')}
|
||||||
</Form.Button>
|
</Form.Button>
|
||||||
<Divider />
|
<Divider />
|
||||||
@ -274,9 +303,11 @@ const OperationSetting = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={() => {
|
<Form.Button
|
||||||
deleteHistoryLogs().then();
|
onClick={() => {
|
||||||
}}>
|
deleteHistoryLogs().then();
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t('setting.operation.log.buttons.clean')}
|
{t('setting.operation.log.buttons.clean')}
|
||||||
</Form.Button>
|
</Form.Button>
|
||||||
|
|
||||||
@ -291,7 +322,9 @@ const OperationSetting = () => {
|
|||||||
value={inputs.ChannelDisableThreshold}
|
value={inputs.ChannelDisableThreshold}
|
||||||
type='number'
|
type='number'
|
||||||
min='0'
|
min='0'
|
||||||
placeholder={t('setting.operation.monitor.max_response_time_placeholder')}
|
placeholder={t(
|
||||||
|
'setting.operation.monitor.max_response_time_placeholder'
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label={t('setting.operation.monitor.quota_reminder')}
|
label={t('setting.operation.monitor.quota_reminder')}
|
||||||
@ -301,7 +334,9 @@ const OperationSetting = () => {
|
|||||||
value={inputs.QuotaRemindThreshold}
|
value={inputs.QuotaRemindThreshold}
|
||||||
type='number'
|
type='number'
|
||||||
min='0'
|
min='0'
|
||||||
placeholder={t('setting.operation.monitor.quota_reminder_placeholder')}
|
placeholder={t(
|
||||||
|
'setting.operation.monitor.quota_reminder_placeholder'
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Group inline>
|
<Form.Group inline>
|
||||||
@ -318,9 +353,11 @@ const OperationSetting = () => {
|
|||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={() => {
|
<Form.Button
|
||||||
submitConfig('monitor').then();
|
onClick={() => {
|
||||||
}}>
|
submitConfig('monitor').then();
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t('setting.operation.monitor.buttons.save')}
|
{t('setting.operation.monitor.buttons.save')}
|
||||||
</Form.Button>
|
</Form.Button>
|
||||||
|
|
||||||
@ -334,7 +371,9 @@ const OperationSetting = () => {
|
|||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
value={inputs.TopUpLink}
|
value={inputs.TopUpLink}
|
||||||
type='link'
|
type='link'
|
||||||
placeholder={t('setting.operation.general.topup_link_placeholder')}
|
placeholder={t(
|
||||||
|
'setting.operation.general.topup_link_placeholder'
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label={t('setting.operation.general.chat_link')}
|
label={t('setting.operation.general.chat_link')}
|
||||||
@ -353,7 +392,9 @@ const OperationSetting = () => {
|
|||||||
value={inputs.QuotaPerUnit}
|
value={inputs.QuotaPerUnit}
|
||||||
type='number'
|
type='number'
|
||||||
step='0.01'
|
step='0.01'
|
||||||
placeholder={t('setting.operation.general.quota_per_unit_placeholder')}
|
placeholder={t(
|
||||||
|
'setting.operation.general.quota_per_unit_placeholder'
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label={t('setting.operation.general.retry_times')}
|
label={t('setting.operation.general.retry_times')}
|
||||||
@ -364,7 +405,9 @@ const OperationSetting = () => {
|
|||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
value={inputs.RetryTimes}
|
value={inputs.RetryTimes}
|
||||||
placeholder={t('setting.operation.general.retry_times_placeholder')}
|
placeholder={t(
|
||||||
|
'setting.operation.general.retry_times_placeholder'
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Group inline>
|
<Form.Group inline>
|
||||||
@ -387,9 +430,11 @@ const OperationSetting = () => {
|
|||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={() => {
|
<Form.Button
|
||||||
submitConfig('general').then();
|
onClick={() => {
|
||||||
}}>
|
submitConfig('general').then();
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t('setting.operation.general.buttons.save')}
|
{t('setting.operation.general.buttons.save')}
|
||||||
</Form.Button>
|
</Form.Button>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -1,10 +1,20 @@
|
|||||||
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 { useTranslation } from 'react-i18next';
|
||||||
import { API, showError, showSuccess } from '../helpers';
|
import {
|
||||||
import { marked } from 'marked';
|
Button,
|
||||||
|
Divider,
|
||||||
|
Form,
|
||||||
|
Grid,
|
||||||
|
Header,
|
||||||
|
Message,
|
||||||
|
Modal,
|
||||||
|
} from 'semantic-ui-react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { API, showError, showSuccess, verifyJSON } from '../helpers';
|
||||||
|
import { marked } from 'marked';
|
||||||
|
|
||||||
const OtherSetting = () => {
|
const OtherSetting = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
let [inputs, setInputs] = useState({
|
let [inputs, setInputs] = useState({
|
||||||
Footer: '',
|
Footer: '',
|
||||||
Notice: '',
|
Notice: '',
|
||||||
@ -12,13 +22,13 @@ const OtherSetting = () => {
|
|||||||
SystemName: '',
|
SystemName: '',
|
||||||
Logo: '',
|
Logo: '',
|
||||||
HomePageContent: '',
|
HomePageContent: '',
|
||||||
Theme: ''
|
Theme: '',
|
||||||
});
|
});
|
||||||
let [loading, setLoading] = useState(false);
|
let [loading, setLoading] = useState(false);
|
||||||
const [showUpdateModal, setShowUpdateModal] = useState(false);
|
const [showUpdateModal, setShowUpdateModal] = useState(false);
|
||||||
const [updateData, setUpdateData] = useState({
|
const [updateData, setUpdateData] = useState({
|
||||||
tag_name: '',
|
tag_name: '',
|
||||||
content: ''
|
content: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const getOptions = async () => {
|
const getOptions = async () => {
|
||||||
@ -45,7 +55,7 @@ const OtherSetting = () => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
const res = await API.put('/api/option/', {
|
const res = await API.put('/api/option/', {
|
||||||
key,
|
key,
|
||||||
value
|
value,
|
||||||
});
|
});
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -64,10 +74,6 @@ const OtherSetting = () => {
|
|||||||
await updateOption('Notice', inputs.Notice);
|
await updateOption('Notice', inputs.Notice);
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitFooter = async () => {
|
|
||||||
await updateOption('Footer', inputs.Footer);
|
|
||||||
};
|
|
||||||
|
|
||||||
const submitSystemName = async () => {
|
const submitSystemName = async () => {
|
||||||
await updateOption('SystemName', inputs.SystemName);
|
await updateOption('SystemName', inputs.SystemName);
|
||||||
};
|
};
|
||||||
@ -89,8 +95,7 @@ const OtherSetting = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const openGitHubRelease = () => {
|
const openGitHubRelease = () => {
|
||||||
window.location =
|
window.location = 'https://github.com/songquanpeng/one-api/releases/latest';
|
||||||
'https://github.com/songquanpeng/one-api/releases/latest';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkUpdate = async () => {
|
const checkUpdate = async () => {
|
||||||
@ -103,7 +108,7 @@ const OtherSetting = () => {
|
|||||||
} else {
|
} else {
|
||||||
setUpdateData({
|
setUpdateData({
|
||||||
tag_name: tag_name,
|
tag_name: tag_name,
|
||||||
content: marked.parse(body)
|
content: marked.parse(body),
|
||||||
});
|
});
|
||||||
setShowUpdateModal(true);
|
setShowUpdateModal(true);
|
||||||
}
|
}
|
||||||
@ -113,87 +118,110 @@ const OtherSetting = () => {
|
|||||||
<Grid columns={1}>
|
<Grid columns={1}>
|
||||||
<Grid.Column>
|
<Grid.Column>
|
||||||
<Form loading={loading}>
|
<Form loading={loading}>
|
||||||
<Header as='h3'>通用设置</Header>
|
<Header as='h3'>{t('setting.other.notice.title')}</Header>
|
||||||
<Form.Button onClick={checkUpdate}>检查更新</Form.Button>
|
|
||||||
<Form.Group widths='equal'>
|
<Form.Group widths='equal'>
|
||||||
<Form.TextArea
|
<Form.TextArea
|
||||||
label='公告'
|
label={t('setting.other.notice.content')}
|
||||||
placeholder='在此输入新的公告内容,支持 Markdown & HTML 代码'
|
placeholder={t('setting.other.notice.content_placeholder')}
|
||||||
value={inputs.Notice}
|
value={inputs.Notice}
|
||||||
name='Notice'
|
name='Notice'
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
|
style={{ minHeight: 100, fontFamily: 'JetBrains Mono, Consolas' }}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={submitNotice}>保存公告</Form.Button>
|
<Form.Button onClick={submitNotice}>
|
||||||
|
{t('setting.other.notice.buttons.save')}
|
||||||
|
</Form.Button>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
<Header as='h3'>个性化设置</Header>
|
<Header as='h3'>{t('setting.other.system.title')}</Header>
|
||||||
<Form.Group widths='equal'>
|
<Form.Group widths='equal'>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='系统名称'
|
label={t('setting.other.system.name')}
|
||||||
placeholder='在此输入系统名称'
|
placeholder={t('setting.other.system.name_placeholder')}
|
||||||
value={inputs.SystemName}
|
value={inputs.SystemName}
|
||||||
name='SystemName'
|
name='SystemName'
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={submitSystemName}>设置系统名称</Form.Button>
|
<Form.Button onClick={submitSystemName}>
|
||||||
|
{t('setting.other.system.buttons.save_name')}
|
||||||
|
</Form.Button>
|
||||||
<Form.Group widths='equal'>
|
<Form.Group widths='equal'>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label={<label>主题名称(<Link
|
label={
|
||||||
to='https://github.com/songquanpeng/one-api/blob/main/web/README.md'>当前可用主题</Link>)</label>}
|
<label>
|
||||||
placeholder='请输入主题名称'
|
{t('setting.other.system.theme.title')}(
|
||||||
|
<Link to='https://github.com/songquanpeng/one-api/blob/main/web/README.md'>
|
||||||
|
{t('setting.other.system.theme.link')}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
placeholder={t('setting.other.system.theme.placeholder')}
|
||||||
value={inputs.Theme}
|
value={inputs.Theme}
|
||||||
name='Theme'
|
name='Theme'
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={submitTheme}>设置主题(重启生效)</Form.Button>
|
<Form.Button onClick={submitTheme}>
|
||||||
|
{t('setting.other.system.buttons.save_theme')}
|
||||||
|
</Form.Button>
|
||||||
<Form.Group widths='equal'>
|
<Form.Group widths='equal'>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='Logo 图片地址'
|
label={t('setting.other.system.logo')}
|
||||||
placeholder='在此输入 Logo 图片地址'
|
placeholder={t('setting.other.system.logo_placeholder')}
|
||||||
value={inputs.Logo}
|
value={inputs.Logo}
|
||||||
name='Logo'
|
name='Logo'
|
||||||
type='url'
|
type='url'
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={submitLogo}>设置 Logo</Form.Button>
|
<Form.Button onClick={submitLogo}>
|
||||||
|
{t('setting.other.system.buttons.save_logo')}
|
||||||
|
</Form.Button>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<Header as='h3'>{t('setting.other.content.title')}</Header>
|
||||||
<Form.Group widths='equal'>
|
<Form.Group widths='equal'>
|
||||||
<Form.TextArea
|
<Form.TextArea
|
||||||
label='首页内容'
|
label={t('setting.other.content.homepage.title')}
|
||||||
placeholder='在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页。'
|
placeholder={t('setting.other.content.homepage.placeholder')}
|
||||||
value={inputs.HomePageContent}
|
value={inputs.HomePageContent}
|
||||||
name='HomePageContent'
|
name='HomePageContent'
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
|
style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={() => submitOption('HomePageContent')}>保存首页内容</Form.Button>
|
<Form.Button onClick={() => submitOption('HomePageContent')}>
|
||||||
|
{t('setting.other.content.buttons.save_homepage')}
|
||||||
|
</Form.Button>
|
||||||
<Form.Group widths='equal'>
|
<Form.Group widths='equal'>
|
||||||
<Form.TextArea
|
<Form.TextArea
|
||||||
label='关于'
|
label={t('setting.other.content.about.title')}
|
||||||
placeholder='在此输入新的关于内容,支持 Markdown & HTML 代码。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为关于页面。'
|
placeholder={t('setting.other.content.about.placeholder')}
|
||||||
value={inputs.About}
|
value={inputs.About}
|
||||||
name='About'
|
name='About'
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
|
style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={submitAbout}>保存关于</Form.Button>
|
<Form.Button onClick={submitAbout}>
|
||||||
<Message>移除 One API
|
{t('setting.other.content.buttons.save_about')}
|
||||||
的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目。</Message>
|
</Form.Button>
|
||||||
|
<Message>{t('setting.other.copyright.notice')}</Message>
|
||||||
<Form.Group widths='equal'>
|
<Form.Group widths='equal'>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label='页脚'
|
label={t('setting.other.content.footer.title')}
|
||||||
placeholder='在此输入新的页脚,留空则使用默认页脚,支持 HTML 代码'
|
placeholder={t('setting.other.content.footer.placeholder')}
|
||||||
value={inputs.Footer}
|
value={inputs.Footer}
|
||||||
name='Footer'
|
name='Footer'
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Button onClick={submitFooter}>设置页脚</Form.Button>
|
<Form.Button onClick={() => submitOption('Footer')}>
|
||||||
|
{t('setting.other.content.buttons.save_footer')}
|
||||||
|
</Form.Button>
|
||||||
</Form>
|
</Form>
|
||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -1,8 +1,23 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Divider, Form, Header, Image, Message, Modal } from 'semantic-ui-react';
|
import {
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
Form,
|
||||||
|
Header,
|
||||||
|
Image,
|
||||||
|
Message,
|
||||||
|
Modal,
|
||||||
|
} from 'semantic-ui-react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { API, copy, showError, showInfo, showNotice, showSuccess } from '../helpers';
|
import {
|
||||||
|
API,
|
||||||
|
copy,
|
||||||
|
showError,
|
||||||
|
showInfo,
|
||||||
|
showNotice,
|
||||||
|
showSuccess,
|
||||||
|
} from '../helpers';
|
||||||
import Turnstile from 'react-turnstile';
|
import Turnstile from 'react-turnstile';
|
||||||
import { UserContext } from '../context/User';
|
import { UserContext } from '../context/User';
|
||||||
import { onGitHubOAuthClicked, onLarkOAuthClicked } from './utils';
|
import { onGitHubOAuthClicked, onLarkOAuthClicked } from './utils';
|
||||||
@ -16,7 +31,7 @@ const PersonalSetting = () => {
|
|||||||
wechat_verification_code: '',
|
wechat_verification_code: '',
|
||||||
email_verification_code: '',
|
email_verification_code: '',
|
||||||
email: '',
|
email: '',
|
||||||
self_account_deletion_confirmation: ''
|
self_account_deletion_confirmation: '',
|
||||||
});
|
});
|
||||||
const [status, setStatus] = useState({});
|
const [status, setStatus] = useState({});
|
||||||
const [showWeChatBindModal, setShowWeChatBindModal] = useState(false);
|
const [showWeChatBindModal, setShowWeChatBindModal] = useState(false);
|
||||||
@ -28,8 +43,8 @@ const PersonalSetting = () => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [disableButton, setDisableButton] = useState(false);
|
const [disableButton, setDisableButton] = useState(false);
|
||||||
const [countdown, setCountdown] = useState(30);
|
const [countdown, setCountdown] = useState(30);
|
||||||
const [affLink, setAffLink] = useState("");
|
const [affLink, setAffLink] = useState('');
|
||||||
const [systemToken, setSystemToken] = useState("");
|
const [systemToken, setSystemToken] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let status = localStorage.getItem('status');
|
let status = localStorage.getItem('status');
|
||||||
@ -65,7 +80,7 @@ const PersonalSetting = () => {
|
|||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
setSystemToken(data);
|
setSystemToken(data);
|
||||||
setAffLink("");
|
setAffLink('');
|
||||||
await copy(data);
|
await copy(data);
|
||||||
showSuccess(`令牌已重置并已复制到剪贴板`);
|
showSuccess(`令牌已重置并已复制到剪贴板`);
|
||||||
} else {
|
} else {
|
||||||
@ -79,7 +94,7 @@ const PersonalSetting = () => {
|
|||||||
if (success) {
|
if (success) {
|
||||||
let link = `${window.location.origin}/register?aff=${data}`;
|
let link = `${window.location.origin}/register?aff=${data}`;
|
||||||
setAffLink(link);
|
setAffLink(link);
|
||||||
setSystemToken("");
|
setSystemToken('');
|
||||||
await copy(link);
|
await copy(link);
|
||||||
showSuccess(`邀请链接已复制到剪切板`);
|
showSuccess(`邀请链接已复制到剪切板`);
|
||||||
} else {
|
} else {
|
||||||
@ -172,9 +187,7 @@ const PersonalSetting = () => {
|
|||||||
return (
|
return (
|
||||||
<div style={{ lineHeight: '40px' }}>
|
<div style={{ lineHeight: '40px' }}>
|
||||||
<Header as='h3'>{t('setting.personal.general.title')}</Header>
|
<Header as='h3'>{t('setting.personal.general.title')}</Header>
|
||||||
<Message>
|
<Message>{t('setting.personal.general.system_token_notice')}</Message>
|
||||||
{t('setting.personal.general.system_token_notice')}
|
|
||||||
</Message>
|
|
||||||
<Button as={Link} to={`/user/edit/`}>
|
<Button as={Link} to={`/user/edit/`}>
|
||||||
{t('setting.personal.general.buttons.update_profile')}
|
{t('setting.personal.general.buttons.update_profile')}
|
||||||
</Button>
|
</Button>
|
||||||
@ -184,9 +197,11 @@ const PersonalSetting = () => {
|
|||||||
<Button onClick={getAffLink}>
|
<Button onClick={getAffLink}>
|
||||||
{t('setting.personal.general.buttons.copy_invite')}
|
{t('setting.personal.general.buttons.copy_invite')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => {
|
<Button
|
||||||
setShowAccountDeleteModal(true);
|
onClick={() => {
|
||||||
}}>
|
setShowAccountDeleteModal(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t('setting.personal.general.buttons.delete_account')}
|
{t('setting.personal.general.buttons.delete_account')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
@ -230,7 +245,9 @@ const PersonalSetting = () => {
|
|||||||
<Form size='large'>
|
<Form size='large'>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
fluid
|
fluid
|
||||||
placeholder={t('setting.personal.binding.wechat.verification_code')}
|
placeholder={t(
|
||||||
|
'setting.personal.binding.wechat.verification_code'
|
||||||
|
)}
|
||||||
name='wechat_verification_code'
|
name='wechat_verification_code'
|
||||||
value={inputs.wechat_verification_code}
|
value={inputs.wechat_verification_code}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
@ -268,21 +285,30 @@ const PersonalSetting = () => {
|
|||||||
<Form size='large'>
|
<Form size='large'>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
fluid
|
fluid
|
||||||
placeholder={t('setting.personal.binding.email.email_placeholder')}
|
placeholder={t(
|
||||||
|
'setting.personal.binding.email.email_placeholder'
|
||||||
|
)}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
name='email'
|
name='email'
|
||||||
type='email'
|
type='email'
|
||||||
action={
|
action={
|
||||||
<Button onClick={sendVerificationCode} disabled={disableButton || loading}>
|
<Button
|
||||||
|
onClick={sendVerificationCode}
|
||||||
|
disabled={disableButton || loading}
|
||||||
|
>
|
||||||
{disableButton
|
{disableButton
|
||||||
? t('setting.personal.binding.email.get_code_retry', { countdown })
|
? t('setting.personal.binding.email.get_code_retry', {
|
||||||
|
countdown,
|
||||||
|
})
|
||||||
: t('setting.personal.binding.email.get_code')}
|
: t('setting.personal.binding.email.get_code')}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
fluid
|
fluid
|
||||||
placeholder={t('setting.personal.binding.email.code_placeholder')}
|
placeholder={t(
|
||||||
|
'setting.personal.binding.email.code_placeholder'
|
||||||
|
)}
|
||||||
name='email_verification_code'
|
name='email_verification_code'
|
||||||
value={inputs.email_verification_code}
|
value={inputs.email_verification_code}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
@ -295,7 +321,13 @@ const PersonalSetting = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '1rem' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginTop: '1rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
color=''
|
color=''
|
||||||
fluid
|
fluid
|
||||||
@ -325,16 +357,21 @@ const PersonalSetting = () => {
|
|||||||
size={'tiny'}
|
size={'tiny'}
|
||||||
style={{ maxWidth: '450px' }}
|
style={{ maxWidth: '450px' }}
|
||||||
>
|
>
|
||||||
<Modal.Header>{t('setting.personal.delete_account.title')}</Modal.Header>
|
<Modal.Header>
|
||||||
|
{t('setting.personal.delete_account.title')}
|
||||||
|
</Modal.Header>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<Message>{t('setting.personal.delete_account.warning')}</Message>
|
<Message>{t('setting.personal.delete_account.warning')}</Message>
|
||||||
<Modal.Description>
|
<Modal.Description>
|
||||||
<Form size='large'>
|
<Form size='large'>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
fluid
|
fluid
|
||||||
placeholder={t('setting.personal.delete_account.confirm_placeholder', {
|
placeholder={t(
|
||||||
username: userState?.user?.username
|
'setting.personal.delete_account.confirm_placeholder',
|
||||||
})}
|
{
|
||||||
|
username: userState?.user?.username,
|
||||||
|
}
|
||||||
|
)}
|
||||||
name='self_account_deletion_confirmation'
|
name='self_account_deletion_confirmation'
|
||||||
value={inputs.self_account_deletion_confirmation}
|
value={inputs.self_account_deletion_confirmation}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
@ -347,7 +384,13 @@ const PersonalSetting = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '1rem' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginTop: '1rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
color='red'
|
color='red'
|
||||||
fluid
|
fluid
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Divider, Form, Grid, Header, Modal, Message } from 'semantic-ui-react';
|
import {
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
Form,
|
||||||
|
Grid,
|
||||||
|
Header,
|
||||||
|
Modal,
|
||||||
|
Message,
|
||||||
|
} from 'semantic-ui-react';
|
||||||
import { API, removeTrailingSlash, showError } from '../helpers';
|
import { API, removeTrailingSlash, showError } from '../helpers';
|
||||||
|
|
||||||
const SystemSetting = () => {
|
const SystemSetting = () => {
|
||||||
@ -33,13 +41,14 @@ const SystemSetting = () => {
|
|||||||
TurnstileSecretKey: '',
|
TurnstileSecretKey: '',
|
||||||
RegisterEnabled: '',
|
RegisterEnabled: '',
|
||||||
EmailDomainRestrictionEnabled: '',
|
EmailDomainRestrictionEnabled: '',
|
||||||
EmailDomainWhitelist: ''
|
EmailDomainWhitelist: '',
|
||||||
});
|
});
|
||||||
const [originInputs, setOriginInputs] = useState({});
|
const [originInputs, setOriginInputs] = useState({});
|
||||||
let [loading, setLoading] = useState(false);
|
let [loading, setLoading] = useState(false);
|
||||||
const [EmailDomainWhitelist, setEmailDomainWhitelist] = useState([]);
|
const [EmailDomainWhitelist, setEmailDomainWhitelist] = useState([]);
|
||||||
const [restrictedDomainInput, setRestrictedDomainInput] = useState('');
|
const [restrictedDomainInput, setRestrictedDomainInput] = useState('');
|
||||||
const [showPasswordWarningModal, setShowPasswordWarningModal] = useState(false);
|
const [showPasswordWarningModal, setShowPasswordWarningModal] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
const getOptions = async () => {
|
const getOptions = async () => {
|
||||||
const res = await API.get('/api/option/');
|
const res = await API.get('/api/option/');
|
||||||
@ -51,13 +60,15 @@ const SystemSetting = () => {
|
|||||||
});
|
});
|
||||||
setInputs({
|
setInputs({
|
||||||
...newInputs,
|
...newInputs,
|
||||||
EmailDomainWhitelist: newInputs.EmailDomainWhitelist.split(',')
|
EmailDomainWhitelist: newInputs.EmailDomainWhitelist.split(','),
|
||||||
});
|
});
|
||||||
setOriginInputs(newInputs);
|
setOriginInputs(newInputs);
|
||||||
|
|
||||||
setEmailDomainWhitelist(newInputs.EmailDomainWhitelist.split(',').map((item) => {
|
setEmailDomainWhitelist(
|
||||||
return { key: item, text: item, value: item };
|
newInputs.EmailDomainWhitelist.split(',').map((item) => {
|
||||||
}));
|
return { key: item, text: item, value: item };
|
||||||
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
}
|
}
|
||||||
@ -85,7 +96,7 @@ const SystemSetting = () => {
|
|||||||
}
|
}
|
||||||
const res = await API.put('/api/option/', {
|
const res = await API.put('/api/option/', {
|
||||||
key,
|
key,
|
||||||
value
|
value,
|
||||||
});
|
});
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -93,7 +104,8 @@ const SystemSetting = () => {
|
|||||||
value = value.split(',');
|
value = value.split(',');
|
||||||
}
|
}
|
||||||
setInputs((inputs) => ({
|
setInputs((inputs) => ({
|
||||||
...inputs, [key]: value
|
...inputs,
|
||||||
|
[key]: value,
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
@ -157,13 +169,16 @@ const SystemSetting = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const submitEmailDomainWhitelist = async () => {
|
const submitEmailDomainWhitelist = async () => {
|
||||||
if (
|
if (
|
||||||
originInputs['EmailDomainWhitelist'] !== inputs.EmailDomainWhitelist.join(',') &&
|
originInputs['EmailDomainWhitelist'] !==
|
||||||
|
inputs.EmailDomainWhitelist.join(',') &&
|
||||||
inputs.SMTPToken !== ''
|
inputs.SMTPToken !== ''
|
||||||
) {
|
) {
|
||||||
await updateOption('EmailDomainWhitelist', inputs.EmailDomainWhitelist.join(','));
|
await updateOption(
|
||||||
|
'EmailDomainWhitelist',
|
||||||
|
inputs.EmailDomainWhitelist.join(',')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -218,7 +233,7 @@ const SystemSetting = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitLarkOAuth = async () => {
|
const submitLarkOAuth = async () => {
|
||||||
if (originInputs['LarkClientId'] !== inputs.LarkClientId) {
|
if (originInputs['LarkClientId'] !== inputs.LarkClientId) {
|
||||||
await updateOption('LarkClientId', inputs.LarkClientId);
|
await updateOption('LarkClientId', inputs.LarkClientId);
|
||||||
}
|
}
|
||||||
@ -244,19 +259,25 @@ const SystemSetting = () => {
|
|||||||
|
|
||||||
const submitNewRestrictedDomain = () => {
|
const submitNewRestrictedDomain = () => {
|
||||||
const localDomainList = inputs.EmailDomainWhitelist;
|
const localDomainList = inputs.EmailDomainWhitelist;
|
||||||
if (restrictedDomainInput !== '' && !localDomainList.includes(restrictedDomainInput)) {
|
if (
|
||||||
|
restrictedDomainInput !== '' &&
|
||||||
|
!localDomainList.includes(restrictedDomainInput)
|
||||||
|
) {
|
||||||
setRestrictedDomainInput('');
|
setRestrictedDomainInput('');
|
||||||
setInputs({
|
setInputs({
|
||||||
...inputs,
|
...inputs,
|
||||||
EmailDomainWhitelist: [...localDomainList, restrictedDomainInput],
|
EmailDomainWhitelist: [...localDomainList, restrictedDomainInput],
|
||||||
});
|
});
|
||||||
setEmailDomainWhitelist([...EmailDomainWhitelist, {
|
setEmailDomainWhitelist([
|
||||||
key: restrictedDomainInput,
|
...EmailDomainWhitelist,
|
||||||
text: restrictedDomainInput,
|
{
|
||||||
value: restrictedDomainInput,
|
key: restrictedDomainInput,
|
||||||
}]);
|
text: restrictedDomainInput,
|
||||||
|
value: restrictedDomainInput,
|
||||||
|
},
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid columns={1}>
|
<Grid columns={1}>
|
||||||
@ -266,7 +287,9 @@ const SystemSetting = () => {
|
|||||||
<Form.Group widths='equal'>
|
<Form.Group widths='equal'>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label={t('setting.system.general.server_address')}
|
label={t('setting.system.general.server_address')}
|
||||||
placeholder={t('setting.system.general.server_address_placeholder')}
|
placeholder={t(
|
||||||
|
'setting.system.general.server_address_placeholder'
|
||||||
|
)}
|
||||||
value={inputs.ServerAddress}
|
value={inputs.ServerAddress}
|
||||||
name='ServerAddress'
|
name='ServerAddress'
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
@ -291,7 +314,9 @@ const SystemSetting = () => {
|
|||||||
size={'tiny'}
|
size={'tiny'}
|
||||||
style={{ maxWidth: '450px' }}
|
style={{ maxWidth: '450px' }}
|
||||||
>
|
>
|
||||||
<Modal.Header>{t('setting.system.password_login.warning.title')}</Modal.Header>
|
<Modal.Header>
|
||||||
|
{t('setting.system.password_login.warning.title')}
|
||||||
|
</Modal.Header>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<p>{t('setting.system.password_login.warning.content')}</p>
|
<p>{t('setting.system.password_login.warning.content')}</p>
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
@ -364,21 +389,28 @@ const SystemSetting = () => {
|
|||||||
<Form.Group widths={3}>
|
<Form.Group widths={3}>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label={t('setting.system.email_restriction.add_domain')}
|
label={t('setting.system.email_restriction.add_domain')}
|
||||||
placeholder={t('setting.system.email_restriction.add_domain_placeholder')}
|
placeholder={t(
|
||||||
|
'setting.system.email_restriction.add_domain_placeholder'
|
||||||
|
)}
|
||||||
value={restrictedDomainInput}
|
value={restrictedDomainInput}
|
||||||
onChange={(e, { value }) => {
|
onChange={(e, { value }) => {
|
||||||
setRestrictedDomainInput(value);
|
setRestrictedDomainInput(value);
|
||||||
}}
|
}}
|
||||||
action={
|
action={
|
||||||
<Button onClick={() => {
|
<Button
|
||||||
if (restrictedDomainInput === '') return;
|
onClick={() => {
|
||||||
setEmailDomainWhitelist([...EmailDomainWhitelist, {
|
if (restrictedDomainInput === '') return;
|
||||||
key: restrictedDomainInput,
|
setEmailDomainWhitelist([
|
||||||
text: restrictedDomainInput,
|
...EmailDomainWhitelist,
|
||||||
value: restrictedDomainInput
|
{
|
||||||
}]);
|
key: restrictedDomainInput,
|
||||||
setRestrictedDomainInput('');
|
text: restrictedDomainInput,
|
||||||
}}>
|
value: restrictedDomainInput,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
setRestrictedDomainInput('');
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t('setting.system.email_restriction.buttons.fill')}
|
{t('setting.system.email_restriction.buttons.fill')}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
@ -392,14 +424,17 @@ const SystemSetting = () => {
|
|||||||
search
|
search
|
||||||
selection
|
selection
|
||||||
allowAdditions
|
allowAdditions
|
||||||
value={EmailDomainWhitelist.map(item => item.value)}
|
value={EmailDomainWhitelist.map((item) => item.value)}
|
||||||
options={EmailDomainWhitelist}
|
options={EmailDomainWhitelist}
|
||||||
onAddItem={(e, { value }) => {
|
onAddItem={(e, { value }) => {
|
||||||
setEmailDomainWhitelist([...EmailDomainWhitelist, {
|
setEmailDomainWhitelist([
|
||||||
key: value,
|
...EmailDomainWhitelist,
|
||||||
text: value,
|
{
|
||||||
value: value
|
key: value,
|
||||||
}]);
|
text: value,
|
||||||
|
value: value,
|
||||||
|
},
|
||||||
|
]);
|
||||||
}}
|
}}
|
||||||
onChange={(e, { value }) => {
|
onChange={(e, { value }) => {
|
||||||
let newEmailDomainWhitelist = [];
|
let newEmailDomainWhitelist = [];
|
||||||
@ -407,7 +442,7 @@ const SystemSetting = () => {
|
|||||||
newEmailDomainWhitelist.push({
|
newEmailDomainWhitelist.push({
|
||||||
key: item,
|
key: item,
|
||||||
text: item,
|
text: item,
|
||||||
value: item
|
value: item,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
setEmailDomainWhitelist(newEmailDomainWhitelist);
|
setEmailDomainWhitelist(newEmailDomainWhitelist);
|
||||||
@ -476,7 +511,7 @@ const SystemSetting = () => {
|
|||||||
<Message>
|
<Message>
|
||||||
{t('setting.system.github.url_notice', {
|
{t('setting.system.github.url_notice', {
|
||||||
server_url: originInputs.ServerAddress,
|
server_url: originInputs.ServerAddress,
|
||||||
callback_url: `${originInputs.ServerAddress}/oauth/github`
|
callback_url: `${originInputs.ServerAddress}/oauth/github`,
|
||||||
})}
|
})}
|
||||||
</Message>
|
</Message>
|
||||||
<Form.Group widths={3}>
|
<Form.Group widths={3}>
|
||||||
@ -514,7 +549,7 @@ const SystemSetting = () => {
|
|||||||
<Message>
|
<Message>
|
||||||
{t('setting.system.lark.url_notice', {
|
{t('setting.system.lark.url_notice', {
|
||||||
server_url: inputs.ServerAddress,
|
server_url: inputs.ServerAddress,
|
||||||
callback_url: `${inputs.ServerAddress}/oauth/lark`
|
callback_url: `${inputs.ServerAddress}/oauth/lark`,
|
||||||
})}
|
})}
|
||||||
</Message>
|
</Message>
|
||||||
<Form.Group widths={3}>
|
<Form.Group widths={3}>
|
||||||
@ -560,7 +595,9 @@ const SystemSetting = () => {
|
|||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
value={inputs.WeChatServerAddress}
|
value={inputs.WeChatServerAddress}
|
||||||
placeholder={t('setting.system.wechat.server_address_placeholder')}
|
placeholder={t(
|
||||||
|
'setting.system.wechat.server_address_placeholder'
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
label={t('setting.system.wechat.token')}
|
label={t('setting.system.wechat.token')}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Card, Header, Segment } from 'semantic-ui-react';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Card } from 'semantic-ui-react';
|
||||||
import { API, showError } from '../../helpers';
|
import { API, showError } from '../../helpers';
|
||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
|
|
||||||
const About = () => {
|
const About = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [about, setAbout] = useState('');
|
const [about, setAbout] = useState('');
|
||||||
const [aboutLoaded, setAboutLoaded] = useState(false);
|
const [aboutLoaded, setAboutLoaded] = useState(false);
|
||||||
|
|
||||||
@ -20,7 +22,7 @@ const About = () => {
|
|||||||
localStorage.setItem('about', aboutContent);
|
localStorage.setItem('about', aboutContent);
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
setAbout('加载关于内容失败...');
|
setAbout(t('about.loading_failed'));
|
||||||
}
|
}
|
||||||
setAboutLoaded(true);
|
setAboutLoaded(true);
|
||||||
};
|
};
|
||||||
@ -28,15 +30,16 @@ const About = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
displayAbout().then();
|
displayAbout().then();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{aboutLoaded && about === '' ? (
|
{aboutLoaded && about === '' ? (
|
||||||
<div className='dashboard-container'>
|
<div className='dashboard-container'>
|
||||||
<Card fluid className='chart-card'>
|
<Card fluid className='chart-card'>
|
||||||
<Card.Content>
|
<Card.Content>
|
||||||
<Card.Header className='header'>关于系统</Card.Header>
|
<Card.Header className='header'>{t('about.title')}</Card.Header>
|
||||||
<p>可在设置页面设置关于内容,支持 HTML & Markdown</p>
|
<p>{t('about.description')}</p>
|
||||||
项目仓库地址:
|
{t('about.repository')}
|
||||||
<a href='https://github.com/songquanpeng/one-api'>
|
<a href='https://github.com/songquanpeng/one-api'>
|
||||||
https://github.com/songquanpeng/one-api
|
https://github.com/songquanpeng/one-api
|
||||||
</a>
|
</a>
|
||||||
|
Loading…
Reference in New Issue
Block a user