mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 12:44:22 +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:
parent
ac89ec724f
commit
a4dae566ce
6 changed files with 252 additions and 247 deletions
|
|
@ -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),
|
||||||
|
|
|
||||||
160
frontend/src/pages/xray/routing/RoutingBasic.tsx
Normal file
160
frontend/src/pages/xray/routing/RoutingBasic.tsx
Normal file
|
|
@ -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],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue