feat(frontend): add text search to the inbound list

The v2.x panel could filter inbounds but the list page only had the node dropdown. Add a search box next to it matching on remark, port, and protocol, composed with the node filter; the dataset is already client-side, so no API change.

Closes #5267
This commit is contained in:
MHSanaei
2026-07-03 09:40:04 +02:00
parent 323cf09d10
commit 05cb70d8a8
@@ -5,6 +5,7 @@ import {
Card, Card,
Checkbox, Checkbox,
Dropdown, Dropdown,
Input,
Select, Select,
Space, Space,
Switch, Switch,
@@ -22,6 +23,7 @@ import {
ReloadOutlined, ReloadOutlined,
InfoCircleOutlined, InfoCircleOutlined,
DeleteOutlined, DeleteOutlined,
SearchOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { HttpUtil } from '@/utils'; import { HttpUtil } from '@/utils';
@@ -56,6 +58,7 @@ export default function InboundList({
// Node filter (#4997): 'all' shows everything, 0 is the local-panel // Node filter (#4997): 'all' shows everything, 0 is the local-panel
// sentinel (inbounds without a nodeId), otherwise a node id. Session-only. // sentinel (inbounds without a nodeId), otherwise a node id. Session-only.
const [nodeFilter, setNodeFilter] = useState<number | 'all'>('all'); const [nodeFilter, setNodeFilter] = useState<number | 'all'>('all');
const [searchKey, setSearchKey] = useState('');
const showNodeFilter = useMemo( const showNodeFilter = useMemo(
() => nodesById.size > 0 || dbInbounds.some((ib) => ib.nodeId != null), () => nodesById.size > 0 || dbInbounds.some((ib) => ib.nodeId != null),
@@ -72,10 +75,17 @@ export default function InboundList({
); );
const visibleInbounds = useMemo(() => { const visibleInbounds = useMemo(() => {
if (nodeFilter === 'all') return dbInbounds; let list = dbInbounds;
if (nodeFilter === 0) return dbInbounds.filter((ib) => ib.nodeId == null); if (nodeFilter === 0) list = list.filter((ib) => ib.nodeId == null);
return dbInbounds.filter((ib) => ib.nodeId === nodeFilter); else if (nodeFilter !== 'all') list = list.filter((ib) => ib.nodeId === nodeFilter);
}, [dbInbounds, 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]);
const onSwitchEnable = useCallback(async (dbInbound: DBInboundRecord, next: boolean) => { const onSwitchEnable = useCallback(async (dbInbound: DBInboundRecord, next: boolean) => {
const previous = dbInbound.enable; const previous = dbInbound.enable;
@@ -180,6 +190,15 @@ export default function InboundList({
aria-label={t('pages.clients.filters.nodes')} aria-label={t('pages.clients.filters.nodes')}
/> />
)} )}
<Input
value={searchKey}
onChange={(e) => setSearchKey(e.target.value)}
placeholder={t('search')}
allowClear
prefix={<SearchOutlined />}
style={{ maxWidth: isMobile ? 110 : 200 }}
aria-label={t('search')}
/>
{selectedRowKeys.length > 0 && ( {selectedRowKeys.length > 0 && (
<> <>
<Tag color="blue" closable onClose={() => setSelectedRowKeys([])} style={{ marginInlineEnd: 0 }}> <Tag color="blue" closable onClose={() => setSelectedRowKeys([])} style={{ marginInlineEnd: 0 }}>