mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-04 17:37:19 +00:00
feat(xray): merge basic routing into the routing rules section
Move the basic routing presets (block torrent/IPs/domains, direct IPs/domains, IPv4) out of the Basics page into a Basic tab in the Routing section, next to the advanced Rules table; both edit the same routing.rules so existing rules stay in sync. Drop the WARP and Nord routing preset rows - WARP/Nord outbounds are still added from the Outbounds page and any existing rules remain editable in the Rules tab. Hide the Source and Balancers columns in the rules table when no rule populates them.
This commit is contained in:
@@ -94,9 +94,6 @@ export default function XrayPage() {
|
|||||||
[setTemplateSettings],
|
[setTemplateSettings],
|
||||||
);
|
);
|
||||||
|
|
||||||
const warpExist = !!templateSettings?.outbounds?.find((o) => o?.tag === 'warp');
|
|
||||||
const nordExist = !!templateSettings?.outbounds?.find((o) => o?.tag?.startsWith?.('nord-'));
|
|
||||||
|
|
||||||
async function onTestOutbound(idx: number, mode: string) {
|
async function onTestOutbound(idx: number, mode: string) {
|
||||||
const outbound = templateSettings?.outbounds?.[idx];
|
const outbound = templateSettings?.outbounds?.[idx];
|
||||||
if (outbound) await testOutbound(idx, outbound, mode);
|
if (outbound) await testOutbound(idx, outbound, mode);
|
||||||
@@ -287,10 +284,6 @@ export default function XrayPage() {
|
|||||||
setTemplateSettings={setTemplateSettings}
|
setTemplateSettings={setTemplateSettings}
|
||||||
outboundTestUrl={outboundTestUrl}
|
outboundTestUrl={outboundTestUrl}
|
||||||
onChangeOutboundTestUrl={setOutboundTestUrl}
|
onChangeOutboundTestUrl={setOutboundTestUrl}
|
||||||
warpExist={warpExist}
|
|
||||||
nordExist={nordExist}
|
|
||||||
onShowWarp={() => setWarpOpen(true)}
|
|
||||||
onShowNord={() => setNordOpen(true)}
|
|
||||||
onResetDefault={resetToDefault}
|
onResetDefault={resetToDefault}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,13 +2,10 @@ import { useCallback } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Alert, Button, Input, Modal, Select, Space, Switch, Tabs } from 'antd';
|
import { Alert, Button, Input, Modal, Select, Space, Switch, Tabs } from 'antd';
|
||||||
import {
|
import {
|
||||||
ApiOutlined,
|
|
||||||
BarChartOutlined,
|
BarChartOutlined,
|
||||||
CloudOutlined,
|
|
||||||
FileTextOutlined,
|
FileTextOutlined,
|
||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
SwapOutlined,
|
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
import { OutboundDomainStrategies } from '@/schemas/primitives';
|
import { OutboundDomainStrategies } from '@/schemas/primitives';
|
||||||
@@ -20,29 +17,17 @@ import './BasicsTab.css';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
ACCESS_LOG,
|
ACCESS_LOG,
|
||||||
BITTORRENT_PROTOCOLS,
|
|
||||||
BLOCK_DOMAINS_OPTIONS,
|
|
||||||
DOMAINS_OPTIONS,
|
|
||||||
ERROR_LOG,
|
ERROR_LOG,
|
||||||
IPS_OPTIONS,
|
|
||||||
LOG_LEVELS,
|
LOG_LEVELS,
|
||||||
MASK_ADDRESS,
|
MASK_ADDRESS,
|
||||||
ROUTING_DOMAIN_STRATEGIES,
|
ROUTING_DOMAIN_STRATEGIES,
|
||||||
SERVICES_OPTIONS,
|
|
||||||
directSettings,
|
|
||||||
ipv4Settings,
|
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { ruleGetter, ruleSetter, syncOutbound } from './helpers';
|
|
||||||
|
|
||||||
interface BasicsTabProps {
|
interface BasicsTabProps {
|
||||||
templateSettings: XraySettingsValue | null;
|
templateSettings: XraySettingsValue | null;
|
||||||
setTemplateSettings: SetTemplate;
|
setTemplateSettings: SetTemplate;
|
||||||
outboundTestUrl: string;
|
outboundTestUrl: string;
|
||||||
onChangeOutboundTestUrl: (v: string) => void;
|
onChangeOutboundTestUrl: (v: string) => void;
|
||||||
warpExist: boolean;
|
|
||||||
nordExist: boolean;
|
|
||||||
onShowWarp: () => void;
|
|
||||||
onShowNord: () => void;
|
|
||||||
onResetDefault: () => void;
|
onResetDefault: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,10 +36,6 @@ export default function BasicsTab({
|
|||||||
setTemplateSettings,
|
setTemplateSettings,
|
||||||
outboundTestUrl,
|
outboundTestUrl,
|
||||||
onChangeOutboundTestUrl,
|
onChangeOutboundTestUrl,
|
||||||
warpExist,
|
|
||||||
nordExist,
|
|
||||||
onShowWarp,
|
|
||||||
onShowNord,
|
|
||||||
onResetDefault,
|
onResetDefault,
|
||||||
}: BasicsTabProps) {
|
}: BasicsTabProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -92,19 +73,6 @@ export default function BasicsTab({
|
|||||||
const log = (templateSettings?.log || {}) as Record<string, unknown>;
|
const log = (templateSettings?.log || {}) as Record<string, unknown>;
|
||||||
const policy = (templateSettings?.policy?.system || {}) as Record<string, boolean>;
|
const policy = (templateSettings?.policy?.system || {}) as Record<string, boolean>;
|
||||||
|
|
||||||
const blockedIPs = ruleGetter(templateSettings, 'blocked', 'ip');
|
|
||||||
const blockedDomains = ruleGetter(templateSettings, 'blocked', 'domain');
|
|
||||||
const blockedProtocols = ruleGetter(templateSettings, 'blocked', 'protocol');
|
|
||||||
const directIPs = ruleGetter(templateSettings, 'direct', 'ip');
|
|
||||||
const directDomains = ruleGetter(templateSettings, 'direct', 'domain');
|
|
||||||
const ipv4Domains = ruleGetter(templateSettings, 'IPv4', 'domain');
|
|
||||||
const warpDomains = ruleGetter(templateSettings, 'warp', 'domain');
|
|
||||||
const nordTag =
|
|
||||||
templateSettings?.outbounds?.find((o) => o?.tag?.startsWith?.('nord-'))?.tag || 'nord';
|
|
||||||
const nordDomains = ruleGetter(templateSettings, nordTag, 'domain');
|
|
||||||
|
|
||||||
const torrentActive = BITTORRENT_PROTOCOLS.every((p) => blockedProtocols.includes(p));
|
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
key: '1',
|
key: '1',
|
||||||
@@ -277,165 +245,6 @@ export default function BasicsTab({
|
|||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: '4',
|
|
||||||
label: catTabLabel(<SwapOutlined />, t('pages.xray.basicRouting'), isMobile),
|
|
||||||
children: (
|
|
||||||
<>
|
|
||||||
<Alert
|
|
||||||
type="warning"
|
|
||||||
showIcon
|
|
||||||
className="mb-12 hint-alert"
|
|
||||||
title={t('pages.xray.blockConnectionsConfigsDesc')}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SettingListItem
|
|
||||||
title={t('pages.xray.Torrent')}
|
|
||||||
paddings="small"
|
|
||||||
control={
|
|
||||||
<Switch
|
|
||||||
checked={torrentActive}
|
|
||||||
onChange={(checked) => mutate((tt) => {
|
|
||||||
const next = checked
|
|
||||||
? [...blockedProtocols, ...BITTORRENT_PROTOCOLS]
|
|
||||||
: blockedProtocols.filter((d) => !BITTORRENT_PROTOCOLS.includes(d));
|
|
||||||
ruleSetter(tt, 'blocked', 'protocol', next);
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SettingListItem
|
|
||||||
title={t('pages.xray.blockips')}
|
|
||||||
paddings="small"
|
|
||||||
control={
|
|
||||||
<Select
|
|
||||||
mode="tags"
|
|
||||||
value={blockedIPs}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
options={IPS_OPTIONS}
|
|
||||||
onChange={(v) => mutate((tt) => ruleSetter(tt, 'blocked', 'ip', v))}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SettingListItem
|
|
||||||
title={t('pages.xray.blockdomains')}
|
|
||||||
paddings="small"
|
|
||||||
control={
|
|
||||||
<Select
|
|
||||||
mode="tags"
|
|
||||||
value={blockedDomains}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
options={BLOCK_DOMAINS_OPTIONS}
|
|
||||||
onChange={(v) => mutate((tt) => ruleSetter(tt, 'blocked', 'domain', v))}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Alert
|
|
||||||
type="warning"
|
|
||||||
showIcon
|
|
||||||
className="mb-12 hint-alert"
|
|
||||||
title={t('pages.xray.directConnectionsConfigsDesc')}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SettingListItem
|
|
||||||
title={t('pages.xray.directips')}
|
|
||||||
paddings="small"
|
|
||||||
control={
|
|
||||||
<Select
|
|
||||||
mode="tags"
|
|
||||||
value={directIPs}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
options={IPS_OPTIONS}
|
|
||||||
onChange={(v) => mutate((tt) => {
|
|
||||||
ruleSetter(tt, 'direct', 'ip', v);
|
|
||||||
syncOutbound(tt, 'direct', directSettings);
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SettingListItem
|
|
||||||
title={t('pages.xray.directdomains')}
|
|
||||||
paddings="small"
|
|
||||||
control={
|
|
||||||
<Select
|
|
||||||
mode="tags"
|
|
||||||
value={directDomains}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
options={DOMAINS_OPTIONS}
|
|
||||||
onChange={(v) => mutate((tt) => {
|
|
||||||
ruleSetter(tt, 'direct', 'domain', v);
|
|
||||||
syncOutbound(tt, 'direct', directSettings);
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SettingListItem
|
|
||||||
title={t('pages.xray.ipv4Routing')}
|
|
||||||
description={t('pages.xray.ipv4RoutingDesc')}
|
|
||||||
paddings="small"
|
|
||||||
control={
|
|
||||||
<Select
|
|
||||||
mode="tags"
|
|
||||||
value={ipv4Domains}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
options={SERVICES_OPTIONS}
|
|
||||||
onChange={(v) => mutate((tt) => {
|
|
||||||
ruleSetter(tt, 'IPv4', 'domain', v);
|
|
||||||
syncOutbound(tt, 'IPv4', ipv4Settings);
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SettingListItem
|
|
||||||
title={t('pages.xray.warpRouting')}
|
|
||||||
description={t('pages.xray.warpRoutingDesc')}
|
|
||||||
paddings="small"
|
|
||||||
control={
|
|
||||||
warpExist ? (
|
|
||||||
<Select
|
|
||||||
mode="tags"
|
|
||||||
value={warpDomains}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
options={SERVICES_OPTIONS}
|
|
||||||
onChange={(v) => mutate((tt) => ruleSetter(tt, 'warp', 'domain', v))}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Button type="primary" onClick={onShowWarp} icon={<CloudOutlined />}>
|
|
||||||
WARP
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SettingListItem
|
|
||||||
title={t('pages.xray.nordRouting')}
|
|
||||||
description={t('pages.xray.nordRoutingDesc')}
|
|
||||||
paddings="small"
|
|
||||||
control={
|
|
||||||
nordExist ? (
|
|
||||||
<Select
|
|
||||||
mode="tags"
|
|
||||||
value={nordDomains}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
options={SERVICES_OPTIONS}
|
|
||||||
onChange={(v) => mutate((tt) => ruleSetter(tt, nordTag, 'domain', v))}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Button type="primary" onClick={onShowNord} icon={<ApiOutlined />}>
|
|
||||||
NordVPN
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'reset',
|
key: 'reset',
|
||||||
label: catTabLabel(<ReloadOutlined />, t('pages.settings.resetDefaultConfig'), isMobile),
|
label: catTabLabel(<ReloadOutlined />, t('pages.settings.resetDefaultConfig'), isMobile),
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Alert, Select, Switch } from 'antd';
|
||||||
|
|
||||||
|
import { SettingListItem } from '@/components/ui';
|
||||||
|
import type { XraySettingsValue, SetTemplate } from '@/hooks/useXraySetting';
|
||||||
|
import {
|
||||||
|
BITTORRENT_PROTOCOLS,
|
||||||
|
BLOCK_DOMAINS_OPTIONS,
|
||||||
|
DOMAINS_OPTIONS,
|
||||||
|
IPS_OPTIONS,
|
||||||
|
SERVICES_OPTIONS,
|
||||||
|
directSettings,
|
||||||
|
ipv4Settings,
|
||||||
|
} from '../basics/constants';
|
||||||
|
import { ruleGetter, ruleSetter, syncOutbound } from '../basics/helpers';
|
||||||
|
|
||||||
|
interface RoutingBasicProps {
|
||||||
|
templateSettings: XraySettingsValue | null;
|
||||||
|
setTemplateSettings: SetTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RoutingBasic({ templateSettings, setTemplateSettings }: RoutingBasicProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const mutate = useCallback(
|
||||||
|
(mutator: (next: XraySettingsValue) => void) => {
|
||||||
|
setTemplateSettings((prev) => {
|
||||||
|
if (!prev) return prev;
|
||||||
|
const clone = JSON.parse(JSON.stringify(prev)) as XraySettingsValue;
|
||||||
|
mutator(clone);
|
||||||
|
return clone;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setTemplateSettings],
|
||||||
|
);
|
||||||
|
|
||||||
|
const blockedIPs = ruleGetter(templateSettings, 'blocked', 'ip');
|
||||||
|
const blockedDomains = ruleGetter(templateSettings, 'blocked', 'domain');
|
||||||
|
const blockedProtocols = ruleGetter(templateSettings, 'blocked', 'protocol');
|
||||||
|
const directIPs = ruleGetter(templateSettings, 'direct', 'ip');
|
||||||
|
const directDomains = ruleGetter(templateSettings, 'direct', 'domain');
|
||||||
|
const ipv4Domains = ruleGetter(templateSettings, 'IPv4', 'domain');
|
||||||
|
|
||||||
|
const torrentActive = BITTORRENT_PROTOCOLS.every((p) => blockedProtocols.includes(p));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Alert
|
||||||
|
type="warning"
|
||||||
|
showIcon
|
||||||
|
className="mb-12 hint-alert"
|
||||||
|
title={t('pages.xray.blockConnectionsConfigsDesc')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SettingListItem
|
||||||
|
title={t('pages.xray.Torrent')}
|
||||||
|
paddings="small"
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
checked={torrentActive}
|
||||||
|
onChange={(checked) => mutate((tt) => {
|
||||||
|
const next = checked
|
||||||
|
? [...blockedProtocols, ...BITTORRENT_PROTOCOLS]
|
||||||
|
: blockedProtocols.filter((d) => !BITTORRENT_PROTOCOLS.includes(d));
|
||||||
|
ruleSetter(tt, 'blocked', 'protocol', next);
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SettingListItem
|
||||||
|
title={t('pages.xray.blockips')}
|
||||||
|
paddings="small"
|
||||||
|
control={
|
||||||
|
<Select
|
||||||
|
mode="tags"
|
||||||
|
value={blockedIPs}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
options={IPS_OPTIONS}
|
||||||
|
onChange={(v) => mutate((tt) => ruleSetter(tt, 'blocked', 'ip', v))}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SettingListItem
|
||||||
|
title={t('pages.xray.blockdomains')}
|
||||||
|
paddings="small"
|
||||||
|
control={
|
||||||
|
<Select
|
||||||
|
mode="tags"
|
||||||
|
value={blockedDomains}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
options={BLOCK_DOMAINS_OPTIONS}
|
||||||
|
onChange={(v) => mutate((tt) => ruleSetter(tt, 'blocked', 'domain', v))}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Alert
|
||||||
|
type="warning"
|
||||||
|
showIcon
|
||||||
|
className="mb-12 hint-alert"
|
||||||
|
title={t('pages.xray.directConnectionsConfigsDesc')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SettingListItem
|
||||||
|
title={t('pages.xray.directips')}
|
||||||
|
paddings="small"
|
||||||
|
control={
|
||||||
|
<Select
|
||||||
|
mode="tags"
|
||||||
|
value={directIPs}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
options={IPS_OPTIONS}
|
||||||
|
onChange={(v) => mutate((tt) => {
|
||||||
|
ruleSetter(tt, 'direct', 'ip', v);
|
||||||
|
syncOutbound(tt, 'direct', directSettings);
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SettingListItem
|
||||||
|
title={t('pages.xray.directdomains')}
|
||||||
|
paddings="small"
|
||||||
|
control={
|
||||||
|
<Select
|
||||||
|
mode="tags"
|
||||||
|
value={directDomains}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
options={DOMAINS_OPTIONS}
|
||||||
|
onChange={(v) => mutate((tt) => {
|
||||||
|
ruleSetter(tt, 'direct', 'domain', v);
|
||||||
|
syncOutbound(tt, 'direct', directSettings);
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SettingListItem
|
||||||
|
title={t('pages.xray.ipv4Routing')}
|
||||||
|
description={t('pages.xray.ipv4RoutingDesc')}
|
||||||
|
paddings="small"
|
||||||
|
control={
|
||||||
|
<Select
|
||||||
|
mode="tags"
|
||||||
|
value={ipv4Domains}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
options={SERVICES_OPTIONS}
|
||||||
|
onChange={(v) => mutate((tt) => {
|
||||||
|
ruleSetter(tt, 'IPv4', 'domain', v);
|
||||||
|
syncOutbound(tt, 'IPv4', ipv4Settings);
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -231,3 +231,7 @@
|
|||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hint-alert {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Modal, Space, Table } from 'antd';
|
import { Button, Modal, Space, Table, Tabs } from 'antd';
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { ControlOutlined, PlusOutlined, UnorderedListOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
import { catTabLabel } from '@/pages/settings/catTabLabel';
|
||||||
|
import RoutingBasic from './RoutingBasic';
|
||||||
import RuleFormModal from './RuleFormModal';
|
import RuleFormModal from './RuleFormModal';
|
||||||
import type { RoutingRule } from './RuleFormModal';
|
import type { RoutingRule } from './RuleFormModal';
|
||||||
import RuleCardList from './RuleCardList';
|
import RuleCardList from './RuleCardList';
|
||||||
@@ -226,9 +228,14 @@ export default function RoutingTab({
|
|||||||
document.addEventListener('pointercancel', onUp);
|
document.addEventListener('pointercancel', onUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasSource = rows.some((r) => r.sourceIP || r.sourcePort || r.vlessRoute);
|
||||||
|
const hasBalancer = rows.some((r) => r.balancerTag);
|
||||||
|
|
||||||
const desktopColumns = useRoutingColumns({
|
const desktopColumns = useRoutingColumns({
|
||||||
isMobile,
|
isMobile,
|
||||||
rowsLength: rows.length,
|
rowsLength: rows.length,
|
||||||
|
showSource: hasSource,
|
||||||
|
showBalancer: hasBalancer,
|
||||||
onHandlePointerDown,
|
onHandlePointerDown,
|
||||||
openEdit,
|
openEdit,
|
||||||
moveUp,
|
moveUp,
|
||||||
@@ -236,9 +243,31 @@ export default function RoutingTab({
|
|||||||
confirmDelete,
|
confirmDelete,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const tableScrollX = desktopColumns.reduce((sum, c) => {
|
||||||
|
const col = c as { width?: number; hidden?: boolean };
|
||||||
|
return col.hidden ? sum : sum + (typeof col.width === 'number' ? col.width : 0);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{modalContextHolder}
|
{modalContextHolder}
|
||||||
|
<Tabs
|
||||||
|
defaultActiveKey="basic"
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
key: 'basic',
|
||||||
|
label: catTabLabel(<ControlOutlined />, t('pages.xray.basicRouting'), isMobile),
|
||||||
|
children: (
|
||||||
|
<RoutingBasic
|
||||||
|
templateSettings={templateSettings}
|
||||||
|
setTemplateSettings={setTemplateSettings}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'rules',
|
||||||
|
label: catTabLabel(<UnorderedListOutlined />, t('pages.xray.Routings'), isMobile),
|
||||||
|
children: (
|
||||||
<Space orientation="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')}
|
||||||
@@ -261,7 +290,7 @@ export default function RoutingTab({
|
|||||||
dataSource={rows}
|
dataSource={rows}
|
||||||
rowKey={(r) => r.key}
|
rowKey={(r) => r.key}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
scroll={{ x: 1150 }}
|
scroll={{ x: tableScrollX }}
|
||||||
size="small"
|
size="small"
|
||||||
className="routing-table"
|
className="routing-table"
|
||||||
onRow={(_record, index) => {
|
onRow={(_record, index) => {
|
||||||
@@ -275,7 +304,11 @@ export default function RoutingTab({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
<RuleFormModal
|
<RuleFormModal
|
||||||
open={ruleModalOpen}
|
open={ruleModalOpen}
|
||||||
rule={editingRule}
|
rule={editingRule}
|
||||||
@@ -285,7 +318,6 @@ export default function RoutingTab({
|
|||||||
onClose={() => setRuleModalOpen(false)}
|
onClose={() => setRuleModalOpen(false)}
|
||||||
onConfirm={onRuleConfirm}
|
onConfirm={onRuleConfirm}
|
||||||
/>
|
/>
|
||||||
</Space>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import type { RuleRow } from './types';
|
|||||||
interface RoutingColumnsParams {
|
interface RoutingColumnsParams {
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
rowsLength: number;
|
rowsLength: number;
|
||||||
|
showSource: boolean;
|
||||||
|
showBalancer: boolean;
|
||||||
onHandlePointerDown: (idx: number, ev: React.PointerEvent) => void;
|
onHandlePointerDown: (idx: number, ev: React.PointerEvent) => void;
|
||||||
openEdit: (idx: number) => void;
|
openEdit: (idx: number) => void;
|
||||||
moveUp: (idx: number) => void;
|
moveUp: (idx: number) => void;
|
||||||
@@ -29,6 +31,8 @@ interface RoutingColumnsParams {
|
|||||||
export function useRoutingColumns({
|
export function useRoutingColumns({
|
||||||
isMobile,
|
isMobile,
|
||||||
rowsLength,
|
rowsLength,
|
||||||
|
showSource,
|
||||||
|
showBalancer,
|
||||||
onHandlePointerDown,
|
onHandlePointerDown,
|
||||||
openEdit,
|
openEdit,
|
||||||
moveUp,
|
moveUp,
|
||||||
@@ -84,6 +88,7 @@ export function useRoutingColumns({
|
|||||||
align: 'left',
|
align: 'left',
|
||||||
width: 180,
|
width: 180,
|
||||||
key: 'source',
|
key: 'source',
|
||||||
|
hidden: !showSource,
|
||||||
render: (_v, record) => (
|
render: (_v, record) => (
|
||||||
<div className="criterion-flow">
|
<div className="criterion-flow">
|
||||||
{record.sourceIP && <CriterionRow label="IP" value={record.sourceIP} title={`Source IP: ${record.sourceIP}`} />}
|
{record.sourceIP && <CriterionRow label="IP" value={record.sourceIP} title={`Source IP: ${record.sourceIP}`} />}
|
||||||
@@ -110,6 +115,7 @@ export function useRoutingColumns({
|
|||||||
{
|
{
|
||||||
title: t('pages.xray.rules.dest'),
|
title: t('pages.xray.rules.dest'),
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
width: 200,
|
||||||
key: 'destination',
|
key: 'destination',
|
||||||
render: (_v, record) => (
|
render: (_v, record) => (
|
||||||
<div className="criterion-flow">
|
<div className="criterion-flow">
|
||||||
@@ -153,6 +159,7 @@ export function useRoutingColumns({
|
|||||||
align: 'left',
|
align: 'left',
|
||||||
width: 150,
|
width: 150,
|
||||||
key: 'balancer',
|
key: 'balancer',
|
||||||
|
hidden: !showBalancer,
|
||||||
render: (_v, record) =>
|
render: (_v, record) =>
|
||||||
record.balancerTag ? (
|
record.balancerTag ? (
|
||||||
<div className="target-row">
|
<div className="target-row">
|
||||||
@@ -165,6 +172,6 @@ export function useRoutingColumns({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[t, isMobile, rowsLength],
|
[t, isMobile, rowsLength, showSource, showBalancer],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user