diff --git a/frontend/src/pages/xray/outbounds/OutboundFormModal.tsx b/frontend/src/pages/xray/outbounds/OutboundFormModal.tsx
index 3be86a46..c1e54a54 100644
--- a/frontend/src/pages/xray/outbounds/OutboundFormModal.tsx
+++ b/frontend/src/pages/xray/outbounds/OutboundFormModal.tsx
@@ -37,7 +37,6 @@ import {
} from '@/schemas/forms/outbound-form';
import {
DOMAIN_STRATEGY_OPTION,
- OutboundDomainStrategies,
SNIFFING_OPTION,
TCP_CONGESTION_OPTION,
WireguardDomainStrategy,
@@ -75,6 +74,7 @@ import {
newStreamSlice,
} from './outbound-form-helpers';
import { OutboundOnlyProtocolFields } from './outbound-only-fields';
+import { FreedomOutboundFields } from './outbound-freedom-fields';
import './OutboundFormModal.css';
// Pattern A rewrite of OutboundFormModal. Built as a sibling `.new.tsx`
@@ -506,261 +506,7 @@ export default function OutboundFormModal({
- {protocol === 'freedom' && (
- <>
-
-
-
-
-
-
-
-
-
-
- {() => {
- const fragment = (form.getFieldValue(['settings', 'fragment']) ?? {}) as {
- packets?: string;
- length?: string;
- interval?: string;
- maxSplit?: string;
- };
- const enabled = !!(fragment.length || fragment.interval || fragment.maxSplit);
- return (
- <>
-
- {
- form.setFieldValue(
- ['settings', 'fragment'],
- checked
- ? {
- packets: 'tlshello',
- length: '100-200',
- interval: '10-20',
- maxSplit: '300-400',
- }
- : { packets: '', length: '', interval: '', maxSplit: '' },
- );
- }}
- />
-
- {enabled && (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
- >
- )}
- >
- );
- }}
-
-
-
- {(fields, { add, remove }) => (
- <>
-
- 0}
- onChange={(checked) => {
- if (checked) {
- add({
- type: 'rand',
- packet: '10-20',
- delay: '10-16',
- applyTo: 'ip',
- });
- } else {
- // remove() with no arg is not supported;
- // walk fields in reverse and drop each.
- for (let i = fields.length - 1; i >= 0; i--) {
- remove(fields[i].name);
- }
- }
- }}
- />
- {fields.length > 0 && (
- }
- onClick={() =>
- add({
- type: 'rand',
- packet: '10-20',
- delay: '10-16',
- applyTo: 'ip',
- })
- }
- />
- )}
-
- {fields.map((field, index) => (
-
-
-
- {t('pages.settings.subFormats.noiseItem', { n: index + 1 })}
- {fields.length > 1 && (
- remove(field.name)}
- />
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
- ))}
- >
- )}
-
-
-
- {(fields, { add, remove }) => (
- <>
-
- }
- onClick={() =>
- add({
- action: 'allow',
- network: '',
- port: '',
- ip: [],
- blockDelay: '',
- })
- }
- />
-
- {t('pages.xray.outboundForm.overrideXrayPrivateIp')}
-
-
- {fields.map((field, index) => (
-
-
-
- {t('pages.xray.outboundForm.ruleN', { n: index + 1 })}
- remove(field.name)}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
- {() => {
- const ruleAction = form.getFieldValue([
- 'settings',
- 'finalRules',
- field.name,
- 'action',
- ]);
- if (ruleAction !== 'block') return null;
- return (
-
-
-
- );
- }}
-
-
- ))}
- >
- )}
-
- >
- )}
+ {protocol === 'freedom' && }
{protocol === 'vless' && (
diff --git a/frontend/src/pages/xray/outbounds/outbound-freedom-fields.tsx b/frontend/src/pages/xray/outbounds/outbound-freedom-fields.tsx
new file mode 100644
index 00000000..4eb8604d
--- /dev/null
+++ b/frontend/src/pages/xray/outbounds/outbound-freedom-fields.tsx
@@ -0,0 +1,265 @@
+import { useTranslation } from 'react-i18next';
+import { Button, Form, Input, Select, Switch, type FormInstance } from 'antd';
+import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
+
+import { OutboundDomainStrategies } from '@/schemas/primitives';
+import type { OutboundFormValues } from '@/schemas/forms/outbound-form';
+
+export function FreedomOutboundFields({ form }: { form: FormInstance }) {
+ const { t } = useTranslation();
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ {() => {
+ const fragment = (form.getFieldValue(['settings', 'fragment']) ?? {}) as {
+ packets?: string;
+ length?: string;
+ interval?: string;
+ maxSplit?: string;
+ };
+ const enabled = !!(fragment.length || fragment.interval || fragment.maxSplit);
+ return (
+ <>
+
+ {
+ form.setFieldValue(
+ ['settings', 'fragment'],
+ checked
+ ? {
+ packets: 'tlshello',
+ length: '100-200',
+ interval: '10-20',
+ maxSplit: '300-400',
+ }
+ : { packets: '', length: '', interval: '', maxSplit: '' },
+ );
+ }}
+ />
+
+ {enabled && (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+ >
+ );
+ }}
+
+
+
+ {(fields, { add, remove }) => (
+ <>
+
+ 0}
+ onChange={(checked) => {
+ if (checked) {
+ add({
+ type: 'rand',
+ packet: '10-20',
+ delay: '10-16',
+ applyTo: 'ip',
+ });
+ } else {
+ // remove() with no arg is not supported;
+ // walk fields in reverse and drop each.
+ for (let i = fields.length - 1; i >= 0; i--) {
+ remove(fields[i].name);
+ }
+ }
+ }}
+ />
+ {fields.length > 0 && (
+ }
+ onClick={() =>
+ add({
+ type: 'rand',
+ packet: '10-20',
+ delay: '10-16',
+ applyTo: 'ip',
+ })
+ }
+ />
+ )}
+
+ {fields.map((field, index) => (
+
+
+
+ {t('pages.settings.subFormats.noiseItem', { n: index + 1 })}
+ {fields.length > 1 && (
+ remove(field.name)}
+ />
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+ >
+ )}
+
+
+
+ {(fields, { add, remove }) => (
+ <>
+
+ }
+ onClick={() =>
+ add({
+ action: 'allow',
+ network: '',
+ port: '',
+ ip: [],
+ blockDelay: '',
+ })
+ }
+ />
+
+ {t('pages.xray.outboundForm.overrideXrayPrivateIp')}
+
+
+ {fields.map((field, index) => (
+
+
+
+ {t('pages.xray.outboundForm.ruleN', { n: index + 1 })}
+ remove(field.name)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {() => {
+ const ruleAction = form.getFieldValue([
+ 'settings',
+ 'finalRules',
+ field.name,
+ 'action',
+ ]);
+ if (ruleAction !== 'block') return null;
+ return (
+
+
+
+ );
+ }}
+
+
+ ))}
+ >
+ )}
+
+ >
+ );
+}