mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-31 07:27:13 +00:00
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.
This commit is contained in:
@@ -37,8 +37,9 @@ import {
|
|||||||
ImportOutlined,
|
ImportOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
import { FileManager, HttpUtil } from '@/utils';
|
import { HttpUtil } from '@/utils';
|
||||||
import PromptModal from '@/components/feedback/PromptModal';
|
import PromptModal from '@/components/feedback/PromptModal';
|
||||||
|
import TextModal from '@/components/feedback/TextModal';
|
||||||
|
|
||||||
import OutboundFormModal from './OutboundFormModal';
|
import OutboundFormModal from './OutboundFormModal';
|
||||||
import { propagateOutboundTagRename } from '../basics/helpers';
|
import { propagateOutboundTagRename } from '../basics/helpers';
|
||||||
@@ -226,11 +227,12 @@ export default function OutboundsTab({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [importOpen, setImportOpen] = useState(false);
|
const [importOpen, setImportOpen] = useState(false);
|
||||||
|
const [exportOpen, setExportOpen] = useState(false);
|
||||||
|
const [exportContent, setExportContent] = useState('');
|
||||||
|
|
||||||
function exportOutbounds() {
|
function exportOutbounds() {
|
||||||
FileManager.downloadTextFile(JSON.stringify(outbounds, null, 2), 'outbounds.json', {
|
setExportContent(JSON.stringify(outbounds, null, 2));
|
||||||
type: 'application/json',
|
setExportOpen(true);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function importOutbounds(value: string) {
|
function importOutbounds(value: string) {
|
||||||
@@ -531,6 +533,14 @@ export default function OutboundsTab({
|
|||||||
json
|
json
|
||||||
onConfirm={importOutbounds}
|
onConfirm={importOutbounds}
|
||||||
/>
|
/>
|
||||||
|
<TextModal
|
||||||
|
open={exportOpen}
|
||||||
|
onClose={() => setExportOpen(false)}
|
||||||
|
title={t('pages.xray.exportOutbounds')}
|
||||||
|
content={exportContent}
|
||||||
|
fileName="outbounds.json"
|
||||||
|
json
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Subscription outbounds (read-only, merged at runtime) */}
|
{/* Subscription outbounds (read-only, merged at runtime) */}
|
||||||
{Array.isArray(subscriptionOutbounds) && subscriptionOutbounds.length > 0 && (
|
{Array.isArray(subscriptionOutbounds) && subscriptionOutbounds.length > 0 && (
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
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, Tabs, message } from 'antd';
|
import { Button, Dropdown, Modal, Space, Table, Tabs, message } from 'antd';
|
||||||
import {
|
import {
|
||||||
AimOutlined,
|
AimOutlined,
|
||||||
ControlOutlined,
|
ControlOutlined,
|
||||||
ExportOutlined,
|
ExportOutlined,
|
||||||
ImportOutlined,
|
ImportOutlined,
|
||||||
|
MoreOutlined,
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
UnorderedListOutlined,
|
UnorderedListOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
import { catTabLabel } from '@/pages/settings/catTabLabel';
|
import { catTabLabel } from '@/pages/settings/catTabLabel';
|
||||||
import { FileManager } from '@/utils';
|
|
||||||
import PromptModal from '@/components/feedback/PromptModal';
|
import PromptModal from '@/components/feedback/PromptModal';
|
||||||
|
import TextModal from '@/components/feedback/TextModal';
|
||||||
import RoutingBasic from './RoutingBasic';
|
import RoutingBasic from './RoutingBasic';
|
||||||
import RouteTester from './RouteTester';
|
import RouteTester from './RouteTester';
|
||||||
import RuleFormModal from './RuleFormModal';
|
import RuleFormModal from './RuleFormModal';
|
||||||
@@ -144,11 +145,12 @@ export default function RoutingTab({
|
|||||||
}, [templateSettings?.routing?.balancers]);
|
}, [templateSettings?.routing?.balancers]);
|
||||||
|
|
||||||
const [importOpen, setImportOpen] = useState(false);
|
const [importOpen, setImportOpen] = useState(false);
|
||||||
|
const [exportOpen, setExportOpen] = useState(false);
|
||||||
|
const [exportContent, setExportContent] = useState('');
|
||||||
|
|
||||||
function exportRules() {
|
function exportRules() {
|
||||||
FileManager.downloadTextFile(JSON.stringify(rules, null, 2), 'routing-rules.json', {
|
setExportContent(JSON.stringify(rules, null, 2));
|
||||||
type: 'application/json',
|
setExportOpen(true);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function importRules(value: string) {
|
function importRules(value: string) {
|
||||||
@@ -333,16 +335,17 @@ export default function RoutingTab({
|
|||||||
<Button type="primary" icon={<PlusOutlined />} onClick={openAdd}>
|
<Button type="primary" icon={<PlusOutlined />} onClick={openAdd}>
|
||||||
{t('pages.xray.Routings')}
|
{t('pages.xray.Routings')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button icon={<ImportOutlined />} onClick={() => setImportOpen(true)}>
|
<Dropdown
|
||||||
{t('pages.xray.importRules')}
|
trigger={['click']}
|
||||||
</Button>
|
menu={{
|
||||||
<Button
|
items: [
|
||||||
icon={<ExportOutlined />}
|
{ key: 'import', icon: <ImportOutlined />, label: t('pages.xray.importRules'), onClick: () => setImportOpen(true) },
|
||||||
onClick={exportRules}
|
{ key: 'export', icon: <ExportOutlined />, label: t('pages.xray.exportRules'), disabled: rules.length === 0, onClick: exportRules },
|
||||||
disabled={rules.length === 0}
|
],
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t('pages.xray.exportRules')}
|
<Button icon={<MoreOutlined />}>{t('more')}</Button>
|
||||||
</Button>
|
</Dropdown>
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
@@ -405,6 +408,14 @@ export default function RoutingTab({
|
|||||||
json
|
json
|
||||||
onConfirm={importRules}
|
onConfirm={importRules}
|
||||||
/>
|
/>
|
||||||
|
<TextModal
|
||||||
|
open={exportOpen}
|
||||||
|
onClose={() => setExportOpen(false)}
|
||||||
|
title={t('pages.xray.exportRules')}
|
||||||
|
content={exportContent}
|
||||||
|
fileName="routing-rules.json"
|
||||||
|
json
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
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 { PlusOutlined, MinusOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
||||||
import { InputAddon } from '@/components/ui';
|
import { InputAddon } from '@/components/ui';
|
||||||
import { useInboundOptions } from '@/api/queries/useInboundOptions';
|
import { useInboundOptions } from '@/api/queries/useInboundOptions';
|
||||||
@@ -156,14 +156,10 @@ export default function RuleFormModal({
|
|||||||
>
|
>
|
||||||
<Form colon={false} labelCol={{ md: { span: 8 } }} wrapperCol={{ md: { span: 14 } }}>
|
<Form colon={false} labelCol={{ md: { span: 8 } }} wrapperCol={{ md: { span: 14 } }}>
|
||||||
<Form.Item label={t('enable')}>
|
<Form.Item label={t('enable')}>
|
||||||
<Select
|
<Switch
|
||||||
value={form.enabled}
|
checked={form.enabled}
|
||||||
onChange={(v) => update('enabled', v)}
|
onChange={(checked) => update('enabled', checked)}
|
||||||
disabled={isApiRule(rule ?? {})}
|
disabled={isApiRule(rule ?? {})}
|
||||||
options={[
|
|
||||||
{ value: true, label: t('enable') },
|
|
||||||
{ value: false, label: t('disable') },
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user