Compare commits

..

7 Commits

Author SHA1 Message Date
JustSong
926951ee03 feat: able to customize system name & logo now 2023-05-14 19:29:02 +08:00
JustSong
2cdc718fde feat: able to use any link as about page (#60) 2023-05-14 18:58:54 +08:00
JustSong
57cb150177 perf: load cached about content first (#60) 2023-05-14 16:13:42 +08:00
JustSong
6167e20b34 style: hide scroll bar 2023-05-14 16:02:40 +08:00
JustSong
8835d8302e chore: fix typo 2023-05-14 16:01:04 +08:00
JustSong
224bebe67a feat: able to customize home page with link (close #60) 2023-05-14 15:34:14 +08:00
JustSong
cf6883778e perf: use slice to improve efficiency (#57) 2023-05-14 12:53:03 +08:00
15 changed files with 211 additions and 122 deletions

View File

@@ -43,8 +43,8 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用
## 功能 ## 功能
1. 支持多种 API 访问渠道,欢迎 PR 或提 issue 添加更多渠道: 1. 支持多种 API 访问渠道,欢迎 PR 或提 issue 添加更多渠道:
+ [x] OpenAI 官方通道 + [x] OpenAI 官方通道
+ [x] [API2D](https://api2d.com/r/197971)
+ [x] Azure OpenAI API + [x] Azure OpenAI API
+ [x] [API2D](https://api2d.com/r/197971)
+ [x] [CloseAI](https://console.openai-asia.com) + [x] [CloseAI](https://console.openai-asia.com)
+ [x] [OpenAI-SB](https://openai-sb.com) + [x] [OpenAI-SB](https://openai-sb.com)
+ [x] [OpenAI Max](https://openaimax.com) + [x] [OpenAI Max](https://openaimax.com)
@@ -55,15 +55,16 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用
4. 支持 HTTP SSE可以通过流式传输实现打字机效果。 4. 支持 HTTP SSE可以通过流式传输实现打字机效果。
5. 支持设置令牌的过期时间和使用次数。 5. 支持设置令牌的过期时间和使用次数。
6. 支持批量生成和导出兑换码,可使用兑换码为令牌进行充值。 6. 支持批量生成和导出兑换码,可使用兑换码为令牌进行充值。
7. 支持为新用户设置初始配额 7. 支持批量创建通道
8. 支持自定义首页,发布公告,自定义关于页面,设置充值链接,自定义页脚。 8. 支持发布公告,自定义关于页面,设置充值链接,自定义页脚。
9. 支持通过系统访问令牌访问管理 API 9. 支持自定义首页,可以选择使用 HTML & Markdown 代码进行自定义,或者使用一个单独的网页通过 iframe 嵌入
10. 多种用户登录注册方式: 10. 支持通过系统访问令牌访问管理 API。
11. 多种用户登录注册方式:
+ 邮箱登录注册以及通过邮箱进行密码重置。 + 邮箱登录注册以及通过邮箱进行密码重置。
+ [GitHub 开放授权](https://github.com/settings/applications/new)。 + [GitHub 开放授权](https://github.com/settings/applications/new)。
+ 微信公众号授权(需要额外部署 [WeChat Server](https://github.com/songquanpeng/wechat-server))。 + 微信公众号授权(需要额外部署 [WeChat Server](https://github.com/songquanpeng/wechat-server))。
11. 支持用户管理。 12. 支持用户管理,支持为新用户设置初始配额。
12. 未来其他大模型开放 API 后,将第一时间支持,并将其封装成同样的 API 访问方式。 13. 未来其他大模型开放 API 后,将第一时间支持,并将其封装成同样的 API 访问方式。
## 部署 ## 部署
### 基于 Docker 进行部署 ### 基于 Docker 进行部署

View File

@@ -11,6 +11,7 @@ var Version = "v0.0.0" // this hard coding will be replaced automatic
var SystemName = "One API" var SystemName = "One API"
var ServerAddress = "http://localhost:3000" var ServerAddress = "http://localhost:3000"
var Footer = "" var Footer = ""
var Logo = ""
var TopUpLink = "" var TopUpLink = ""
var UsingSQLite = false var UsingSQLite = false

View File

@@ -20,6 +20,7 @@ func GetStatus(c *gin.Context) {
"github_oauth": common.GitHubOAuthEnabled, "github_oauth": common.GitHubOAuthEnabled,
"github_client_id": common.GitHubClientId, "github_client_id": common.GitHubClientId,
"system_name": common.SystemName, "system_name": common.SystemName,
"logo": common.Logo,
"footer_html": common.Footer, "footer_html": common.Footer,
"wechat_qrcode": common.WeChatAccountQRCodeImageURL, "wechat_qrcode": common.WeChatAccountQRCodeImageURL,
"wechat_login": common.WeChatAuthEnabled, "wechat_login": common.WeChatAuthEnabled,

View File

@@ -210,7 +210,7 @@ func relayHelper(c *gin.Context) error {
select { select {
case data := <-dataChan: case data := <-dataChan:
if strings.HasPrefix(data, "data: [DONE]") { if strings.HasPrefix(data, "data: [DONE]") {
data = "data: [DONE]" data = data[:12]
} }
c.Render(-1, common.CustomEvent{Data: data}) c.Render(-1, common.CustomEvent{Data: data})
return true return true

View File

@@ -41,6 +41,8 @@ func InitOptionMap() {
common.OptionMap["About"] = "" common.OptionMap["About"] = ""
common.OptionMap["HomePageContent"] = "" common.OptionMap["HomePageContent"] = ""
common.OptionMap["Footer"] = common.Footer common.OptionMap["Footer"] = common.Footer
common.OptionMap["SystemName"] = common.SystemName
common.OptionMap["Logo"] = common.Logo
common.OptionMap["ServerAddress"] = "" common.OptionMap["ServerAddress"] = ""
common.OptionMap["GitHubClientId"] = "" common.OptionMap["GitHubClientId"] = ""
common.OptionMap["GitHubClientSecret"] = "" common.OptionMap["GitHubClientSecret"] = ""
@@ -134,6 +136,10 @@ func updateOptionMap(key string, value string) (err error) {
common.GitHubClientSecret = value common.GitHubClientSecret = value
case "Footer": case "Footer":
common.Footer = value common.Footer = value
case "SystemName":
common.SystemName = value
case "Logo":
common.Logo = value
case "WeChatServerAddress": case "WeChatServerAddress":
common.WeChatServerAddress = value common.WeChatServerAddress = value
case "WeChatServerToken": case "WeChatServerToken":

View File

@@ -42,6 +42,8 @@ function App() {
if (success) { if (success) {
localStorage.setItem('status', JSON.stringify(data)); localStorage.setItem('status', JSON.stringify(data));
statusDispatch({ type: 'set', payload: data }); statusDispatch({ type: 'set', payload: data });
localStorage.setItem('system_name', data.system_name);
localStorage.setItem('logo', data.logo);
localStorage.setItem('footer_html', data.footer_html); localStorage.setItem('footer_html', data.footer_html);
if ( if (
data.version !== process.env.REACT_APP_VERSION && data.version !== process.env.REACT_APP_VERSION &&

View File

@@ -1,40 +1,37 @@
import React, { useEffect, useState } from 'react'; import React from 'react';
import { Container, Segment } from 'semantic-ui-react'; import { Container, Segment } from 'semantic-ui-react';
import { getFooterHTML, getSystemName } from '../helpers';
const Footer = () => { const Footer = () => {
const [Footer, setFooter] = useState(''); const systemName = getSystemName();
useEffect(() => { const footer = getFooterHTML();
let savedFooter = localStorage.getItem('footer_html');
if (!savedFooter) savedFooter = '';
setFooter(savedFooter);
});
return ( return (
<Segment vertical> <Segment vertical>
<Container textAlign="center"> <Container textAlign='center'>
{Footer === '' ? ( {footer ? (
<div className="custom-footer"> <div
className='custom-footer'
dangerouslySetInnerHTML={{ __html: footer }}
></div>
) : (
<div className='custom-footer'>
<a <a
href="https://github.com/songquanpeng/one-api" href='https://github.com/songquanpeng/one-api'
target="_blank" target='_blank'
> >
One API {process.env.REACT_APP_VERSION}{' '} {systemName} {process.env.REACT_APP_VERSION}{' '}
</a> </a>
{' '} {' '}
<a href="https://github.com/songquanpeng" target="_blank"> <a href='https://github.com/songquanpeng' target='_blank'>
JustSong JustSong
</a>{' '} </a>{' '}
构建源代码遵循{' '} 构建源代码遵循{' '}
<a href="https://opensource.org/licenses/mit-license.php"> <a href='https://opensource.org/licenses/mit-license.php'>
MIT 协议 MIT 协议
</a> </a>
</div> </div>
) : (
<div
className="custom-footer"
dangerouslySetInnerHTML={{ __html: Footer }}
></div>
)} )}
</Container> </Container>
</Segment> </Segment>

View File

@@ -3,7 +3,7 @@ import { Link, useNavigate } from 'react-router-dom';
import { UserContext } from '../context/User'; import { UserContext } from '../context/User';
import { Button, Container, Dropdown, Icon, Menu, Segment } from 'semantic-ui-react'; import { Button, Container, Dropdown, Icon, Menu, Segment } from 'semantic-ui-react';
import { API, isAdmin, isMobile, showSuccess } from '../helpers'; import { API, getLogo, getSystemName, isAdmin, isMobile, showSuccess } from '../helpers';
import '../index.css'; import '../index.css';
// Header Buttons // Header Buttons
@@ -53,6 +53,8 @@ const Header = () => {
let navigate = useNavigate(); let navigate = useNavigate();
const [showSidebar, setShowSidebar] = useState(false); const [showSidebar, setShowSidebar] = useState(false);
const systemName = getSystemName();
const logo = getLogo();
async function logout() { async function logout() {
setShowSidebar(false); setShowSidebar(false);
@@ -111,12 +113,12 @@ const Header = () => {
<Container> <Container>
<Menu.Item as={Link} to='/'> <Menu.Item as={Link} to='/'>
<img <img
src='/logo.png' src={logo}
alt='logo' alt='logo'
style={{ marginRight: '0.75em' }} style={{ marginRight: '0.75em' }}
/> />
<div style={{ fontSize: '20px' }}> <div style={{ fontSize: '20px' }}>
<b>One API</b> <b>{systemName}</b>
</div> </div>
</Menu.Item> </Menu.Item>
<Menu.Menu position='right'> <Menu.Menu position='right'>
@@ -168,9 +170,9 @@ const Header = () => {
<Menu borderless style={{ borderTop: 'none' }}> <Menu borderless style={{ borderTop: 'none' }}>
<Container> <Container>
<Menu.Item as={Link} to='/' className={'hide-on-mobile'}> <Menu.Item as={Link} to='/' className={'hide-on-mobile'}>
<img src='/logo.png' alt='logo' style={{ marginRight: '0.75em' }} /> <img src={logo} alt='logo' style={{ marginRight: '0.75em' }} />
<div style={{ fontSize: '20px' }}> <div style={{ fontSize: '20px' }}>
<b>One API</b> <b>{systemName}</b>
</div> </div>
</Menu.Item> </Menu.Item>
{renderButtons(false)} {renderButtons(false)}

View File

@@ -12,7 +12,7 @@ import {
} from 'semantic-ui-react'; } from 'semantic-ui-react';
import { Link, useNavigate, useSearchParams } from 'react-router-dom'; import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { UserContext } from '../context/User'; import { UserContext } from '../context/User';
import { API, showError, showSuccess } from '../helpers'; import { API, getLogo, showError, showSuccess } from '../helpers';
const LoginForm = () => { const LoginForm = () => {
const [inputs, setInputs] = useState({ const [inputs, setInputs] = useState({
@@ -27,6 +27,7 @@ const LoginForm = () => {
let navigate = useNavigate(); let navigate = useNavigate();
const [status, setStatus] = useState({}); const [status, setStatus] = useState({});
const logo = getLogo();
useEffect(() => { useEffect(() => {
if (searchParams.get("expired")) { if (searchParams.get("expired")) {
@@ -95,7 +96,7 @@ const LoginForm = () => {
<Grid textAlign="center" style={{ marginTop: '48px' }}> <Grid textAlign="center" style={{ marginTop: '48px' }}>
<Grid.Column style={{ maxWidth: 450 }}> <Grid.Column style={{ maxWidth: 450 }}>
<Header as="h2" color="" textAlign="center"> <Header as="h2" color="" textAlign="center">
<Image src="/logo.png" /> 用户登录 <Image src={logo} /> 用户登录
</Header> </Header>
<Form size="large"> <Form size="large">
<Segment> <Segment>

View File

@@ -8,6 +8,8 @@ const OtherSetting = () => {
Footer: '', Footer: '',
Notice: '', Notice: '',
About: '', About: '',
SystemName: '',
Logo: '',
HomePageContent: '', HomePageContent: '',
}); });
let originInputs = {}; let originInputs = {};
@@ -66,6 +68,14 @@ const OtherSetting = () => {
await updateOption('Footer', inputs.Footer); await updateOption('Footer', inputs.Footer);
}; };
const submitSystemName = async () => {
await updateOption('SystemName', inputs.SystemName);
};
const submitLogo = async () => {
await updateOption('Logo', inputs.Logo);
};
const submitAbout = async () => { const submitAbout = async () => {
await updateOption('About', inputs.About); await updateOption('About', inputs.About);
}; };
@@ -114,21 +124,42 @@ const OtherSetting = () => {
<Form.Button onClick={submitNotice}>保存公告</Form.Button> <Form.Button onClick={submitNotice}>保存公告</Form.Button>
<Divider /> <Divider />
<Header as='h3'>个性化设置</Header> <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='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.Group widths='equal'>
<Form.TextArea <Form.TextArea
label='首页内容' label='首页内容'
placeholder='在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示' placeholder='在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页。'
value={inputs.HomePageContent} value={inputs.HomePageContent}
name='HomePageContent' name='HomePageContent'
onChange={handleInputChange} onChange={handleInputChange}
style={{ minHeight: 300, 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')}>保存首页内容</Form.Button>
<Form.Group widths='equal'> <Form.Group widths='equal'>
<Form.TextArea <Form.TextArea
label='关于' label='关于'
placeholder='在此输入新的关于内容,支持 Markdown & HTML 代码' placeholder='在此输入新的关于内容,支持 Markdown & HTML 代码。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为关于页面。'
value={inputs.About} value={inputs.About}
name='About' name='About'
onChange={handleInputChange} onChange={handleInputChange}

View File

@@ -9,7 +9,7 @@ import {
Segment, Segment,
} from 'semantic-ui-react'; } from 'semantic-ui-react';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { API, showError, showInfo, showSuccess } from '../helpers'; import { API, getLogo, showError, showInfo, showSuccess } from '../helpers';
import Turnstile from 'react-turnstile'; import Turnstile from 'react-turnstile';
const RegisterForm = () => { const RegisterForm = () => {
@@ -26,6 +26,7 @@ const RegisterForm = () => {
const [turnstileSiteKey, setTurnstileSiteKey] = useState(''); const [turnstileSiteKey, setTurnstileSiteKey] = useState('');
const [turnstileToken, setTurnstileToken] = useState(''); const [turnstileToken, setTurnstileToken] = useState('');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const logo = getLogo();
useEffect(() => { useEffect(() => {
let status = localStorage.getItem('status'); let status = localStorage.getItem('status');
@@ -100,7 +101,7 @@ const RegisterForm = () => {
<Grid textAlign='center' style={{ marginTop: '48px' }}> <Grid textAlign='center' style={{ marginTop: '48px' }}>
<Grid.Column style={{ maxWidth: 450 }}> <Grid.Column style={{ maxWidth: 450 }}>
<Header as='h2' color='' textAlign='center'> <Header as='h2' color='' textAlign='center'>
<Image src='/logo.png' /> 新用户注册 <Image src={logo} /> 新用户注册
</Header> </Header>
<Form size='large'> <Form size='large'>
<Segment> <Segment>

View File

@@ -15,6 +15,22 @@ export function isRoot() {
return user.role >= 100; return user.role >= 100;
} }
export function getSystemName() {
let system_name = localStorage.getItem('system_name');
if (!system_name) return 'One API';
return system_name;
}
export function getLogo() {
let logo = localStorage.getItem('logo');
if (!logo) return '/logo.png';
return logo
}
export function getFooterHTML() {
return localStorage.getItem('footer_html');
}
export async function copy(text) { export async function copy(text) {
let okay = true; let okay = true;
try { try {

View File

@@ -5,6 +5,11 @@ body {
font-family: Lato, 'Helvetica Neue', Arial, Helvetica, "Microsoft YaHei", sans-serif; font-family: Lato, 'Helvetica Neue', Arial, Helvetica, "Microsoft YaHei", sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
scrollbar-width: none;
}
body::-webkit-scrollbar {
display: none;
} }
code { code {

View File

@@ -5,18 +5,24 @@ import { marked } from 'marked';
const About = () => { const About = () => {
const [about, setAbout] = useState(''); const [about, setAbout] = useState('');
const [aboutLoaded, setAboutLoaded] = useState(false);
const displayAbout = async () => { const displayAbout = async () => {
setAbout(localStorage.getItem('about') || '');
const res = await API.get('/api/about'); const res = await API.get('/api/about');
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
let HTMLAbout = marked.parse(data); let aboutContent = data;
localStorage.setItem('about', HTMLAbout); if (!data.startsWith('https://')) {
setAbout(HTMLAbout); aboutContent = marked.parse(data);
}
setAbout(aboutContent);
localStorage.setItem('about', aboutContent);
} else { } else {
showError(message); showError(message);
setAbout('加载关于内容失败...'); setAbout('加载关于内容失败...');
} }
setAboutLoaded(true);
}; };
useEffect(() => { useEffect(() => {
@@ -25,20 +31,27 @@ const About = () => {
return ( return (
<> <>
<Segment> {
{ aboutLoaded && about === '' ? <>
about === '' ? <> <Segment>
<Header as='h3'>关于</Header> <Header as='h3'>关于</Header>
<p>可在设置页面设置关于内容支持 HTML & Markdown</p> <p>可在设置页面设置关于内容支持 HTML & Markdown</p>
项目仓库地址 项目仓库地址
<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>
</> : <> </Segment>
<div dangerouslySetInnerHTML={{ __html: about}}></div> </> : <>
</> {
} about.startsWith('https://') ? <iframe
</Segment> src={about}
style={{ width: '100%', height: '100vh', border: 'none' }}
/> : <Segment>
<div style={{ fontSize: 'larger' }} dangerouslySetInnerHTML={{ __html: about }}></div>
</Segment>
}
</>
}
</> </>
); );
}; };

View File

@@ -6,6 +6,7 @@ import { marked } from 'marked';
const Home = () => { const Home = () => {
const [statusState, statusDispatch] = useContext(StatusContext); const [statusState, statusDispatch] = useContext(StatusContext);
const [homePageContentLoaded, setHomePageContentLoaded] = useState(false);
const [homePageContent, setHomePageContent] = useState(''); const [homePageContent, setHomePageContent] = useState('');
const displayNotice = async () => { const displayNotice = async () => {
@@ -23,16 +24,21 @@ const Home = () => {
}; };
const displayHomePageContent = async () => { const displayHomePageContent = async () => {
setHomePageContent(localStorage.getItem('home_page_content') || '');
const res = await API.get('/api/home_page_content'); const res = await API.get('/api/home_page_content');
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
let HTMLContent = marked.parse(data); let content = data;
localStorage.setItem('home_page_content', HTMLContent); if (!data.startsWith('https://')) {
setHomePageContent(HTMLContent); content = marked.parse(data);
}
setHomePageContent(content);
localStorage.setItem('home_page_content', content);
} else { } else {
showError(message); showError(message);
setHomePageContent('加载首页内容失败...'); setHomePageContent('加载首页内容失败...');
} }
setHomePageContentLoaded(true);
}; };
const getStartTimeString = () => { const getStartTimeString = () => {
@@ -46,73 +52,79 @@ const Home = () => {
}, []); }, []);
return ( return (
<> <>
<Segment> {
{ homePageContentLoaded && homePageContent === '' ? <>
homePageContent === '' ? <> <Segment>
<Header as='h3'>系统状况</Header> <Header as='h3'>系统状况</Header>
<Grid columns={2} stackable> <Grid columns={2} stackable>
<Grid.Column> <Grid.Column>
<Card fluid> <Card fluid>
<Card.Content> <Card.Content>
<Card.Header>系统信息</Card.Header> <Card.Header>系统信息</Card.Header>
<Card.Meta>系统信息总览</Card.Meta> <Card.Meta>系统信息总览</Card.Meta>
<Card.Description> <Card.Description>
<p>名称{statusState?.status?.system_name}</p> <p>名称{statusState?.status?.system_name}</p>
<p>版本{statusState?.status?.version}</p> <p>版本{statusState?.status?.version}</p>
<p> <p>
源码 源码
<a <a
href='https://github.com/songquanpeng/one-api' href='https://github.com/songquanpeng/one-api'
target='_blank' target='_blank'
> >
https://github.com/songquanpeng/one-api https://github.com/songquanpeng/one-api
</a> </a>
</p> </p>
<p>启动时间{getStartTimeString()}</p> <p>启动时间{getStartTimeString()}</p>
</Card.Description> </Card.Description>
</Card.Content> </Card.Content>
</Card> </Card>
</Grid.Column> </Grid.Column>
<Grid.Column> <Grid.Column>
<Card fluid> <Card fluid>
<Card.Content> <Card.Content>
<Card.Header>系统配置</Card.Header> <Card.Header>系统配置</Card.Header>
<Card.Meta>系统配置总览</Card.Meta> <Card.Meta>系统配置总览</Card.Meta>
<Card.Description> <Card.Description>
<p> <p>
邮箱验证 邮箱验证
{statusState?.status?.email_verification === true {statusState?.status?.email_verification === true
? '已启用' ? '已启用'
: '未启用'} : '未启用'}
</p> </p>
<p> <p>
GitHub 身份验证 GitHub 身份验证
{statusState?.status?.github_oauth === true {statusState?.status?.github_oauth === true
? '已启用' ? '已启用'
: '未启用'} : '未启用'}
</p> </p>
<p> <p>
微信身份验证 微信身份验证
{statusState?.status?.wechat_login === true {statusState?.status?.wechat_login === true
? '已启用' ? '已启用'
: '未启用'} : '未启用'}
</p> </p>
<p> <p>
Turnstile 用户校验 Turnstile 用户校验
{statusState?.status?.turnstile_check === true {statusState?.status?.turnstile_check === true
? '已启用' ? '已启用'
: '未启用'} : '未启用'}
</p> </p>
</Card.Description> </Card.Description>
</Card.Content> </Card.Content>
</Card> </Card>
</Grid.Column> </Grid.Column>
</Grid> </Grid>
</> : <> </Segment>
<div dangerouslySetInnerHTML={{ __html: homePageContent}}></div> </> : <>
</> {
} homePageContent.startsWith('https://') ? <iframe
</Segment> src={homePageContent}
style={{ width: '100%', height: '100vh', border: 'none' }}
/> : <div style={{ fontSize: 'larger' }} dangerouslySetInnerHTML={{ __html: homePageContent }}></div>
}
</>
}
</> </>
); );
}; };