mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 12:44:22 +00:00
refactor(frontend): drop CustomStatistic wrapper, move overrides to theme tokens
- Delete `<CustomStatistic>` (a pass-through wrapper over <Statistic>)
and its unscoped global `.ant-statistic-*` CSS overrides; consumers
(IndexPage, ClientsPage, InboundsPage, NodesPage) now import AntD
`<Statistic>` directly.
- Add Statistic component tokens to ConfigProvider so the title (11px)
and content (17px) font sizes still apply, without `!important`
global selectors.
- Move dark / ultra-dark card border colours from `body.dark .ant-card`
+ `html[data-theme='ultra-dark'] .ant-card` selectors into Card
`colorBorderSecondary` tokens; page-cards.css now only carries the
custom radius/shadow/transition that has no token equivalent.
- Simplify XrayStatusCard badge: remove the custom `xray-pulse` dot
keyframe and per-state ring-colour overrides; AntD `<Badge
status="processing" color={…}>` already pulses the ring in the same
colour, no extra CSS needed.
This commit is contained in:
parent
6f9fdb154d
commit
7e5f279284
11 changed files with 49 additions and 384 deletions
|
|
@ -1,52 +0,0 @@
|
|||
.ant-statistic-content {
|
||||
font-size: 17px !important;
|
||||
line-height: 1.4 !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ant-statistic-content-value,
|
||||
.ant-statistic-content-prefix,
|
||||
.ant-statistic-content-suffix {
|
||||
font-size: 17px !important;
|
||||
}
|
||||
|
||||
.ant-statistic-content-prefix {
|
||||
margin-inline-end: 8px !important;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.ant-statistic-content-prefix .anticon {
|
||||
font-size: 17px !important;
|
||||
}
|
||||
|
||||
.ant-statistic-content-suffix {
|
||||
font-size: 12px !important;
|
||||
opacity: 0.55;
|
||||
margin-inline-start: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ant-statistic-title {
|
||||
font-size: 11px !important;
|
||||
margin-bottom: 6px !important;
|
||||
letter-spacing: 0.6px;
|
||||
text-transform: uppercase;
|
||||
color: rgba(0, 0, 0, 0.55);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
body.dark .ant-statistic-content {
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
}
|
||||
|
||||
body.dark .ant-statistic-title {
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
|
||||
html[data-theme='ultra-dark'] .ant-statistic-content {
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
|
||||
html[data-theme='ultra-dark'] .ant-statistic-title {
|
||||
color: rgba(255, 255, 255, 0.70);
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import type { ReactNode } from 'react';
|
||||
import { Statistic } from 'antd';
|
||||
import './CustomStatistic.css';
|
||||
|
||||
interface CustomStatisticProps {
|
||||
title?: string;
|
||||
value?: string | number;
|
||||
prefix?: ReactNode;
|
||||
suffix?: ReactNode;
|
||||
}
|
||||
|
||||
export default function CustomStatistic({ title = '', value = '', prefix, suffix }: CustomStatisticProps) {
|
||||
return <Statistic title={title} value={value} prefix={prefix} suffix={suffix} />;
|
||||
}
|
||||
|
|
@ -68,10 +68,25 @@ const ULTRA_DARK_MENU_TOKENS = {
|
|||
darkSubMenuItemBg: '#000',
|
||||
darkPopupBg: '#101013',
|
||||
};
|
||||
const DARK_CARD_TOKENS = {
|
||||
colorBorderSecondary: 'rgba(255, 255, 255, 0.06)',
|
||||
};
|
||||
const ULTRA_DARK_CARD_TOKENS = {
|
||||
colorBorderSecondary: 'rgba(255, 255, 255, 0.04)',
|
||||
};
|
||||
const STATISTIC_TOKENS = {
|
||||
contentFontSize: 17,
|
||||
titleFontSize: 11,
|
||||
};
|
||||
|
||||
export function buildAntdThemeConfig(isDark: boolean, isUltra: boolean): ThemeConfig {
|
||||
if (!isDark) {
|
||||
return { algorithm: antdTheme.defaultAlgorithm };
|
||||
return {
|
||||
algorithm: antdTheme.defaultAlgorithm,
|
||||
components: {
|
||||
Statistic: STATISTIC_TOKENS,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
algorithm: antdTheme.darkAlgorithm,
|
||||
|
|
@ -79,6 +94,8 @@ export function buildAntdThemeConfig(isDark: boolean, isUltra: boolean): ThemeCo
|
|||
components: {
|
||||
Layout: isUltra ? ULTRA_DARK_LAYOUT_TOKENS : DARK_LAYOUT_TOKENS,
|
||||
Menu: isUltra ? ULTRA_DARK_MENU_TOKENS : DARK_MENU_TOKENS,
|
||||
Card: isUltra ? ULTRA_DARK_CARD_TOKENS : DARK_CARD_TOKENS,
|
||||
Statistic: STATISTIC_TOKENS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
Select,
|
||||
Space,
|
||||
Spin,
|
||||
Statistic,
|
||||
Switch,
|
||||
Table,
|
||||
Tag,
|
||||
|
|
@ -49,7 +50,6 @@ import { useClients } from '@/hooks/useClients';
|
|||
import { useDatepicker } from '@/hooks/useDatepicker';
|
||||
import type { ClientRecord, InboundOption } from '@/hooks/useClients';
|
||||
import AppSidebar from '@/components/AppSidebar';
|
||||
import CustomStatistic from '@/components/CustomStatistic';
|
||||
import { IntlUtil, SizeFormatter } from '@/utils';
|
||||
import { setMessageInstance } from '@/utils/messageBus';
|
||||
import LazyMount from '@/components/LazyMount';
|
||||
|
|
@ -624,7 +624,7 @@ export default function ClientsPage() {
|
|||
<Card size="small" hoverable className="summary-card">
|
||||
<Row gutter={[16, 12]}>
|
||||
<Col xs={12} sm={8} md={4}>
|
||||
<CustomStatistic title={t('clients')} value={String(summary.total)} prefix={<TeamOutlined />} />
|
||||
<Statistic title={t('clients')} value={String(summary.total)} prefix={<TeamOutlined />} />
|
||||
</Col>
|
||||
<Col xs={12} sm={8} md={4}>
|
||||
<Popover
|
||||
|
|
@ -632,7 +632,7 @@ export default function ClientsPage() {
|
|||
open={summary.online.length ? undefined : false}
|
||||
content={<div className="client-email-list">{summary.online.map((e) => <div key={e}>{e}</div>)}</div>}
|
||||
>
|
||||
<CustomStatistic title={t('online')} value={String(summary.online.length)} prefix={<span className="dot dot-blue" />} />
|
||||
<Statistic title={t('online')} value={String(summary.online.length)} prefix={<span className="dot dot-blue" />} />
|
||||
</Popover>
|
||||
</Col>
|
||||
<Col xs={12} sm={8} md={4}>
|
||||
|
|
@ -641,7 +641,7 @@ export default function ClientsPage() {
|
|||
open={summary.depleted.length ? undefined : false}
|
||||
content={<div className="client-email-list">{summary.depleted.map((e) => <div key={e}>{e}</div>)}</div>}
|
||||
>
|
||||
<CustomStatistic title={t('depleted')} value={String(summary.depleted.length)} prefix={<span className="dot dot-red" />} />
|
||||
<Statistic title={t('depleted')} value={String(summary.depleted.length)} prefix={<span className="dot dot-red" />} />
|
||||
</Popover>
|
||||
</Col>
|
||||
<Col xs={12} sm={8} md={4}>
|
||||
|
|
@ -650,7 +650,7 @@ export default function ClientsPage() {
|
|||
open={summary.expiring.length ? undefined : false}
|
||||
content={<div className="client-email-list">{summary.expiring.map((e) => <div key={e}>{e}</div>)}</div>}
|
||||
>
|
||||
<CustomStatistic title={t('depletingSoon')} value={String(summary.expiring.length)} prefix={<span className="dot dot-orange" />} />
|
||||
<Statistic title={t('depletingSoon')} value={String(summary.expiring.length)} prefix={<span className="dot dot-orange" />} />
|
||||
</Popover>
|
||||
</Col>
|
||||
<Col xs={12} sm={8} md={4}>
|
||||
|
|
@ -659,11 +659,11 @@ export default function ClientsPage() {
|
|||
open={summary.deactive.length ? undefined : false}
|
||||
content={<div className="client-email-list">{summary.deactive.map((e) => <div key={e}>{e}</div>)}</div>}
|
||||
>
|
||||
<CustomStatistic title={t('disabled')} value={String(summary.deactive.length)} prefix={<span className="dot dot-gray" />} />
|
||||
<Statistic title={t('disabled')} value={String(summary.deactive.length)} prefix={<span className="dot dot-gray" />} />
|
||||
</Popover>
|
||||
</Col>
|
||||
<Col xs={12} sm={8} md={4}>
|
||||
<CustomStatistic title={t('subscription.active')} value={String(summary.active)} prefix={<span className="dot dot-green" />} />
|
||||
<Statistic title={t('subscription.active')} value={String(summary.active)} prefix={<span className="dot dot-green" />} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
Modal,
|
||||
Row,
|
||||
Spin,
|
||||
Statistic,
|
||||
message,
|
||||
} from 'antd';
|
||||
|
||||
|
|
@ -26,7 +27,6 @@ import { useMediaQuery } from '@/hooks/useMediaQuery';
|
|||
import { useWebSocket } from '@/hooks/useWebSocket';
|
||||
import { useNodesQuery } from '@/api/queries/useNodesQuery';
|
||||
import AppSidebar from '@/components/AppSidebar';
|
||||
import CustomStatistic from '@/components/CustomStatistic';
|
||||
const TextModal = lazy(() => import('@/components/TextModal'));
|
||||
const PromptModal = lazy(() => import('@/components/PromptModal'));
|
||||
|
||||
|
|
@ -463,21 +463,21 @@ export default function InboundsPage() {
|
|||
<Card size="small" hoverable className="summary-card">
|
||||
<Row gutter={[16, 12]}>
|
||||
<Col xs={12} sm={12} md={8}>
|
||||
<CustomStatistic
|
||||
<Statistic
|
||||
title={t('pages.inbounds.totalDownUp')}
|
||||
value={`${SizeFormatter.sizeFormat(totals.up)} / ${SizeFormatter.sizeFormat(totals.down)}`}
|
||||
prefix={<SwapOutlined />}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={12} sm={12} md={8}>
|
||||
<CustomStatistic
|
||||
<Statistic
|
||||
title={t('pages.inbounds.totalUsage')}
|
||||
value={SizeFormatter.sizeFormat(totals.up + totals.down)}
|
||||
prefix={<PieChartOutlined />}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={8}>
|
||||
<CustomStatistic
|
||||
<Statistic
|
||||
title={t('pages.inbounds.inboundCount')}
|
||||
value={String(dbInbounds.length)}
|
||||
prefix={<BarsOutlined />}
|
||||
|
|
|
|||
|
|
@ -40,152 +40,11 @@
|
|||
min-height: calc(100vh - 120px);
|
||||
}
|
||||
|
||||
.index-page .ant-card {
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
transition: transform 0.2s ease, box-shadow 0.25s ease, border-color 0.2s ease;
|
||||
}
|
||||
|
||||
body.dark .index-page .ant-card {
|
||||
border-color: rgba(255, 255, 255, 0.06);
|
||||
box-shadow:
|
||||
0 1px 2px rgba(0, 0, 0, 0.4),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
html[data-theme='ultra-dark'] .index-page .ant-card {
|
||||
border-color: rgba(255, 255, 255, 0.04);
|
||||
box-shadow:
|
||||
0 1px 2px rgba(0, 0, 0, 0.6),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.025);
|
||||
}
|
||||
|
||||
.index-page .ant-card.ant-card-hoverable:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: rgba(0, 0, 0, 0.10);
|
||||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
body.dark .index-page .ant-card.ant-card-hoverable:hover {
|
||||
border-color: rgba(255, 255, 255, 0.12);
|
||||
box-shadow:
|
||||
0 8px 24px rgba(0, 0, 0, 0.5),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
html[data-theme='ultra-dark'] .index-page .ant-card.ant-card-hoverable:hover {
|
||||
border-color: rgba(255, 255, 255, 0.08);
|
||||
box-shadow:
|
||||
0 8px 24px rgba(0, 0, 0, 0.75),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.index-page .ant-card .ant-card-head {
|
||||
min-height: 44px;
|
||||
padding-inline: 16px;
|
||||
}
|
||||
|
||||
.index-page .ant-card .ant-card-head-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
text-transform: uppercase;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.index-page .ant-card .ant-card-body {
|
||||
padding: 18px 20px;
|
||||
}
|
||||
|
||||
.index-page .ant-card .ant-card-body > .ant-row > .ant-col {
|
||||
position: relative;
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
.index-page .ant-card .ant-card-body > .ant-row > .ant-col + .ant-col::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 10%;
|
||||
bottom: 10%;
|
||||
width: 1px;
|
||||
background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.10), transparent);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
body.dark .index-page .ant-card .ant-card-body > .ant-row > .ant-col + .ant-col::before {
|
||||
background: linear-gradient(180deg, transparent, rgba(255, 255, 255, 0.12), transparent);
|
||||
}
|
||||
|
||||
.index-page .ant-card .ant-card-head {
|
||||
border-bottom-color: rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.index-page .ant-card .ant-card-actions {
|
||||
border-top-color: rgba(0, 0, 0, 0.06);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.index-page .ant-card .ant-card-actions > li {
|
||||
border-inline-end-color: rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
body.dark .index-page .ant-card .ant-card-head {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
body.dark .index-page .ant-card .ant-card-actions {
|
||||
border-top-color: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
body.dark .index-page .ant-card .ant-card-actions > li {
|
||||
border-inline-end-color: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
html[data-theme='ultra-dark'] .index-page .ant-card .ant-card-head {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
html[data-theme='ultra-dark'] .index-page .ant-card .ant-card-actions {
|
||||
border-top-color: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
html[data-theme='ultra-dark'] .index-page .ant-card .ant-card-actions > li {
|
||||
border-inline-end-color: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
.index-page .action {
|
||||
cursor: pointer;
|
||||
justify-content: center;
|
||||
max-width: 100%;
|
||||
padding: 0 8px;
|
||||
flex-wrap: nowrap;
|
||||
color: rgba(0, 0, 0, 0.78);
|
||||
font-weight: 500;
|
||||
transition: opacity 0.15s ease, transform 0.15s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
.index-page .action .anticon {
|
||||
color: rgba(0, 0, 0, 0.72);
|
||||
}
|
||||
|
||||
body.dark .index-page .action {
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
}
|
||||
|
||||
body.dark .index-page .action .anticon {
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
html[data-theme='ultra-dark'] .index-page .action {
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
}
|
||||
|
||||
html[data-theme='ultra-dark'] .index-page .action .anticon {
|
||||
color: rgba(255, 255, 255, 0.78);
|
||||
}
|
||||
|
||||
.index-page .action > span:not(.anticon):not(.tg-icon) {
|
||||
|
|
@ -195,16 +54,6 @@ html[data-theme='ultra-dark'] .index-page .action .anticon {
|
|||
min-width: 0;
|
||||
}
|
||||
|
||||
.index-page .action:hover {
|
||||
opacity: 0.75;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.index-page .ant-card-actions > li {
|
||||
margin: 8px 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.index-page .action-update {
|
||||
color: #fa8c16;
|
||||
font-weight: 600;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
Row,
|
||||
Space,
|
||||
Spin,
|
||||
Statistic,
|
||||
Tag,
|
||||
Tooltip,
|
||||
} from 'antd';
|
||||
|
|
@ -39,7 +40,6 @@ import { useTheme } from '@/hooks/useTheme';
|
|||
import { useStatusQuery } from '@/api/queries/useStatusQuery';
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||
import AppSidebar from '@/components/AppSidebar';
|
||||
import CustomStatistic from '@/components/CustomStatistic';
|
||||
import LazyMount from '@/components/LazyMount';
|
||||
import { setMessageInstance } from '@/utils/messageBus';
|
||||
import StatusCard from './StatusCard';
|
||||
|
|
@ -285,14 +285,14 @@ export default function IndexPage() {
|
|||
<Card title={t('pages.index.operationHours')} hoverable>
|
||||
<Row gutter={isMobile ? [8, 8] : 0}>
|
||||
<Col span={12}>
|
||||
<CustomStatistic
|
||||
<Statistic
|
||||
title="Xray"
|
||||
value={TimeFormatter.formatSecond(status.appStats.uptime)}
|
||||
prefix={<ThunderboltOutlined />}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<CustomStatistic
|
||||
<Statistic
|
||||
title="OS"
|
||||
value={TimeFormatter.formatSecond(status.uptime)}
|
||||
prefix={<DesktopOutlined />}
|
||||
|
|
@ -306,14 +306,14 @@ export default function IndexPage() {
|
|||
<Card title={t('usage')} hoverable>
|
||||
<Row gutter={isMobile ? [8, 8] : 0}>
|
||||
<Col span={12}>
|
||||
<CustomStatistic
|
||||
<Statistic
|
||||
title={t('pages.index.memory')}
|
||||
value={SizeFormatter.sizeFormat(status.appStats.mem)}
|
||||
prefix={<DatabaseOutlined />}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<CustomStatistic
|
||||
<Statistic
|
||||
title={t('pages.index.threads')}
|
||||
value={status.appStats.threads}
|
||||
prefix={<ForkOutlined />}
|
||||
|
|
@ -327,7 +327,7 @@ export default function IndexPage() {
|
|||
<Card title={t('pages.index.overallSpeed')} hoverable>
|
||||
<Row gutter={isMobile ? [8, 8] : 0}>
|
||||
<Col span={12}>
|
||||
<CustomStatistic
|
||||
<Statistic
|
||||
title={t('pages.index.upload')}
|
||||
value={SizeFormatter.sizeFormat(status.netIO.up)}
|
||||
prefix={<ArrowUpOutlined />}
|
||||
|
|
@ -335,7 +335,7 @@ export default function IndexPage() {
|
|||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<CustomStatistic
|
||||
<Statistic
|
||||
title={t('pages.index.download')}
|
||||
value={SizeFormatter.sizeFormat(status.netIO.down)}
|
||||
prefix={<ArrowDownOutlined />}
|
||||
|
|
@ -350,14 +350,14 @@ export default function IndexPage() {
|
|||
<Card title={t('pages.index.totalData')} hoverable>
|
||||
<Row gutter={isMobile ? [8, 8] : 0}>
|
||||
<Col span={12}>
|
||||
<CustomStatistic
|
||||
<Statistic
|
||||
title={t('pages.index.sent')}
|
||||
value={SizeFormatter.sizeFormat(status.netTraffic.sent)}
|
||||
prefix={<CloudUploadOutlined />}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<CustomStatistic
|
||||
<Statistic
|
||||
title={t('pages.index.received')}
|
||||
value={SizeFormatter.sizeFormat(status.netTraffic.recv)}
|
||||
prefix={<CloudDownloadOutlined />}
|
||||
|
|
@ -392,14 +392,14 @@ export default function IndexPage() {
|
|||
>
|
||||
<Row className={showIp ? 'ip-visible' : 'ip-hidden'} gutter={isMobile ? [8, 8] : 0}>
|
||||
<Col span={isMobile ? 24 : 12}>
|
||||
<CustomStatistic
|
||||
<Statistic
|
||||
title="IPv4"
|
||||
value={status.publicIP.ipv4}
|
||||
prefix={<GlobalOutlined />}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={isMobile ? 24 : 12}>
|
||||
<CustomStatistic
|
||||
<Statistic
|
||||
title="IPv6"
|
||||
value={status.publicIP.ipv6}
|
||||
prefix={<GlobalOutlined />}
|
||||
|
|
@ -413,14 +413,14 @@ export default function IndexPage() {
|
|||
<Card title={t('pages.index.connectionCount')} hoverable>
|
||||
<Row gutter={isMobile ? [8, 8] : 0}>
|
||||
<Col span={12}>
|
||||
<CustomStatistic
|
||||
<Statistic
|
||||
title="TCP"
|
||||
value={status.tcpCount}
|
||||
prefix={<SwapOutlined />}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<CustomStatistic
|
||||
<Statistic
|
||||
title="UDP"
|
||||
value={status.udpCount}
|
||||
prefix={<SwapOutlined />}
|
||||
|
|
|
|||
|
|
@ -12,33 +12,3 @@
|
|||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.xray-processing-animation .ant-badge-status-dot {
|
||||
animation: xray-pulse 1.2s linear infinite;
|
||||
}
|
||||
|
||||
.xray-running-animation .ant-badge-status-processing::after {
|
||||
border-color: #1677ff;
|
||||
}
|
||||
|
||||
.xray-stop-animation .ant-badge-status-processing::after {
|
||||
border-color: #fa8c16;
|
||||
}
|
||||
|
||||
.xray-error-animation .ant-badge-status-processing::after {
|
||||
border-color: #f5222d;
|
||||
}
|
||||
|
||||
@keyframes xray-pulse {
|
||||
0%,
|
||||
50%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
10% {
|
||||
transform: scale(1.5);
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,13 +28,6 @@ const XRAY_STATE_KEYS: Record<string, string> = {
|
|||
error: 'pages.index.xrayStatusError',
|
||||
};
|
||||
|
||||
function badgeAnimationClass(color: string): string {
|
||||
if (color === 'green') return 'xray-running-animation';
|
||||
if (color === 'orange') return 'xray-stop-animation';
|
||||
if (color === 'red') return 'xray-error-animation';
|
||||
return 'xray-processing-animation';
|
||||
}
|
||||
|
||||
export default function XrayStatusCard({
|
||||
status,
|
||||
isMobile,
|
||||
|
|
@ -65,12 +58,7 @@ export default function XrayStatusCard({
|
|||
|
||||
const extra =
|
||||
status.xray.state !== 'error' ? (
|
||||
<Badge
|
||||
status="processing"
|
||||
className={`xray-processing-animation ${badgeAnimationClass(status.xray.color)}`}
|
||||
text={stateText}
|
||||
color={status.xray.color}
|
||||
/>
|
||||
<Badge status="processing" text={stateText} color={status.xray.color} />
|
||||
) : (
|
||||
<Popover
|
||||
title={
|
||||
|
|
@ -93,12 +81,7 @@ export default function XrayStatusCard({
|
|||
</>
|
||||
}
|
||||
>
|
||||
<Badge
|
||||
status="processing"
|
||||
text={stateText}
|
||||
color={status.xray.color}
|
||||
className="xray-processing-animation xray-error-animation"
|
||||
/>
|
||||
<Badge status="processing" text={stateText} color={status.xray.color} />
|
||||
</Popover>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card, Col, ConfigProvider, Layout, Modal, Row, Spin, message } from 'antd';
|
||||
import { Card, Col, ConfigProvider, Layout, Modal, Row, Spin, Statistic, message } from 'antd';
|
||||
import {
|
||||
CheckCircleOutlined,
|
||||
CloseCircleOutlined,
|
||||
|
|
@ -14,7 +14,6 @@ import { useNodesQuery } from '@/api/queries/useNodesQuery';
|
|||
import type { NodeRecord } from '@/api/queries/useNodesQuery';
|
||||
import { useNodeMutations } from '@/api/queries/useNodeMutations';
|
||||
import AppSidebar from '@/components/AppSidebar';
|
||||
import CustomStatistic from '@/components/CustomStatistic';
|
||||
import NodeList from './NodeList';
|
||||
import NodeFormModal from './NodeFormModal';
|
||||
import { setMessageInstance } from '@/utils/messageBus';
|
||||
|
|
@ -109,28 +108,28 @@ export default function NodesPage() {
|
|||
<Card size="small" hoverable className="summary-card">
|
||||
<Row gutter={[16, isMobile ? 16 : 12]}>
|
||||
<Col xs={12} sm={12} md={6}>
|
||||
<CustomStatistic
|
||||
<Statistic
|
||||
title={t('pages.nodes.totalNodes')}
|
||||
value={String(totals.total)}
|
||||
prefix={<CloudServerOutlined />}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={12} sm={12} md={6}>
|
||||
<CustomStatistic
|
||||
<Statistic
|
||||
title={t('pages.nodes.onlineNodes')}
|
||||
value={String(totals.online)}
|
||||
prefix={<CheckCircleOutlined style={{ color: '#52c41a' }} />}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={12} sm={12} md={6}>
|
||||
<CustomStatistic
|
||||
<Statistic
|
||||
title={t('pages.nodes.offlineNodes')}
|
||||
value={String(totals.offline)}
|
||||
prefix={<CloseCircleOutlined style={{ color: '#ff4d4f' }} />}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={12} sm={12} md={6}>
|
||||
<CustomStatistic
|
||||
<Statistic
|
||||
title={t('pages.nodes.avgLatency')}
|
||||
value={totals.avgLatency > 0 ? `${totals.avgLatency} ms` : '-'}
|
||||
prefix={<ThunderboltOutlined />}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
.nodes-page .ant-card,
|
||||
.api-docs-page .ant-card {
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
transition: transform 0.2s ease, box-shadow 0.25s ease, border-color 0.2s ease;
|
||||
}
|
||||
|
|
@ -18,7 +17,6 @@ body.dark .xray-page .ant-card,
|
|||
body.dark .settings-page .ant-card,
|
||||
body.dark .nodes-page .ant-card,
|
||||
body.dark .api-docs-page .ant-card {
|
||||
border-color: rgba(255, 255, 255, 0.06);
|
||||
box-shadow:
|
||||
0 1px 2px rgba(0, 0, 0, 0.4),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.03);
|
||||
|
|
@ -31,7 +29,6 @@ html[data-theme='ultra-dark'] .xray-page .ant-card,
|
|||
html[data-theme='ultra-dark'] .settings-page .ant-card,
|
||||
html[data-theme='ultra-dark'] .nodes-page .ant-card,
|
||||
html[data-theme='ultra-dark'] .api-docs-page .ant-card {
|
||||
border-color: rgba(255, 255, 255, 0.04);
|
||||
box-shadow:
|
||||
0 1px 2px rgba(0, 0, 0, 0.6),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.025);
|
||||
|
|
@ -45,7 +42,6 @@ html[data-theme='ultra-dark'] .api-docs-page .ant-card {
|
|||
.nodes-page .ant-card.ant-card-hoverable:hover,
|
||||
.api-docs-page .ant-card.ant-card-hoverable:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: rgba(0, 0, 0, 0.10);
|
||||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +52,6 @@ body.dark .xray-page .ant-card.ant-card-hoverable:hover,
|
|||
body.dark .settings-page .ant-card.ant-card-hoverable:hover,
|
||||
body.dark .nodes-page .ant-card.ant-card-hoverable:hover,
|
||||
body.dark .api-docs-page .ant-card.ant-card-hoverable:hover {
|
||||
border-color: rgba(255, 255, 255, 0.12);
|
||||
box-shadow:
|
||||
0 8px 24px rgba(0, 0, 0, 0.5),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
|
|
@ -69,22 +64,11 @@ html[data-theme='ultra-dark'] .xray-page .ant-card.ant-card-hoverable:hover,
|
|||
html[data-theme='ultra-dark'] .settings-page .ant-card.ant-card-hoverable:hover,
|
||||
html[data-theme='ultra-dark'] .nodes-page .ant-card.ant-card-hoverable:hover,
|
||||
html[data-theme='ultra-dark'] .api-docs-page .ant-card.ant-card-hoverable:hover {
|
||||
border-color: rgba(255, 255, 255, 0.08);
|
||||
box-shadow:
|
||||
0 8px 24px rgba(0, 0, 0, 0.75),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.index-page .ant-card .ant-card-head,
|
||||
.clients-page .ant-card .ant-card-head,
|
||||
.inbounds-page .ant-card .ant-card-head,
|
||||
.xray-page .ant-card .ant-card-head,
|
||||
.settings-page .ant-card .ant-card-head,
|
||||
.nodes-page .ant-card .ant-card-head,
|
||||
.api-docs-page .ant-card .ant-card-head {
|
||||
border-bottom-color: rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.index-page .ant-card .ant-card-actions,
|
||||
.clients-page .ant-card .ant-card-actions,
|
||||
.inbounds-page .ant-card .ant-card-actions,
|
||||
|
|
@ -92,76 +76,5 @@ html[data-theme='ultra-dark'] .api-docs-page .ant-card.ant-card-hoverable:hover
|
|||
.settings-page .ant-card .ant-card-actions,
|
||||
.nodes-page .ant-card .ant-card-actions,
|
||||
.api-docs-page .ant-card .ant-card-actions {
|
||||
border-top-color: rgba(0, 0, 0, 0.06);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.index-page .ant-card .ant-card-actions > li,
|
||||
.clients-page .ant-card .ant-card-actions > li,
|
||||
.inbounds-page .ant-card .ant-card-actions > li,
|
||||
.xray-page .ant-card .ant-card-actions > li,
|
||||
.settings-page .ant-card .ant-card-actions > li,
|
||||
.nodes-page .ant-card .ant-card-actions > li,
|
||||
.api-docs-page .ant-card .ant-card-actions > li {
|
||||
border-inline-end-color: rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
body.dark .index-page .ant-card .ant-card-head,
|
||||
body.dark .clients-page .ant-card .ant-card-head,
|
||||
body.dark .inbounds-page .ant-card .ant-card-head,
|
||||
body.dark .xray-page .ant-card .ant-card-head,
|
||||
body.dark .settings-page .ant-card .ant-card-head,
|
||||
body.dark .nodes-page .ant-card .ant-card-head,
|
||||
body.dark .api-docs-page .ant-card .ant-card-head {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
body.dark .index-page .ant-card .ant-card-actions,
|
||||
body.dark .clients-page .ant-card .ant-card-actions,
|
||||
body.dark .inbounds-page .ant-card .ant-card-actions,
|
||||
body.dark .xray-page .ant-card .ant-card-actions,
|
||||
body.dark .settings-page .ant-card .ant-card-actions,
|
||||
body.dark .nodes-page .ant-card .ant-card-actions,
|
||||
body.dark .api-docs-page .ant-card .ant-card-actions {
|
||||
border-top-color: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
body.dark .index-page .ant-card .ant-card-actions > li,
|
||||
body.dark .clients-page .ant-card .ant-card-actions > li,
|
||||
body.dark .inbounds-page .ant-card .ant-card-actions > li,
|
||||
body.dark .xray-page .ant-card .ant-card-actions > li,
|
||||
body.dark .settings-page .ant-card .ant-card-actions > li,
|
||||
body.dark .nodes-page .ant-card .ant-card-actions > li,
|
||||
body.dark .api-docs-page .ant-card .ant-card-actions > li {
|
||||
border-inline-end-color: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
html[data-theme='ultra-dark'] .index-page .ant-card .ant-card-head,
|
||||
html[data-theme='ultra-dark'] .clients-page .ant-card .ant-card-head,
|
||||
html[data-theme='ultra-dark'] .inbounds-page .ant-card .ant-card-head,
|
||||
html[data-theme='ultra-dark'] .xray-page .ant-card .ant-card-head,
|
||||
html[data-theme='ultra-dark'] .settings-page .ant-card .ant-card-head,
|
||||
html[data-theme='ultra-dark'] .nodes-page .ant-card .ant-card-head,
|
||||
html[data-theme='ultra-dark'] .api-docs-page .ant-card .ant-card-head {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
html[data-theme='ultra-dark'] .index-page .ant-card .ant-card-actions,
|
||||
html[data-theme='ultra-dark'] .clients-page .ant-card .ant-card-actions,
|
||||
html[data-theme='ultra-dark'] .inbounds-page .ant-card .ant-card-actions,
|
||||
html[data-theme='ultra-dark'] .xray-page .ant-card .ant-card-actions,
|
||||
html[data-theme='ultra-dark'] .settings-page .ant-card .ant-card-actions,
|
||||
html[data-theme='ultra-dark'] .nodes-page .ant-card .ant-card-actions,
|
||||
html[data-theme='ultra-dark'] .api-docs-page .ant-card .ant-card-actions {
|
||||
border-top-color: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
html[data-theme='ultra-dark'] .index-page .ant-card .ant-card-actions > li,
|
||||
html[data-theme='ultra-dark'] .clients-page .ant-card .ant-card-actions > li,
|
||||
html[data-theme='ultra-dark'] .inbounds-page .ant-card .ant-card-actions > li,
|
||||
html[data-theme='ultra-dark'] .xray-page .ant-card .ant-card-actions > li,
|
||||
html[data-theme='ultra-dark'] .settings-page .ant-card .ant-card-actions > li,
|
||||
html[data-theme='ultra-dark'] .nodes-page .ant-card .ant-card-actions > li,
|
||||
html[data-theme='ultra-dark'] .api-docs-page .ant-card .ant-card-actions > li {
|
||||
border-inline-end-color: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue