mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-28 00:24:19 +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,
|
||||
} 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}
|
||||
/>
|
||||
<TextModal
|
||||
open={exportOpen}
|
||||
onClose={() => 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 && (
|
||||
|
||||
@@ -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({
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={openAdd}>
|
||||
{t('pages.xray.Routings')}
|
||||
</Button>
|
||||
<Button icon={<ImportOutlined />} onClick={() => setImportOpen(true)}>
|
||||
{t('pages.xray.importRules')}
|
||||
</Button>
|
||||
<Button
|
||||
icon={<ExportOutlined />}
|
||||
onClick={exportRules}
|
||||
disabled={rules.length === 0}
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
menu={{
|
||||
items: [
|
||||
{ key: 'import', icon: <ImportOutlined />, label: t('pages.xray.importRules'), onClick: () => setImportOpen(true) },
|
||||
{ key: 'export', icon: <ExportOutlined />, label: t('pages.xray.exportRules'), disabled: rules.length === 0, onClick: exportRules },
|
||||
],
|
||||
}}
|
||||
>
|
||||
{t('pages.xray.exportRules')}
|
||||
</Button>
|
||||
<Button icon={<MoreOutlined />}>{t('more')}</Button>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
|
||||
{isMobile ? (
|
||||
@@ -405,6 +408,14 @@ export default function RoutingTab({
|
||||
json
|
||||
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 { 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({
|
||||
>
|
||||
<Form colon={false} labelCol={{ md: { span: 8 } }} wrapperCol={{ md: { span: 14 } }}>
|
||||
<Form.Item label={t('enable')}>
|
||||
<Select
|
||||
value={form.enabled}
|
||||
onChange={(v) => update('enabled', v)}
|
||||
<Switch
|
||||
checked={form.enabled}
|
||||
onChange={(checked) => update('enabled', checked)}
|
||||
disabled={isApiRule(rule ?? {})}
|
||||
options={[
|
||||
{ value: true, label: t('enable') },
|
||||
{ value: false, label: t('disable') },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user