mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-16 16:20:59 +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,56 +243,81 @@ 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}
|
||||||
<Space orientation="vertical" size="middle" style={{ width: '100%' }}>
|
<Tabs
|
||||||
<Button type="primary" icon={<PlusOutlined />} onClick={openAdd}>
|
defaultActiveKey="basic"
|
||||||
{t('pages.xray.Routings')}
|
items={[
|
||||||
</Button>
|
{
|
||||||
|
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%' }}>
|
||||||
|
<Button type="primary" icon={<PlusOutlined />} onClick={openAdd}>
|
||||||
|
{t('pages.xray.Routings')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<RuleCardList
|
<RuleCardList
|
||||||
rows={rows}
|
rows={rows}
|
||||||
draggedIndex={draggedIndex}
|
draggedIndex={draggedIndex}
|
||||||
dropTargetIndex={dropTargetIndex}
|
dropTargetIndex={dropTargetIndex}
|
||||||
onHandlePointerDown={onHandlePointerDown}
|
onHandlePointerDown={onHandlePointerDown}
|
||||||
openEdit={openEdit}
|
openEdit={openEdit}
|
||||||
moveUp={moveUp}
|
moveUp={moveUp}
|
||||||
moveDown={moveDown}
|
moveDown={moveDown}
|
||||||
confirmDelete={confirmDelete}
|
confirmDelete={confirmDelete}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Table
|
<Table
|
||||||
columns={desktopColumns}
|
columns={desktopColumns}
|
||||||
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) => {
|
||||||
const classes: string[] = [];
|
const classes: string[] = [];
|
||||||
const i = index ?? -1;
|
const i = index ?? -1;
|
||||||
if (draggedIndex === i) classes.push('row-dragging');
|
if (draggedIndex === i) classes.push('row-dragging');
|
||||||
if (dropTargetIndex === i && draggedIndex !== i && draggedIndex != null) {
|
if (dropTargetIndex === i && draggedIndex !== i && draggedIndex != null) {
|
||||||
classes.push(i > draggedIndex ? 'drop-after' : 'drop-before');
|
classes.push(i > draggedIndex ? 'drop-after' : 'drop-before');
|
||||||
}
|
}
|
||||||
return { className: classes.join(' '), 'data-row-key': i } as React.HTMLAttributes<HTMLElement>;
|
return { className: classes.join(' '), 'data-row-key': i } as React.HTMLAttributes<HTMLElement>;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</Space>
|
||||||
<RuleFormModal
|
),
|
||||||
open={ruleModalOpen}
|
},
|
||||||
rule={editingRule}
|
]}
|
||||||
inboundTags={inboundTagOptions}
|
/>
|
||||||
outboundTags={outboundTagOptions}
|
<RuleFormModal
|
||||||
balancerTags={balancerTagOptions}
|
open={ruleModalOpen}
|
||||||
onClose={() => setRuleModalOpen(false)}
|
rule={editingRule}
|
||||||
onConfirm={onRuleConfirm}
|
inboundTags={inboundTagOptions}
|
||||||
/>
|
outboundTags={outboundTagOptions}
|
||||||
</Space>
|
balancerTags={balancerTagOptions}
|
||||||
|
onClose={() => setRuleModalOpen(false)}
|
||||||
|
onConfirm={onRuleConfirm}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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