Compare commits

..

5 Commits

Author SHA1 Message Date
JustSong
a316ed7abc fix: handle empty dashboard data and improve summary calculation 2025-02-01 01:54:00 +08:00
JustSong
0895d8660e fix: fix about page 2025-02-01 01:42:38 +08:00
JustSong
be1ed114f4 chore: add gcc and sqlite-dev dependencies to Dockerfile 2025-02-01 01:37:21 +08:00
JustSong
eb6da573a3 chore: optimize Dockerfile for multi-directory npm installation and build 2025-02-01 01:12:26 +08:00
JustSong
0a6273fc08 chore: bug fix for home page 2025-02-01 01:08:28 +08:00
4 changed files with 266 additions and 233 deletions

View File

@@ -4,21 +4,20 @@ WORKDIR /web
COPY ./VERSION . COPY ./VERSION .
COPY ./web . COPY ./web .
WORKDIR /web/default RUN npm install --prefix /web/default & \
RUN npm install npm install --prefix /web/berry & \
RUN DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat VERSION) npm run build npm install --prefix /web/air & \
wait
WORKDIR /web/berry RUN DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat /web/default/VERSION) npm run build --prefix /web/default & \
RUN npm install DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat /web/berry/VERSION) npm run build --prefix /web/berry & \
RUN DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat VERSION) npm run build DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat /web/air/VERSION) npm run build --prefix /web/air & \
wait
WORKDIR /web/air
RUN npm install
RUN DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat VERSION) npm run build
FROM golang:alpine AS builder2 FROM golang:alpine AS builder2
RUN apk add --no-cache g++ RUN apk add --no-cache g++
RUN apk add --no-cache gcc musl-dev libc-dev sqlite-dev
ENV GO111MODULE=on \ ENV GO111MODULE=on \
CGO_ENABLED=1 \ CGO_ENABLED=1 \

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Card } from 'semantic-ui-react'; import { Card, Header, Segment } from 'semantic-ui-react';
import { API, showError } from '../../helpers'; import { API, showError } from '../../helpers';
import { marked } from 'marked'; import { marked } from 'marked';
@@ -7,39 +7,58 @@ const About = () => {
const [about, setAbout] = useState(''); const [about, setAbout] = useState('');
const [aboutLoaded, setAboutLoaded] = useState(false); const [aboutLoaded, setAboutLoaded] = useState(false);
// ... 其他函数保持不变 ... const displayAbout = async () => {
setAbout(localStorage.getItem('about') || '');
const res = await API.get('/api/about');
const { success, message, data } = res.data;
if (success) {
let aboutContent = data;
if (!data.startsWith('https://')) {
aboutContent = marked.parse(data);
}
setAbout(aboutContent);
localStorage.setItem('about', aboutContent);
} else {
showError(message);
setAbout('加载关于内容失败...');
}
setAboutLoaded(true);
};
useEffect(() => {
displayAbout().then();
}, []);
return ( return (
<div className='dashboard-container'> <>
<Card fluid className='chart-card'> {aboutLoaded && about === '' ? (
<Card.Content> <div className='dashboard-container'>
<Card.Header className='header'>关于系统</Card.Header> <Card fluid className='chart-card'>
{aboutLoaded && about === '' ? ( <Card.Content>
<> <Card.Header className='header'>关于系统</Card.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>
</> </Card.Content>
</Card>
</div>
) : (
<>
{about.startsWith('https://') ? (
<iframe
src={about}
style={{ width: '100%', height: '100vh', border: 'none' }}
/>
) : ( ) : (
<> <div
{about.startsWith('https://') ? ( style={{ fontSize: 'larger' }}
<iframe dangerouslySetInnerHTML={{ __html: about }}
src={about} ></div>
style={{ width: '100%', height: '100vh', border: 'none' }}
/>
) : (
<div
style={{ fontSize: 'larger' }}
dangerouslySetInnerHTML={{ __html: about }}
></div>
)}
</>
)} )}
</Card.Content> </>
</Card> )}
</div> </>
); );
}; };

View File

@@ -68,16 +68,27 @@ const Dashboard = () => {
try { try {
const response = await axios.get('/api/user/dashboard'); const response = await axios.get('/api/user/dashboard');
if (response.data.success) { if (response.data.success) {
const dashboardData = response.data.data; const dashboardData = response.data.data || [];
setData(dashboardData); setData(dashboardData);
calculateSummary(dashboardData); calculateSummary(dashboardData);
} }
} catch (error) { } catch (error) {
console.error('Failed to fetch dashboard data:', error); console.error('Failed to fetch dashboard data:', error);
setData([]);
calculateSummary([]);
} }
}; };
const calculateSummary = (dashboardData) => { const calculateSummary = (dashboardData) => {
if (!Array.isArray(dashboardData) || dashboardData.length === 0) {
setSummaryData({
todayRequests: 0,
todayQuota: 0,
todayTokens: 0
});
return;
}
const today = new Date().toISOString().split('T')[0]; const today = new Date().toISOString().split('T')[0];
const todayData = dashboardData.filter((item) => item.Day === today); const todayData = dashboardData.filter((item) => item.Day === today);
@@ -87,7 +98,7 @@ const Dashboard = () => {
0 0
), ),
todayQuota: todayQuota:
todayData.reduce((sum, item) => sum + item.Quota, 0) / 1000000, // 转换为美元 todayData.reduce((sum, item) => sum + item.Quota, 0) / 1000000,
todayTokens: todayData.reduce( todayTokens: todayData.reduce(
(sum, item) => sum + item.PromptTokens + item.CompletionTokens, (sum, item) => sum + item.PromptTokens + item.CompletionTokens,
0 0

View File

@@ -56,218 +56,222 @@ const Home = () => {
}, []); }, []);
return ( return (
<div className='dashboard-container'> <>
<Card fluid className='chart-card'>
<Card.Content>
<Card.Header className='header'>欢迎使用 One API</Card.Header>
<Card.Description style={{ lineHeight: '1.6' }}>
<p>
One API 是一个 LLM API
接口管理和分发系统可以帮助您更好地管理和使用各大厂商的 LLM API
</p>
{!userState.user && (
<p>
如需使用请先<Link to='/login'>登录</Link>
<Link to='/register'>注册</Link>
</p>
)}
</Card.Description>
</Card.Content>
</Card>
{homePageContentLoaded && homePageContent === '' ? ( {homePageContentLoaded && homePageContent === '' ? (
<Card fluid className='chart-card'> <div className='dashboard-container'>
<Card.Content> <Card fluid className='chart-card'>
<Card.Header> <Card.Content>
<Header as='h3'>系统状况</Header> <Card.Header className='header'>欢迎使用 One API</Card.Header>
</Card.Header> <Card.Description style={{ lineHeight: '1.6' }}>
<Grid columns={2} stackable> <p>
<Grid.Column> One API 是一个 LLM API
<Card 接口管理和分发系统可以帮助您更好地管理和使用各大厂商的 LLM
fluid API
className='chart-card' </p>
style={{ boxShadow: '0 1px 3px rgba(0,0,0,0.12)' }} {!userState.user && (
> <p>
<Card.Content> 如需使用请先<Link to='/login'>登录</Link>
<Card.Header> <Link to='/register'>注册</Link>
<Header as='h3' style={{ color: '#444' }}> </p>
系统信息 )}
</Header> </Card.Description>
</Card.Header> </Card.Content>
<Card.Description </Card>
style={{ lineHeight: '2', marginTop: '1em' }} <Card fluid className='chart-card'>
> <Card.Content>
<p <Card.Header>
style={{ <Header as='h3'>系统状况</Header>
display: 'flex', </Card.Header>
alignItems: 'center', <Grid columns={2} stackable>
gap: '0.5em', <Grid.Column>
}} <Card
fluid
className='chart-card'
style={{ boxShadow: '0 1px 3px rgba(0,0,0,0.12)' }}
>
<Card.Content>
<Card.Header>
<Header as='h3' style={{ color: '#444' }}>
系统信息
</Header>
</Card.Header>
<Card.Description
style={{ lineHeight: '2', marginTop: '1em' }}
> >
<i className='info circle icon'></i> <p
<span style={{ fontWeight: 'bold' }}>名称</span> style={{
<span>{statusState?.status?.system_name}</span> display: 'flex',
</p> alignItems: 'center',
<p gap: '0.5em',
style={{ }}
display: 'flex',
alignItems: 'center',
gap: '0.5em',
}}
>
<i className='code branch icon'></i>
<span style={{ fontWeight: 'bold' }}>版本</span>
<span>{statusState?.status?.version || 'unknown'}</span>
</p>
<p
style={{
display: 'flex',
alignItems: 'center',
gap: '0.5em',
}}
>
<i className='github icon'></i>
<span style={{ fontWeight: 'bold' }}>源码</span>
<a
href='https://github.com/songquanpeng/one-api'
target='_blank'
style={{ color: '#2185d0' }}
> >
GitHub 仓库 <i className='info circle icon'></i>
</a> <span style={{ fontWeight: 'bold' }}>名称</span>
</p> <span>{statusState?.status?.system_name}</span>
<p </p>
style={{ <p
display: 'flex', style={{
alignItems: 'center', display: 'flex',
gap: '0.5em', alignItems: 'center',
}} gap: '0.5em',
> }}
<i className='clock outline icon'></i> >
<span style={{ fontWeight: 'bold' }}>启动时间</span> <i className='code branch icon'></i>
<span>{getStartTimeString()}</span> <span style={{ fontWeight: 'bold' }}>版本</span>
</p> <span>
</Card.Description> {statusState?.status?.version || 'unknown'}
</Card.Content> </span>
</Card> </p>
</Grid.Column> <p
style={{
display: 'flex',
alignItems: 'center',
gap: '0.5em',
}}
>
<i className='github icon'></i>
<span style={{ fontWeight: 'bold' }}>源码</span>
<a
href='https://github.com/songquanpeng/one-api'
target='_blank'
style={{ color: '#2185d0' }}
>
GitHub 仓库
</a>
</p>
<p
style={{
display: 'flex',
alignItems: 'center',
gap: '0.5em',
}}
>
<i className='clock outline icon'></i>
<span style={{ fontWeight: 'bold' }}>启动时间</span>
<span>{getStartTimeString()}</span>
</p>
</Card.Description>
</Card.Content>
</Card>
</Grid.Column>
<Grid.Column> <Grid.Column>
<Card <Card
fluid fluid
className='chart-card' className='chart-card'
style={{ boxShadow: '0 1px 3px rgba(0,0,0,0.12)' }} style={{ boxShadow: '0 1px 3px rgba(0,0,0,0.12)' }}
> >
<Card.Content> <Card.Content>
<Card.Header> <Card.Header>
<Header as='h3' style={{ color: '#444' }}> <Header as='h3' style={{ color: '#444' }}>
系统配置 系统配置
</Header> </Header>
</Card.Header> </Card.Header>
<Card.Description <Card.Description
style={{ lineHeight: '2', marginTop: '1em' }} style={{ lineHeight: '2', marginTop: '1em' }}
>
<p
style={{
display: 'flex',
alignItems: 'center',
gap: '0.5em',
}}
> >
<i className='envelope icon'></i> <p
<span style={{ fontWeight: 'bold' }}>邮箱验证</span>
<span
style={{ style={{
color: statusState?.status?.email_verification display: 'flex',
? '#21ba45' alignItems: 'center',
: '#db2828', gap: '0.5em',
fontWeight: '500',
}} }}
> >
{statusState?.status?.email_verification <i className='envelope icon'></i>
? '已启用' <span style={{ fontWeight: 'bold' }}>邮箱验证</span>
: '未启用'} <span
</span> style={{
</p> color: statusState?.status?.email_verification
<p ? '#21ba45'
style={{ : '#db2828',
display: 'flex', fontWeight: '500',
alignItems: 'center', }}
gap: '0.5em', >
}} {statusState?.status?.email_verification
> ? '已启用'
<i className='github icon'></i> : '未启用'}
<span style={{ fontWeight: 'bold' }}> </span>
GitHub 身份验证 </p>
</span> <p
<span
style={{ style={{
color: statusState?.status?.github_oauth display: 'flex',
? '#21ba45' alignItems: 'center',
: '#db2828', gap: '0.5em',
fontWeight: '500',
}} }}
> >
{statusState?.status?.github_oauth <i className='github icon'></i>
? '已启用' <span style={{ fontWeight: 'bold' }}>
: '未启用'} GitHub 身份验证
</span> </span>
</p> <span
<p style={{
style={{ color: statusState?.status?.github_oauth
display: 'flex', ? '#21ba45'
alignItems: 'center', : '#db2828',
gap: '0.5em', fontWeight: '500',
}} }}
> >
<i className='wechat icon'></i> {statusState?.status?.github_oauth
<span style={{ fontWeight: 'bold' }}> ? '已启用'
微信身份验证 : '未启用'}
</span> </span>
<span </p>
<p
style={{ style={{
color: statusState?.status?.wechat_login display: 'flex',
? '#21ba45' alignItems: 'center',
: '#db2828', gap: '0.5em',
fontWeight: '500',
}} }}
> >
{statusState?.status?.wechat_login <i className='wechat icon'></i>
? '已启用' <span style={{ fontWeight: 'bold' }}>
: '未启用'} 微信身份验证
</span> </span>
</p> <span
<p style={{
style={{ color: statusState?.status?.wechat_login
display: 'flex', ? '#21ba45'
alignItems: 'center', : '#db2828',
gap: '0.5em', fontWeight: '500',
}} }}
> >
<i className='shield alternate icon'></i> {statusState?.status?.wechat_login
<span style={{ fontWeight: 'bold' }}> ? '已启用'
Turnstile 校验 : '未启用'}
</span> </span>
<span </p>
<p
style={{ style={{
color: statusState?.status?.turnstile_check display: 'flex',
? '#21ba45' alignItems: 'center',
: '#db2828', gap: '0.5em',
fontWeight: '500',
}} }}
> >
{statusState?.status?.turnstile_check <i className='shield alternate icon'></i>
? '已启用' <span style={{ fontWeight: 'bold' }}>
: '未启用'} Turnstile 校验
</span> </span>
</p> <span
</Card.Description> style={{
</Card.Content> color: statusState?.status?.turnstile_check
</Card> ? '#21ba45'
</Grid.Column> : '#db2828',
</Grid> fontWeight: '500',
</Card.Content> }}
</Card> >
{statusState?.status?.turnstile_check
? '已启用'
: '未启用'}
</span>
</p>
</Card.Description>
</Card.Content>
</Card>
</Grid.Column>
</Grid>
</Card.Content>
</Card>{' '}
</div>
) : ( ) : (
<> <>
{homePageContent.startsWith('https://') ? ( {homePageContent.startsWith('https://') ? (
@@ -283,7 +287,7 @@ const Home = () => {
)} )}
</> </>
)} )}
</div> </>
); );
}; };