diff --git a/frontend/src/pages/inbounds/InboundsPage.tsx b/frontend/src/pages/inbounds/InboundsPage.tsx
index 3a2b557b8..705933113 100644
--- a/frontend/src/pages/inbounds/InboundsPage.tsx
+++ b/frontend/src/pages/inbounds/InboundsPage.tsx
@@ -803,6 +803,7 @@ export default function InboundsPage() {
subEnable={subSettings.enable}
nodesById={nodesById}
hasActiveNode={showNodeInfo}
+ hosts={hosts}
onAddInbound={onAddInbound}
onGeneralAction={onGeneralAction}
onRowAction={({ key, dbInbound }) =>
diff --git a/frontend/src/pages/inbounds/list/InboundList.css b/frontend/src/pages/inbounds/list/InboundList.css
index 16f2db9a9..e1b30f7fb 100644
--- a/frontend/src/pages/inbounds/list/InboundList.css
+++ b/frontend/src/pages/inbounds/list/InboundList.css
@@ -180,3 +180,34 @@
padding: 4px;
}
}
+
+.inbound-remark-cell {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 2px;
+ min-width: 0;
+}
+
+.inbound-remark {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.inbound-host-remarks {
+ font-size: 11px;
+ font-weight: 400;
+ opacity: 0.65;
+ line-height: 1.2;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ cursor: default;
+}
+
+.tag-name .inbound-host-remarks {
+ font-weight: 400;
+}
diff --git a/frontend/src/pages/inbounds/list/InboundList.tsx b/frontend/src/pages/inbounds/list/InboundList.tsx
index 1b3931585..33748a4d8 100644
--- a/frontend/src/pages/inbounds/list/InboundList.tsx
+++ b/frontend/src/pages/inbounds/list/InboundList.tsx
@@ -32,10 +32,21 @@ import { activateOnKey } from '@/utils/a11y';
import { buildRowActionsMenu } from './RowActions';
import { useInboundColumns } from './useInboundColumns';
+import { buildHostRemarksByInboundId, formatHostRemarksLabel } from './helpers';
import InboundStatsModal from './InboundStatsModal';
import type { DBInboundRecord, GeneralAction, InboundListProps, RowAction } from './types';
import './InboundList.css';
+function HostRemarksSuffix({ remarks }: { remarks: string[] }) {
+ if (remarks.length === 0) return null;
+ const { display, full } = formatHostRemarksLabel(remarks);
+ return (
+
+ ({display})
+
+ );
+}
+
export default function InboundList({
dbInbounds,
clientCount,
@@ -48,6 +59,7 @@ export default function InboundList({
subEnable,
nodesById,
hasActiveNode,
+ hosts,
onAddInbound,
onGeneralAction,
onRowAction,
@@ -86,19 +98,22 @@ export default function InboundList({
[nodesById, t],
);
+ const hostRemarksByInboundId = useMemo(() => buildHostRemarksByInboundId(hosts), [hosts]);
+
const visibleInbounds = useMemo(() => {
let list = dbInbounds;
if (nodeFilter === 0) list = list.filter((ib) => ib.nodeId == null);
else if (nodeFilter !== 'all') list = list.filter((ib) => ib.nodeId === nodeFilter);
const q = searchKey.trim().toLowerCase();
if (!q) return list;
- return list.filter(
- (ib) =>
- (ib.remark || '').toLowerCase().includes(q) ||
- String(ib.port).includes(q) ||
- (ib.protocol || '').toLowerCase().includes(q),
- );
- }, [dbInbounds, nodeFilter, searchKey]);
+ return list.filter((ib) => {
+ if ((ib.remark || '').toLowerCase().includes(q)) return true;
+ if (String(ib.port).includes(q)) return true;
+ if ((ib.protocol || '').toLowerCase().includes(q)) return true;
+ const hostRemarks = hostRemarksByInboundId.get(ib.id) ?? [];
+ return hostRemarks.some((remark) => remark.toLowerCase().includes(q));
+ });
+ }, [dbInbounds, nodeFilter, searchKey, hostRemarksByInboundId]);
const onSwitchEnable = useCallback(async (dbInbound: DBInboundRecord, next: boolean) => {
const previous = dbInbound.enable;
@@ -114,8 +129,10 @@ export default function InboundList({
}, []);
const hasAnyRemark = useMemo(
- () => dbInbounds.some((i) => typeof i.remark === 'string' && i.remark.trim() !== ''),
- [dbInbounds],
+ () =>
+ dbInbounds.some((i) => typeof i.remark === 'string' && i.remark.trim() !== '') ||
+ dbInbounds.some((i) => (hostRemarksByInboundId.get(i.id)?.length ?? 0) > 0),
+ [dbInbounds, hostRemarksByInboundId],
);
const hasAnySubSortIndex = useMemo(
@@ -154,6 +171,7 @@ export default function InboundList({
hasAnySubSortIndex,
hasActiveNode,
nodesById,
+ hostRemarksByInboundId,
clientCount,
inboundSpeed,
subEnable,
@@ -293,7 +311,10 @@ export default function InboundList({
onChange={(e) => toggleSelect(record.id, e.target.checked)}
/>
#{record.id}
- {record.remark}
+
+ {record.remark}
+
+
[],
+): Map {
+ const map = new Map();
+ for (const host of hosts) {
+ if (host.isDisabled) continue;
+ const addressFallback = Array.isArray(host.hosts)
+ ? host.hosts.map((h) => (h || '').trim()).find(Boolean) || ''
+ : '';
+ const label = (host.remark || '').trim() || addressFallback;
+ if (!label) continue;
+ for (const inboundId of host.inboundIds || []) {
+ if (!Number.isFinite(inboundId)) continue;
+ const list = map.get(inboundId) ?? [];
+ if (!list.includes(label)) list.push(label);
+ map.set(inboundId, list);
+ }
+ }
+ return map;
+}
+
+export function formatHostRemarksLabel(
+ remarks: string[],
+ visibleLimit = HOST_REMARK_VISIBLE_LIMIT,
+): { display: string; full: string } {
+ const full = remarks.join(', ');
+ if (remarks.length <= visibleLimit) {
+ return { display: full, full };
+ }
+ const visible = remarks.slice(0, visibleLimit).join(', ');
+ const more = remarks.length - visibleLimit;
+ return { display: `${visible}, +${more}`, full };
+}
diff --git a/frontend/src/pages/inbounds/list/types.ts b/frontend/src/pages/inbounds/list/types.ts
index 5891dbcc2..49c759175 100644
--- a/frontend/src/pages/inbounds/list/types.ts
+++ b/frontend/src/pages/inbounds/list/types.ts
@@ -1,4 +1,5 @@
import type { NodeRecord } from '@/api/queries/useNodesQuery';
+import type { HostRecord } from '@/schemas/api/host';
export interface StreamHints {
network: string;
@@ -78,6 +79,7 @@ export interface InboundListProps {
subEnable: boolean;
nodesById: Map;
hasActiveNode: boolean;
+ hosts: HostRecord[];
onAddInbound: () => void;
onGeneralAction: (key: GeneralAction) => void;
onRowAction: (action: { key: RowAction; dbInbound: DBInboundRecord }) => void;
diff --git a/frontend/src/pages/inbounds/list/useInboundColumns.tsx b/frontend/src/pages/inbounds/list/useInboundColumns.tsx
index 1089d95e8..9f08e868c 100644
--- a/frontend/src/pages/inbounds/list/useInboundColumns.tsx
+++ b/frontend/src/pages/inbounds/list/useInboundColumns.tsx
@@ -23,6 +23,7 @@ import {
shadowsocksNetworkLabel,
tunnelNetworkLabel,
mixedNetworkLabel,
+ formatHostRemarksLabel,
} from './helpers';
import type { ClientCountEntry, DBInboundRecord, InboundSpeedEntry, RowAction } from './types';
@@ -31,6 +32,7 @@ interface UseInboundColumnsParams {
hasAnySubSortIndex: boolean;
hasActiveNode: boolean;
nodesById: Map;
+ hostRemarksByInboundId: Map;
clientCount: Record;
inboundSpeed: Record;
subEnable: boolean;
@@ -45,6 +47,7 @@ export function useInboundColumns({
hasAnySubSortIndex,
hasActiveNode,
nodesById,
+ hostRemarksByInboundId,
clientCount,
inboundSpeed,
subEnable,
@@ -138,8 +141,23 @@ export function useInboundColumns({
dataIndex: 'remark',
key: 'remark',
align: 'center',
- width: 90,
+ width: 140,
sorter: (a, b) => compareText(a.remark, b.remark),
+ render: (_, record) => {
+ const hostRemarks = hostRemarksByInboundId.get(record.id) ?? [];
+ if (hostRemarks.length === 0) {
+ return record.remark || null;
+ }
+ const { display, full } = formatHostRemarksLabel(hostRemarks);
+ return (
+
+
{record.remark}
+
+ ({display})
+
+
+ );
+ },
});
}
@@ -453,6 +471,7 @@ export function useInboundColumns({
hasAnySubSortIndex,
hasActiveNode,
nodesById,
+ hostRemarksByInboundId,
clientCount,
inboundSpeed,
subEnable,
diff --git a/frontend/src/test/inbound-host-remarks.test.ts b/frontend/src/test/inbound-host-remarks.test.ts
new file mode 100644
index 000000000..896bc620d
--- /dev/null
+++ b/frontend/src/test/inbound-host-remarks.test.ts
@@ -0,0 +1,17 @@
+import { describe, expect, it } from 'vitest';
+
+import { buildHostRemarksByInboundId } from '@/pages/inbounds/list/helpers';
+
+describe('buildHostRemarksByInboundId', () => {
+ it('joins only host groups a client can reach (#6026)', () => {
+ const map = buildHostRemarksByInboundId([
+ { remark: 'USA IPv4', inboundIds: [1, 2], hosts: ['1.2.3.4:443'], isDisabled: false },
+ { remark: 'USA IPv6', inboundIds: [1], hosts: ['[2001:db8::1]:443'], isDisabled: true },
+ { remark: '', inboundIds: [2], hosts: ['', 'cdn.example.com:443'], isDisabled: false },
+ { remark: 'USA IPv4', inboundIds: [2], hosts: ['5.6.7.8'] },
+ ]);
+ expect(map.get(1)).toEqual(['USA IPv4']);
+ expect(map.get(2)).toEqual(['USA IPv4', 'cdn.example.com:443']);
+ expect(map.has(3)).toBe(false);
+ });
+});