mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-30 17:09:34 +00:00
refactor(inbounds): cleaner network tags and cover Mixed/Tunnel + client form select polish
The InboundList protocol column had a few rough edges: raw transports rendered with mixed casing (TCP vs ws vs grpc), WireGuard never got a network tag at all, and Mixed/Tunnel rows had no L4 indication even though they listen on tcp/udp combinations through their own settings keys (settings.udp for Mixed, settings.allowedNetwork for Tunnel). Normalise the column: a small networkLabel helper upper-cases every known transport (so TCP / UDP / KCP / QUIC / WS / GRPC / HTTP all share the same visual weight, with HTTPUpgrade / SplitHTTP / XHTTP keeping a touch of casing for readability). Add an extra UDP tag beside KCP / QUIC so the user sees the underlying L4 without having to know each transport's wire shape. Add isTunnel to the dbinbound model and per-protocol branches for Mixed (TCP / TCP,UDP) and Tunnel (reads settings.allowedNetwork the same shape Shadowsocks uses for settings.network). Also polish the attached-inbounds Select in the client form: open upwards (placement="topLeft") with a 220px listHeight and maxTagCount="responsive" so a long selection doesn't push the modal's Save button below the viewport.
This commit is contained in:
@@ -155,6 +155,10 @@ export class DBInbound {
|
||||
return this.protocol === Protocols.HYSTERIA;
|
||||
}
|
||||
|
||||
get isTunnel() {
|
||||
return this.protocol === Protocols.TUNNEL;
|
||||
}
|
||||
|
||||
get address(): string {
|
||||
let address = location.hostname;
|
||||
if (!ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0") {
|
||||
|
||||
@@ -515,6 +515,9 @@ export default function ClientFormModal({
|
||||
onChange={(v) => update('inboundIds', v)}
|
||||
options={inboundOptions}
|
||||
placeholder={t('pages.clients.selectInbound')}
|
||||
maxTagCount="responsive"
|
||||
placement="topLeft"
|
||||
listHeight={220}
|
||||
showSearch={{
|
||||
filterOption: (input, option) => ((option?.label as string) || '').toLowerCase().includes(input.toLowerCase()),
|
||||
}}
|
||||
|
||||
@@ -53,8 +53,58 @@ function readStreamHints(streamSettings: unknown): StreamHints {
|
||||
};
|
||||
}
|
||||
|
||||
function readSettings(settings: unknown): { method?: string } {
|
||||
return coerceInboundJsonField(settings) as { method?: string };
|
||||
// Display label for a network value. All known transports render in
|
||||
// upper-case for visual consistency with the TCP/UDP/TLS/Reality tags
|
||||
// already shown alongside; compound names (`httpupgrade`, `splithttp`,
|
||||
// `xhttp`) get a tiny touch of casing so they don't read as one word.
|
||||
function networkLabel(network: string): string {
|
||||
const n = (network || '').toLowerCase();
|
||||
if (!n) return 'TCP';
|
||||
switch (n) {
|
||||
case 'httpupgrade': return 'HTTPUpgrade';
|
||||
case 'splithttp': return 'SplitHTTP';
|
||||
case 'xhttp': return 'XHTTP';
|
||||
}
|
||||
return n.toUpperCase();
|
||||
}
|
||||
|
||||
// Returns the underlying L4 protocol for transports whose name isn't
|
||||
// already TCP/UDP. `kcp` and `quic` both ride on UDP; everything else
|
||||
// (`ws`, `grpc`, `http`, `httpupgrade`, `xhttp`) is TCP-based and gets
|
||||
// no extra tag (the transport name implies TCP).
|
||||
function networkL4(network: string): 'UDP' | '' {
|
||||
const n = (network || '').toLowerCase();
|
||||
if (n === 'kcp' || n === 'quic') return 'UDP';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Shadowsocks settings.network ("tcp" / "udp" / "tcp,udp") and Tunnel
|
||||
// settings.allowedNetwork (same shape, different field name) both carry
|
||||
// the L4 transport list independent of streamSettings. Returns a
|
||||
// comma-separated label.
|
||||
function commaNetworkLabel(raw: string): string {
|
||||
const parts = (raw || 'tcp').toLowerCase().split(',').map((p) => p.trim()).filter(Boolean);
|
||||
if (parts.length === 0) return 'TCP';
|
||||
return parts.map(networkLabel).join(',');
|
||||
}
|
||||
|
||||
function shadowsocksNetworkLabel(settings: unknown): string {
|
||||
return commaNetworkLabel(readSettings(settings).network || '');
|
||||
}
|
||||
|
||||
function tunnelNetworkLabel(settings: unknown): string {
|
||||
return commaNetworkLabel(readSettings(settings).allowedNetwork || '');
|
||||
}
|
||||
|
||||
// Mixed (socks+http combo) is always TCP at L4; settings.udp=true adds
|
||||
// UDP-associate support on the same port (SOCKS5 UDP).
|
||||
function mixedNetworkLabel(settings: unknown): string {
|
||||
const st = coerceInboundJsonField(settings) as { udp?: boolean };
|
||||
return st.udp ? 'TCP,UDP' : 'TCP';
|
||||
}
|
||||
|
||||
function readSettings(settings: unknown): { method?: string; network?: string; allowedNetwork?: string } {
|
||||
return coerceInboundJsonField(settings) as { method?: string; network?: string; allowedNetwork?: string };
|
||||
}
|
||||
|
||||
function isInboundMultiUser(record: { protocol: string; settings: unknown }): boolean {
|
||||
@@ -80,6 +130,7 @@ type ProtocolFlags = {
|
||||
isMixed?: boolean;
|
||||
isHTTP?: boolean;
|
||||
isWireguard?: boolean;
|
||||
isTunnel?: boolean;
|
||||
};
|
||||
|
||||
interface DBInboundRecord extends ProtocolFlags {
|
||||
@@ -368,13 +419,21 @@ export default function InboundList({
|
||||
...sorterFor('protocol'),
|
||||
render: (_, record) => {
|
||||
const tags: ReactElement[] = [<Tag key="p" color="purple">{record.protocol}</Tag>];
|
||||
if (record.isVMess || record.isVLess || record.isTrojan || record.isSS || record.isHysteria) {
|
||||
if (record.isWireguard || record.isHysteria) {
|
||||
tags.push(<Tag key="n" color="green">UDP</Tag>);
|
||||
} else if (record.isSS) {
|
||||
const stream = readStreamHints(record.streamSettings);
|
||||
tags.push(
|
||||
<Tag key="n" color="green">
|
||||
{record.isHysteria ? 'UDP' : stream.network}
|
||||
</Tag>,
|
||||
);
|
||||
tags.push(<Tag key="n" color="green">{shadowsocksNetworkLabel(record.settings)}</Tag>);
|
||||
if (stream.isTls) tags.push(<Tag key="tls" color="blue">TLS</Tag>);
|
||||
} else if (record.isTunnel) {
|
||||
tags.push(<Tag key="n" color="green">{tunnelNetworkLabel(record.settings)}</Tag>);
|
||||
} else if (record.isMixed) {
|
||||
tags.push(<Tag key="n" color="green">{mixedNetworkLabel(record.settings)}</Tag>);
|
||||
} else if (record.isVMess || record.isVLess || record.isTrojan) {
|
||||
const stream = readStreamHints(record.streamSettings);
|
||||
tags.push(<Tag key="n" color="green">{networkLabel(stream.network)}</Tag>);
|
||||
const l4 = networkL4(stream.network);
|
||||
if (l4) tags.push(<Tag key="l4" color="green">{l4}</Tag>);
|
||||
if (stream.isTls) tags.push(<Tag key="tls" color="blue">TLS</Tag>);
|
||||
if (stream.isReality) tags.push(<Tag key="reality" color="blue">Reality</Tag>);
|
||||
}
|
||||
@@ -606,13 +665,31 @@ export default function InboundList({
|
||||
<div className="stat-row">
|
||||
<span className="stat-label">{t('pages.inbounds.protocol')}</span>
|
||||
<Tag color="purple">{statsRecord.protocol}</Tag>
|
||||
{(statsRecord.isVMess || statsRecord.isVLess || statsRecord.isTrojan || statsRecord.isSS || statsRecord.isHysteria) && (() => {
|
||||
{(statsRecord.isWireguard || statsRecord.isHysteria) && (
|
||||
<Tag color="green">UDP</Tag>
|
||||
)}
|
||||
{statsRecord.isSS && (() => {
|
||||
const stream = readStreamHints(statsRecord.streamSettings);
|
||||
return (
|
||||
<>
|
||||
<Tag color="green">
|
||||
{statsRecord.isHysteria ? 'UDP' : stream.network}
|
||||
</Tag>
|
||||
<Tag color="green">{shadowsocksNetworkLabel(statsRecord.settings)}</Tag>
|
||||
{stream.isTls && <Tag color="blue">TLS</Tag>}
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
{statsRecord.isTunnel && (
|
||||
<Tag color="green">{tunnelNetworkLabel(statsRecord.settings)}</Tag>
|
||||
)}
|
||||
{statsRecord.isMixed && (
|
||||
<Tag color="green">{mixedNetworkLabel(statsRecord.settings)}</Tag>
|
||||
)}
|
||||
{(statsRecord.isVMess || statsRecord.isVLess || statsRecord.isTrojan) && (() => {
|
||||
const stream = readStreamHints(statsRecord.streamSettings);
|
||||
const l4 = networkL4(stream.network);
|
||||
return (
|
||||
<>
|
||||
<Tag color="green">{networkLabel(stream.network)}</Tag>
|
||||
{l4 && <Tag color="green">{l4}</Tag>}
|
||||
{stream.isTls && <Tag color="blue">TLS</Tag>}
|
||||
{stream.isReality && <Tag color="blue">Reality</Tag>}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user