chore(frontend): adopt antd v6 api updates

Sweep deprecated props across the React tree:
- Modal: destroyOnClose -> destroyOnHidden, maskClosable -> mask.closable
- Space: direction -> orientation (or removed when redundant)
- Input.Group compact -> Space.Compact block
- Drawer: width -> size
- Spin: tip -> description
- Progress: trailColor -> railColor
- Alert: message -> title
- Popover: overlayClassName -> rootClassName
- BackTop -> FloatButton.BackTop

Also refresh dashboard theming for v6: rename dark/ultra Layout and Menu
tokens (siderBg, darkItemBg, darkSubMenuItemBg, darkPopupBg), tweak gauge
size/stroke, add font-size overrides for Statistic and Progress so the
overview numbers stay legible under v6 defaults.
This commit is contained in:
MHSanaei 2026-05-22 00:42:20 +02:00
parent d6f42b3395
commit 7a4317086b
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A
45 changed files with 163 additions and 136 deletions

View file

@ -199,7 +199,7 @@ export default function AppSidebar({ basePath = '', requestUri = '' }: AppSideba
closable={false} closable={false}
open={drawerOpen} open={drawerOpen}
rootClassName={currentTheme} rootClassName={currentTheme}
width="min(82vw, 320px)" size="min(82vw, 320px)"
styles={{ styles={{
wrapper: { padding: 0 }, wrapper: { padding: 0 },
body: { padding: 0, display: 'flex', flexDirection: 'column', height: '100%' }, body: { padding: 0, display: 'flex', flexDirection: 'column', height: '100%' },

View file

@ -1,5 +1,25 @@
.ant-statistic-content { .ant-statistic-content {
font-size: 16px; font-size: 15px !important;
line-height: 1.4 !important;
}
.ant-statistic-content-value,
.ant-statistic-content-prefix,
.ant-statistic-content-suffix {
font-size: 15px !important;
}
.ant-statistic-content-prefix {
margin-inline-end: 6px !important;
}
.ant-statistic-content-prefix .anticon {
font-size: 16px !important;
}
.ant-statistic-title {
font-size: 12px !important;
margin-bottom: 4px !important;
} }
body.dark .ant-statistic-content { body.dark .ant-statistic-content {

View file

@ -55,11 +55,11 @@ export default function PromptModal({
title={title} title={title}
okText={okText} okText={okText}
cancelText="Cancel" cancelText="Cancel"
maskClosable={false} mask={{ closable: false }}
confirmLoading={loading} confirmLoading={loading}
onOk={() => onConfirm(value)} onOk={() => onConfirm(value)}
onCancel={onClose} onCancel={onClose}
destroyOnClose destroyOnHidden
> >
{type === 'textarea' ? ( {type === 'textarea' ? (
<Input.TextArea <Input.TextArea

View file

@ -30,8 +30,7 @@ export default function TextModal({ open, onClose, title, content, fileName = ''
open={open} open={open}
title={title} title={title}
onCancel={onClose} onCancel={onClose}
closable destroyOnHidden
destroyOnClose
footer={( footer={(
<> <>
{fileName && ( {fileName && (

View file

@ -42,24 +42,32 @@ const ULTRA_DARK_TOKENS = {
colorBgElevated: '#141414', colorBgElevated: '#141414',
}; };
const DARK_LAYOUT_TOKENS = { const DARK_LAYOUT_TOKENS = {
colorBgHeader: '#252526', bodyBg: '#1e1e1e',
colorBgTrigger: '#333333', headerBg: '#252526',
colorBgBody: '#1e1e1e', headerColor: '#ffffff',
footerBg: '#1e1e1e',
siderBg: '#252526',
triggerBg: '#333333',
triggerColor: '#ffffff',
}; };
const ULTRA_DARK_LAYOUT_TOKENS = { const ULTRA_DARK_LAYOUT_TOKENS = {
colorBgHeader: '#0a0a0a', bodyBg: '#000',
colorBgTrigger: '#141414', headerBg: '#0a0a0a',
colorBgBody: '#000', headerColor: '#ffffff',
footerBg: '#000',
siderBg: '#0a0a0a',
triggerBg: '#141414',
triggerColor: '#ffffff',
}; };
const DARK_MENU_TOKENS = { const DARK_MENU_TOKENS = {
colorItemBg: '#252526', darkItemBg: '#252526',
colorSubItemBg: '#1e1e1e', darkSubMenuItemBg: '#1e1e1e',
menuSubMenuBg: '#252526', darkPopupBg: '#252526',
}; };
const ULTRA_DARK_MENU_TOKENS = { const ULTRA_DARK_MENU_TOKENS = {
colorItemBg: '#0a0a0a', darkItemBg: '#0a0a0a',
colorSubItemBg: '#000', darkSubMenuItemBg: '#000',
menuSubMenuBg: '#0a0a0a', darkPopupBg: '#0a0a0a',
}; };
export function buildAntdThemeConfig(isDark: boolean, isUltra: boolean): ThemeConfig { export function buildAntdThemeConfig(isDark: boolean, isUltra: boolean): ThemeConfig {

View file

@ -210,7 +210,7 @@ export default function ClientBulkAddModal({
okText={t('create')} okText={t('create')}
cancelText={t('close')} cancelText={t('close')}
confirmLoading={saving} confirmLoading={saving}
maskClosable={false} mask={{ closable: false }}
width={640} width={640}
onOk={submit} onOk={submit}
onCancel={() => onOpenChange(false)} onCancel={() => onOpenChange(false)}

View file

@ -327,7 +327,7 @@ export default function ClientFormModal({
<Modal <Modal
open={open} open={open}
title={isEdit ? t('pages.clients.editTitle') : t('pages.clients.addTitle')} title={isEdit ? t('pages.clients.editTitle') : t('pages.clients.addTitle')}
destroyOnClose destroyOnHidden
okText={isEdit ? t('save') : t('create')} okText={isEdit ? t('save') : t('create')}
cancelText={t('cancel')} cancelText={t('cancel')}
okButtonProps={{ loading: submitting }} okButtonProps={{ loading: submitting }}

View file

@ -626,7 +626,7 @@ export default function ClientsPage() {
<Layout className="content-shell"> <Layout className="content-shell">
<Layout.Content id="content-layout" className="content-area"> <Layout.Content id="content-layout" className="content-area">
<Spin spinning={!fetched} delay={200} tip={t('loading')} size="large"> <Spin spinning={!fetched} delay={200} description={t('loading')} size="large">
{!fetched ? ( {!fetched ? (
<div className="loading-spacer" /> <div className="loading-spacer" />
) : ( ) : (

View file

@ -648,14 +648,14 @@ export default function InboundFormModal({
const sniffingFallback = () => inboundRef.current?.sniffing?.toJson?.() || {}; const sniffingFallback = () => inboundRef.current?.sniffing?.toJson?.() || {};
const streamFallback = () => inboundRef.current?.stream?.toJson?.() || {}; const streamFallback = () => inboundRef.current?.stream?.toJson?.() || {};
const parseAdvancedSliceWithLabel = (rawText: string, fallback: unknown, label: string) => { const parseAdvancedSliceWithLabel = useCallback((rawText: string, fallback: unknown, label: string) => {
try { try {
return parseAdvancedSliceOrFallback(rawText, fallback); return parseAdvancedSliceOrFallback(rawText, fallback);
} catch (e) { } catch (e) {
message.error(`${label} JSON invalid: ${(e as Error).message}`); message.error(`${label} JSON invalid: ${(e as Error).message}`);
throw e; throw e;
} }
}; }, []);
const compactAdvancedJson = (raw: string, fallback: string, label: string) => { const compactAdvancedJson = (raw: string, fallback: string, label: string) => {
try { try {
@ -696,7 +696,7 @@ export default function InboundFormModal({
return false; return false;
} }
return true; return true;
}, [t, refresh]); }, [t, refresh, parseAdvancedSliceWithLabel]);
const handleTabChange = (next: string) => { const handleTabChange = (next: string) => {
if (activeTabKey === 'advanced' && next !== 'advanced') { if (activeTabKey === 'advanced' && next !== 'advanced') {
@ -886,16 +886,15 @@ export default function InboundFormModal({
} }
}, [canEnableStream, t, mode, dbInbound, isFallbackHost, saveFallbacks, onSaved, onClose]); }, [canEnableStream, t, mode, dbInbound, isFallbackHost, saveFallbacks, onSaved, onClose]);
const protocolSnapshot = inboundRef.current?.protocol;
const streamSnapshot = JSON.stringify(inboundRef.current?.stream?.toJson?.() || {});
const sniffingSnapshot = JSON.stringify(inboundRef.current?.sniffing?.toJson?.() || {});
const settingsSnapshot = JSON.stringify(inboundRef.current?.settings?.toJson?.() || {});
useEffect(() => { useEffect(() => {
if (!inboundRef.current) return; if (!inboundRef.current) return;
(['stream', 'sniffing', 'settings'] as const).forEach(stampAdvancedTextFor); (['stream', 'sniffing', 'settings'] as const).forEach(stampAdvancedTextFor);
// eslint-disable-next-line react-hooks/exhaustive-deps }, [protocolSnapshot, streamSnapshot, sniffingSnapshot, settingsSnapshot, stampAdvancedTextFor]);
}, [
inboundRef.current?.protocol,
JSON.stringify(inboundRef.current?.stream?.toJson?.() || {}),
JSON.stringify(inboundRef.current?.sniffing?.toJson?.() || {}),
JSON.stringify(inboundRef.current?.settings?.toJson?.() || {}),
]);
const title = mode === 'edit' ? t('pages.inbounds.modifyInbound') : t('pages.inbounds.addInbound'); const title = mode === 'edit' ? t('pages.inbounds.modifyInbound') : t('pages.inbounds.addInbound');
const okText = mode === 'edit' ? t('pages.clients.submitEdit') : t('create'); const okText = mode === 'edit' ? t('pages.clients.submitEdit') : t('create');
@ -998,7 +997,7 @@ export default function InboundFormModal({
<div key={record.rowKey} style={{ border: '1px solid var(--app-border-tertiary)', borderRadius: 6, padding: '10px 12px', marginBottom: 8 }}> <div key={record.rowKey} style={{ border: '1px solid var(--app-border-tertiary)', borderRadius: 6, padding: '10px 12px', marginBottom: 8 }}>
<Row gutter={8} align="middle" wrap={false}> <Row gutter={8} align="middle" wrap={false}>
<Col flex="none"> <Col flex="none">
<Space direction="vertical" size={2}> <Space orientation="vertical" size={2}>
<Button size="small" type="text" disabled={index === 0} onClick={() => moveFallback(index, -1)}> <Button size="small" type="text" disabled={index === 0} onClick={() => moveFallback(index, -1)}>
<CaretUpOutlined /> <CaretUpOutlined />
</Button> </Button>
@ -1146,7 +1145,7 @@ export default function InboundFormModal({
</Form.Item> </Form.Item>
<Form.Item wrapperCol={{ span: 24 }}> <Form.Item wrapperCol={{ span: 24 }}>
{(ib.settings.accounts || []).map((account: any, idx: number) => ( {(ib.settings.accounts || []).map((account: any, idx: number) => (
<Input.Group key={idx} compact className="mb-8"> <Space.Compact key={idx} className="mb-8" block>
<Input style={{ width: '45%' }} value={account.user} <Input style={{ width: '45%' }} value={account.user}
addonBefore={String(idx + 1)} placeholder="Username" addonBefore={String(idx + 1)} placeholder="Username"
onChange={(e) => { account.user = e.target.value; refresh(); }} /> onChange={(e) => { account.user = e.target.value; refresh(); }} />
@ -1155,7 +1154,7 @@ export default function InboundFormModal({
<Button onClick={() => { ib.settings.delAccount(idx); refresh(); }}> <Button onClick={() => { ib.settings.delAccount(idx); refresh(); }}>
<MinusOutlined /> <MinusOutlined />
</Button> </Button>
</Input.Group> </Space.Compact>
))} ))}
</Form.Item> </Form.Item>
{ib.protocol === Protocols.HTTP && ( {ib.protocol === Protocols.HTTP && (
@ -1208,7 +1207,7 @@ export default function InboundFormModal({
{(ib.settings.portMap || []).length > 0 && ( {(ib.settings.portMap || []).length > 0 && (
<Form.Item wrapperCol={{ span: 24 }}> <Form.Item wrapperCol={{ span: 24 }}>
{(ib.settings.portMap as { name: string; value: string }[]).map((pm, idx) => ( {(ib.settings.portMap as { name: string; value: string }[]).map((pm, idx) => (
<Input.Group key={`pm-${idx}`} compact className="mb-8"> <Space.Compact key={`pm-${idx}`} className="mb-8" block>
<Input style={{ width: '30%' }} value={pm.name} placeholder="5555" addonBefore={String(idx + 1)} <Input style={{ width: '30%' }} value={pm.name} placeholder="5555" addonBefore={String(idx + 1)}
onChange={(e) => { pm.name = e.target.value; refresh(); }} /> onChange={(e) => { pm.name = e.target.value; refresh(); }} />
<Input style={{ width: '60%' }} value={pm.value} placeholder="1.1.1.1:7777" <Input style={{ width: '60%' }} value={pm.value} placeholder="1.1.1.1:7777"
@ -1216,7 +1215,7 @@ export default function InboundFormModal({
<Button onClick={() => { ib.settings.removePortMap(idx); refresh(); }}> <Button onClick={() => { ib.settings.removePortMap(idx); refresh(); }}>
<MinusOutlined /> <MinusOutlined />
</Button> </Button>
</Input.Group> </Space.Compact>
))} ))}
</Form.Item> </Form.Item>
)} )}
@ -1405,7 +1404,7 @@ export default function InboundFormModal({
{(ib.stream.tcp.request.headers || []).length > 0 && ( {(ib.stream.tcp.request.headers || []).length > 0 && (
<Form.Item wrapperCol={{ span: 24 }}> <Form.Item wrapperCol={{ span: 24 }}>
{(ib.stream.tcp.request.headers as { name: string; value: string }[]).map((h, idx) => ( {(ib.stream.tcp.request.headers as { name: string; value: string }[]).map((h, idx) => (
<Input.Group key={`tcp-rh-${idx}`} compact className="mb-8"> <Space.Compact key={`tcp-rh-${idx}`} className="mb-8" block>
<Input style={{ width: '45%' }} value={h.name} addonBefore={String(idx + 1)} <Input style={{ width: '45%' }} value={h.name} addonBefore={String(idx + 1)}
placeholder={t('pages.inbounds.stream.general.name')} placeholder={t('pages.inbounds.stream.general.name')}
onChange={(e) => { h.name = e.target.value; refresh(); }} /> onChange={(e) => { h.name = e.target.value; refresh(); }} />
@ -1415,7 +1414,7 @@ export default function InboundFormModal({
<Button onClick={() => { ib.stream.tcp.request.removeHeader(idx); refresh(); }}> <Button onClick={() => { ib.stream.tcp.request.removeHeader(idx); refresh(); }}>
<MinusOutlined /> <MinusOutlined />
</Button> </Button>
</Input.Group> </Space.Compact>
))} ))}
</Form.Item> </Form.Item>
)} )}
@ -1440,7 +1439,7 @@ export default function InboundFormModal({
{(ib.stream.tcp.response.headers || []).length > 0 && ( {(ib.stream.tcp.response.headers || []).length > 0 && (
<Form.Item wrapperCol={{ span: 24 }}> <Form.Item wrapperCol={{ span: 24 }}>
{(ib.stream.tcp.response.headers as { name: string; value: string }[]).map((h, idx) => ( {(ib.stream.tcp.response.headers as { name: string; value: string }[]).map((h, idx) => (
<Input.Group key={`tcp-rsh-${idx}`} compact className="mb-8"> <Space.Compact key={`tcp-rsh-${idx}`} className="mb-8" block>
<Input style={{ width: '45%' }} value={h.name} addonBefore={String(idx + 1)} <Input style={{ width: '45%' }} value={h.name} addonBefore={String(idx + 1)}
placeholder={t('pages.inbounds.stream.general.name')} placeholder={t('pages.inbounds.stream.general.name')}
onChange={(e) => { h.name = e.target.value; refresh(); }} /> onChange={(e) => { h.name = e.target.value; refresh(); }} />
@ -1450,7 +1449,7 @@ export default function InboundFormModal({
<Button onClick={() => { ib.stream.tcp.response.removeHeader(idx); refresh(); }}> <Button onClick={() => { ib.stream.tcp.response.removeHeader(idx); refresh(); }}>
<MinusOutlined /> <MinusOutlined />
</Button> </Button>
</Input.Group> </Space.Compact>
))} ))}
</Form.Item> </Form.Item>
)} )}
@ -1482,7 +1481,7 @@ export default function InboundFormModal({
{(ib.stream.ws.headers || []).length > 0 && ( {(ib.stream.ws.headers || []).length > 0 && (
<Form.Item wrapperCol={{ span: 24 }}> <Form.Item wrapperCol={{ span: 24 }}>
{(ib.stream.ws.headers as { name: string; value: string }[]).map((h, idx) => ( {(ib.stream.ws.headers as { name: string; value: string }[]).map((h, idx) => (
<Input.Group key={`ws-h-${idx}`} compact className="mb-8"> <Space.Compact key={`ws-h-${idx}`} className="mb-8" block>
<Input style={{ width: '45%' }} value={h.name} addonBefore={String(idx + 1)} <Input style={{ width: '45%' }} value={h.name} addonBefore={String(idx + 1)}
placeholder={t('pages.inbounds.stream.general.name')} placeholder={t('pages.inbounds.stream.general.name')}
onChange={(e) => { h.name = e.target.value; refresh(); }} /> onChange={(e) => { h.name = e.target.value; refresh(); }} />
@ -1492,7 +1491,7 @@ export default function InboundFormModal({
<Button onClick={() => { ib.stream.ws.removeHeader(idx); refresh(); }}> <Button onClick={() => { ib.stream.ws.removeHeader(idx); refresh(); }}>
<MinusOutlined /> <MinusOutlined />
</Button> </Button>
</Input.Group> </Space.Compact>
))} ))}
</Form.Item> </Form.Item>
)} )}
@ -1518,7 +1517,7 @@ export default function InboundFormModal({
{(ib.stream.httpupgrade.headers || []).length > 0 && ( {(ib.stream.httpupgrade.headers || []).length > 0 && (
<Form.Item wrapperCol={{ span: 24 }}> <Form.Item wrapperCol={{ span: 24 }}>
{(ib.stream.httpupgrade.headers as { name: string; value: string }[]).map((h, idx) => ( {(ib.stream.httpupgrade.headers as { name: string; value: string }[]).map((h, idx) => (
<Input.Group key={`hu-h-${idx}`} compact className="mb-8"> <Space.Compact key={`hu-h-${idx}`} className="mb-8" block>
<Input style={{ width: '45%' }} value={h.name} addonBefore={String(idx + 1)} <Input style={{ width: '45%' }} value={h.name} addonBefore={String(idx + 1)}
placeholder={t('pages.inbounds.stream.general.name')} placeholder={t('pages.inbounds.stream.general.name')}
onChange={(e) => { h.name = e.target.value; refresh(); }} /> onChange={(e) => { h.name = e.target.value; refresh(); }} />
@ -1528,7 +1527,7 @@ export default function InboundFormModal({
<Button onClick={() => { ib.stream.httpupgrade.removeHeader(idx); refresh(); }}> <Button onClick={() => { ib.stream.httpupgrade.removeHeader(idx); refresh(); }}>
<MinusOutlined /> <MinusOutlined />
</Button> </Button>
</Input.Group> </Space.Compact>
))} ))}
</Form.Item> </Form.Item>
)} )}
@ -1545,7 +1544,7 @@ export default function InboundFormModal({
{(ib.stream.xhttp.headers || []).length > 0 && ( {(ib.stream.xhttp.headers || []).length > 0 && (
<Form.Item wrapperCol={{ span: 24 }}> <Form.Item wrapperCol={{ span: 24 }}>
{(ib.stream.xhttp.headers as { name: string; value: string }[]).map((h, idx) => ( {(ib.stream.xhttp.headers as { name: string; value: string }[]).map((h, idx) => (
<Input.Group key={`xh-h-${idx}`} compact className="mb-8"> <Space.Compact key={`xh-h-${idx}`} className="mb-8" block>
<Input style={{ width: '45%' }} value={h.name} addonBefore={String(idx + 1)} <Input style={{ width: '45%' }} value={h.name} addonBefore={String(idx + 1)}
placeholder={t('pages.inbounds.stream.general.name')} placeholder={t('pages.inbounds.stream.general.name')}
onChange={(e) => { h.name = e.target.value; refresh(); }} /> onChange={(e) => { h.name = e.target.value; refresh(); }} />
@ -1555,7 +1554,7 @@ export default function InboundFormModal({
<Button onClick={() => { ib.stream.xhttp.removeHeader(idx); refresh(); }}> <Button onClick={() => { ib.stream.xhttp.removeHeader(idx); refresh(); }}>
<MinusOutlined /> <MinusOutlined />
</Button> </Button>
</Input.Group> </Space.Compact>
))} ))}
</Form.Item> </Form.Item>
)} )}
@ -1652,7 +1651,7 @@ export default function InboundFormModal({
{externalProxyOn && ( {externalProxyOn && (
<Form.Item wrapperCol={{ span: 24 }}> <Form.Item wrapperCol={{ span: 24 }}>
{(ib.stream.externalProxy as { forceTls: string; dest: string; port: number; remark: string }[]).map((row, idx) => ( {(ib.stream.externalProxy as { forceTls: string; dest: string; port: number; remark: string }[]).map((row, idx) => (
<Input.Group key={`ep-${idx}`} compact style={{ margin: '8px 0' }}> <Space.Compact key={`ep-${idx}`} style={{ margin: '8px 0' }} block>
<Tooltip title="Force TLS"> <Tooltip title="Force TLS">
<Select value={row.forceTls} style={{ width: '20%' }} onChange={(v) => { row.forceTls = v; refresh(); }}> <Select value={row.forceTls} style={{ width: '20%' }} onChange={(v) => { row.forceTls = v; refresh(); }}>
<Select.Option value="same">{t('pages.inbounds.same')}</Select.Option> <Select.Option value="same">{t('pages.inbounds.same')}</Select.Option>
@ -1669,7 +1668,7 @@ export default function InboundFormModal({
<Input style={{ width: '35%' }} value={row.remark} placeholder={t('pages.inbounds.remark')} <Input style={{ width: '35%' }} value={row.remark} placeholder={t('pages.inbounds.remark')}
onChange={(e) => { row.remark = e.target.value; refresh(); }} onChange={(e) => { row.remark = e.target.value; refresh(); }}
addonAfter={<MinusOutlined onClick={() => { ib.stream.externalProxy.splice(idx, 1); refresh(); }} />} /> addonAfter={<MinusOutlined onClick={() => { ib.stream.externalProxy.splice(idx, 1); refresh(); }} />} />
</Input.Group> </Space.Compact>
))} ))}
</Form.Item> </Form.Item>
)} )}
@ -1762,7 +1761,7 @@ export default function InboundFormModal({
{(ib.stream.hysteria.masquerade.headers || []).length > 0 && ( {(ib.stream.hysteria.masquerade.headers || []).length > 0 && (
<Form.Item wrapperCol={{ span: 24 }}> <Form.Item wrapperCol={{ span: 24 }}>
{(ib.stream.hysteria.masquerade.headers as { name: string; value: string }[]).map((h, idx) => ( {(ib.stream.hysteria.masquerade.headers as { name: string; value: string }[]).map((h, idx) => (
<Input.Group key={`mh-${idx}`} compact className="mb-8"> <Space.Compact key={`mh-${idx}`} className="mb-8" block>
<Input style={{ width: '45%' }} value={h.name} addonBefore={String(idx + 1)} placeholder="Name" <Input style={{ width: '45%' }} value={h.name} addonBefore={String(idx + 1)} placeholder="Name"
onChange={(e) => { h.name = e.target.value; refresh(); }} /> onChange={(e) => { h.name = e.target.value; refresh(); }} />
<Input style={{ width: '45%' }} value={h.value} placeholder="Value" <Input style={{ width: '45%' }} value={h.value} placeholder="Value"
@ -1770,7 +1769,7 @@ export default function InboundFormModal({
<Button onClick={() => { ib.stream.hysteria.masquerade.removeHeader(idx); refresh(); }}> <Button onClick={() => { ib.stream.hysteria.masquerade.removeHeader(idx); refresh(); }}>
<MinusOutlined /> <MinusOutlined />
</Button> </Button>
</Input.Group> </Space.Compact>
))} ))}
</Form.Item> </Form.Item>
)} )}
@ -1808,14 +1807,14 @@ export default function InboundFormModal({
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item label="Min/Max Version"> <Form.Item label="Min/Max Version">
<Input.Group compact> <Space.Compact block>
<Select value={ib.stream.tls.minVersion} style={{ width: '50%' }} onChange={(v) => { ib.stream.tls.minVersion = v; refresh(); }}> <Select value={ib.stream.tls.minVersion} style={{ width: '50%' }} onChange={(v) => { ib.stream.tls.minVersion = v; refresh(); }}>
{TLS_VERSIONS.map((v) => <Select.Option key={v} value={v}>{v}</Select.Option>)} {TLS_VERSIONS.map((v) => <Select.Option key={v} value={v}>{v}</Select.Option>)}
</Select> </Select>
<Select value={ib.stream.tls.maxVersion} style={{ width: '50%' }} onChange={(v) => { ib.stream.tls.maxVersion = v; refresh(); }}> <Select value={ib.stream.tls.maxVersion} style={{ width: '50%' }} onChange={(v) => { ib.stream.tls.maxVersion = v; refresh(); }}>
{TLS_VERSIONS.map((v) => <Select.Option key={v} value={v}>{v}</Select.Option>)} {TLS_VERSIONS.map((v) => <Select.Option key={v} value={v}>{v}</Select.Option>)}
</Select> </Select>
</Input.Group> </Space.Compact>
</Form.Item> </Form.Item>
<Form.Item label="uTLS"> <Form.Item label="uTLS">
<Select value={ib.stream.tls.settings.fingerprint} style={{ width: '100%' }} onChange={(v) => { ib.stream.tls.settings.fingerprint = v; refresh(); }}> <Select value={ib.stream.tls.settings.fingerprint} style={{ width: '100%' }} onChange={(v) => { ib.stream.tls.settings.fingerprint = v; refresh(); }}>
@ -2085,11 +2084,11 @@ export default function InboundFormModal({
okText={okText} okText={okText}
cancelText={t('close')} cancelText={t('close')}
confirmLoading={saving} confirmLoading={saving}
maskClosable={false} mask={{ closable: false }}
width={780} width={780}
onOk={submit} onOk={submit}
onCancel={onClose} onCancel={onClose}
destroyOnClose destroyOnHidden
> >
<Tabs activeKey={activeTabKey} onChange={handleTabChange} items={tabItems} /> <Tabs activeKey={activeTabKey} onChange={handleTabChange} items={tabItems} />
</Modal> </Modal>

View file

@ -882,7 +882,7 @@ export default function InboundInfoModal({
tabItems.push({ key: 'inbound', label: t('pages.xray.rules.inbound'), children: inboundTab }); tabItems.push({ key: 'inbound', label: t('pages.xray.rules.inbound'), children: inboundTab });
return ( return (
<Modal open={open} onCancel={onClose} title={t('pages.inbounds.inboundData')} footer={null} width={640} destroyOnClose> <Modal open={open} onCancel={onClose} title={t('pages.inbounds.inboundData')} footer={null} width={640} destroyOnHidden>
<Tabs activeKey={activeTab} onChange={setActiveTab} items={tabItems} /> <Tabs activeKey={activeTab} onChange={setActiveTab} items={tabItems} />
</Modal> </Modal>
); );

View file

@ -245,12 +245,12 @@ export default function InboundList({
[dbInbounds], [dbInbounds],
); );
const sorterFor = (key: SortKey) => ({ const sorterFor = useCallback((key: SortKey) => ({
sorter: true as const, sorter: true as const,
showSorterTooltip: false, showSorterTooltip: false,
sortOrder: sortKey === key ? sortOrder : null, sortOrder: sortKey === key ? sortOrder : null,
sortDirections: ['ascend' as const, 'descend' as const], sortDirections: ['ascend' as const, 'descend' as const],
}); }), [sortKey, sortOrder]);
const columns: TableColumnType<DBInboundRecord>[] = useMemo(() => { const columns: TableColumnType<DBInboundRecord>[] = useMemo(() => {
const cols: TableColumnType<DBInboundRecord>[] = [ const cols: TableColumnType<DBInboundRecord>[] = [
@ -474,7 +474,7 @@ export default function InboundList({
); );
return cols; return cols;
}, [t, hasAnyRemark, hasActiveNode, nodesById, clientCount, subEnable, expireDiff, trafficDiff, datepicker, onRowAction, onSwitchEnable]); }, [t, hasAnyRemark, hasActiveNode, nodesById, clientCount, subEnable, expireDiff, trafficDiff, datepicker, onRowAction, onSwitchEnable, sorterFor]);
const paginationFor = (rows: DBInboundRecord[]) => { const paginationFor = (rows: DBInboundRecord[]) => {
const size = pageSize > 0 ? pageSize : rows.length || 1; const size = pageSize > 0 ? pageSize : rows.length || 1;
@ -497,7 +497,7 @@ export default function InboundList({
<Card <Card
hoverable hoverable
title={( title={(
<Space direction="horizontal"> <Space>
<Button type="primary" onClick={onAddInbound} icon={<PlusOutlined />}> <Button type="primary" onClick={onAddInbound} icon={<PlusOutlined />}>
{!isMobile && t('pages.inbounds.addInbound')} {!isMobile && t('pages.inbounds.addInbound')}
</Button> </Button>
@ -509,7 +509,7 @@ export default function InboundList({
</Space> </Space>
)} )}
> >
<Space direction="vertical" style={{ width: '100%' }}> <Space orientation="vertical" style={{ width: '100%' }}>
{isMobile ? ( {isMobile ? (
<div className="inbound-cards"> <div className="inbound-cards">
{sortedInbounds.length === 0 ? ( {sortedInbounds.length === 0 ? (
@ -571,7 +571,7 @@ export default function InboundList({
centered centered
title={statsRecord ? `#${statsRecord.id} ${statsRecord.remark || ''}`.trim() : ''} title={statsRecord ? `#${statsRecord.id} ${statsRecord.remark || ''}`.trim() : ''}
onCancel={() => setStatsRecord(null)} onCancel={() => setStatsRecord(null)}
destroyOnClose destroyOnHidden
> >
{statsRecord && ( {statsRecord && (
<div className="card-stats"> <div className="card-stats">

View file

@ -435,7 +435,7 @@ export default function InboundsPage() {
<Layout className="content-shell"> <Layout className="content-shell">
<Layout.Content id="content-layout" className="content-area"> <Layout.Content id="content-layout" className="content-area">
<Spin spinning={!fetched} delay={200} tip="Loading…" size="large"> <Spin spinning={!fetched} delay={200} description="Loading…" size="large">
{!fetched ? ( {!fetched ? (
<div className="loading-spacer" /> <div className="loading-spacer" />
) : ( ) : (

View file

@ -125,7 +125,7 @@ export default function QrCodeModal({
})); }));
return ( return (
<Modal open={open} onCancel={onClose} title={t('qrCode')} footer={null} width={420} destroyOnClose> <Modal open={open} onCancel={onClose} title={t('qrCode')} footer={null} width={420} destroyOnHidden>
{dbInbound && ( {dbInbound && (
<Collapse <Collapse
ghost ghost

View file

@ -62,7 +62,6 @@ export default function BackupModal({ open, basePath: _basePath, onClose, onBusy
<Modal <Modal
open={open} open={open}
title={t('pages.index.backupTitle')} title={t('pages.index.backupTitle')}
closable
footer={null} footer={null}
onCancel={onClose} onCancel={onClose}
> >

View file

@ -234,7 +234,7 @@ export default function CustomGeoSection({ active }: CustomGeoSectionProps) {
type="info" type="info"
showIcon showIcon
className="mb-10" className="mb-10"
message={t('pages.index.customGeoRoutingHint')} title={t('pages.index.customGeoRoutingHint')}
/> />
<div className="toolbar"> <div className="toolbar">

View file

@ -158,7 +158,7 @@ export default function IndexPage() {
<Spin <Spin
spinning={loading || !fetched} spinning={loading || !fetched}
delay={200} delay={200}
tip={loading ? loadingTip : t('loading')} description={loading ? loadingTip : t('loading')}
size="large" size="large"
> >
{!fetched ? ( {!fetched ? (
@ -450,7 +450,6 @@ export default function IndexPage() {
title={t('pages.index.config')} title={t('pages.index.config')}
width={isMobile ? '100%' : 900} width={isMobile ? '100%' : 900}
style={isMobile ? { top: 20, maxWidth: 'calc(100vw - 16px)' } : undefined} style={isMobile ? { top: 20, maxWidth: 'calc(100vw - 16px)' } : undefined}
closable
onCancel={() => setConfigTextOpen(false)} onCancel={() => setConfigTextOpen(false)}
footer={[ footer={[
<Button <Button

View file

@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button, Checkbox, Form, Input, Modal, Select } from 'antd'; import { Button, Checkbox, Form, Modal, Select, Space } from 'antd';
import { DownloadOutlined, SyncOutlined } from '@ant-design/icons'; import { DownloadOutlined, SyncOutlined } from '@ant-design/icons';
import { HttpUtil, FileManager, PromiseUtil } from '@/utils'; import { HttpUtil, FileManager, PromiseUtil } from '@/utils';
@ -107,7 +107,6 @@ export default function LogModal({ open, onClose }: LogModalProps) {
return ( return (
<Modal <Modal
open={open} open={open}
closable
footer={null} footer={null}
width={isMobile ? '100vw' : 800} width={isMobile ? '100vw' : 800}
className={isMobile ? 'logmodal-mobile' : undefined} className={isMobile ? 'logmodal-mobile' : undefined}
@ -116,7 +115,7 @@ export default function LogModal({ open, onClose }: LogModalProps) {
> >
<Form layout="inline" className="log-toolbar"> <Form layout="inline" className="log-toolbar">
<Form.Item> <Form.Item>
<Input.Group compact> <Space.Compact>
<Select value={rows} size="small" style={{ width: 70 }} onChange={setRows}> <Select value={rows} size="small" style={{ width: 70 }} onChange={setRows}>
<Select.Option value="10">10</Select.Option> <Select.Option value="10">10</Select.Option>
<Select.Option value="20">20</Select.Option> <Select.Option value="20">20</Select.Option>
@ -131,7 +130,7 @@ export default function LogModal({ open, onClose }: LogModalProps) {
<Select.Option value="warning">Warning</Select.Option> <Select.Option value="warning">Warning</Select.Option>
<Select.Option value="err">Error</Select.Option> <Select.Option value="err">Error</Select.Option>
</Select> </Select>
</Input.Group> </Space.Compact>
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Checkbox checked={syslog} onChange={(e) => setSyslog(e.target.checked)}> <Checkbox checked={syslog} onChange={(e) => setSyslog(e.target.checked)}>

View file

@ -72,7 +72,6 @@ export default function PanelUpdateModal({ open, info, onClose, onBusy }: PanelU
<Modal <Modal
open={open} open={open}
title={t('pages.index.updatePanel')} title={t('pages.index.updatePanel')}
closable
footer={null} footer={null}
onCancel={onClose} onCancel={onClose}
> >
@ -80,7 +79,7 @@ export default function PanelUpdateModal({ open, info, onClose, onBusy }: PanelU
<Alert <Alert
type="warning" type="warning"
className="mb-12" className="mb-12"
message={t('pages.index.panelUpdateDesc')} title={t('pages.index.panelUpdateDesc')}
showIcon showIcon
/> />
)} )}

View file

@ -2,7 +2,8 @@
text-align: center; text-align: center;
} }
.status-card .ant-progress-text { .status-card .ant-progress-text,
font-size: 14px !important; .status-card .ant-progress-indicator {
font-size: 12px !important;
font-weight: 500; font-weight: 500;
} }

View file

@ -3,6 +3,7 @@ import { Card, Col, Progress, Row, Tooltip } from 'antd';
import { AreaChartOutlined } from '@ant-design/icons'; import { AreaChartOutlined } from '@ant-design/icons';
import { CPUFormatter, SizeFormatter } from '@/utils'; import { CPUFormatter, SizeFormatter } from '@/utils';
import { useTheme } from '@/hooks/useTheme';
import type { Status } from '@/models/status'; import type { Status } from '@/models/status';
import './StatusCard.css'; import './StatusCard.css';
@ -11,11 +12,14 @@ interface StatusCardProps {
isMobile: boolean; isMobile: boolean;
} }
const TRAIL_COLOR = 'rgba(128, 128, 128, 0.25)';
export default function StatusCard({ status, isMobile }: StatusCardProps) { export default function StatusCard({ status, isMobile }: StatusCardProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const gaugeSize = isMobile ? 60 : 70; const { isDark, isUltra } = useTheme();
const gaugeSize = isMobile ? 60 : 90;
const strokeWidth = isMobile ? 7 : 5;
const railColor = isDark
? isUltra ? 'rgba(255, 255, 255, 0.1)' : 'rgba(255, 255, 255, 0.16)'
: 'rgba(0, 0, 0, 0.08)';
return ( return (
<Card hoverable className="status-card"> <Card hoverable className="status-card">
@ -27,7 +31,8 @@ export default function StatusCard({ status, isMobile }: StatusCardProps) {
type="dashboard" type="dashboard"
status="normal" status="normal"
strokeColor={status.cpu.color} strokeColor={status.cpu.color}
trailColor={TRAIL_COLOR} railColor={railColor}
strokeWidth={strokeWidth}
percent={status.cpu.percent} percent={status.cpu.percent}
size={gaugeSize} size={gaugeSize}
/> />
@ -56,7 +61,8 @@ export default function StatusCard({ status, isMobile }: StatusCardProps) {
type="dashboard" type="dashboard"
status="normal" status="normal"
strokeColor={status.mem.color} strokeColor={status.mem.color}
trailColor={TRAIL_COLOR} railColor={railColor}
strokeWidth={strokeWidth}
percent={status.mem.percent} percent={status.mem.percent}
size={gaugeSize} size={gaugeSize}
/> />
@ -75,7 +81,8 @@ export default function StatusCard({ status, isMobile }: StatusCardProps) {
type="dashboard" type="dashboard"
status="normal" status="normal"
strokeColor={status.swap.color} strokeColor={status.swap.color}
trailColor={TRAIL_COLOR} railColor={railColor}
strokeWidth={strokeWidth}
percent={status.swap.percent} percent={status.swap.percent}
size={gaugeSize} size={gaugeSize}
/> />
@ -90,7 +97,8 @@ export default function StatusCard({ status, isMobile }: StatusCardProps) {
type="dashboard" type="dashboard"
status="normal" status="normal"
strokeColor={status.disk.color} strokeColor={status.disk.color}
trailColor={TRAIL_COLOR} railColor={railColor}
strokeWidth={strokeWidth}
percent={status.disk.percent} percent={status.disk.percent}
size={gaugeSize} size={gaugeSize}
/> />

View file

@ -106,7 +106,6 @@ export default function SystemHistoryModal({ open, status, onClose }: SystemHist
return ( return (
<Modal <Modal
open={open} open={open}
closable
footer={null} footer={null}
width={isMobile ? '95vw' : 900} width={isMobile ? '95vw' : 900}
onCancel={onClose} onCancel={onClose}

View file

@ -98,7 +98,6 @@ export default function VersionModal({ open, status, onClose, onBusy }: VersionM
<Modal <Modal
open={open} open={open}
title={t('pages.index.xrayUpdates')} title={t('pages.index.xrayUpdates')}
closable
footer={null} footer={null}
onCancel={onClose} onCancel={onClose}
> >
@ -117,7 +116,7 @@ export default function VersionModal({ open, status, onClose, onBusy }: VersionM
<Alert <Alert
type="warning" type="warning"
className="mb-12" className="mb-12"
message={t('pages.index.xraySwitchClickDesk')} title={t('pages.index.xraySwitchClickDesk')}
showIcon showIcon
/> />
<List bordered className="version-list"> <List bordered className="version-list">

View file

@ -110,7 +110,6 @@ export default function XrayLogModal({ open, onClose }: XrayLogModalProps) {
return ( return (
<Modal <Modal
open={open} open={open}
closable
footer={null} footer={null}
width={isMobile ? '100vw' : '80vw'} width={isMobile ? '100vw' : '80vw'}
className={isMobile ? 'xraylog-modal-mobile' : undefined} className={isMobile ? 'xraylog-modal-mobile' : undefined}

View file

@ -220,7 +220,6 @@ export default function XrayMetricsModal({ open, onClose }: XrayMetricsModalProp
return ( return (
<Modal <Modal
open={open} open={open}
closable
footer={null} footer={null}
width={isMobile ? '95vw' : 900} width={isMobile ? '95vw' : 900}
onCancel={onClose} onCancel={onClose}
@ -249,7 +248,7 @@ export default function XrayMetricsModal({ open, onClose }: XrayMetricsModalProp
type="warning" type="warning"
showIcon showIcon
className="metrics-alert" className="metrics-alert"
message={t('pages.index.xrayMetricsDisabled')} title={t('pages.index.xrayMetricsDisabled')}
description={state.reason || t('pages.index.xrayMetricsHint')} description={state.reason || t('pages.index.xrayMetricsHint')}
/> />
)} )}
@ -269,7 +268,7 @@ export default function XrayMetricsModal({ open, onClose }: XrayMetricsModalProp
type="info" type="info"
showIcon showIcon
className="metrics-alert" className="metrics-alert"
message={t('pages.index.xrayObservatoryEmpty')} title={t('pages.index.xrayObservatoryEmpty')}
description={t('pages.index.xrayObservatoryHint')} description={t('pages.index.xrayObservatoryHint')}
/> />
) : ( ) : (

View file

@ -50,7 +50,7 @@ export default function XrayStatusCard({
const stateText = t(XRAY_STATE_KEYS[status.xray.state] ?? 'pages.index.xrayStatusUnknown'); const stateText = t(XRAY_STATE_KEYS[status.xray.state] ?? 'pages.index.xrayStatusUnknown');
const title = ( const title = (
<Space direction="horizontal"> <Space>
<span>{t('pages.index.xrayStatus')}</span> <span>{t('pages.index.xrayStatus')}</span>
{isMobile && status.xray.version && status.xray.version !== 'Unknown' && ( {isMobile && status.xray.version && status.xray.version !== 'Unknown' && (
<Tag color="green">v{status.xray.version}</Tag> <Tag color="green">v{status.xray.version}</Tag>
@ -105,21 +105,21 @@ export default function XrayStatusCard({
const actions = [ const actions = [
...(ipLimitEnable ...(ipLimitEnable
? [ ? [
<Space direction="horizontal" className="action" key="xraylogs" onClick={onOpenXrayLogs}> <Space className="action" key="xraylogs" onClick={onOpenXrayLogs}>
<BarsOutlined /> <BarsOutlined />
{!isMobile && <span>{t('pages.index.logs')}</span>} {!isMobile && <span>{t('pages.index.logs')}</span>}
</Space>, </Space>,
] ]
: []), : []),
<Space direction="horizontal" className="action" key="stop" onClick={onStopXray}> <Space className="action" key="stop" onClick={onStopXray}>
<PoweroffOutlined /> <PoweroffOutlined />
{!isMobile && <span>{t('pages.index.stopXray')}</span>} {!isMobile && <span>{t('pages.index.stopXray')}</span>}
</Space>, </Space>,
<Space direction="horizontal" className="action" key="restart" onClick={onRestartXray}> <Space className="action" key="restart" onClick={onRestartXray}>
<ReloadOutlined /> <ReloadOutlined />
{!isMobile && <span>{t('pages.index.restartXray')}</span>} {!isMobile && <span>{t('pages.index.restartXray')}</span>}
</Space>, </Space>,
<Space direction="horizontal" className="action" key="switch" onClick={onOpenVersionSwitch}> <Space className="action" key="switch" onClick={onOpenVersionSwitch}>
<ToolOutlined /> <ToolOutlined />
{!isMobile && ( {!isMobile && (
<span> <span>

View file

@ -145,12 +145,12 @@ export default function LoginPage() {
{themeIcon} {themeIcon}
</button> </button>
<Popover <Popover
overlayClassName={isDark ? 'dark' : 'light'} rootClassName={isDark ? 'dark' : 'light'}
title={t('pages.settings.language')} title={t('pages.settings.language')}
placement="bottomRight" placement="bottomRight"
trigger="click" trigger="click"
content={ content={
<Space direction="vertical" size={10} className="settings-popover"> <Space orientation="vertical" size={10} className="settings-popover">
<Select <Select
className="lang-select" className="lang-select"
value={lang} value={lang}
@ -240,7 +240,7 @@ export default function LoginPage() {
size="large" size="large"
block block
> >
{submitting ? '' : t('login')} {t('login')}
</Button> </Button>
</Form.Item> </Form.Item>
</Form> </Form>

View file

@ -174,7 +174,7 @@ export default function NodeFormModal({
confirmLoading={submitting} confirmLoading={submitting}
okText={t('save')} okText={t('save')}
cancelText={t('cancel')} cancelText={t('cancel')}
maskClosable={false} mask={{ closable: false }}
width="640px" width="640px"
onOk={onSave} onOk={onSave}
onCancel={close} onCancel={close}
@ -276,14 +276,14 @@ export default function NodeFormModal({
<Alert <Alert
type="success" type="success"
showIcon showIcon
message={t('pages.nodes.connectionOk', { ms: testResult.latencyMs })} title={t('pages.nodes.connectionOk', { ms: testResult.latencyMs })}
description={testResult.xrayVersion ? `Xray ${testResult.xrayVersion}` : undefined} description={testResult.xrayVersion ? `Xray ${testResult.xrayVersion}` : undefined}
/> />
) : ( ) : (
<Alert <Alert
type="error" type="error"
showIcon showIcon
message={t('pages.nodes.connectionFailed')} title={t('pages.nodes.connectionFailed')}
description={testResult.error} description={testResult.error}
/> />
)} )}

View file

@ -111,7 +111,7 @@ export default function NodesPage() {
<Layout className="content-shell"> <Layout className="content-shell">
<Layout.Content id="content-layout" className="content-area"> <Layout.Content id="content-layout" className="content-area">
<Spin spinning={!fetched} delay={200} tip="Loading…" size="large"> <Spin spinning={!fetched} delay={200} description="Loading…" size="large">
{!fetched ? ( {!fetched ? (
<div className="loading-spacer" /> <div className="loading-spacer" />
) : ( ) : (

View file

@ -279,7 +279,7 @@ export default function SecurityTab({ allSetting, updateSetting }: SecurityTabPr
onChange={(e) => updateUserField('newPassword', e.target.value)} /> onChange={(e) => updateUserField('newPassword', e.target.value)} />
</SettingListItem> </SettingListItem>
<List.Item> <List.Item>
<Space direction="horizontal" style={{ padding: '0 20px' }}> <Space style={{ padding: '0 20px' }}>
<Button type="primary" loading={updating} onClick={onUpdateUserClick}> <Button type="primary" loading={updating} onClick={onUpdateUserClick}>
{t('confirm')} {t('confirm')}
</Button> </Button>

View file

@ -2,11 +2,11 @@ import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
Alert, Alert,
BackTop,
Button, Button,
Card, Card,
Col, Col,
ConfigProvider, ConfigProvider,
FloatButton,
Layout, Layout,
Modal, Modal,
Row, Row,
@ -265,7 +265,7 @@ export default function SettingsPage() {
<Layout className="content-shell"> <Layout className="content-shell">
<Layout.Content id="content-layout" className="content-area"> <Layout.Content id="content-layout" className="content-area">
<Spin spinning={spinning || !fetched} delay={200} tip="Loading…" size="large"> <Spin spinning={spinning || !fetched} delay={200} description="Loading…" size="large">
{!fetched ? ( {!fetched ? (
<div className="loading-spacer" /> <div className="loading-spacer" />
) : ( ) : (
@ -277,7 +277,7 @@ export default function SettingsPage() {
closable closable
className="conf-alert" className="conf-alert"
onClose={() => setAlertVisible(false)} onClose={() => setAlertVisible(false)}
message="Security warnings" title="Security warnings"
description={( description={(
<> <>
<b>Your panel may be exposed:</b> <b>Your panel may be exposed:</b>
@ -294,7 +294,7 @@ export default function SettingsPage() {
<Card hoverable> <Card hoverable>
<Row className="header-row"> <Row className="header-row">
<Col xs={24} sm={10} className="header-actions"> <Col xs={24} sm={10} className="header-actions">
<Space direction="horizontal"> <Space>
<Button type="primary" disabled={saveDisabled} onClick={saveAll}> <Button type="primary" disabled={saveDisabled} onClick={saveAll}>
{t('pages.settings.save')} {t('pages.settings.save')}
</Button> </Button>
@ -304,8 +304,8 @@ export default function SettingsPage() {
</Space> </Space>
</Col> </Col>
<Col xs={24} sm={14} className="header-info"> <Col xs={24} sm={14} className="header-info">
<BackTop target={scrollTarget} visibilityHeight={200} /> <FloatButton.BackTop target={scrollTarget} visibilityHeight={200} />
<Alert type="warning" showIcon message={t('pages.settings.infoDesc')} /> <Alert type="warning" showIcon title={t('pages.settings.infoDesc')} />
</Col> </Col>
</Row> </Row>
</Card> </Card>

View file

@ -329,7 +329,7 @@ export default function SubscriptionFormatsTab({ allSetting, updateSetting }: Su
options={['ip', 'ipv4', 'ipv6'].map((p) => ({ value: p, label: p }))} options={['ip', 'ipv4', 'ipv6'].map((p) => ({ value: p, label: p }))}
/> />
</SettingListItem> </SettingListItem>
<Space direction="horizontal" style={{ padding: '10px 20px' }}> <Space style={{ padding: '10px 20px' }}>
{noisesArray.length > 1 && ( {noisesArray.length > 1 && (
<Button type="primary" danger onClick={() => removeNoise(index)}> <Button type="primary" danger onClick={() => removeNoise(index)}>
{t('delete')} {t('delete')}

View file

@ -256,7 +256,7 @@ export default function SubPage() {
placement="bottomRight" placement="bottomRight"
trigger="click" trigger="click"
content={ content={
<Space direction="vertical" size={10} className="settings-popover"> <Space orientation="vertical" size={10} className="settings-popover">
<Select <Select
className="lang-select" className="lang-select"
value={lang} value={lang}

View file

@ -97,7 +97,7 @@ export default function BalancerFormModal({
okText={okText} okText={okText}
cancelText={t('close')} cancelText={t('close')}
okButtonProps={{ disabled: !isValid }} okButtonProps={{ disabled: !isValid }}
maskClosable={false} mask={{ closable: false }}
onOk={submit} onOk={submit}
onCancel={onClose} onCancel={onClose}
> >

View file

@ -306,7 +306,7 @@ export default function BalancersTab({
return ( return (
<> <>
{modalContextHolder} {modalContextHolder}
<Space direction="vertical" size="middle" style={{ width: '100%' }}> <Space orientation="vertical" size="middle" style={{ width: '100%' }}>
{rows.length === 0 ? ( {rows.length === 0 ? (
<Empty description={t('emptyBalancersDesc')}> <Empty description={t('emptyBalancersDesc')}>
<Button type="primary" icon={<PlusOutlined />} onClick={openAdd}> <Button type="primary" icon={<PlusOutlined />} onClick={openAdd}>

View file

@ -206,7 +206,7 @@ export default function BasicsTab({
<Alert <Alert
type="warning" type="warning"
className="mb-12 hint-alert" className="mb-12 hint-alert"
message={t('pages.xray.generalConfigsDesc')} title={t('pages.xray.generalConfigsDesc')}
icon={<ExclamationCircleFilled style={{ color: '#FFA031' }} />} icon={<ExclamationCircleFilled style={{ color: '#FFA031' }} />}
/> />
<SettingListItem <SettingListItem
@ -300,7 +300,7 @@ export default function BasicsTab({
<Alert <Alert
type="warning" type="warning"
className="mb-12 hint-alert" className="mb-12 hint-alert"
message={t('pages.xray.logConfigsDesc')} title={t('pages.xray.logConfigsDesc')}
icon={<ExclamationCircleFilled style={{ color: '#FFA031' }} />} icon={<ExclamationCircleFilled style={{ color: '#FFA031' }} />}
/> />
<SettingListItem <SettingListItem
@ -377,7 +377,7 @@ export default function BasicsTab({
<Alert <Alert
type="warning" type="warning"
className="mb-12 hint-alert" className="mb-12 hint-alert"
message={t('pages.xray.blockConnectionsConfigsDesc')} title={t('pages.xray.blockConnectionsConfigsDesc')}
icon={<ExclamationCircleFilled style={{ color: '#FFA031' }} />} icon={<ExclamationCircleFilled style={{ color: '#FFA031' }} />}
/> />
@ -428,7 +428,7 @@ export default function BasicsTab({
<Alert <Alert
type="warning" type="warning"
className="mb-12 hint-alert" className="mb-12 hint-alert"
message={t('pages.xray.directConnectionsConfigsDesc')} title={t('pages.xray.directConnectionsConfigsDesc')}
icon={<ExclamationCircleFilled style={{ color: '#FFA031' }} />} icon={<ExclamationCircleFilled style={{ color: '#FFA031' }} />}
/> />
@ -532,7 +532,7 @@ export default function BasicsTab({
key: 'reset', key: 'reset',
label: t('pages.settings.resetDefaultConfig'), label: t('pages.settings.resetDefaultConfig'),
children: ( children: (
<Space direction="horizontal" style={{ padding: '0 20px' }}> <Space style={{ padding: '0 20px' }}>
<Button danger onClick={confirmResetDefault}> <Button danger onClick={confirmResetDefault}>
{t('pages.settings.resetDefaultConfig')} {t('pages.settings.resetDefaultConfig')}
</Button> </Button>

View file

@ -44,7 +44,7 @@ export default function DnsPresetsModal({ open, onClose, onInstall }: DnsPresets
open={open} open={open}
title={t('pages.xray.dns.dnsPresetTitle')} title={t('pages.xray.dns.dnsPresetTitle')}
footer={null} footer={null}
maskClosable={false} mask={{ closable: false }}
onCancel={onClose} onCancel={onClose}
> >
<List bordered> <List bordered>

View file

@ -156,7 +156,7 @@ export default function DnsServerModal({
title={title} title={title}
okText={t('confirm')} okText={t('confirm')}
cancelText={t('close')} cancelText={t('close')}
maskClosable={false} mask={{ closable: false }}
onOk={submit} onOk={submit}
onCancel={onClose} onCancel={onClose}
> >

View file

@ -427,7 +427,7 @@ export default function DnsTab({ templateSettings, setTemplateSettings }: DnsTab
</Button> </Button>
</Empty> </Empty>
) : ( ) : (
<Space direction="vertical" size="middle" style={{ width: '100%' }}> <Space orientation="vertical" size="middle" style={{ width: '100%' }}>
<Button type="primary" icon={<PlusOutlined />} onClick={() => syncHosts([...hostsList, { domain: '', values: [] }])}> <Button type="primary" icon={<PlusOutlined />} onClick={() => syncHosts([...hostsList, { domain: '', values: [] }])}>
{t('pages.xray.dns.hostsAdd')} {t('pages.xray.dns.hostsAdd')}
</Button> </Button>
@ -475,7 +475,7 @@ export default function DnsTab({ templateSettings, setTemplateSettings }: DnsTab
</Space> </Space>
</Empty> </Empty>
) : ( ) : (
<Space direction="vertical" size="middle" style={{ width: '100%' }}> <Space orientation="vertical" size="middle" style={{ width: '100%' }}>
<Space wrap> <Space wrap>
<Button type="primary" icon={<PlusOutlined />} onClick={openAddServer}> <Button type="primary" icon={<PlusOutlined />} onClick={openAddServer}>
{t('pages.xray.dns.add')} {t('pages.xray.dns.add')}
@ -509,7 +509,7 @@ export default function DnsTab({ templateSettings, setTemplateSettings }: DnsTab
</Button> </Button>
</Empty> </Empty>
) : ( ) : (
<Space direction="vertical" size="middle" style={{ width: '100%' }}> <Space orientation="vertical" size="middle" style={{ width: '100%' }}>
<Button type="primary" icon={<PlusOutlined />} onClick={addFakedns}> <Button type="primary" icon={<PlusOutlined />} onClick={addFakedns}>
{t('pages.xray.fakedns.add')} {t('pages.xray.fakedns.add')}
</Button> </Button>

View file

@ -235,7 +235,7 @@ export default function NordModal({
} }
return ( return (
<Modal open={open} title="NordVPN NordLynx" footer={null} closable maskClosable onCancel={onClose}> <Modal open={open} title="NordVPN NordLynx" footer={null} onCancel={onClose}>
{nordData == null ? ( {nordData == null ? (
<Tabs <Tabs
defaultActiveKey="token" defaultActiveKey="token"

View file

@ -266,7 +266,7 @@ export default function OutboundFormModal({
title={title} title={title}
okText={okText} okText={okText}
cancelText={t('close')} cancelText={t('close')}
maskClosable={false} mask={{ closable: false }}
width={780} width={780}
onOk={onOk} onOk={onOk}
onCancel={onClose} onCancel={onClose}
@ -434,7 +434,7 @@ export default function OutboundFormModal({
key: '2', key: '2',
label: 'JSON', label: 'JSON',
children: ( children: (
<Space direction="vertical" size={10} style={{ width: '100%', marginTop: 10 }}> <Space orientation="vertical" size={10} style={{ width: '100%', marginTop: 10 }}>
<Input.Search <Input.Search
value={linkInput} value={linkInput}
placeholder="vmess:// vless:// trojan:// ss:// hysteria2://" placeholder="vmess:// vless:// trojan:// ss:// hysteria2://"

View file

@ -326,7 +326,7 @@ export default function OutboundsTab({
return ( return (
<Popover <Popover
placement="topLeft" placement="topLeft"
overlayClassName="outbound-test-popover" rootClassName="outbound-test-popover"
content={ content={
<div className="timing-breakdown"> <div className="timing-breakdown">
<div className={`td-head ${r.success ? 'ok' : 'fail'}`}> <div className={`td-head ${r.success ? 'ok' : 'fail'}`}>
@ -386,7 +386,7 @@ export default function OutboundsTab({
return ( return (
<> <>
{modalContextHolder} {modalContextHolder}
<Space direction="vertical" size="middle" style={{ width: '100%' }}> <Space orientation="vertical" size="middle" style={{ width: '100%' }}>
<Row gutter={[12, 12]} align="middle" justify="space-between"> <Row gutter={[12, 12]} align="middle" justify="space-between">
<Col xs={24} sm={12}> <Col xs={24} sm={12}>
<Space size="small" wrap> <Space size="small" wrap>

View file

@ -402,7 +402,7 @@ export default function RoutingTab({
return ( return (
<> <>
{modalContextHolder} {modalContextHolder}
<Space direction="vertical" size="middle" style={{ width: '100%' }}> <Space orientation="vertical" size="middle" style={{ width: '100%' }}>
<Button type="primary" icon={<PlusOutlined />} onClick={openAdd}> <Button type="primary" icon={<PlusOutlined />} onClick={openAdd}>
{t('pages.xray.Routings')} {t('pages.xray.Routings')}
</Button> </Button>

View file

@ -149,7 +149,7 @@ export default function RuleFormModal({
title={title} title={title}
okText={okText} okText={okText}
cancelText={t('close')} cancelText={t('close')}
maskClosable={false} mask={{ closable: false }}
width={640} width={640}
onOk={submit} onOk={submit}
onCancel={onClose} onCancel={onClose}

View file

@ -207,7 +207,7 @@ export default function WarpModal({
const hasConfig = !ObjectUtil.isEmpty(warpConfig); const hasConfig = !ObjectUtil.isEmpty(warpConfig);
return ( return (
<Modal open={open} title="Cloudflare WARP" footer={null} closable maskClosable onCancel={onClose}> <Modal open={open} title="Cloudflare WARP" footer={null} onCancel={onClose}>
{!hasWarp ? ( {!hasWarp ? (
<Button type="primary" loading={loading} icon={<ApiOutlined />} onClick={register}> <Button type="primary" loading={loading} icon={<ApiOutlined />} onClick={register}>
Create WARP account Create WARP account
@ -268,7 +268,7 @@ export default function WarpModal({
Update Update
</Button> </Button>
{licenseError && ( {licenseError && (
<Alert message={licenseError} type="error" showIcon className="license-error" /> <Alert title={licenseError} type="error" showIcon className="license-error" />
)} )}
</div> </div>
</Form.Item> </Form.Item>

View file

@ -1,12 +1,12 @@
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
BackTop,
Alert, Alert,
Button, Button,
Card, Card,
Col, Col,
ConfigProvider, ConfigProvider,
FloatButton,
Layout, Layout,
message, message,
Modal, Modal,
@ -258,7 +258,7 @@ export default function XrayPage() {
<Layout className="content-shell"> <Layout className="content-shell">
<Layout.Content id="content-layout" className="content-area"> <Layout.Content id="content-layout" className="content-area">
<Spin spinning={spinning || !fetched} delay={200} tip="Loading…" size="large"> <Spin spinning={spinning || !fetched} delay={200} description="Loading…" size="large">
{!fetched ? ( {!fetched ? (
<div className="loading-spacer" /> <div className="loading-spacer" />
) : fetchError ? ( ) : fetchError ? (
@ -274,7 +274,7 @@ export default function XrayPage() {
<Card hoverable> <Card hoverable>
<Row className="header-row"> <Row className="header-row">
<Col xs={24} sm={14} className="header-actions"> <Col xs={24} sm={14} className="header-actions">
<Space direction="horizontal"> <Space>
<Button type="primary" disabled={saveDisabled} onClick={onSaveAll}> <Button type="primary" disabled={saveDisabled} onClick={onSaveAll}>
{t('pages.xray.save')} {t('pages.xray.save')}
</Button> </Button>
@ -293,8 +293,8 @@ export default function XrayPage() {
</Space> </Space>
</Col> </Col>
<Col xs={24} sm={10} className="header-info"> <Col xs={24} sm={10} className="header-info">
<BackTop target={scrollTarget} visibilityHeight={200} /> <FloatButton.BackTop target={scrollTarget} visibilityHeight={200} />
<Alert type="warning" showIcon message={t('pages.settings.infoDesc')} /> <Alert type="warning" showIcon title={t('pages.settings.infoDesc')} />
</Col> </Col>
</Row> </Row>
</Card> </Card>