|
@@ -1,8 +1,22 @@
|
|
|
import { useEffect, useMemo, useState } from 'react';
|
|
import { useEffect, useMemo, useState } from 'react';
|
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
-import { Form, Input, InputNumber, Modal, Select, Space, Switch, Tabs, message } from 'antd';
|
|
|
|
|
|
|
+import {
|
|
|
|
|
+ Button,
|
|
|
|
|
+ Form,
|
|
|
|
|
+ Input,
|
|
|
|
|
+ InputNumber,
|
|
|
|
|
+ Modal,
|
|
|
|
|
+ Select,
|
|
|
|
|
+ Space,
|
|
|
|
|
+ Switch,
|
|
|
|
|
+ Tabs,
|
|
|
|
|
+ message,
|
|
|
|
|
+} from 'antd';
|
|
|
|
|
+import { DeleteOutlined, MinusOutlined, PlusOutlined, SyncOutlined } from '@ant-design/icons';
|
|
|
|
|
|
|
|
|
|
+import InputAddon from '@/components/InputAddon';
|
|
|
import JsonEditor from '@/components/JsonEditor';
|
|
import JsonEditor from '@/components/JsonEditor';
|
|
|
|
|
+import { Wireguard } from '@/utils';
|
|
|
import {
|
|
import {
|
|
|
formValuesToWirePayload,
|
|
formValuesToWirePayload,
|
|
|
rawOutboundToFormValues,
|
|
rawOutboundToFormValues,
|
|
@@ -19,6 +33,7 @@ import {
|
|
|
OutboundProtocols as Protocols,
|
|
OutboundProtocols as Protocols,
|
|
|
TLS_FLOW_CONTROL,
|
|
TLS_FLOW_CONTROL,
|
|
|
USERS_SECURITY,
|
|
USERS_SECURITY,
|
|
|
|
|
+ WireguardDomainStrategy,
|
|
|
} from '@/schemas/primitives';
|
|
} from '@/schemas/primitives';
|
|
|
import { SSMethodSchema } from '@/schemas/protocols/inbound/shadowsocks';
|
|
import { SSMethodSchema } from '@/schemas/protocols/inbound/shadowsocks';
|
|
|
import { antdRule } from '@/utils/zodForm';
|
|
import { antdRule } from '@/utils/zodForm';
|
|
@@ -315,6 +330,175 @@ export default function OutboundFormModalNew({
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
|
</>
|
|
</>
|
|
|
)}
|
|
)}
|
|
|
|
|
+
|
|
|
|
|
+ {(protocol === 'socks' || protocol === 'http') && (
|
|
|
|
|
+ <>
|
|
|
|
|
+ <Form.Item label={t('username')} name={['settings', 'user']}>
|
|
|
|
|
+ <Input />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ <Form.Item label={t('password')} name={['settings', 'pass']}>
|
|
|
|
|
+ <Input />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ {protocol === 'hysteria' && (
|
|
|
|
|
+ <Form.Item label="Version" name={['settings', 'version']}>
|
|
|
|
|
+ <InputNumber min={2} max={2} disabled />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ {protocol === 'loopback' && (
|
|
|
|
|
+ <Form.Item label="Inbound tag" name={['settings', 'inboundTag']}>
|
|
|
|
|
+ <Input placeholder="inbound tag used in routing rules" />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ {protocol === 'blackhole' && (
|
|
|
|
|
+ <Form.Item label="Response type" name={['settings', 'type']}>
|
|
|
|
|
+ <Select
|
|
|
|
|
+ options={[
|
|
|
|
|
+ { value: '', label: '(empty)' },
|
|
|
|
|
+ { value: 'none', label: 'none' },
|
|
|
|
|
+ { value: 'http', label: 'http' },
|
|
|
|
|
+ ]}
|
|
|
|
|
+ />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ {protocol === 'wireguard' && (
|
|
|
|
|
+ <>
|
|
|
|
|
+ <Form.Item label={t('pages.inbounds.address')} name={['settings', 'address']}>
|
|
|
|
|
+ <Input placeholder="comma-separated, e.g. 10.0.0.1,fd00::1" />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ <Form.Item
|
|
|
|
|
+ label={
|
|
|
|
|
+ <>
|
|
|
|
|
+ {t('pages.inbounds.privatekey')}
|
|
|
|
|
+ <SyncOutlined
|
|
|
|
|
+ className="random-icon"
|
|
|
|
|
+ onClick={() => {
|
|
|
|
|
+ const pair = Wireguard.generateKeypair();
|
|
|
|
|
+ form.setFieldValue(['settings', 'secretKey'], pair.privateKey);
|
|
|
|
|
+ form.setFieldValue(['settings', 'pubKey'], pair.publicKey);
|
|
|
|
|
+ }}
|
|
|
|
|
+ />
|
|
|
|
|
+ </>
|
|
|
|
|
+ }
|
|
|
|
|
+ name={['settings', 'secretKey']}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Input />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ <Form.Item label={t('pages.inbounds.publicKey')} name={['settings', 'pubKey']}>
|
|
|
|
|
+ <Input disabled />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ <Form.Item label="Domain strategy" name={['settings', 'domainStrategy']}>
|
|
|
|
|
+ <Select
|
|
|
|
|
+ options={[
|
|
|
|
|
+ { value: '', label: `(${t('none')})` },
|
|
|
|
|
+ ...WireguardDomainStrategy.map((s) => ({ value: s, label: s })),
|
|
|
|
|
+ ]}
|
|
|
|
|
+ />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ <Form.Item label="MTU" name={['settings', 'mtu']}>
|
|
|
|
|
+ <InputNumber min={0} />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ <Form.Item label="Workers" name={['settings', 'workers']}>
|
|
|
|
|
+ <InputNumber min={0} />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ <Form.Item
|
|
|
|
|
+ label="No-kernel TUN"
|
|
|
|
|
+ name={['settings', 'noKernelTun']}
|
|
|
|
|
+ valuePropName="checked"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Switch />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ <Form.Item label="Reserved" name={['settings', 'reserved']}>
|
|
|
|
|
+ <Input placeholder="comma-separated bytes, e.g. 1,2,3" />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ <Form.List name={['settings', 'peers']}>
|
|
|
|
|
+ {(fields, { add, remove }) => (
|
|
|
|
|
+ <>
|
|
|
|
|
+ <Form.Item label="Peers">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ icon={<PlusOutlined />}
|
|
|
|
|
+ onClick={() =>
|
|
|
|
|
+ add({
|
|
|
|
|
+ publicKey: '',
|
|
|
|
|
+ psk: '',
|
|
|
|
|
+ allowedIPs: ['0.0.0.0/0', '::/0'],
|
|
|
|
|
+ endpoint: '',
|
|
|
|
|
+ keepAlive: 0,
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ {fields.map((field, index) => (
|
|
|
|
|
+ <div key={field.key}>
|
|
|
|
|
+ <Form.Item wrapperCol={{ md: { span: 14, offset: 8 } }}>
|
|
|
|
|
+ <div className="item-heading">
|
|
|
|
|
+ <span>Peer {index + 1}</span>
|
|
|
|
|
+ {fields.length > 1 && (
|
|
|
|
|
+ <DeleteOutlined
|
|
|
|
|
+ className="danger-icon"
|
|
|
|
|
+ onClick={() => remove(field.name)}
|
|
|
|
|
+ />
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ <Form.Item label="Endpoint" name={[field.name, 'endpoint']}>
|
|
|
|
|
+ <Input />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ <Form.Item
|
|
|
|
|
+ label={t('pages.inbounds.publicKey')}
|
|
|
|
|
+ name={[field.name, 'publicKey']}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Input />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ <Form.Item label="PSK" name={[field.name, 'psk']}>
|
|
|
|
|
+ <Input />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ <Form.Item label="Allowed IPs">
|
|
|
|
|
+ <Form.List name={[field.name, 'allowedIPs']}>
|
|
|
|
|
+ {(ipFields, { add: addIp, remove: removeIp }) => (
|
|
|
|
|
+ <>
|
|
|
|
|
+ {ipFields.map((ipField, ipIdx) => (
|
|
|
|
|
+ <Space.Compact
|
|
|
|
|
+ key={ipField.key}
|
|
|
|
|
+ block
|
|
|
|
|
+ style={{ marginBottom: 4 }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Form.Item noStyle name={ipField.name}>
|
|
|
|
|
+ <Input />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ {ipFields.length > 1 && (
|
|
|
|
|
+ <InputAddon onClick={() => removeIp(ipIdx)}>
|
|
|
|
|
+ <MinusOutlined />
|
|
|
|
|
+ </InputAddon>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </Space.Compact>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ <Button
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ icon={<PlusOutlined />}
|
|
|
|
|
+ onClick={() => addIp('')}
|
|
|
|
|
+ />
|
|
|
|
|
+ </>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </Form.List>
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ <Form.Item label="Keep alive" name={[field.name, 'keepAlive']}>
|
|
|
|
|
+ <InputNumber min={0} />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </Form.List>
|
|
|
|
|
+ </>
|
|
|
|
|
+ )}
|
|
|
</>
|
|
</>
|
|
|
),
|
|
),
|
|
|
},
|
|
},
|