Browse Source

feat(frontend): protocol tab Wireguard section (Pattern A)

Adds the Wireguard sub-form: server secretKey input with regen icon,
derived disabled public-key display, mtu, noKernelTun toggle, and a
Form.List of peers — each peer having its own privateKey (regen icon),
publicKey, preSharedKey, allowedIPs (nested Form.List for the string
array), keepAlive.

pubKey is purely derived (computed via Wireguard.generateKeypair from
the watched secretKey) and is NOT stored in the form value — the schema
omits it from the wire shape on purpose. The disabled display shows the
live derivation without polluting form state.

regenInboundWg generates a fresh keypair and writes only the
secretKey path; pubKey re-derives automatically. regenWgPeerKeypair
writes both privateKey and publicKey at the peer's path index.

The preSharedKey wire-shape name is used instead of the legacy class's
internal psk — matches WireguardInboundPeerSchema.

Tab visibility widens to Wireguard.
MHSanaei 20 giờ trước cách đây
mục cha
commit
b1ccf915db
1 tập tin đã thay đổi với 122 bổ sung1 xóa
  1. 122 1
      frontend/src/pages/inbounds/InboundFormModal.new.tsx

+ 122 - 1
frontend/src/pages/inbounds/InboundFormModal.new.tsx

@@ -18,7 +18,7 @@ import {
 } from 'antd';
 import { MinusOutlined, PlusOutlined, SyncOutlined } from '@ant-design/icons';
 
-import { HttpUtil, NumberFormatter, RandomUtil, SizeFormatter } from '@/utils';
+import { HttpUtil, NumberFormatter, RandomUtil, SizeFormatter, Wireguard } from '@/utils';
 import {
   rawInboundToFormValues,
   formValuesToWirePayload,
@@ -105,6 +105,21 @@ export default function InboundFormModalNew({
     settings: typeof ssMethod === 'string' ? { method: ssMethod } : {},
   });
   const mixedUdpOn = Form.useWatch(['settings', 'udp'], form) ?? false;
+  const wgSecretKey = Form.useWatch(['settings', 'secretKey'], form);
+  const wgPubKey = typeof wgSecretKey === 'string' && wgSecretKey.length > 0
+    ? Wireguard.generateKeypair(wgSecretKey).publicKey
+    : '';
+
+  const regenInboundWg = () => {
+    const kp = Wireguard.generateKeypair();
+    form.setFieldValue(['settings', 'secretKey'], kp.privateKey);
+  };
+
+  const regenWgPeerKeypair = (peerName: number) => {
+    const kp = Wireguard.generateKeypair();
+    form.setFieldValue(['settings', 'peers', peerName, 'privateKey'], kp.privateKey);
+    form.setFieldValue(['settings', 'peers', peerName, 'publicKey'], kp.publicKey);
+  };
 
   const matchesVlessAuth = (
     block: { id?: string; label?: string } | undefined | null,
@@ -336,6 +351,111 @@ export default function InboundFormModalNew({
 
   const protocolTab = (
     <>
+      {protocol === Protocols.WIREGUARD && (
+        <>
+          <Form.Item
+            name={['settings', 'secretKey']}
+            label={
+              <>
+                Secret key{' '}
+                <SyncOutlined className="random-icon" onClick={regenInboundWg} />
+              </>
+            }
+          >
+            <Input />
+          </Form.Item>
+          <Form.Item label="Public key">
+            <Input value={wgPubKey} disabled />
+          </Form.Item>
+          <Form.Item name={['settings', 'mtu']} label="MTU">
+            <InputNumber />
+          </Form.Item>
+          <Form.Item
+            name={['settings', 'noKernelTun']}
+            label="No-kernel TUN"
+            valuePropName="checked"
+          >
+            <Switch />
+          </Form.Item>
+          <Form.List name={['settings', 'peers']}>
+            {(fields, { add, remove }) => (
+              <>
+                <Form.Item label="Peers">
+                  <Button
+                    size="small"
+                    onClick={() => add({
+                      publicKey: '',
+                      allowedIPs: [],
+                    })}
+                  >
+                    <PlusOutlined /> Add peer
+                  </Button>
+                </Form.Item>
+                {fields.map((field, idx) => (
+                  <div key={field.key} className="wg-peer">
+                    <Form.Item label={`Peer ${idx + 1}`}>
+                      {fields.length > 1 && (
+                        <Button
+                          size="small"
+                          danger
+                          onClick={() => remove(field.name)}
+                        >
+                          <MinusOutlined />
+                        </Button>
+                      )}
+                    </Form.Item>
+                    <Form.Item
+                      name={[field.name, 'privateKey']}
+                      label={
+                        <>
+                          Secret key{' '}
+                          <SyncOutlined
+                            className="random-icon"
+                            onClick={() => regenWgPeerKeypair(field.name)}
+                          />
+                        </>
+                      }
+                    >
+                      <Input />
+                    </Form.Item>
+                    <Form.Item name={[field.name, 'publicKey']} label="Public key">
+                      <Input />
+                    </Form.Item>
+                    <Form.Item name={[field.name, 'preSharedKey']} label="PSK">
+                      <Input />
+                    </Form.Item>
+                    <Form.List name={[field.name, 'allowedIPs']}>
+                      {(ipFields, { add: addIp, remove: removeIp }) => (
+                        <Form.Item label="Allowed IPs">
+                          <Button size="small" onClick={() => addIp('')}>
+                            <PlusOutlined />
+                          </Button>
+                          {ipFields.map((ipField) => (
+                            <Space.Compact key={ipField.key} block className="mt-4">
+                              <Form.Item name={ipField.name} noStyle>
+                                <Input />
+                              </Form.Item>
+                              {ipFields.length > 1 && (
+                                <Button size="small" onClick={() => removeIp(ipField.name)}>
+                                  <MinusOutlined />
+                                </Button>
+                              )}
+                            </Space.Compact>
+                          ))}
+                        </Form.Item>
+                      )}
+                    </Form.List>
+                    <Form.Item name={[field.name, 'keepAlive']} label="Keep-alive">
+                      <InputNumber min={0} />
+                    </Form.Item>
+                  </div>
+                ))}
+              </>
+            )}
+          </Form.List>
+        </>
+      )}
+
       {protocol === Protocols.TUN && (
         <>
           <Form.Item name={['settings', 'name']} label="Interface name">
@@ -715,6 +835,7 @@ export default function InboundFormModalNew({
               Protocols.MIXED,
               Protocols.TUNNEL,
               Protocols.TUN,
+              Protocols.WIREGUARD,
             ] as string[]).includes(protocol)
               ? [{ key: 'protocol', label: t('pages.inbounds.protocol'), children: protocolTab }]
               : []),