mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-05 01:47:15 +00:00
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:
@@ -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 }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user