Files
3x-ui/frontend/src/schemas/protocols/inbound/wireguard.ts
T
MHSanaei a329882e0e feat(wireguard): client config UX, collapsible config card, configurable DNS
Land the WireGuard client-config UX work on main (the upstream PR #5642
branch could not be pushed to).

- Reusable collapsible ConfigBlock (copy/download/QR, actions aligned right)
  for the client .conf, used by client info and the public sub page.
- Correct .conf: canonical PresharedKey casing and DNS sourced from the inbound
  (configurable per-inbound, default 1.1.1.1, 1.0.0.1).
- Configurable per-inbound DNS for WireGuard (schema + form + backend hint via
  InboundOption.WgDns); inert at the Xray layer.
- Public sub page now shows the WireGuard config, rebuilt from the share link;
  the Go wireguard:// link carries dns/presharedkey/keepalive for completeness.
- QR enabled for the wireguard:// link; link rows are compact like other protocols.
- Client information order is subscription, copy URL, WireGuard config; the
  redundant config tab is removed from the add/edit client modal.
- Drop the Inbound Information and QR Code row actions for WireGuard inbounds.
2026-06-29 00:50:34 +02:00

71 lines
3.1 KiB
TypeScript

import { z } from 'zod';
export const WireguardDomainStrategySchema = z.enum([
'ForceIP',
'ForceIPv4',
'ForceIPv4v6',
'ForceIPv6',
'ForceIPv6v4',
]);
export type WireguardDomainStrategy = z.infer<typeof WireguardDomainStrategySchema>;
// AntD InputNumber emits null (not undefined) when the user clears it, and
// the form store hands that null straight to safeParse on submit — a bare
// .optional() would reject it and block the save.
const optionalClearedInt = (schema: z.ZodNumber) =>
z.preprocess((v) => (v == null ? undefined : v), schema.optional());
// Wireguard inbound is peer-based (no clients). Each peer is a client device
// the server accepts; secretKey is the server-side private key and pubKey is
// derived from it at runtime (not persisted on the wire). Inbound peers
// optionally store the client's privateKey so the panel can render configs
// for the user — outbound peers never have a privateKey.
export const WireguardInboundPeerSchema = z.object({
privateKey: z.string().optional(),
publicKey: z.string().min(1),
preSharedKey: z.string().optional(),
allowedIPs: z.array(z.string()).default([]),
keepAlive: optionalClearedInt(z.number().int().min(0)),
// Panel-only annotation (#5168): which client/device this peer belongs to.
// Rides along in the settings JSON like privateKey does; xray-core ignores
// unknown peer fields.
comment: z.string().optional(),
});
export type WireguardInboundPeer = z.infer<typeof WireguardInboundPeerSchema>;
// A WireGuard inbound client (multi-client model). Each client is one peer the
// server accepts: the panel stores its keypair so it can render a full .conf/QR,
// and allowedIPs is the client's unique tunnel address (allocated server-side
// when left blank). Keys are optional on the wire — the backend generates them
// when absent.
export const WireguardClientSchema = z.object({
privateKey: z.string().optional(),
publicKey: z.string().optional(),
preSharedKey: z.string().optional(),
allowedIPs: z.array(z.string()).default([]),
keepAlive: optionalClearedInt(z.number().int().min(0)),
email: z.string().min(1),
limitIp: z.number().int().min(0).default(0),
totalGB: z.number().int().min(0).default(0),
expiryTime: z.number().int().default(0),
enable: z.boolean().default(true),
tgId: z.union([z.number(), z.string()]).transform((v) => Number(v) || 0).default(0),
subId: z.string().default(''),
comment: z.string().default(''),
reset: z.number().int().min(0).default(0),
created_at: z.number().int().optional(),
updated_at: z.number().int().optional(),
});
export type WireguardClient = z.infer<typeof WireguardClientSchema>;
export const WireguardInboundSettingsSchema = z.object({
mtu: optionalClearedInt(z.number().int().min(1)),
secretKey: z.string().min(1),
dns: z.string().optional(),
peers: z.array(WireguardInboundPeerSchema).default([]),
clients: z.array(WireguardClientSchema).default([]),
noKernelTun: z.boolean().default(false),
domainStrategy: WireguardDomainStrategySchema.optional(),
});
export type WireguardInboundSettings = z.infer<typeof WireguardInboundSettingsSchema>;