Przeglądaj źródła

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.
MHSanaei 9 godzin temu
rodzic
commit
96a5c73e02

+ 4 - 0
frontend/src/models/dbinbound.ts

@@ -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") {

+ 3 - 0
frontend/src/pages/clients/ClientFormModal.tsx

@@ -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()),
               }}

+ 89 - 12
frontend/src/pages/inbounds/InboundList.tsx

@@ -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">{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">
-                      {statsRecord.isHysteria ? 'UDP' : stream.network}
-                    </Tag>
+                    <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>}
                   </>