From b70e17ed7312317d52bb2caf4c315cfd340ff055 Mon Sep 17 00:00:00 2001 From: YoungReckless4 <132759713+YoungReckless4@users.noreply.github.com> Date: Mon, 31 Aug 2026 14:39:58 +0300 Subject: [PATCH] feat(clients): let admins set PersistentKeepalive on tunnel clients model.Client already carries KeepAlive, and every AmneziaWG/WireGuard client config emitter already writes PersistentKeepalive when it is above zero -- but nothing in the UI could set it, so it stayed 0 and the line was never emitted. Without it a peer that goes quiet has nothing to trigger a handshake: WireGuard only initiates when it has data to send. An idle client stays disconnected after any interruption -- a NAT mapping timing out, a device sleeping, the panel restarting -- until the user generates traffic themselves. New clients default to 25, the conventional value, which also keeps the NAT mapping open. Existing clients keep whatever they have, and 0 remains valid and means "do not send keepalives". --- frontend/src/pages/clients/ClientFormModal.tsx | 11 +++++++++++ internal/web/translation/ar-EG.json | 2 ++ internal/web/translation/en-US.json | 2 ++ internal/web/translation/es-ES.json | 2 ++ internal/web/translation/fa-IR.json | 2 ++ internal/web/translation/id-ID.json | 2 ++ internal/web/translation/ja-JP.json | 2 ++ internal/web/translation/pt-BR.json | 2 ++ internal/web/translation/ru-RU.json | 2 ++ internal/web/translation/tr-TR.json | 2 ++ internal/web/translation/uk-UA.json | 2 ++ internal/web/translation/vi-VN.json | 2 ++ internal/web/translation/zh-CN.json | 2 ++ internal/web/translation/zh-TW.json | 2 ++ 14 files changed, 37 insertions(+) diff --git a/frontend/src/pages/clients/ClientFormModal.tsx b/frontend/src/pages/clients/ClientFormModal.tsx index f7b28f42c..3f5912557 100644 --- a/frontend/src/pages/clients/ClientFormModal.tsx +++ b/frontend/src/pages/clients/ClientFormModal.tsx @@ -132,6 +132,7 @@ type Values = ClientFormValues & { wgAllowedIPs: string; awgAllowedIPs: string; awgForwardedPorts: string; + wgKeepAlive: number; secret: string; adTag: string; }; @@ -168,6 +169,7 @@ const EMPTY: Values = { wgAllowedIPs: '', awgAllowedIPs: '', awgForwardedPorts: '', + wgKeepAlive: 25, secret: '', adTag: '', }; @@ -378,6 +380,7 @@ export default function ClientFormModal({ wgAllowedIPs: wgTunnelIPs ?? client.allowedIPs ?? '', awgAllowedIPs: awgTunnelIPs ?? client.allowedIPs ?? '', awgForwardedPorts: client.forwardedPorts || '', + wgKeepAlive: client.keepAlive ?? 0, secret: client.secret || '', adTag: client.adTag || '', }; @@ -693,6 +696,7 @@ export default function ClientFormModal({ // so both protocols share this one field set — see wgPrivateKey etc. // below and the AmneziaWG-labeled variants of the same inputs. clientPayload.privateKey = values.wgPrivateKey; + clientPayload.keepAlive = values.wgKeepAlive; clientPayload.publicKey = values.wgPublicKey; if (values.wgPreSharedKey) { clientPayload.preSharedKey = values.wgPreSharedKey; @@ -1271,6 +1275,13 @@ export default function ClientFormModal({ )} + + + {showAmneziawg && (