mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-27 01:32:08 +03:00
feat(amneziawg): complete frontend parity for the Inbounds list page
The Clients page (form, CRUD, QR/config) already worked from the prior commit; this closes the remaining gap on the Inbounds side and in a couple of protocol allowlists that a plain search for existing wireguard/mtproto handling turned up. lib/xray/inbound-link.ts gets amneziawg-specific link/config builders (genAmneziaWGLink/genAmneziaWGConfig, plus the *s fan-out variants) mirroring the wireguard ones — AmneziaWG has no legacy peers-array to fall back to, so these read settings.clients directly and add the obfuscation lines every client must share with the server. Wired into genInboundLinks generically, and into three consumers that call the wireguard builders directly rather than through that dispatcher: QrCodeModal, InboundInfoModal, and InboundsPage's bulk export. ClientInfoModal, ClientBulkAddModal, and the bulk attach/detach modals each had their own protocol allowlist that needed amneziawg added alongside wireguard/mtproto. Two real gaps surfaced by grepping every remaining 'wireguard' / Protocols.WIREGUARD hit in frontend/src rather than trusting the checklist was exhaustive: - useInbounds.ts's TRACKED_PROTOCOLS gates the deactive/depleted/ expiring/online client counts shown per inbound on the list page; without amneziawg those counts would silently read zero. - inbound-tag.ts is an explicit client-side mirror of the Go backend's port_conflict.go (the file says so itself: "Keep in sync"). It still only special-cased wireguard for UDP, so an amneziawg inbound would have fallen through to the TCP default and disagreed with the backend's own port-conflict math. Also finishes translating the AmneziaWG UI strings into the 11 locale files that were still falling back to English (ar-EG, es-ES, fa-IR, id-ID, ja-JP, pt-BR, tr-TR, uk-UA, vi-VN, zh-CN, zh-TW), matching en-US/ru-RU key-for-key (26 new keys, verified by count in every file). Not run anywhere: npm run typecheck / build. This machine has neither Node nor npm, so nothing here has compiled — reviewed by hand plus brace/paren balance checks and cross-referencing the generated Zod/TS types. Treat this as needing a real typecheck before shipping. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Base64, Wireguard } from '@/utils';
|
||||
|
||||
import type { Inbound } from '@/schemas/api/inbound';
|
||||
import type { AmneziawgInboundSettings } from '@/schemas/protocols/inbound/amneziawg';
|
||||
import type { VlessClient } from '@/schemas/protocols/inbound/vless';
|
||||
import type { VmessSecurity } from '@/schemas/protocols/shared/vmess';
|
||||
import type {
|
||||
@@ -869,6 +870,134 @@ export function genWireguardConfig(input: GenWireguardLinkInput): string {
|
||||
return txt;
|
||||
}
|
||||
|
||||
// Shared input shape for both the per-client amneziawg:// link and .conf
|
||||
// builders below — settings.clients (not a peers array; unlike WireGuard,
|
||||
// AmneziaWG was multi-client from day one, so there's no legacy format).
|
||||
export interface GenAmneziaWGLinkInput {
|
||||
settings: AmneziawgInboundSettings;
|
||||
address: string;
|
||||
port: number;
|
||||
remark?: string;
|
||||
peerIndex: number;
|
||||
}
|
||||
|
||||
function amneziaWGHLine(key: string, value: string | undefined, fallback: string): string {
|
||||
return `${key} = ${value && value.trim() !== '' ? value : fallback}`;
|
||||
}
|
||||
|
||||
// AmneziaWG share link: amneziawg://<clientPrivKey>@<host>:<port>
|
||||
// ?publickey=<serverPub>&address=<clientAllowedIP>&mtu=<mtu>#<remark>
|
||||
// Unlike WireGuard, the server's publicKey is a real persisted field (not
|
||||
// derived from a secretKey at call time), so this just reads it straight off
|
||||
// settings.server. Mirrors genWireguardLink.
|
||||
export function genAmneziaWGLink(input: GenAmneziaWGLinkInput): string {
|
||||
const { settings, address, port, remark = '', peerIndex } = input;
|
||||
const client = settings.clients[peerIndex];
|
||||
if (!client) return '';
|
||||
const server = settings.server;
|
||||
|
||||
const url = new URL(`amneziawg://${formatUrlHost(address)}:${port}`);
|
||||
url.username = client.privateKey ?? '';
|
||||
|
||||
if (server.publicKey.length > 0) url.searchParams.set('publickey', server.publicKey);
|
||||
if ((client.allowedIPs ?? []).length > 0) {
|
||||
url.searchParams.set('address', client.allowedIPs.join(','));
|
||||
}
|
||||
if (typeof server.mtu === 'number' && server.mtu > 0) {
|
||||
url.searchParams.set('mtu', String(server.mtu));
|
||||
}
|
||||
|
||||
url.hash = encodeURIComponent(remark);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
// Plain-text AmneziaWG client config (.conf format). Mirrors
|
||||
// genWireguardConfig, plus the obfuscation lines every AmneziaWG client must
|
||||
// share with the server (see internal/amneziawg.writeObfuscation on the Go
|
||||
// side).
|
||||
export function genAmneziaWGConfig(input: GenAmneziaWGLinkInput): string {
|
||||
const { settings, address, port, remark = '', peerIndex } = input;
|
||||
const client = settings.clients[peerIndex];
|
||||
if (!client) return '';
|
||||
const server = settings.server;
|
||||
|
||||
let txt = `[Interface]\n`;
|
||||
txt += `PrivateKey = ${client.privateKey ?? ''}\n`;
|
||||
txt += `Address = ${(client.allowedIPs ?? []).join(', ')}\n`;
|
||||
const dns = [server.primaryDns, server.secondaryDns].filter((v) => !!v && v.trim() !== '');
|
||||
if (dns.length > 0) txt += `DNS = ${dns.join(', ')}\n`;
|
||||
if (typeof server.mtu === 'number' && server.mtu > 0) {
|
||||
txt += `MTU = ${server.mtu}\n`;
|
||||
}
|
||||
txt += `Jc = ${server.jc}\n`;
|
||||
txt += `Jmin = ${server.jmin}\n`;
|
||||
txt += `Jmax = ${server.jmax}\n`;
|
||||
txt += `S1 = ${server.s1}\n`;
|
||||
txt += `S2 = ${server.s2}\n`;
|
||||
if (server.s3) txt += `S3 = ${server.s3}\n`;
|
||||
if (server.s4) txt += `S4 = ${server.s4}\n`;
|
||||
txt += `${amneziaWGHLine('H1', server.h1, '1')}\n`;
|
||||
txt += `${amneziaWGHLine('H2', server.h2, '2')}\n`;
|
||||
txt += `${amneziaWGHLine('H3', server.h3, '3')}\n`;
|
||||
txt += `${amneziaWGHLine('H4', server.h4, '4')}\n`;
|
||||
if (server.i1) txt += `I1 = ${server.i1}\n`;
|
||||
txt += `\n# ${remark}\n`;
|
||||
txt += `[Peer]\n`;
|
||||
txt += `PublicKey = ${server.publicKey ?? ''}\n`;
|
||||
txt += `AllowedIPs = 0.0.0.0/0, ::/0\n`;
|
||||
txt += `Endpoint = ${address}:${port}`;
|
||||
if (client.preSharedKey && client.preSharedKey.length > 0) {
|
||||
txt += `\nPresharedKey = ${client.preSharedKey}`;
|
||||
}
|
||||
if (typeof client.keepAlive === 'number' && client.keepAlive > 0) {
|
||||
txt += `\nPersistentKeepalive = ${client.keepAlive}\n`;
|
||||
}
|
||||
return txt;
|
||||
}
|
||||
|
||||
export interface GenAmneziaWGFanoutInput {
|
||||
inbound: Inbound;
|
||||
remark?: string;
|
||||
hostOverride?: string;
|
||||
fallbackHostname: string;
|
||||
}
|
||||
|
||||
export function genAmneziaWGLinks(input: GenAmneziaWGFanoutInput): string {
|
||||
const { inbound, remark = '', hostOverride = '', fallbackHostname } = input;
|
||||
if (inbound.protocol !== 'amneziawg') return '';
|
||||
const addr = resolveAddr(inbound, hostOverride, fallbackHostname);
|
||||
const sep = '-';
|
||||
const settings = inbound.settings as AmneziawgInboundSettings;
|
||||
const clients = settings.clients ?? [];
|
||||
return clients
|
||||
.map((c, i) => genAmneziaWGLink({
|
||||
settings,
|
||||
address: addr,
|
||||
port: inbound.port,
|
||||
remark: `${remark}${sep}${i + 1}${wgPeerCommentSuffix(c)}`,
|
||||
peerIndex: i,
|
||||
}))
|
||||
.join('\r\n');
|
||||
}
|
||||
|
||||
export function genAmneziaWGConfigs(input: GenAmneziaWGFanoutInput): string {
|
||||
const { inbound, remark = '', hostOverride = '', fallbackHostname } = input;
|
||||
if (inbound.protocol !== 'amneziawg') return '';
|
||||
const addr = resolveAddr(inbound, hostOverride, fallbackHostname);
|
||||
const sep = '-';
|
||||
const settings = inbound.settings as AmneziawgInboundSettings;
|
||||
const clients = settings.clients ?? [];
|
||||
return clients
|
||||
.map((c, i) => genAmneziaWGConfig({
|
||||
settings,
|
||||
address: addr,
|
||||
port: inbound.port,
|
||||
remark: `${remark}${sep}${i + 1}${wgPeerCommentSuffix(c)}`,
|
||||
peerIndex: i,
|
||||
}))
|
||||
.join('\r\n');
|
||||
}
|
||||
|
||||
export function wireguardConfigFromLink(link: string, fallbackRemark = ''): string {
|
||||
let url: URL;
|
||||
try {
|
||||
@@ -1201,7 +1330,7 @@ export interface GenInboundLinksInput {
|
||||
// Top-level entrypoint that produces the full \r\n-joined block a user
|
||||
// pastes into a client. Iterates per-client for protocols with clients,
|
||||
// falls back to a single SS link for single-user 2022-blake3-chacha20,
|
||||
// and emits per-peer .conf blocks for wireguard. Returns '' for the
|
||||
// and emits per-peer .conf blocks for wireguard and amneziawg. Returns '' for the
|
||||
// other clientless protocols (http, mixed, tunnel).
|
||||
export function genInboundLinks(input: GenInboundLinksInput): string {
|
||||
const {
|
||||
@@ -1226,6 +1355,9 @@ export function genInboundLinks(input: GenInboundLinksInput): string {
|
||||
if (inbound.protocol === 'wireguard') {
|
||||
return genWireguardConfigs({ inbound, remark, hostOverride, fallbackHostname });
|
||||
}
|
||||
if (inbound.protocol === 'amneziawg') {
|
||||
return genAmneziaWGConfigs({ inbound, remark, hostOverride, fallbackHostname });
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ function inboundTransports(
|
||||
streamSettings: Record<string, unknown> | undefined,
|
||||
settings: Record<string, unknown> | undefined,
|
||||
): TransportBits {
|
||||
if (protocol === 'hysteria' || protocol === 'wireguard') return UDP;
|
||||
if (protocol === 'hysteria' || protocol === 'wireguard' || protocol === 'amneziawg') return UDP;
|
||||
|
||||
let bits: TransportBits = 0;
|
||||
const network = asString(streamSettings?.network);
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { InboundOption } from '@/hooks/useClients';
|
||||
import { formatInboundLabel } from '@/lib/inbounds/label';
|
||||
import type { BulkAttachResult } from '@/schemas/client';
|
||||
|
||||
const MULTI_USER_PROTOCOLS = new Set(['vmess', 'vless', 'trojan', 'hysteria', 'shadowsocks', 'wireguard', 'mtproto']);
|
||||
const MULTI_USER_PROTOCOLS = new Set(['vmess', 'vless', 'trojan', 'hysteria', 'shadowsocks', 'wireguard', 'mtproto', 'amneziawg']);
|
||||
|
||||
interface BulkAttachInboundsModalProps {
|
||||
open: boolean;
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { InboundOption } from '@/hooks/useClients';
|
||||
import { formatInboundLabel } from '@/lib/inbounds/label';
|
||||
import type { BulkDetachResult } from '@/schemas/client';
|
||||
|
||||
const MULTI_USER_PROTOCOLS = new Set(['vmess', 'vless', 'trojan', 'hysteria', 'shadowsocks', 'wireguard', 'mtproto']);
|
||||
const MULTI_USER_PROTOCOLS = new Set(['vmess', 'vless', 'trojan', 'hysteria', 'shadowsocks', 'wireguard', 'mtproto', 'amneziawg']);
|
||||
|
||||
interface BulkDetachInboundsModalProps {
|
||||
open: boolean;
|
||||
|
||||
@@ -18,7 +18,7 @@ import { ClientBulkAddFormSchema, type ClientBulkAddFormValues } from '@/schemas
|
||||
const FLOW_OPTIONS = Object.values(TLS_FLOW_CONTROL);
|
||||
|
||||
const MULTI_CLIENT_PROTOCOLS = new Set([
|
||||
'shadowsocks', 'vless', 'vmess', 'trojan', 'hysteria', 'wireguard',
|
||||
'shadowsocks', 'vless', 'vmess', 'trojan', 'hysteria', 'wireguard', 'amneziawg',
|
||||
]);
|
||||
|
||||
const EMPTY: ClientBulkAddFormValues = {
|
||||
|
||||
@@ -13,6 +13,7 @@ import { LinkTags, linkMetaText, parseLinkParts } from '@/lib/xray/link-label';
|
||||
import { QrPanel } from '@/pages/inbounds/qr';
|
||||
import ConfigBlock from '@/components/clients/ConfigBlock';
|
||||
import { buildWireguardClientConfig, findWireguardInbound, isWireguardClient } from './wireguardConfig';
|
||||
import { buildAmneziaWGClientConfig, findAmneziaWGInbound, isAmneziaWGClient } from './amneziawgConfig';
|
||||
import './ClientInfoModal.css';
|
||||
|
||||
const INBOUND_PROTOCOL_COLORS: Record<string, string> = {
|
||||
@@ -23,6 +24,7 @@ const INBOUND_PROTOCOL_COLORS: Record<string, string> = {
|
||||
hysteria: 'cyan',
|
||||
hysteria2: 'green',
|
||||
wireguard: 'gold',
|
||||
amneziawg: 'yellow',
|
||||
http: 'purple',
|
||||
mixed: 'lime',
|
||||
tunnel: 'orange',
|
||||
@@ -149,6 +151,12 @@ export default function ClientInfoModal({
|
||||
return buildWireguardClientConfig(client, wgInbound, window.location.hostname, subSettings?.publicHost ?? '');
|
||||
}, [client, wgInbound, subSettings?.publicHost]);
|
||||
|
||||
const awgInbound = useMemo(() => findAmneziaWGInbound(client, inboundsById), [client, inboundsById]);
|
||||
const awgConfigText = useMemo(() => {
|
||||
if (!client || !awgInbound || !isAmneziaWGClient(client)) return '';
|
||||
return buildAmneziaWGClientConfig(client, awgInbound, window.location.hostname, subSettings?.publicHost ?? '');
|
||||
}, [client, awgInbound, subSettings?.publicHost]);
|
||||
|
||||
async function copyValue(text: string) {
|
||||
if (!text) return;
|
||||
const ok = await ClipboardManager.copyText(String(text));
|
||||
@@ -538,6 +546,18 @@ export default function ClientInfoModal({
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{awgConfigText && client && (
|
||||
<>
|
||||
<Divider>{t('pages.clients.amneziaWgConfig')}</Divider>
|
||||
<ConfigBlock
|
||||
label={t('pages.clients.config')}
|
||||
text={awgConfigText}
|
||||
fileName={`${client.email}.conf`}
|
||||
qrRemark={client.email || 'peer'}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
@@ -24,8 +24,9 @@ import {
|
||||
|
||||
import { HttpUtil, SizeFormatter, RandomUtil } from '@/utils';
|
||||
import { createDefaultInboundSettings } from '@/lib/xray/inbound-defaults';
|
||||
import { genInboundLinks, genWireguardLinks, preferPublicHost } from '@/lib/xray/inbound-link';
|
||||
import { genAmneziaWGLinks, genInboundLinks, genWireguardLinks, preferPublicHost } from '@/lib/xray/inbound-link';
|
||||
import { inboundFromDb } from '@/lib/xray/inbound-from-db';
|
||||
import { Protocols } from '@/schemas/primitives';
|
||||
import { coerceInboundJsonField, type DBInbound } from '@/models/dbinbound';
|
||||
import { useTheme } from '@/hooks/useTheme';
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||
@@ -274,7 +275,12 @@ export default function InboundsPage() {
|
||||
{ key: 'config', label: t('pages.clients.config'), content },
|
||||
{ key: 'links', label: t('pages.clients.tabLinks'), content: genWireguardLinks(genInput) },
|
||||
]
|
||||
: undefined;
|
||||
: projected.protocol === Protocols.AMNEZIAWG
|
||||
? [
|
||||
{ key: 'config', label: t('pages.clients.config'), content },
|
||||
{ key: 'links', label: t('pages.clients.tabLinks'), content: genAmneziaWGLinks(genInput) },
|
||||
]
|
||||
: undefined;
|
||||
openText({
|
||||
title: t('pages.inbounds.exportLinksTitle'),
|
||||
content,
|
||||
|
||||
@@ -10,6 +10,8 @@ import { InfinityIcon } from '@/components/ui';
|
||||
import { useDatepicker } from '@/hooks/useDatepicker';
|
||||
import {
|
||||
genAllLinks,
|
||||
genAmneziaWGConfigs,
|
||||
genAmneziaWGLinks,
|
||||
genWireguardConfigs,
|
||||
genWireguardLinks,
|
||||
preferPublicHost,
|
||||
@@ -49,6 +51,8 @@ export default function InboundInfoModal({
|
||||
const [links, setLinks] = useState<{ remark?: string; link: string }[]>([]);
|
||||
const [wireguardConfigs, setWireguardConfigs] = useState<string[]>([]);
|
||||
const [wireguardLinks, setWireguardLinks] = useState<string[]>([]);
|
||||
const [amneziawgConfigs, setAmneziawgConfigs] = useState<string[]>([]);
|
||||
const [amneziawgLinks, setAmneziawgLinks] = useState<string[]>([]);
|
||||
const [subLink, setSubLink] = useState('');
|
||||
const [subJsonLink, setSubJsonLink] = useState('');
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
@@ -132,6 +136,28 @@ export default function InboundInfoModal({
|
||||
fallbackHostname,
|
||||
}).split('\r\n'),
|
||||
);
|
||||
setAmneziawgConfigs([]);
|
||||
setAmneziawgLinks([]);
|
||||
setLinks([]);
|
||||
} else if (info.protocol === Protocols.AMNEZIAWG) {
|
||||
setAmneziawgConfigs(
|
||||
genAmneziaWGConfigs({
|
||||
inbound: inboundForLinks,
|
||||
remark: dbInbound.remark,
|
||||
hostOverride: nodeAddress,
|
||||
fallbackHostname,
|
||||
}).split('\r\n'),
|
||||
);
|
||||
setAmneziawgLinks(
|
||||
genAmneziaWGLinks({
|
||||
inbound: inboundForLinks,
|
||||
remark: dbInbound.remark,
|
||||
hostOverride: nodeAddress,
|
||||
fallbackHostname,
|
||||
}).split('\r\n'),
|
||||
);
|
||||
setWireguardConfigs([]);
|
||||
setWireguardLinks([]);
|
||||
setLinks([]);
|
||||
} else {
|
||||
setLinks(
|
||||
@@ -145,6 +171,8 @@ export default function InboundInfoModal({
|
||||
);
|
||||
setWireguardConfigs([]);
|
||||
setWireguardLinks([]);
|
||||
setAmneziawgConfigs([]);
|
||||
setAmneziawgLinks([]);
|
||||
}
|
||||
|
||||
if (clientSet?.subId) {
|
||||
@@ -851,6 +879,41 @@ export default function InboundInfoModal({
|
||||
</>
|
||||
)}
|
||||
|
||||
{inbound?.protocol === Protocols.AMNEZIAWG && amneziawgConfigs.length > 0 && (
|
||||
<>
|
||||
<Divider>{t('pages.inbounds.copyLink')}</Divider>
|
||||
{amneziawgConfigs.map((cfg, idx) => (
|
||||
<Fragment key={idx}>
|
||||
{cfg && (
|
||||
<div className="link-panel">
|
||||
<div className="link-panel-header">
|
||||
<Tag color="green">{t('pages.inbounds.info.peerNumberConfig', { n: idx + 1 })}</Tag>
|
||||
<Tooltip title={t('copy')}>
|
||||
<Button size="small" icon={<CopyOutlined />} aria-label={t('copy')} onClick={() => copyText(cfg, t)} />
|
||||
</Tooltip>
|
||||
<Tooltip title={t('download')}>
|
||||
<Button size="small" icon={<DownloadOutlined />} aria-label={t('download')} onClick={() => downloadText(cfg, `peer-${idx + 1}.conf`)} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<code className="link-panel-text">{cfg}</code>
|
||||
</div>
|
||||
)}
|
||||
{amneziawgLinks[idx] && (
|
||||
<div className="link-panel">
|
||||
<div className="link-panel-header">
|
||||
<Tag color="green">Peer {idx + 1} link</Tag>
|
||||
<Tooltip title={t('copy')}>
|
||||
<Button size="small" icon={<CopyOutlined />} aria-label={t('copy')} onClick={() => copyText(amneziawgLinks[idx], t)} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<code className="link-panel-text">{amneziawgLinks[idx]}</code>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
{dbInbound.isSS && !inbound.isSSMultiUser && links.length > 0 && (
|
||||
<>
|
||||
<Divider>{t('pages.inbounds.copyLink')}</Divider>
|
||||
|
||||
@@ -6,6 +6,8 @@ import type { CollapseProps } from 'antd';
|
||||
import { Protocols } from '@/schemas/primitives';
|
||||
import {
|
||||
genAllLinks,
|
||||
genAmneziaWGConfigs,
|
||||
genAmneziaWGLinks,
|
||||
genWireguardConfigs,
|
||||
genWireguardLinks,
|
||||
isPostQuantumLink,
|
||||
@@ -50,6 +52,8 @@ export default function QrCodeModal({
|
||||
const [links, setLinks] = useState<{ remark?: string; link: string }[]>([]);
|
||||
const [wireguardConfigs, setWireguardConfigs] = useState<string[]>([]);
|
||||
const [wireguardLinks, setWireguardLinks] = useState<string[]>([]);
|
||||
const [amneziawgConfigs, setAmneziawgConfigs] = useState<string[]>([]);
|
||||
const [amneziawgLinks, setAmneziawgLinks] = useState<string[]>([]);
|
||||
const [subLink, setSubLink] = useState('');
|
||||
const [subJsonLink, setSubJsonLink] = useState('');
|
||||
const [activeKey, setActiveKey] = useState<string[]>([]);
|
||||
@@ -78,6 +82,31 @@ export default function QrCodeModal({
|
||||
fallbackHostname,
|
||||
}).split('\r\n'),
|
||||
);
|
||||
setAmneziawgConfigs([]);
|
||||
setAmneziawgLinks([]);
|
||||
setLinks([]);
|
||||
} else if (inbound.protocol === Protocols.AMNEZIAWG) {
|
||||
const peerRemark = client?.email
|
||||
? `${dbInbound.remark}-${client.email}`
|
||||
: dbInbound.remark || '';
|
||||
setAmneziawgConfigs(
|
||||
genAmneziaWGConfigs({
|
||||
inbound,
|
||||
remark: peerRemark,
|
||||
hostOverride: nodeAddress,
|
||||
fallbackHostname,
|
||||
}).split('\r\n'),
|
||||
);
|
||||
setAmneziawgLinks(
|
||||
genAmneziaWGLinks({
|
||||
inbound,
|
||||
remark: peerRemark,
|
||||
hostOverride: nodeAddress,
|
||||
fallbackHostname,
|
||||
}).split('\r\n'),
|
||||
);
|
||||
setWireguardConfigs([]);
|
||||
setWireguardLinks([]);
|
||||
setLinks([]);
|
||||
} else {
|
||||
setLinks(
|
||||
@@ -91,6 +120,8 @@ export default function QrCodeModal({
|
||||
);
|
||||
setWireguardConfigs([]);
|
||||
setWireguardLinks([]);
|
||||
setAmneziawgConfigs([]);
|
||||
setAmneziawgLinks([]);
|
||||
}
|
||||
|
||||
const subId = client?.subId;
|
||||
@@ -126,8 +157,19 @@ export default function QrCodeModal({
|
||||
items.push({ key: `wl${idx}`, header: `Peer ${idx + 1} link`, value: wireguardLinks[idx], showQr: false });
|
||||
}
|
||||
});
|
||||
amneziawgConfigs.forEach((cfg, idx) => {
|
||||
items.push({
|
||||
key: `ac${idx}`,
|
||||
header: `Peer ${idx + 1} config`,
|
||||
value: cfg,
|
||||
downloadName: `peer-${idx + 1}.conf`,
|
||||
});
|
||||
if (amneziawgLinks[idx]) {
|
||||
items.push({ key: `al${idx}`, header: `Peer ${idx + 1} link`, value: amneziawgLinks[idx], showQr: false });
|
||||
}
|
||||
});
|
||||
return items;
|
||||
}, [subLink, subJsonLink, links, wireguardConfigs, wireguardLinks, t]);
|
||||
}, [subLink, subJsonLink, links, wireguardConfigs, wireguardLinks, amneziawgConfigs, amneziawgLinks, t]);
|
||||
|
||||
const collapseItems: CollapseProps['items'] = useMemo(
|
||||
() => qrItems.map((item) => ({
|
||||
|
||||
@@ -62,6 +62,7 @@ const TRACKED_PROTOCOLS: readonly string[] = [
|
||||
Protocols.HYSTERIA,
|
||||
Protocols.WIREGUARD,
|
||||
Protocols.MTPROTO,
|
||||
Protocols.AMNEZIAWG,
|
||||
];
|
||||
|
||||
async function fetchSlimInbounds(): Promise<unknown[]> {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { NetworkSettingsSchema, StreamExtrasSchema } from '@/schemas/protocols/s
|
||||
|
||||
// Top-level inbound shape on the wire. Composes:
|
||||
// - Per-protocol settings via the InboundSettingsSchema discriminated
|
||||
// union (10 protocols, tagged-wrapper {protocol, settings}).
|
||||
// union (11 protocols, tagged-wrapper {protocol, settings}).
|
||||
// - StreamSettings as an intersection of the network DU (6 branches),
|
||||
// security DU (3 branches), and the orthogonal extras (finalmask,
|
||||
// sockopt, externalProxy). Zod 4 supports DU intersection — each
|
||||
|
||||
Reference in New Issue
Block a user