From 97c02ef69f020f0283903ea638985e6606c143eb Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 21 Jun 2026 16:29:46 +0200 Subject: [PATCH] feat(xray): preview export in a modal and switch rule enable toggle Routing and Outbounds export now opens a TextModal showing the JSON with copy/download buttons instead of auto-downloading the file. Routing import and export are collapsed into a "More" dropdown to match the Outbounds tab. The rule form Enabled field becomes a Switch instead of an Enabled/Disabled Select. --- .../src/pages/xray/outbounds/OutboundsTab.tsx | 18 +++++++-- .../src/pages/xray/routing/RoutingTab.tsx | 39 ++++++++++++------- .../src/pages/xray/routing/RuleFormModal.tsx | 12 ++---- 3 files changed, 43 insertions(+), 26 deletions(-) diff --git a/frontend/src/pages/xray/outbounds/OutboundsTab.tsx b/frontend/src/pages/xray/outbounds/OutboundsTab.tsx index d5d643652..21fbe3ab8 100644 --- a/frontend/src/pages/xray/outbounds/OutboundsTab.tsx +++ b/frontend/src/pages/xray/outbounds/OutboundsTab.tsx @@ -37,8 +37,9 @@ import { ImportOutlined, } from '@ant-design/icons'; -import { FileManager, HttpUtil } from '@/utils'; +import { HttpUtil } from '@/utils'; import PromptModal from '@/components/feedback/PromptModal'; +import TextModal from '@/components/feedback/TextModal'; import OutboundFormModal from './OutboundFormModal'; import { propagateOutboundTagRename } from '../basics/helpers'; @@ -226,11 +227,12 @@ export default function OutboundsTab({ } const [importOpen, setImportOpen] = useState(false); + const [exportOpen, setExportOpen] = useState(false); + const [exportContent, setExportContent] = useState(''); function exportOutbounds() { - FileManager.downloadTextFile(JSON.stringify(outbounds, null, 2), 'outbounds.json', { - type: 'application/json', - }); + setExportContent(JSON.stringify(outbounds, null, 2)); + setExportOpen(true); } function importOutbounds(value: string) { @@ -531,6 +533,14 @@ export default function OutboundsTab({ json onConfirm={importOutbounds} /> + setExportOpen(false)} + title={t('pages.xray.exportOutbounds')} + content={exportContent} + fileName="outbounds.json" + json + /> {/* Subscription outbounds (read-only, merged at runtime) */} {Array.isArray(subscriptionOutbounds) && subscriptionOutbounds.length > 0 && ( diff --git a/frontend/src/pages/xray/routing/RoutingTab.tsx b/frontend/src/pages/xray/routing/RoutingTab.tsx index 0eb5df0b1..59e48ccb0 100644 --- a/frontend/src/pages/xray/routing/RoutingTab.tsx +++ b/frontend/src/pages/xray/routing/RoutingTab.tsx @@ -1,18 +1,19 @@ import { useCallback, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Button, Modal, Space, Table, Tabs, message } from 'antd'; +import { Button, Dropdown, Modal, Space, Table, Tabs, message } from 'antd'; import { AimOutlined, ControlOutlined, ExportOutlined, ImportOutlined, + MoreOutlined, PlusOutlined, UnorderedListOutlined, } from '@ant-design/icons'; import { catTabLabel } from '@/pages/settings/catTabLabel'; -import { FileManager } from '@/utils'; import PromptModal from '@/components/feedback/PromptModal'; +import TextModal from '@/components/feedback/TextModal'; import RoutingBasic from './RoutingBasic'; import RouteTester from './RouteTester'; import RuleFormModal from './RuleFormModal'; @@ -144,11 +145,12 @@ export default function RoutingTab({ }, [templateSettings?.routing?.balancers]); const [importOpen, setImportOpen] = useState(false); + const [exportOpen, setExportOpen] = useState(false); + const [exportContent, setExportContent] = useState(''); function exportRules() { - FileManager.downloadTextFile(JSON.stringify(rules, null, 2), 'routing-rules.json', { - type: 'application/json', - }); + setExportContent(JSON.stringify(rules, null, 2)); + setExportOpen(true); } function importRules(value: string) { @@ -333,16 +335,17 @@ export default function RoutingTab({ - - + + {isMobile ? ( @@ -405,6 +408,14 @@ export default function RoutingTab({ json onConfirm={importRules} /> + setExportOpen(false)} + title={t('pages.xray.exportRules')} + content={exportContent} + fileName="routing-rules.json" + json + /> ); } diff --git a/frontend/src/pages/xray/routing/RuleFormModal.tsx b/frontend/src/pages/xray/routing/RuleFormModal.tsx index a92ce665d..1ce9fc62b 100644 --- a/frontend/src/pages/xray/routing/RuleFormModal.tsx +++ b/frontend/src/pages/xray/routing/RuleFormModal.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Button, Form, Input, Modal, Select, Space, Tooltip } from 'antd'; +import { Button, Form, Input, Modal, Select, Space, Switch, Tooltip } from 'antd'; import { PlusOutlined, MinusOutlined, QuestionCircleOutlined } from '@ant-design/icons'; import { InputAddon } from '@/components/ui'; import { useInboundOptions } from '@/api/queries/useInboundOptions'; @@ -156,14 +156,10 @@ export default function RuleFormModal({ >
-