freedom.tsx 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. import { useTranslation } from 'react-i18next';
  2. import { Button, Form, Input, InputNumber, Select, Switch, type FormInstance } from 'antd';
  3. import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
  4. import { OutboundDomainStrategies } from '@/schemas/primitives';
  5. import type { OutboundFormValues } from '@/schemas/forms/outbound-form';
  6. export default function FreedomFields({ form }: { form: FormInstance<OutboundFormValues> }) {
  7. const { t } = useTranslation();
  8. return (
  9. <>
  10. <Form.Item label={t('pages.xray.balancer.balancerStrategy')} name={['settings', 'domainStrategy']}>
  11. <Select
  12. options={[
  13. { value: '', label: `(${t('none')})` },
  14. ...OutboundDomainStrategies.map((s) => ({ value: s, label: s })),
  15. ]}
  16. />
  17. </Form.Item>
  18. <Form.Item label={t('pages.xray.outboundForm.redirect')} name={['settings', 'redirect']}>
  19. <Input />
  20. </Form.Item>
  21. <Form.Item label={t('pages.xray.tun.userLevel')} name={['settings', 'userLevel']}>
  22. <InputNumber min={0} style={{ width: '100%' }} />
  23. </Form.Item>
  24. <Form.Item label={t('pages.xray.outboundForm.proxyProtocol')} name={['settings', 'proxyProtocol']}>
  25. <Select
  26. options={[
  27. { value: 0, label: `(${t('none')})` },
  28. { value: 1, label: 'v1' },
  29. { value: 2, label: 'v2' },
  30. ]}
  31. />
  32. </Form.Item>
  33. <Form.Item label={t('pages.xray.outboundForm.fragment')} shouldUpdate noStyle>
  34. {() => {
  35. const fragment = (form.getFieldValue(['settings', 'fragment']) ?? {}) as {
  36. packets?: string;
  37. length?: string;
  38. interval?: string;
  39. maxSplit?: string;
  40. };
  41. const enabled = !!(fragment.length || fragment.interval || fragment.maxSplit);
  42. return (
  43. <>
  44. <Form.Item label="Fragment">
  45. <Switch
  46. checked={enabled}
  47. onChange={(checked) => {
  48. form.setFieldValue(
  49. ['settings', 'fragment'],
  50. checked
  51. ? {
  52. packets: 'tlshello',
  53. length: '100-200',
  54. interval: '10-20',
  55. maxSplit: '300-400',
  56. }
  57. : { packets: '', length: '', interval: '', maxSplit: '' },
  58. );
  59. }}
  60. />
  61. </Form.Item>
  62. {enabled && (
  63. <>
  64. <Form.Item
  65. label={t('pages.settings.subFormats.packets')}
  66. name={['settings', 'fragment', 'packets']}
  67. >
  68. <Select
  69. options={[
  70. { value: '1-3', label: '1-3' },
  71. { value: 'tlshello', label: 'tlshello' },
  72. ]}
  73. />
  74. </Form.Item>
  75. <Form.Item label={t('pages.settings.subFormats.length')} name={['settings', 'fragment', 'length']}>
  76. <Input />
  77. </Form.Item>
  78. <Form.Item
  79. label={t('pages.settings.subFormats.interval')}
  80. name={['settings', 'fragment', 'interval']}
  81. >
  82. <Input />
  83. </Form.Item>
  84. <Form.Item
  85. label={t('pages.settings.subFormats.maxSplit')}
  86. name={['settings', 'fragment', 'maxSplit']}
  87. >
  88. <Input />
  89. </Form.Item>
  90. </>
  91. )}
  92. </>
  93. );
  94. }}
  95. </Form.Item>
  96. <Form.List name={['settings', 'noises']}>
  97. {(fields, { add, remove }) => (
  98. <>
  99. <Form.Item label={t('pages.settings.subFormats.noises')}>
  100. <Switch
  101. checked={fields.length > 0}
  102. onChange={(checked) => {
  103. if (checked) {
  104. add({
  105. type: 'rand',
  106. packet: '10-20',
  107. delay: '10-16',
  108. applyTo: 'ip',
  109. });
  110. } else {
  111. // remove() with no arg is not supported;
  112. // walk fields in reverse and drop each.
  113. for (let i = fields.length - 1; i >= 0; i--) {
  114. remove(fields[i].name);
  115. }
  116. }
  117. }}
  118. />
  119. {fields.length > 0 && (
  120. <Button
  121. size="small"
  122. type="primary"
  123. className="ml-8"
  124. icon={<PlusOutlined />}
  125. onClick={() =>
  126. add({
  127. type: 'rand',
  128. packet: '10-20',
  129. delay: '10-16',
  130. applyTo: 'ip',
  131. })
  132. }
  133. />
  134. )}
  135. </Form.Item>
  136. {fields.map((field, index) => (
  137. <div key={field.key}>
  138. <Form.Item wrapperCol={{ md: { span: 14, offset: 8 } }}>
  139. <div className="item-heading">
  140. <span>{t('pages.settings.subFormats.noiseItem', { n: index + 1 })}</span>
  141. {fields.length > 1 && (
  142. <DeleteOutlined
  143. className="danger-icon"
  144. onClick={() => remove(field.name)}
  145. />
  146. )}
  147. </div>
  148. </Form.Item>
  149. <Form.Item label={t('pages.settings.subFormats.type')} name={[field.name, 'type']}>
  150. <Select
  151. options={['rand', 'base64', 'str', 'hex'].map((v) => ({
  152. value: v,
  153. label: v,
  154. }))}
  155. />
  156. </Form.Item>
  157. <Form.Item label={t('pages.settings.subFormats.packet')} name={[field.name, 'packet']}>
  158. <Input />
  159. </Form.Item>
  160. <Form.Item label={t('pages.settings.subFormats.delayMs')} name={[field.name, 'delay']}>
  161. <Input />
  162. </Form.Item>
  163. <Form.Item label={t('pages.settings.subFormats.applyTo')} name={[field.name, 'applyTo']}>
  164. <Select
  165. options={['ip', 'ipv4', 'ipv6'].map((v) => ({
  166. value: v,
  167. label: v,
  168. }))}
  169. />
  170. </Form.Item>
  171. </div>
  172. ))}
  173. </>
  174. )}
  175. </Form.List>
  176. <Form.List name={['settings', 'finalRules']}>
  177. {(fields, { add, remove }) => (
  178. <>
  179. <Form.Item label={t('pages.xray.outboundForm.finalRules')}>
  180. <Button
  181. size="small"
  182. type="primary"
  183. icon={<PlusOutlined />}
  184. onClick={() =>
  185. add({
  186. action: 'allow',
  187. network: '',
  188. port: '',
  189. ip: [],
  190. blockDelay: '',
  191. })
  192. }
  193. />
  194. <span className="ml-8" style={{ opacity: 0.6 }}>
  195. {t('pages.xray.outboundForm.overrideXrayPrivateIp')}
  196. </span>
  197. </Form.Item>
  198. {fields.map((field, index) => (
  199. <div key={field.key}>
  200. <Form.Item wrapperCol={{ md: { span: 14, offset: 8 } }}>
  201. <div className="item-heading">
  202. <span>{t('pages.xray.outboundForm.ruleN', { n: index + 1 })}</span>
  203. <DeleteOutlined
  204. className="danger-icon"
  205. onClick={() => remove(field.name)}
  206. />
  207. </div>
  208. </Form.Item>
  209. <Form.Item label={t('pages.xray.outboundForm.action')} name={[field.name, 'action']}>
  210. <Select
  211. options={['allow', 'block'].map((v) => ({
  212. value: v,
  213. label: v,
  214. }))}
  215. />
  216. </Form.Item>
  217. <Form.Item label={t('pages.inbounds.network')} name={[field.name, 'network']}>
  218. <Select
  219. allowClear
  220. placeholder="(any)"
  221. options={['tcp', 'udp', 'tcp,udp'].map((v) => ({
  222. value: v,
  223. label: v,
  224. }))}
  225. />
  226. </Form.Item>
  227. <Form.Item label={t('pages.inbounds.port')} name={[field.name, 'port']}>
  228. <Input placeholder="e.g. 80,443 or 1000-2000" />
  229. </Form.Item>
  230. <Form.Item label="IP / CIDR / geoip" name={[field.name, 'ip']}>
  231. <Select
  232. mode="tags"
  233. tokenSeparators={[',', ' ']}
  234. placeholder="e.g. 10.0.0.0/8, geoip:private"
  235. />
  236. </Form.Item>
  237. <Form.Item shouldUpdate noStyle>
  238. {() => {
  239. const ruleAction = form.getFieldValue([
  240. 'settings',
  241. 'finalRules',
  242. field.name,
  243. 'action',
  244. ]);
  245. if (ruleAction !== 'block') return null;
  246. return (
  247. <Form.Item
  248. label={t('pages.xray.outboundForm.blockDelay')}
  249. name={[field.name, 'blockDelay']}
  250. >
  251. <Input placeholder="optional: 5000-10000" />
  252. </Form.Item>
  253. );
  254. }}
  255. </Form.Item>
  256. </div>
  257. ))}
  258. </>
  259. )}
  260. </Form.List>
  261. </>
  262. );
  263. }