wireguard.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import { useTranslation } from 'react-i18next';
  2. import { Button, Divider, Form, Input, InputNumber, Space, Switch } from 'antd';
  3. import { MinusOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons';
  4. import { Wireguard } from '@/utils';
  5. interface WireguardFieldsProps {
  6. wgPubKey: string;
  7. regenInboundWg: () => void;
  8. regenWgPeerKeypair: (name: number) => void;
  9. }
  10. function nextWgPeerAllowedIP(peers: Array<{ allowedIPs?: string[] }> | undefined): string {
  11. const fallback = '10.0.0.2/32';
  12. let maxInt = -1;
  13. let prefix = 32;
  14. for (const peer of peers ?? []) {
  15. for (const ip of peer?.allowedIPs ?? []) {
  16. const m = /^\s*(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(?:\/(\d{1,2}))?\s*$/.exec(String(ip));
  17. if (!m) continue;
  18. const octets = [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
  19. if (octets.some((o) => o > 255)) continue;
  20. const asInt = octets[0] * 16777216 + octets[1] * 65536 + octets[2] * 256 + octets[3];
  21. if (asInt > maxInt) {
  22. maxInt = asInt;
  23. prefix = m[5] !== undefined ? Math.min(Number(m[5]), 32) : 32;
  24. }
  25. }
  26. }
  27. if (maxInt < 0) return fallback;
  28. const next = maxInt + 1;
  29. const a = Math.floor(next / 16777216) % 256;
  30. const b = Math.floor(next / 65536) % 256;
  31. const c = Math.floor(next / 256) % 256;
  32. const d = next % 256;
  33. return `${a}.${b}.${c}.${d}/${prefix}`;
  34. }
  35. export default function WireguardFields({ wgPubKey, regenInboundWg, regenWgPeerKeypair }: WireguardFieldsProps) {
  36. const { t } = useTranslation();
  37. const form = Form.useFormInstance();
  38. return (
  39. <>
  40. <Form.Item label={t('pages.xray.wireguard.secretKey')}>
  41. <Space.Compact block>
  42. <Form.Item name={['settings', 'secretKey']} noStyle>
  43. <Input style={{ width: 'calc(100% - 32px)' }} />
  44. </Form.Item>
  45. <Button icon={<ReloadOutlined />} onClick={regenInboundWg} />
  46. </Space.Compact>
  47. </Form.Item>
  48. <Form.Item label={t('pages.xray.wireguard.publicKey')}>
  49. <Input value={wgPubKey} disabled />
  50. </Form.Item>
  51. <Form.Item name={['settings', 'mtu']} label="MTU">
  52. <InputNumber />
  53. </Form.Item>
  54. <Form.Item
  55. name={['settings', 'noKernelTun']}
  56. label={t('pages.inbounds.info.noKernelTun')}
  57. valuePropName="checked"
  58. >
  59. <Switch />
  60. </Form.Item>
  61. <Form.List name={['settings', 'peers']}>
  62. {(fields, { add, remove }) => (
  63. <>
  64. <Form.Item label={t('pages.inbounds.form.peers')}>
  65. <Button
  66. size="small"
  67. onClick={() => {
  68. const kp = Wireguard.generateKeypair();
  69. const peers = form.getFieldValue(['settings', 'peers']) as Array<{ allowedIPs?: string[] }> | undefined;
  70. add({
  71. privateKey: kp.privateKey,
  72. publicKey: kp.publicKey,
  73. allowedIPs: [nextWgPeerAllowedIP(peers)],
  74. keepAlive: 0,
  75. });
  76. }}
  77. >
  78. <PlusOutlined /> {t('pages.inbounds.form.addPeer')}
  79. </Button>
  80. </Form.Item>
  81. {fields.map((field, idx) => (
  82. <div key={field.key} className="wg-peer">
  83. <Divider titlePlacement="center">
  84. <Space>
  85. <span>{t('pages.inbounds.info.peerNumber', { n: idx + 1 })}</span>
  86. {fields.length > 1 && (
  87. <Button
  88. size="small"
  89. danger
  90. icon={<MinusOutlined />}
  91. onClick={() => remove(field.name)}
  92. />
  93. )}
  94. </Space>
  95. </Divider>
  96. <Form.Item label={t('pages.xray.wireguard.secretKey')}>
  97. <Space.Compact block>
  98. <Form.Item name={[field.name, 'privateKey']} noStyle>
  99. <Input style={{ width: 'calc(100% - 32px)' }} />
  100. </Form.Item>
  101. <Button
  102. icon={<ReloadOutlined />}
  103. onClick={() => regenWgPeerKeypair(field.name)}
  104. />
  105. </Space.Compact>
  106. </Form.Item>
  107. <Form.Item name={[field.name, 'publicKey']} label={t('pages.xray.wireguard.publicKey')}>
  108. <Input />
  109. </Form.Item>
  110. <Form.Item name={[field.name, 'preSharedKey']} label="PSK">
  111. <Input />
  112. </Form.Item>
  113. <Form.List name={[field.name, 'allowedIPs']}>
  114. {(ipFields, { add: addIp, remove: removeIp }) => (
  115. <Form.Item label={t('pages.xray.wireguard.allowedIPs')}>
  116. <Button size="small" onClick={() => addIp('')}>
  117. <PlusOutlined />
  118. </Button>
  119. {ipFields.map((ipField) => (
  120. <Space.Compact key={ipField.key} block className="mt-4">
  121. <Form.Item name={ipField.name} noStyle>
  122. <Input />
  123. </Form.Item>
  124. {ipFields.length > 1 && (
  125. <Button size="small" onClick={() => removeIp(ipField.name)}>
  126. <MinusOutlined />
  127. </Button>
  128. )}
  129. </Space.Compact>
  130. ))}
  131. </Form.Item>
  132. )}
  133. </Form.List>
  134. <Form.Item name={[field.name, 'keepAlive']} label={t('pages.inbounds.form.keepAlive')}>
  135. <InputNumber min={0} />
  136. </Form.Item>
  137. </div>
  138. ))}
  139. </>
  140. )}
  141. </Form.List>
  142. </>
  143. );
  144. }