Explorar el Código

feat(clients,routing): label inbounds by remark with tag fallback

Inbound pickers and chips across the Users area, the inbounds attach-clients modals, and the routing rule inbound-tags selector showed the auto-generated tag (in-443-tcp). Show the inbound remark when set, falling back to the tag.

Only display labels change; option values keep using the inbound id (or tag for routing rules, which match inbounds by tag), so filtering, attaching, and saved rules are unaffected. Routing reads remarks via a shared useInboundOptions hook that reuses the existing options query cache.
MHSanaei hace 16 horas
padre
commit
61105c2b1a

+ 21 - 0
frontend/src/api/queries/useInboundOptions.ts

@@ -0,0 +1,21 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { HttpUtil } from '@/utils';
+import { parseMsg } from '@/utils/zodValidate';
+import { keys } from '@/api/queryKeys';
+import { InboundOptionsSchema, type InboundOption } from '@/schemas/client';
+
+async function fetchInboundOptions(): Promise<InboundOption[]> {
+  const msg = await HttpUtil.get('/panel/api/inbounds/options', undefined, { silent: true });
+  if (!msg?.success) throw new Error(msg?.msg || 'Failed to fetch inbound options');
+  const validated = parseMsg(msg, InboundOptionsSchema, 'inbounds/options');
+  return Array.isArray(validated.obj) ? validated.obj : [];
+}
+
+export function useInboundOptions() {
+  return useQuery({
+    queryKey: keys.inbounds.options(),
+    queryFn: fetchInboundOptions,
+    staleTime: Infinity,
+  });
+}

+ 1 - 1
frontend/src/pages/clients/BulkAttachInboundsModal.tsx

@@ -36,7 +36,7 @@ export default function BulkAttachInboundsModal({
       .filter((ib) => MULTI_USER_PROTOCOLS.has((ib.protocol || '').toLowerCase()))
       .map((ib) => ({
         value: ib.id,
-        label: ib.tag,
+        label: ib.remark?.trim() || ib.tag || '',
       }));
   }, [inbounds]);
 

+ 1 - 1
frontend/src/pages/clients/BulkDetachInboundsModal.tsx

@@ -36,7 +36,7 @@ export default function BulkDetachInboundsModal({
       .filter((ib) => MULTI_USER_PROTOCOLS.has((ib.protocol || '').toLowerCase()))
       .map((ib) => ({
         value: ib.id,
-        label: ib.tag,
+        label: ib.remark?.trim() || ib.tag || '',
       }));
   }, [inbounds]);
 

+ 1 - 1
frontend/src/pages/clients/ClientBulkAddModal.tsx

@@ -100,7 +100,7 @@ export default function ClientBulkAddModal({
     () => (inbounds || [])
       .filter((ib) => MULTI_CLIENT_PROTOCOLS.has(ib.protocol || ''))
       .map((ib) => ({
-        label: ib.tag ?? '',
+        label: ib.remark?.trim() || ib.tag || '',
         value: ib.id,
       })),
     [inbounds],

+ 2 - 2
frontend/src/pages/clients/ClientFormModal.tsx

@@ -261,9 +261,9 @@ export default function ClientFormModal({
     () => (inbounds || [])
       .filter((ib) => MULTI_CLIENT_PROTOCOLS.has(ib.protocol || ''))
       .map((ib) => ({
-        label: ib.tag ?? '',
+        label: ib.remark?.trim() || ib.tag || '',
         value: ib.id,
-        title: ib.tag ?? '',
+        title: ib.remark?.trim() || ib.tag || '',
       })),
     [inbounds],
   );

+ 1 - 1
frontend/src/pages/clients/ClientInfoModal.tsx

@@ -382,7 +382,7 @@ export default function ClientInfoModal({
                         const ib = inboundsById[id];
                         const proto = (ib?.protocol || '').toLowerCase();
                         const color = INBOUND_PROTOCOL_COLORS[proto] ?? 'default';
-                        const label = ib?.tag ?? '';
+                        const label = ib?.remark?.trim() || ib?.tag || '';
                         return (
                           <Tooltip key={id} title={label}>
                             <Tag color={color}>{label}</Tag>

+ 2 - 2
frontend/src/pages/clients/ClientsPage.tsx

@@ -304,7 +304,7 @@ export default function ClientsPage() {
 
   function inboundLabel(id: number) {
     const ib = inboundsById[id];
-    return ib?.tag ?? '';
+    return ib?.remark?.trim() || ib?.tag || '';
   }
 
   const clientBucket = useCallback((row: ClientRecord | null | undefined): Bucket | null => {
@@ -694,7 +694,7 @@ export default function ClientsPage() {
           const ib = inboundsById[id];
           const proto = (ib?.protocol || '').toLowerCase();
           const color = INBOUND_PROTOCOL_COLORS[proto] ?? 'default';
-          const compactLabel = ib?.tag ?? '';
+          const compactLabel = ib?.remark?.trim() || ib?.tag || '';
           return (
             <Tooltip key={id} title={inboundLabel(id)}>
               <Tag color={color} style={{ margin: 2 }}>

+ 1 - 1
frontend/src/pages/clients/FilterDrawer.tsx

@@ -50,7 +50,7 @@ export default function FilterDrawer({
   const inboundOptions = useMemo(
     () => inbounds.map((ib) => ({
       value: ib.id,
-      label: ib.tag ?? '',
+      label: ib.remark?.trim() || ib.tag || '',
     })),
     [inbounds],
   );

+ 2 - 2
frontend/src/pages/inbounds/clients/AttachClientsModal.tsx

@@ -69,7 +69,7 @@ export default function AttachClientsModal({
     if (!source) return [];
     return (dbInbounds || [])
       .filter((ib) => ib.id !== source.id && isInboundMultiUser(ib))
-      .map((ib) => ({ value: ib.id, label: ib.tag ?? '' }));
+      .map((ib) => ({ value: ib.id, label: ib.remark?.trim() || ib.tag || '' }));
   }, [dbInbounds, source]);
 
   const filteredRows = useMemo(() => {
@@ -150,7 +150,7 @@ export default function AttachClientsModal({
       }}
       okText={t('pages.inbounds.attachClients')}
       cancelText={t('cancel')}
-      title={t('pages.inbounds.attachClientsTitle', { remark: source?.tag ?? '' })}
+      title={t('pages.inbounds.attachClientsTitle', { remark: source?.remark?.trim() || source?.tag || '' })}
       width={680}
     >
       {messageContextHolder}

+ 1 - 1
frontend/src/pages/inbounds/clients/AttachExistingClientsModal.tsx

@@ -170,7 +170,7 @@ export default function AttachExistingClientsModal({
       okButtonProps={{ disabled: selectedEmails.length === 0, loading: saving }}
       okText={t('pages.inbounds.attachClients')}
       cancelText={t('cancel')}
-      title={t('pages.inbounds.attachExistingTitle', { remark: target?.tag ?? '' })}
+      title={t('pages.inbounds.attachExistingTitle', { remark: target?.remark?.trim() || target?.tag || '' })}
       width={680}
     >
       {messageContextHolder}

+ 12 - 2
frontend/src/pages/xray/routing/RuleFormModal.tsx

@@ -1,8 +1,9 @@
-import { useEffect, useState } from 'react';
+import { useEffect, useMemo, useState } from 'react';
 import { useTranslation } from 'react-i18next';
 import { Button, Form, Input, Modal, Select, Space, Tooltip } from 'antd';
 import { PlusOutlined, MinusOutlined, QuestionCircleOutlined } from '@ant-design/icons';
 import { InputAddon } from '@/components/ui';
+import { useInboundOptions } from '@/api/queries/useInboundOptions';
 import { RuleFormSchema, type RuleFormValues } from '@/schemas/xray';
 
 export interface RoutingRule {
@@ -72,6 +73,15 @@ export default function RuleFormModal({
   const [form, setForm] = useState<FormState>(initialForm);
   const isEdit = rule != null;
 
+  const { data: inboundOptions } = useInboundOptions();
+  const remarkByTag = useMemo(() => {
+    const map: Record<string, string> = {};
+    for (const ib of inboundOptions || []) {
+      if (ib.tag) map[ib.tag] = ib.remark?.trim() || ib.tag;
+    }
+    return map;
+  }, [inboundOptions]);
+
   useEffect(() => {
     if (!open) return;
     if (rule) {
@@ -269,7 +279,7 @@ export default function RuleFormModal({
             mode="multiple"
             value={form.inboundTag}
             onChange={(v) => update('inboundTag', v)}
-            options={inboundTags.map((tag) => ({ value: tag, label: tag }))}
+            options={inboundTags.map((tag) => ({ value: tag, label: remarkByTag[tag] || tag }))}
           />
         </Form.Item>