Quellcode durchsuchen

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
MHSanaei vor 6 Stunden
Ursprung
Commit
05cb70d8a8
1 geänderte Dateien mit 23 neuen und 4 gelöschten Zeilen
  1. 23 4
      frontend/src/pages/inbounds/list/InboundList.tsx

+ 23 - 4
frontend/src/pages/inbounds/list/InboundList.tsx

@@ -5,6 +5,7 @@ import {
   Card,
   Checkbox,
   Dropdown,
+  Input,
   Select,
   Space,
   Switch,
@@ -22,6 +23,7 @@ import {
   ReloadOutlined,
   InfoCircleOutlined,
   DeleteOutlined,
+  SearchOutlined,
 } from '@ant-design/icons';
 
 import { HttpUtil } from '@/utils';
@@ -56,6 +58,7 @@ export default function InboundList({
   // Node filter (#4997): 'all' shows everything, 0 is the local-panel
   // sentinel (inbounds without a nodeId), otherwise a node id. Session-only.
   const [nodeFilter, setNodeFilter] = useState<number | 'all'>('all');
+  const [searchKey, setSearchKey] = useState('');
 
   const showNodeFilter = useMemo(
     () => nodesById.size > 0 || dbInbounds.some((ib) => ib.nodeId != null),
@@ -72,10 +75,17 @@ export default function InboundList({
   );
 
   const visibleInbounds = useMemo(() => {
-    if (nodeFilter === 'all') return dbInbounds;
-    if (nodeFilter === 0) return dbInbounds.filter((ib) => ib.nodeId == null);
-    return dbInbounds.filter((ib) => ib.nodeId === nodeFilter);
-  }, [dbInbounds, nodeFilter]);
+    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]);
 
   const onSwitchEnable = useCallback(async (dbInbound: DBInboundRecord, next: boolean) => {
     const previous = dbInbound.enable;
@@ -180,6 +190,15 @@ export default function InboundList({
               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 && (
             <>
               <Tag color="blue" closable onClose={() => setSelectedRowKeys([])} style={{ marginInlineEnd: 0 }}>