🎨 chore(web): apply ESLint and Prettier auto-fixes (baseline)
- Ran: bun run eslint:fix && bun run lint:fix - Inserted AGPL license header via eslint-plugin-header - Enforced no-multiple-empty-lines and other lint rules - Formatted code using Prettier v3 (@so1ve/prettier-config) - No functional changes; formatting-only baseline across JS/JSX files
This commit is contained in:
@@ -21,7 +21,10 @@ import React from 'react';
|
||||
import { Card, Tag, Timeline, Empty } from '@douyinfe/semi-ui';
|
||||
import { Bell } from 'lucide-react';
|
||||
import { marked } from 'marked';
|
||||
import { IllustrationConstruction, IllustrationConstructionDark } from '@douyinfe/semi-illustrations';
|
||||
import {
|
||||
IllustrationConstruction,
|
||||
IllustrationConstructionDark,
|
||||
} from '@douyinfe/semi-illustrations';
|
||||
import ScrollableContainer from '../common/ui/ScrollableContainer';
|
||||
|
||||
const AnnouncementsPanel = ({
|
||||
@@ -29,36 +32,43 @@ const AnnouncementsPanel = ({
|
||||
announcementLegendData,
|
||||
CARD_PROPS,
|
||||
ILLUSTRATION_SIZE,
|
||||
t
|
||||
t,
|
||||
}) => {
|
||||
return (
|
||||
<Card
|
||||
{...CARD_PROPS}
|
||||
className="shadow-sm !rounded-2xl lg:col-span-2"
|
||||
className='shadow-sm !rounded-2xl lg:col-span-2'
|
||||
title={
|
||||
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-2 w-full">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className='flex flex-col lg:flex-row lg:items-center lg:justify-between gap-2 w-full'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Bell size={16} />
|
||||
{t('系统公告')}
|
||||
<Tag color="white" shape="circle">
|
||||
<Tag color='white' shape='circle'>
|
||||
{t('显示最新20条')}
|
||||
</Tag>
|
||||
</div>
|
||||
{/* 图例 */}
|
||||
<div className="flex flex-wrap gap-3 text-xs">
|
||||
<div className='flex flex-wrap gap-3 text-xs'>
|
||||
{announcementLegendData.map((legend, index) => (
|
||||
<div key={index} className="flex items-center gap-1">
|
||||
<div key={index} className='flex items-center gap-1'>
|
||||
<div
|
||||
className="w-2 h-2 rounded-full"
|
||||
className='w-2 h-2 rounded-full'
|
||||
style={{
|
||||
backgroundColor: legend.color === 'grey' ? '#8b9aa7' :
|
||||
legend.color === 'blue' ? '#3b82f6' :
|
||||
legend.color === 'green' ? '#10b981' :
|
||||
legend.color === 'orange' ? '#f59e0b' :
|
||||
legend.color === 'red' ? '#ef4444' : '#8b9aa7'
|
||||
backgroundColor:
|
||||
legend.color === 'grey'
|
||||
? '#8b9aa7'
|
||||
: legend.color === 'blue'
|
||||
? '#3b82f6'
|
||||
: legend.color === 'green'
|
||||
? '#10b981'
|
||||
: legend.color === 'orange'
|
||||
? '#f59e0b'
|
||||
: legend.color === 'red'
|
||||
? '#ef4444'
|
||||
: '#8b9aa7',
|
||||
}}
|
||||
/>
|
||||
<span className="text-gray-600">{legend.label}</span>
|
||||
<span className='text-gray-600'>{legend.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -66,9 +76,9 @@ const AnnouncementsPanel = ({
|
||||
}
|
||||
bodyStyle={{ padding: 0 }}
|
||||
>
|
||||
<ScrollableContainer maxHeight="24rem">
|
||||
<ScrollableContainer maxHeight='24rem'>
|
||||
{announcementData.length > 0 ? (
|
||||
<Timeline mode="left">
|
||||
<Timeline mode='left'>
|
||||
{announcementData.map((item, idx) => {
|
||||
const htmlExtra = item.extra ? marked.parse(item.extra) : '';
|
||||
return (
|
||||
@@ -76,16 +86,20 @@ const AnnouncementsPanel = ({
|
||||
key={idx}
|
||||
type={item.type || 'default'}
|
||||
time={`${item.relative ? item.relative + ' ' : ''}${item.time}`}
|
||||
extra={item.extra ? (
|
||||
<div
|
||||
className="text-xs text-gray-500"
|
||||
dangerouslySetInnerHTML={{ __html: htmlExtra }}
|
||||
/>
|
||||
) : null}
|
||||
extra={
|
||||
item.extra ? (
|
||||
<div
|
||||
className='text-xs text-gray-500'
|
||||
dangerouslySetInnerHTML={{ __html: htmlExtra }}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: marked.parse(item.content || '') }}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: marked.parse(item.content || ''),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Timeline.Item>
|
||||
@@ -93,10 +107,12 @@ const AnnouncementsPanel = ({
|
||||
})}
|
||||
</Timeline>
|
||||
) : (
|
||||
<div className="flex justify-center items-center py-8">
|
||||
<div className='flex justify-center items-center py-8'>
|
||||
<Empty
|
||||
image={<IllustrationConstruction style={ILLUSTRATION_SIZE} />}
|
||||
darkModeImage={<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />}
|
||||
darkModeImage={
|
||||
<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />
|
||||
}
|
||||
title={t('暂无系统公告')}
|
||||
description={t('请联系管理员在系统设置中配置公告信息')}
|
||||
/>
|
||||
@@ -107,4 +123,4 @@ const AnnouncementsPanel = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default AnnouncementsPanel;
|
||||
export default AnnouncementsPanel;
|
||||
|
||||
@@ -20,7 +20,10 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
import React from 'react';
|
||||
import { Card, Avatar, Tag, Divider, Empty } from '@douyinfe/semi-ui';
|
||||
import { Server, Gauge, ExternalLink } from 'lucide-react';
|
||||
import { IllustrationConstruction, IllustrationConstructionDark } from '@douyinfe/semi-illustrations';
|
||||
import {
|
||||
IllustrationConstruction,
|
||||
IllustrationConstructionDark,
|
||||
} from '@douyinfe/semi-illustrations';
|
||||
import ScrollableContainer from '../common/ui/ScrollableContainer';
|
||||
|
||||
const ApiInfoPanel = ({
|
||||
@@ -30,12 +33,12 @@ const ApiInfoPanel = ({
|
||||
CARD_PROPS,
|
||||
FLEX_CENTER_GAP2,
|
||||
ILLUSTRATION_SIZE,
|
||||
t
|
||||
t,
|
||||
}) => {
|
||||
return (
|
||||
<Card
|
||||
{...CARD_PROPS}
|
||||
className="bg-gray-50 border-0 !rounded-2xl"
|
||||
className='bg-gray-50 border-0 !rounded-2xl'
|
||||
title={
|
||||
<div className={FLEX_CENTER_GAP2}>
|
||||
<Server size={16} />
|
||||
@@ -44,66 +47,65 @@ const ApiInfoPanel = ({
|
||||
}
|
||||
bodyStyle={{ padding: 0 }}
|
||||
>
|
||||
<ScrollableContainer maxHeight="24rem">
|
||||
<ScrollableContainer maxHeight='24rem'>
|
||||
{apiInfoData.length > 0 ? (
|
||||
apiInfoData.map((api) => (
|
||||
<React.Fragment key={api.id}>
|
||||
<div className="flex p-2 hover:bg-white rounded-lg transition-colors cursor-pointer">
|
||||
<div className="flex-shrink-0 mr-3">
|
||||
<Avatar
|
||||
size="extra-small"
|
||||
color={api.color}
|
||||
>
|
||||
<div className='flex p-2 hover:bg-white rounded-lg transition-colors cursor-pointer'>
|
||||
<div className='flex-shrink-0 mr-3'>
|
||||
<Avatar size='extra-small' color={api.color}>
|
||||
{api.route.substring(0, 2)}
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex flex-wrap items-center justify-between mb-1 w-full gap-2">
|
||||
<span className="text-sm font-medium text-gray-900 !font-bold break-all">
|
||||
<div className='flex-1'>
|
||||
<div className='flex flex-wrap items-center justify-between mb-1 w-full gap-2'>
|
||||
<span className='text-sm font-medium text-gray-900 !font-bold break-all'>
|
||||
{api.route}
|
||||
</span>
|
||||
<div className="flex items-center gap-1 mt-1 lg:mt-0">
|
||||
<div className='flex items-center gap-1 mt-1 lg:mt-0'>
|
||||
<Tag
|
||||
prefixIcon={<Gauge size={12} />}
|
||||
size="small"
|
||||
color="white"
|
||||
size='small'
|
||||
color='white'
|
||||
shape='circle'
|
||||
onClick={() => handleSpeedTest(api.url)}
|
||||
className="cursor-pointer hover:opacity-80 text-xs"
|
||||
className='cursor-pointer hover:opacity-80 text-xs'
|
||||
>
|
||||
{t('测速')}
|
||||
</Tag>
|
||||
<Tag
|
||||
prefixIcon={<ExternalLink size={12} />}
|
||||
size="small"
|
||||
color="white"
|
||||
size='small'
|
||||
color='white'
|
||||
shape='circle'
|
||||
onClick={() => window.open(api.url, '_blank', 'noopener,noreferrer')}
|
||||
className="cursor-pointer hover:opacity-80 text-xs"
|
||||
onClick={() =>
|
||||
window.open(api.url, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
className='cursor-pointer hover:opacity-80 text-xs'
|
||||
>
|
||||
{t('跳转')}
|
||||
</Tag>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="!text-semi-color-primary break-all cursor-pointer hover:underline mb-1"
|
||||
className='!text-semi-color-primary break-all cursor-pointer hover:underline mb-1'
|
||||
onClick={() => handleCopyUrl(api.url)}
|
||||
>
|
||||
{api.url}
|
||||
</div>
|
||||
<div className="text-gray-500">
|
||||
{api.description}
|
||||
</div>
|
||||
<div className='text-gray-500'>{api.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Divider />
|
||||
</React.Fragment>
|
||||
))
|
||||
) : (
|
||||
<div className="flex justify-center items-center py-8">
|
||||
<div className='flex justify-center items-center py-8'>
|
||||
<Empty
|
||||
image={<IllustrationConstruction style={ILLUSTRATION_SIZE} />}
|
||||
darkModeImage={<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />}
|
||||
darkModeImage={
|
||||
<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />
|
||||
}
|
||||
title={t('暂无API信息')}
|
||||
description={t('请联系管理员在系统设置中配置API信息')}
|
||||
/>
|
||||
@@ -114,4 +116,4 @@ const ApiInfoPanel = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default ApiInfoPanel;
|
||||
export default ApiInfoPanel;
|
||||
|
||||
@@ -23,7 +23,7 @@ import { PieChart } from 'lucide-react';
|
||||
import {
|
||||
IconHistogram,
|
||||
IconPulse,
|
||||
IconPieChart2Stroked
|
||||
IconPieChart2Stroked,
|
||||
} from '@douyinfe/semi-icons';
|
||||
import { VChart } from '@visactor/react-vchart';
|
||||
|
||||
@@ -38,80 +38,80 @@ const ChartsPanel = ({
|
||||
CHART_CONFIG,
|
||||
FLEX_CENTER_GAP2,
|
||||
hasApiInfoPanel,
|
||||
t
|
||||
t,
|
||||
}) => {
|
||||
return (
|
||||
<Card
|
||||
{...CARD_PROPS}
|
||||
className={`!rounded-2xl ${hasApiInfoPanel ? 'lg:col-span-3' : ''}`}
|
||||
title={
|
||||
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between w-full gap-3">
|
||||
<div className='flex flex-col lg:flex-row lg:items-center lg:justify-between w-full gap-3'>
|
||||
<div className={FLEX_CENTER_GAP2}>
|
||||
<PieChart size={16} />
|
||||
{t('模型数据分析')}
|
||||
</div>
|
||||
<Tabs
|
||||
type="button"
|
||||
type='button'
|
||||
activeKey={activeChartTab}
|
||||
onChange={setActiveChartTab}
|
||||
>
|
||||
<TabPane tab={
|
||||
<span>
|
||||
<IconHistogram />
|
||||
{t('消耗分布')}
|
||||
</span>
|
||||
} itemKey="1" />
|
||||
<TabPane tab={
|
||||
<span>
|
||||
<IconPulse />
|
||||
{t('消耗趋势')}
|
||||
</span>
|
||||
} itemKey="2" />
|
||||
<TabPane tab={
|
||||
<span>
|
||||
<IconPieChart2Stroked />
|
||||
{t('调用次数分布')}
|
||||
</span>
|
||||
} itemKey="3" />
|
||||
<TabPane tab={
|
||||
<span>
|
||||
<IconHistogram />
|
||||
{t('调用次数排行')}
|
||||
</span>
|
||||
} itemKey="4" />
|
||||
<TabPane
|
||||
tab={
|
||||
<span>
|
||||
<IconHistogram />
|
||||
{t('消耗分布')}
|
||||
</span>
|
||||
}
|
||||
itemKey='1'
|
||||
/>
|
||||
<TabPane
|
||||
tab={
|
||||
<span>
|
||||
<IconPulse />
|
||||
{t('消耗趋势')}
|
||||
</span>
|
||||
}
|
||||
itemKey='2'
|
||||
/>
|
||||
<TabPane
|
||||
tab={
|
||||
<span>
|
||||
<IconPieChart2Stroked />
|
||||
{t('调用次数分布')}
|
||||
</span>
|
||||
}
|
||||
itemKey='3'
|
||||
/>
|
||||
<TabPane
|
||||
tab={
|
||||
<span>
|
||||
<IconHistogram />
|
||||
{t('调用次数排行')}
|
||||
</span>
|
||||
}
|
||||
itemKey='4'
|
||||
/>
|
||||
</Tabs>
|
||||
</div>
|
||||
}
|
||||
bodyStyle={{ padding: 0 }}
|
||||
>
|
||||
<div className="h-96 p-2">
|
||||
<div className='h-96 p-2'>
|
||||
{activeChartTab === '1' && (
|
||||
<VChart
|
||||
spec={spec_line}
|
||||
option={CHART_CONFIG}
|
||||
/>
|
||||
<VChart spec={spec_line} option={CHART_CONFIG} />
|
||||
)}
|
||||
{activeChartTab === '2' && (
|
||||
<VChart
|
||||
spec={spec_model_line}
|
||||
option={CHART_CONFIG}
|
||||
/>
|
||||
<VChart spec={spec_model_line} option={CHART_CONFIG} />
|
||||
)}
|
||||
{activeChartTab === '3' && (
|
||||
<VChart
|
||||
spec={spec_pie}
|
||||
option={CHART_CONFIG}
|
||||
/>
|
||||
<VChart spec={spec_pie} option={CHART_CONFIG} />
|
||||
)}
|
||||
{activeChartTab === '4' && (
|
||||
<VChart
|
||||
spec={spec_rank_bar}
|
||||
option={CHART_CONFIG}
|
||||
/>
|
||||
<VChart spec={spec_rank_bar} option={CHART_CONFIG} />
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChartsPanel;
|
||||
export default ChartsPanel;
|
||||
|
||||
@@ -27,19 +27,19 @@ const DashboardHeader = ({
|
||||
showSearchModal,
|
||||
refresh,
|
||||
loading,
|
||||
t
|
||||
t,
|
||||
}) => {
|
||||
const ICON_BUTTON_CLASS = "text-white hover:bg-opacity-80 !rounded-full";
|
||||
const ICON_BUTTON_CLASS = 'text-white hover:bg-opacity-80 !rounded-full';
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className='flex items-center justify-between mb-4'>
|
||||
<h2
|
||||
className="text-2xl font-semibold text-gray-800 transition-opacity duration-1000 ease-in-out"
|
||||
className='text-2xl font-semibold text-gray-800 transition-opacity duration-1000 ease-in-out'
|
||||
style={{ opacity: greetingVisible ? 1 : 0 }}
|
||||
>
|
||||
{getGreeting}
|
||||
</h2>
|
||||
<div className="flex gap-3">
|
||||
<div className='flex gap-3'>
|
||||
<Button
|
||||
type='tertiary'
|
||||
icon={<Search size={16} />}
|
||||
@@ -58,4 +58,4 @@ const DashboardHeader = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardHeader;
|
||||
export default DashboardHeader;
|
||||
|
||||
@@ -22,7 +22,10 @@ import { Card, Collapse, Empty } from '@douyinfe/semi-ui';
|
||||
import { HelpCircle } from 'lucide-react';
|
||||
import { IconPlus, IconMinus } from '@douyinfe/semi-icons';
|
||||
import { marked } from 'marked';
|
||||
import { IllustrationConstruction, IllustrationConstructionDark } from '@douyinfe/semi-illustrations';
|
||||
import {
|
||||
IllustrationConstruction,
|
||||
IllustrationConstructionDark,
|
||||
} from '@douyinfe/semi-illustrations';
|
||||
import ScrollableContainer from '../common/ui/ScrollableContainer';
|
||||
|
||||
const FaqPanel = ({
|
||||
@@ -30,12 +33,12 @@ const FaqPanel = ({
|
||||
CARD_PROPS,
|
||||
FLEX_CENTER_GAP2,
|
||||
ILLUSTRATION_SIZE,
|
||||
t
|
||||
t,
|
||||
}) => {
|
||||
return (
|
||||
<Card
|
||||
{...CARD_PROPS}
|
||||
className="shadow-sm !rounded-2xl lg:col-span-1"
|
||||
className='shadow-sm !rounded-2xl lg:col-span-1'
|
||||
title={
|
||||
<div className={FLEX_CENTER_GAP2}>
|
||||
<HelpCircle size={16} />
|
||||
@@ -44,7 +47,7 @@ const FaqPanel = ({
|
||||
}
|
||||
bodyStyle={{ padding: 0 }}
|
||||
>
|
||||
<ScrollableContainer maxHeight="24rem">
|
||||
<ScrollableContainer maxHeight='24rem'>
|
||||
{faqData.length > 0 ? (
|
||||
<Collapse
|
||||
accordion
|
||||
@@ -58,16 +61,20 @@ const FaqPanel = ({
|
||||
itemKey={index.toString()}
|
||||
>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: marked.parse(item.answer || '') }}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: marked.parse(item.answer || ''),
|
||||
}}
|
||||
/>
|
||||
</Collapse.Panel>
|
||||
))}
|
||||
</Collapse>
|
||||
) : (
|
||||
<div className="flex justify-center items-center py-8">
|
||||
<div className='flex justify-center items-center py-8'>
|
||||
<Empty
|
||||
image={<IllustrationConstruction style={ILLUSTRATION_SIZE} />}
|
||||
darkModeImage={<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />}
|
||||
darkModeImage={
|
||||
<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />
|
||||
}
|
||||
title={t('暂无常见问答')}
|
||||
description={t('请联系管理员在系统设置中配置常见问答')}
|
||||
/>
|
||||
@@ -78,4 +85,4 @@ const FaqPanel = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default FaqPanel;
|
||||
export default FaqPanel;
|
||||
|
||||
@@ -28,13 +28,13 @@ const StatsCards = ({
|
||||
loading,
|
||||
getTrendSpec,
|
||||
CARD_PROPS,
|
||||
CHART_CONFIG
|
||||
CHART_CONFIG,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div className='mb-4'>
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4'>
|
||||
{groupedStatsData.map((group, idx) => (
|
||||
<Card
|
||||
key={idx}
|
||||
@@ -42,24 +42,24 @@ const StatsCards = ({
|
||||
className={`${group.color} border-0 !rounded-2xl w-full`}
|
||||
title={group.title}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className='space-y-4'>
|
||||
{group.items.map((item, itemIdx) => (
|
||||
<div
|
||||
key={itemIdx}
|
||||
className="flex items-center justify-between cursor-pointer"
|
||||
className='flex items-center justify-between cursor-pointer'
|
||||
onClick={item.onClick}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className='flex items-center'>
|
||||
<Avatar
|
||||
className="mr-3"
|
||||
size="small"
|
||||
className='mr-3'
|
||||
size='small'
|
||||
color={item.avatarColor}
|
||||
>
|
||||
{item.icon}
|
||||
</Avatar>
|
||||
<div>
|
||||
<div className="text-xs text-gray-500">{item.title}</div>
|
||||
<div className="text-lg font-semibold">
|
||||
<div className='text-xs text-gray-500'>{item.title}</div>
|
||||
<div className='text-lg font-semibold'>
|
||||
<Skeleton
|
||||
loading={loading}
|
||||
active
|
||||
@@ -67,7 +67,11 @@ const StatsCards = ({
|
||||
<Skeleton.Paragraph
|
||||
active
|
||||
rows={1}
|
||||
style={{ width: '65px', height: '24px', marginTop: '4px' }}
|
||||
style={{
|
||||
width: '65px',
|
||||
height: '24px',
|
||||
marginTop: '4px',
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
@@ -78,9 +82,9 @@ const StatsCards = ({
|
||||
</div>
|
||||
{item.title === t('当前余额') ? (
|
||||
<Tag
|
||||
color="white"
|
||||
color='white'
|
||||
shape='circle'
|
||||
size="large"
|
||||
size='large'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate('/console/topup');
|
||||
@@ -88,13 +92,16 @@ const StatsCards = ({
|
||||
>
|
||||
{t('充值')}
|
||||
</Tag>
|
||||
) : (loading || (item.trendData && item.trendData.length > 0)) && (
|
||||
<div className="w-24 h-10">
|
||||
<VChart
|
||||
spec={getTrendSpec(item.trendData, item.trendColor)}
|
||||
option={CHART_CONFIG}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
(loading ||
|
||||
(item.trendData && item.trendData.length > 0)) && (
|
||||
<div className='w-24 h-10'>
|
||||
<VChart
|
||||
spec={getTrendSpec(item.trendData, item.trendColor)}
|
||||
option={CHART_CONFIG}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
@@ -106,4 +113,4 @@ const StatsCards = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default StatsCards;
|
||||
export default StatsCards;
|
||||
|
||||
@@ -18,9 +18,20 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Card, Button, Spin, Tabs, TabPane, Tag, Empty } from '@douyinfe/semi-ui';
|
||||
import {
|
||||
Card,
|
||||
Button,
|
||||
Spin,
|
||||
Tabs,
|
||||
TabPane,
|
||||
Tag,
|
||||
Empty,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { Gauge, RefreshCw } from 'lucide-react';
|
||||
import { IllustrationConstruction, IllustrationConstructionDark } from '@douyinfe/semi-illustrations';
|
||||
import {
|
||||
IllustrationConstruction,
|
||||
IllustrationConstructionDark,
|
||||
} from '@douyinfe/semi-illustrations';
|
||||
import ScrollableContainer from '../common/ui/ScrollableContainer';
|
||||
|
||||
const UptimePanel = ({
|
||||
@@ -33,15 +44,15 @@ const UptimePanel = ({
|
||||
renderMonitorList,
|
||||
CARD_PROPS,
|
||||
ILLUSTRATION_SIZE,
|
||||
t
|
||||
t,
|
||||
}) => {
|
||||
return (
|
||||
<Card
|
||||
{...CARD_PROPS}
|
||||
className="shadow-sm !rounded-2xl lg:col-span-1"
|
||||
className='shadow-sm !rounded-2xl lg:col-span-1'
|
||||
title={
|
||||
<div className="flex items-center justify-between w-full gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className='flex items-center justify-between w-full gap-2'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Gauge size={16} />
|
||||
{t('服务可用性')}
|
||||
</div>
|
||||
@@ -49,39 +60,43 @@ const UptimePanel = ({
|
||||
icon={<RefreshCw size={14} />}
|
||||
onClick={loadUptimeData}
|
||||
loading={uptimeLoading}
|
||||
size="small"
|
||||
theme="borderless"
|
||||
size='small'
|
||||
theme='borderless'
|
||||
type='tertiary'
|
||||
className="text-gray-500 hover:text-blue-500 hover:bg-blue-50 !rounded-full"
|
||||
className='text-gray-500 hover:text-blue-500 hover:bg-blue-50 !rounded-full'
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
bodyStyle={{ padding: 0 }}
|
||||
>
|
||||
{/* 内容区域 */}
|
||||
<div className="relative">
|
||||
<div className='relative'>
|
||||
<Spin spinning={uptimeLoading}>
|
||||
{uptimeData.length > 0 ? (
|
||||
uptimeData.length === 1 ? (
|
||||
<ScrollableContainer maxHeight="24rem">
|
||||
<ScrollableContainer maxHeight='24rem'>
|
||||
{renderMonitorList(uptimeData[0].monitors)}
|
||||
</ScrollableContainer>
|
||||
) : (
|
||||
<Tabs
|
||||
type="card"
|
||||
type='card'
|
||||
collapsible
|
||||
activeKey={activeUptimeTab}
|
||||
onChange={setActiveUptimeTab}
|
||||
size="small"
|
||||
size='small'
|
||||
>
|
||||
{uptimeData.map((group, groupIdx) => (
|
||||
<TabPane
|
||||
tab={
|
||||
<span className="flex items-center gap-2">
|
||||
<span className='flex items-center gap-2'>
|
||||
<Gauge size={14} />
|
||||
{group.categoryName}
|
||||
<Tag
|
||||
color={activeUptimeTab === group.categoryName ? 'red' : 'grey'}
|
||||
color={
|
||||
activeUptimeTab === group.categoryName
|
||||
? 'red'
|
||||
: 'grey'
|
||||
}
|
||||
size='small'
|
||||
shape='circle'
|
||||
>
|
||||
@@ -92,7 +107,7 @@ const UptimePanel = ({
|
||||
itemKey={group.categoryName}
|
||||
key={groupIdx}
|
||||
>
|
||||
<ScrollableContainer maxHeight="21.5rem">
|
||||
<ScrollableContainer maxHeight='21.5rem'>
|
||||
{renderMonitorList(group.monitors)}
|
||||
</ScrollableContainer>
|
||||
</TabPane>
|
||||
@@ -100,10 +115,12 @@ const UptimePanel = ({
|
||||
</Tabs>
|
||||
)
|
||||
) : (
|
||||
<div className="flex justify-center items-center py-8">
|
||||
<div className='flex justify-center items-center py-8'>
|
||||
<Empty
|
||||
image={<IllustrationConstruction style={ILLUSTRATION_SIZE} />}
|
||||
darkModeImage={<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />}
|
||||
darkModeImage={
|
||||
<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />
|
||||
}
|
||||
title={t('暂无监控数据')}
|
||||
description={t('请联系管理员在系统设置中配置Uptime')}
|
||||
/>
|
||||
@@ -114,15 +131,15 @@ const UptimePanel = ({
|
||||
|
||||
{/* 图例 */}
|
||||
{uptimeData.length > 0 && (
|
||||
<div className="p-3 bg-gray-50 rounded-b-2xl">
|
||||
<div className="flex flex-wrap gap-3 text-xs justify-center">
|
||||
<div className='p-3 bg-gray-50 rounded-b-2xl'>
|
||||
<div className='flex flex-wrap gap-3 text-xs justify-center'>
|
||||
{uptimeLegendData.map((legend, index) => (
|
||||
<div key={index} className="flex items-center gap-1">
|
||||
<div key={index} className='flex items-center gap-1'>
|
||||
<div
|
||||
className="w-2 h-2 rounded-full"
|
||||
className='w-2 h-2 rounded-full'
|
||||
style={{ backgroundColor: legend.color }}
|
||||
/>
|
||||
<span className="text-gray-600">{legend.label}</span>
|
||||
<span className='text-gray-600'>{legend.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -132,4 +149,4 @@ const UptimePanel = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default UptimePanel;
|
||||
export default UptimePanel;
|
||||
|
||||
@@ -41,7 +41,7 @@ import {
|
||||
FLEX_CENTER_GAP2,
|
||||
ILLUSTRATION_SIZE,
|
||||
ANNOUNCEMENT_LEGEND_DATA,
|
||||
UPTIME_STATUS_MAP
|
||||
UPTIME_STATUS_MAP,
|
||||
} from '../../constants/dashboard.constants';
|
||||
import {
|
||||
getTrendSpec,
|
||||
@@ -49,7 +49,7 @@ import {
|
||||
handleSpeedTest,
|
||||
getUptimeStatusColor,
|
||||
getUptimeStatusText,
|
||||
renderMonitorList
|
||||
renderMonitorList,
|
||||
} from '../../helpers/dashboard';
|
||||
|
||||
const Dashboard = () => {
|
||||
@@ -70,7 +70,7 @@ const Dashboard = () => {
|
||||
dashboardData.setPieData,
|
||||
dashboardData.setLineData,
|
||||
dashboardData.setModelColors,
|
||||
dashboardData.t
|
||||
dashboardData.t,
|
||||
);
|
||||
|
||||
// ========== 统计数据 ==========
|
||||
@@ -82,12 +82,12 @@ const Dashboard = () => {
|
||||
dashboardData.trendData,
|
||||
dashboardData.performanceMetrics,
|
||||
dashboardData.navigate,
|
||||
dashboardData.t
|
||||
dashboardData.t,
|
||||
);
|
||||
|
||||
// ========== 数据处理 ==========
|
||||
const initChart = async () => {
|
||||
await dashboardData.loadQuotaData().then(data => {
|
||||
await dashboardData.loadQuotaData().then((data) => {
|
||||
if (data && data.length > 0) {
|
||||
dashboardCharts.updateChartData(data);
|
||||
}
|
||||
@@ -108,25 +108,30 @@ const Dashboard = () => {
|
||||
|
||||
// ========== 数据准备 ==========
|
||||
const apiInfoData = statusState?.status?.api_info || [];
|
||||
const announcementData = (statusState?.status?.announcements || []).map(item => {
|
||||
const pubDate = item?.publishDate ? new Date(item.publishDate) : null;
|
||||
const absoluteTime = pubDate && !isNaN(pubDate.getTime())
|
||||
? `${pubDate.getFullYear()}-${String(pubDate.getMonth() + 1).padStart(2, '0')}-${String(pubDate.getDate()).padStart(2, '0')} ${String(pubDate.getHours()).padStart(2, '0')}:${String(pubDate.getMinutes()).padStart(2, '0')}`
|
||||
: (item?.publishDate || '');
|
||||
const relativeTime = getRelativeTime(item.publishDate);
|
||||
return ({
|
||||
...item,
|
||||
time: absoluteTime,
|
||||
relative: relativeTime
|
||||
});
|
||||
});
|
||||
const announcementData = (statusState?.status?.announcements || []).map(
|
||||
(item) => {
|
||||
const pubDate = item?.publishDate ? new Date(item.publishDate) : null;
|
||||
const absoluteTime =
|
||||
pubDate && !isNaN(pubDate.getTime())
|
||||
? `${pubDate.getFullYear()}-${String(pubDate.getMonth() + 1).padStart(2, '0')}-${String(pubDate.getDate()).padStart(2, '0')} ${String(pubDate.getHours()).padStart(2, '0')}:${String(pubDate.getMinutes()).padStart(2, '0')}`
|
||||
: item?.publishDate || '';
|
||||
const relativeTime = getRelativeTime(item.publishDate);
|
||||
return {
|
||||
...item,
|
||||
time: absoluteTime,
|
||||
relative: relativeTime,
|
||||
};
|
||||
},
|
||||
);
|
||||
const faqData = statusState?.status?.faq || [];
|
||||
|
||||
const uptimeLegendData = Object.entries(UPTIME_STATUS_MAP).map(([status, info]) => ({
|
||||
status: Number(status),
|
||||
color: info.color,
|
||||
label: dashboardData.t(info.label)
|
||||
}));
|
||||
const uptimeLegendData = Object.entries(UPTIME_STATUS_MAP).map(
|
||||
([status, info]) => ({
|
||||
status: Number(status),
|
||||
color: info.color,
|
||||
label: dashboardData.t(info.label),
|
||||
}),
|
||||
);
|
||||
|
||||
// ========== Effects ==========
|
||||
useEffect(() => {
|
||||
@@ -134,7 +139,7 @@ const Dashboard = () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-full">
|
||||
<div className='h-full'>
|
||||
<DashboardHeader
|
||||
getGreeting={dashboardData.getGreeting}
|
||||
greetingVisible={dashboardData.greetingVisible}
|
||||
@@ -166,8 +171,10 @@ const Dashboard = () => {
|
||||
/>
|
||||
|
||||
{/* API信息和图表面板 */}
|
||||
<div className="mb-4">
|
||||
<div className={`grid grid-cols-1 gap-4 ${dashboardData.hasApiInfoPanel ? 'lg:grid-cols-4' : ''}`}>
|
||||
<div className='mb-4'>
|
||||
<div
|
||||
className={`grid grid-cols-1 gap-4 ${dashboardData.hasApiInfoPanel ? 'lg:grid-cols-4' : ''}`}
|
||||
>
|
||||
<ChartsPanel
|
||||
activeChartTab={dashboardData.activeChartTab}
|
||||
setActiveChartTab={dashboardData.setActiveChartTab}
|
||||
@@ -198,16 +205,18 @@ const Dashboard = () => {
|
||||
|
||||
{/* 系统公告和常见问答卡片 */}
|
||||
{dashboardData.hasInfoPanels && (
|
||||
<div className="mb-4">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4">
|
||||
<div className='mb-4'>
|
||||
<div className='grid grid-cols-1 lg:grid-cols-4 gap-4'>
|
||||
{/* 公告卡片 */}
|
||||
{dashboardData.announcementsEnabled && (
|
||||
<AnnouncementsPanel
|
||||
announcementData={announcementData}
|
||||
announcementLegendData={ANNOUNCEMENT_LEGEND_DATA.map(item => ({
|
||||
...item,
|
||||
label: dashboardData.t(item.label)
|
||||
}))}
|
||||
announcementLegendData={ANNOUNCEMENT_LEGEND_DATA.map(
|
||||
(item) => ({
|
||||
...item,
|
||||
label: dashboardData.t(item.label),
|
||||
}),
|
||||
)}
|
||||
CARD_PROPS={CARD_PROPS}
|
||||
ILLUSTRATION_SIZE={ILLUSTRATION_SIZE}
|
||||
t={dashboardData.t}
|
||||
@@ -234,12 +243,19 @@ const Dashboard = () => {
|
||||
setActiveUptimeTab={dashboardData.setActiveUptimeTab}
|
||||
loadUptimeData={dashboardData.loadUptimeData}
|
||||
uptimeLegendData={uptimeLegendData}
|
||||
renderMonitorList={(monitors) => renderMonitorList(
|
||||
monitors,
|
||||
(status) => getUptimeStatusColor(status, UPTIME_STATUS_MAP),
|
||||
(status) => getUptimeStatusText(status, UPTIME_STATUS_MAP, dashboardData.t),
|
||||
dashboardData.t
|
||||
)}
|
||||
renderMonitorList={(monitors) =>
|
||||
renderMonitorList(
|
||||
monitors,
|
||||
(status) => getUptimeStatusColor(status, UPTIME_STATUS_MAP),
|
||||
(status) =>
|
||||
getUptimeStatusText(
|
||||
status,
|
||||
UPTIME_STATUS_MAP,
|
||||
dashboardData.t,
|
||||
),
|
||||
dashboardData.t,
|
||||
)
|
||||
}
|
||||
CARD_PROPS={CARD_PROPS}
|
||||
ILLUSTRATION_SIZE={ILLUSTRATION_SIZE}
|
||||
t={dashboardData.t}
|
||||
@@ -252,4 +268,4 @@ const Dashboard = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
export default Dashboard;
|
||||
|
||||
@@ -30,12 +30,12 @@ const SearchModal = ({
|
||||
dataExportDefaultTime,
|
||||
timeOptions,
|
||||
handleInputChange,
|
||||
t
|
||||
t,
|
||||
}) => {
|
||||
const formRef = useRef();
|
||||
|
||||
const FORM_FIELD_PROPS = {
|
||||
className: "w-full mb-2 !rounded-lg",
|
||||
className: 'w-full mb-2 !rounded-lg',
|
||||
};
|
||||
|
||||
const createFormField = (Component, props) => (
|
||||
@@ -54,7 +54,7 @@ const SearchModal = ({
|
||||
size={isMobile ? 'full-width' : 'small'}
|
||||
centered
|
||||
>
|
||||
<Form ref={formRef} layout='vertical' className="w-full">
|
||||
<Form ref={formRef} layout='vertical' className='w-full'>
|
||||
{createFormField(Form.DatePicker, {
|
||||
field: 'start_timestamp',
|
||||
label: t('起始时间'),
|
||||
@@ -62,7 +62,7 @@ const SearchModal = ({
|
||||
value: start_timestamp,
|
||||
type: 'dateTime',
|
||||
name: 'start_timestamp',
|
||||
onChange: (value) => handleInputChange(value, 'start_timestamp')
|
||||
onChange: (value) => handleInputChange(value, 'start_timestamp'),
|
||||
})}
|
||||
|
||||
{createFormField(Form.DatePicker, {
|
||||
@@ -72,7 +72,7 @@ const SearchModal = ({
|
||||
value: end_timestamp,
|
||||
type: 'dateTime',
|
||||
name: 'end_timestamp',
|
||||
onChange: (value) => handleInputChange(value, 'end_timestamp')
|
||||
onChange: (value) => handleInputChange(value, 'end_timestamp'),
|
||||
})}
|
||||
|
||||
{createFormField(Form.Select, {
|
||||
@@ -82,20 +82,22 @@ const SearchModal = ({
|
||||
placeholder: t('时间粒度'),
|
||||
name: 'data_export_default_time',
|
||||
optionList: timeOptions,
|
||||
onChange: (value) => handleInputChange(value, 'data_export_default_time')
|
||||
onChange: (value) =>
|
||||
handleInputChange(value, 'data_export_default_time'),
|
||||
})}
|
||||
|
||||
{isAdminUser && createFormField(Form.Input, {
|
||||
field: 'username',
|
||||
label: t('用户名称'),
|
||||
value: username,
|
||||
placeholder: t('可选值'),
|
||||
name: 'username',
|
||||
onChange: (value) => handleInputChange(value, 'username')
|
||||
})}
|
||||
{isAdminUser &&
|
||||
createFormField(Form.Input, {
|
||||
field: 'username',
|
||||
label: t('用户名称'),
|
||||
value: username,
|
||||
placeholder: t('可选值'),
|
||||
name: 'username',
|
||||
onChange: (value) => handleInputChange(value, 'username'),
|
||||
})}
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchModal;
|
||||
export default SearchModal;
|
||||
|
||||
Reference in New Issue
Block a user