mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-30 06:57:14 +00:00
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.
This commit is contained in:
@@ -11,6 +11,8 @@ import type { ClientRecord, InboundOption } from '@/hooks/useClients';
|
||||
import { isPostQuantumLink } from '@/lib/xray/inbound-link';
|
||||
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 './ClientInfoModal.css';
|
||||
|
||||
const INBOUND_PROTOCOL_COLORS: Record<string, string> = {
|
||||
@@ -35,6 +37,7 @@ interface SubSettings {
|
||||
subJsonEnable: boolean;
|
||||
subClashURI: string;
|
||||
subClashEnable: boolean;
|
||||
publicHost?: string;
|
||||
}
|
||||
|
||||
interface ClientInfoModalProps {
|
||||
@@ -58,6 +61,7 @@ const DEFAULT_SUB: SubSettings = {
|
||||
subJsonEnable: false,
|
||||
subClashURI: '',
|
||||
subClashEnable: false,
|
||||
publicHost: '',
|
||||
};
|
||||
|
||||
export default function ClientInfoModal({
|
||||
@@ -132,6 +136,11 @@ export default function ClientInfoModal({
|
||||
}, [client?.subId, subSettings?.subClashEnable, subSettings?.subClashURI]);
|
||||
|
||||
const showSubscription = !!(subSettings?.enable && client?.subId);
|
||||
const wgInbound = useMemo(() => findWireguardInbound(client, inboundsById), [client, inboundsById]);
|
||||
const wgConfigText = useMemo(() => {
|
||||
if (!client || !isWireguardClient(client)) return '';
|
||||
return buildWireguardClientConfig(client, wgInbound, window.location.hostname, subSettings?.publicHost ?? '');
|
||||
}, [client, wgInbound, subSettings?.publicHost]);
|
||||
|
||||
async function copyValue(text: string) {
|
||||
if (!text) return;
|
||||
@@ -350,44 +359,6 @@ export default function ClientInfoModal({
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{links.length > 0 && (
|
||||
<>
|
||||
<Divider>{t('pages.inbounds.copyLink')}</Divider>
|
||||
{links.map((link, idx) => {
|
||||
const parts = parseLinkParts(link);
|
||||
const fallback = `${t('pages.clients.link')} ${idx + 1}`;
|
||||
const rowTitle = (parts && linkMetaText(parts)) || fallback;
|
||||
const qrRemark = parts?.remark || rowTitle;
|
||||
const canQr = !isPostQuantumLink(link);
|
||||
return (
|
||||
<div key={idx} className="link-row">
|
||||
{parts
|
||||
? <LinkTags parts={parts} />
|
||||
: <Tag className="link-row-tag">LINK</Tag>}
|
||||
<span className="link-row-title" title={rowTitle}>{rowTitle}</span>
|
||||
<div className="link-row-actions">
|
||||
<Tooltip title={t('copy')}>
|
||||
<Button size="small" icon={<CopyOutlined />} onClick={() => copyValue(link)} />
|
||||
</Tooltip>
|
||||
{canQr && (
|
||||
<Popover
|
||||
trigger="click"
|
||||
placement="left"
|
||||
destroyOnHidden
|
||||
content={<QrPanel value={link} remark={qrRemark} size={220} />}
|
||||
>
|
||||
<Tooltip title={t('pages.clients.qrCode')}>
|
||||
<Button size="small" icon={<QrcodeOutlined />} />
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
|
||||
{showSubscription && subLink && (
|
||||
<>
|
||||
<Divider>{t('subscription.title')}</Divider>
|
||||
@@ -480,6 +451,56 @@ export default function ClientInfoModal({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{links.length > 0 && (
|
||||
<>
|
||||
<Divider>{t('pages.inbounds.copyLink')}</Divider>
|
||||
{links.map((link, idx) => {
|
||||
const parts = parseLinkParts(link);
|
||||
const fallback = `${t('pages.clients.link')} ${idx + 1}`;
|
||||
const rowTitle = (parts && linkMetaText(parts)) || fallback;
|
||||
const qrRemark = parts?.remark || rowTitle;
|
||||
const canQr = !isPostQuantumLink(link);
|
||||
return (
|
||||
<div key={idx} className="link-row">
|
||||
{parts
|
||||
? <LinkTags parts={parts} />
|
||||
: <Tag className="link-row-tag">LINK</Tag>}
|
||||
<span className="link-row-title" title={rowTitle}>{rowTitle}</span>
|
||||
<div className="link-row-actions">
|
||||
<Tooltip title={t('copy')}>
|
||||
<Button size="small" icon={<CopyOutlined />} onClick={() => copyValue(link)} />
|
||||
</Tooltip>
|
||||
{canQr && (
|
||||
<Popover
|
||||
trigger="click"
|
||||
placement="left"
|
||||
destroyOnHidden
|
||||
content={<QrPanel value={link} remark={qrRemark} size={220} />}
|
||||
>
|
||||
<Tooltip title={t('pages.clients.qrCode')}>
|
||||
<Button size="small" icon={<QrcodeOutlined />} />
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
|
||||
{wgConfigText && client && (
|
||||
<>
|
||||
<Divider>{t('pages.clients.wireguardConfig')}</Divider>
|
||||
<ConfigBlock
|
||||
label={t('pages.clients.conf')}
|
||||
text={wgConfigText}
|
||||
fileName={`${client.email}.conf`}
|
||||
qrRemark={client.email || 'peer'}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
Reference in New Issue
Block a user