Sfoglia il codice sorgente

[Feature]: Add a tooltip/hint to the "Password" field in the client form clarifying which protocols use it (#5809)

* feat(clients): clarify which protocols use the Password and Hysteria Auth fields

Add tooltips to the Password and Hysteria Auth Form.Items in the client
form, explaining that Password is only consumed by Trojan and Shadowsocks
(ignored for VLESS, VMess, Hysteria, WireGuard) and that Hysteria Auth is
the credential Hysteria actually uses. Adds passwordDesc/hysteriaAuthDesc
keys to all 13 locale files, following the existing limitIpDesc/totalGBDesc
tooltip convention.

Closes #5803

* test(clients): assert Password/Hysteria Auth tooltip hints render
ecgang 8 ore fa
parent
commit
6e75938c61

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

@@ -791,7 +791,7 @@ export default function ClientFormModal({
                       </Space.Compact>
                     </Form.Item>
 
-                    <Form.Item label={t('pages.clients.password')}>
+                    <Form.Item label={t('pages.clients.password')} tooltip={t('pages.clients.passwordDesc')}>
                       <Space.Compact style={{ display: 'flex' }}>
                         <Input value={form.password} style={{ flex: 1 }} onChange={(e) => update('password', e.target.value)} />
                         <Button aria-label={t('regenerate')} icon={<ReloadOutlined />} onClick={regeneratePassword} />
@@ -805,7 +805,7 @@ export default function ClientFormModal({
                       </Space.Compact>
                     </Form.Item>
 
-                    <Form.Item label={t('pages.clients.hysteriaAuth')}>
+                    <Form.Item label={t('pages.clients.hysteriaAuth')} tooltip={t('pages.clients.hysteriaAuthDesc')}>
                       <Space.Compact style={{ display: 'flex' }}>
                         <Input value={form.auth} style={{ flex: 1 }} onChange={(e) => update('auth', e.target.value)} />
                         <Button aria-label={t('regenerate')} icon={<ReloadOutlined />} onClick={() => update('auth', RandomUtil.randomLowerAndNum(16))} />

+ 71 - 0
frontend/src/test/client-form-modal.test.tsx

@@ -0,0 +1,71 @@
+import { describe, it, expect, vi } from 'vitest';
+import { fireEvent, waitFor } from '@testing-library/react';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+
+import ClientFormModal from '@/pages/clients/ClientFormModal';
+import { renderWithProviders } from './test-utils';
+
+// ClientFormModal reads server state via react-query (useFail2banStatusQuery),
+// so it needs a QueryClientProvider on top of the shared ThemeProvider wrapper.
+function renderModal() {
+  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
+  renderWithProviders(
+    <QueryClientProvider client={queryClient}>
+      <ClientFormModal
+        open
+        mode="add"
+        client={null}
+        inbounds={[]}
+        save={vi.fn().mockResolvedValue(null)}
+        onOpenChange={() => {}}
+      />
+    </QueryClientProvider>,
+  );
+}
+
+function openCredentialsTab() {
+  const tab = Array.from(document.querySelectorAll('.ant-tabs-tab'))
+    .find((t) => (t.textContent ?? '').trim() === 'Credentials');
+  if (!tab) throw new Error('Credentials tab not found');
+  fireEvent.click(tab);
+}
+
+function tooltipIconForLabel(label: string): HTMLElement {
+  const labelEl = Array.from(document.querySelectorAll('.ant-form-item-label label'))
+    .find((l) => (l.textContent ?? '').trim() === label);
+  const item = labelEl?.closest('.ant-form-item') as HTMLElement | null;
+  if (!item) throw new Error(`Form item not found for label: ${label}`);
+  const tip = item.querySelector('.ant-form-item-tooltip') as HTMLElement | null;
+  if (!tip) throw new Error(`No tooltip on form item: ${label}`);
+  return tip;
+}
+
+describe('ClientFormModal credential tooltips', () => {
+  it('explains that the Password field is only consumed by Trojan/Shadowsocks', async () => {
+    renderModal();
+    openCredentialsTab();
+
+    const tip = tooltipIconForLabel('Password');
+    fireEvent.mouseEnter(tip);
+
+    await waitFor(() => {
+      expect(document.body.textContent).toContain(
+        'Only used by Trojan and Shadowsocks clients; ignored for VLESS, VMess, Hysteria, and WireGuard.',
+      );
+    });
+  });
+
+  it('explains that Hysteria Auth is the credential Hysteria actually uses', async () => {
+    renderModal();
+    openCredentialsTab();
+
+    const tip = tooltipIconForLabel('Hysteria Auth');
+    fireEvent.mouseEnter(tip);
+
+    await waitFor(() => {
+      expect(document.body.textContent).toContain(
+        'Credential used only by Hysteria clients. Trojan and Shadowsocks use the Password field instead.',
+      );
+    });
+  });
+});

+ 2 - 0
internal/web/translation/ar-EG.json

@@ -806,6 +806,7 @@
       "limitIpFail2banWindows": "Fail2ban غير متوفّر على نظام Windows، لذا لا يمكن تطبيق حد عناوين IP.",
       "limitIpDisabled": "ميزة حد عناوين IP معطّلة على هذا الخادم.",
       "password": "كلمة المرور",
+      "passwordDesc": "تُستخدم فقط من قبل عملاء Trojan و Shadowsocks؛ ويتم تجاهلها لـ VLESS و VMess و Hysteria و WireGuard.",
       "subId": "معرّف الاشتراك",
       "online": "متصل",
       "email": "البريد",
@@ -906,6 +907,7 @@
       "delOrphansConfirmContent": "يزيل كل عميل غير مرتبط بأي اتصال وارد مع سجل حركة مروره. لا يمكن التراجع.",
       "auth": "Auth",
       "hysteriaAuth": "Hysteria Auth",
+      "hysteriaAuthDesc": "بيانات اعتماد يستخدمها عملاء Hysteria فقط. أما Trojan و Shadowsocks فيستخدمان حقل «كلمة المرور» بدلاً منها.",
       "uuid": "UUID",
       "flow": "Flow",
       "vmessSecurity": "أمان VMess",

+ 2 - 0
internal/web/translation/en-US.json

@@ -806,6 +806,7 @@
       "limitIpFail2banWindows": "Fail2ban is not available on Windows, so the IP limit cannot be enforced.",
       "limitIpDisabled": "The IP limit feature is disabled on this server.",
       "password": "Password",
+      "passwordDesc": "Only used by Trojan and Shadowsocks clients; ignored for VLESS, VMess, Hysteria, and WireGuard.",
       "subId": "Subscription ID",
       "online": "Online",
       "email": "Email",
@@ -906,6 +907,7 @@
       "delOrphansConfirmContent": "Removes every client that is not attached to any inbound, along with its traffic record. This cannot be undone.",
       "auth": "Auth",
       "hysteriaAuth": "Hysteria Auth",
+      "hysteriaAuthDesc": "Credential used only by Hysteria clients. Trojan and Shadowsocks use the Password field instead.",
       "uuid": "UUID",
       "flow": "Flow",
       "vmessSecurity": "VMess Security",

+ 2 - 0
internal/web/translation/es-ES.json

@@ -806,6 +806,7 @@
       "limitIpFail2banWindows": "Fail2ban no está disponible en Windows, por lo que no se puede aplicar el límite de IP.",
       "limitIpDisabled": "La función de límite de IP está deshabilitada en este servidor.",
       "password": "Contraseña",
+      "passwordDesc": "Solo la usan los clientes Trojan y Shadowsocks; se ignora para VLESS, VMess, Hysteria y WireGuard.",
       "subId": "ID de suscripción",
       "online": "En línea",
       "email": "Email",
@@ -906,6 +907,7 @@
       "delOrphansConfirmContent": "Elimina todos los clientes que no están vinculados a ningún entrante, junto con su registro de tráfico. No se puede deshacer.",
       "auth": "Auth",
       "hysteriaAuth": "Hysteria Auth",
+      "hysteriaAuthDesc": "Credencial usada únicamente por los clientes Hysteria. Trojan y Shadowsocks usan el campo «Contraseña» en su lugar.",
       "uuid": "UUID",
       "flow": "Flow",
       "vmessSecurity": "Seguridad VMess",

+ 2 - 0
internal/web/translation/fa-IR.json

@@ -806,6 +806,7 @@
       "limitIpFail2banWindows": "Fail2ban روی ویندوز در دسترس نیست، بنابراین محدودیت IP قابل اعمال نیست.",
       "limitIpDisabled": "قابلیت محدودیت IP روی این سرور غیرفعال است.",
       "password": "رمز عبور",
+      "passwordDesc": "فقط توسط کلاینت‌های Trojan و Shadowsocks استفاده می‌شود؛ برای VLESS، VMess، Hysteria و WireGuard نادیده گرفته می‌شود.",
       "subId": "شناسه اشتراک",
       "online": "آنلاین",
       "email": "ایمیل",
@@ -906,6 +907,7 @@
       "delOrphansConfirmContent": "هر کلاینتی که به هیچ اینباندی متصل نیست، همراه با رکورد ترافیک‌اش حذف می‌شود. این عمل غیرقابل بازگشت است.",
       "auth": "احراز",
       "hysteriaAuth": "احراز Hysteria",
+      "hysteriaAuthDesc": "اعتباری که فقط کلاینت‌های Hysteria از آن استفاده می‌کنند. Trojan و Shadowsocks به‌جای آن از فیلد «رمز عبور» استفاده می‌کنند.",
       "uuid": "UUID",
       "flow": "Flow",
       "vmessSecurity": "امنیت VMess",

+ 2 - 0
internal/web/translation/id-ID.json

@@ -806,6 +806,7 @@
       "limitIpFail2banWindows": "Fail2ban tidak tersedia di Windows, sehingga batas IP tidak dapat diterapkan.",
       "limitIpDisabled": "Fitur batas IP dinonaktifkan di server ini.",
       "password": "Kata sandi",
+      "passwordDesc": "Hanya digunakan oleh klien Trojan dan Shadowsocks; diabaikan untuk VLESS, VMess, Hysteria, dan WireGuard.",
       "subId": "ID Langganan",
       "online": "Online",
       "email": "Email",
@@ -906,6 +907,7 @@
       "delOrphansConfirmContent": "Menghapus setiap klien yang tidak terhubung ke inbound mana pun, beserta catatan lalu lintasnya. Tidak dapat dibatalkan.",
       "auth": "Auth",
       "hysteriaAuth": "Hysteria Auth",
+      "hysteriaAuthDesc": "Kredensial yang hanya digunakan oleh klien Hysteria. Trojan dan Shadowsocks menggunakan kolom \"Kata sandi\" sebagai gantinya.",
       "uuid": "UUID",
       "flow": "Flow",
       "vmessSecurity": "Keamanan VMess",

+ 2 - 0
internal/web/translation/ja-JP.json

@@ -806,6 +806,7 @@
       "limitIpFail2banWindows": "Windows では Fail2ban を利用できないため、IP 制限を適用できません。",
       "limitIpDisabled": "このサーバーでは IP 制限機能が無効になっています。",
       "password": "パスワード",
+      "passwordDesc": "Trojan と Shadowsocks のクライアントのみが使用します。VLESS、VMess、Hysteria、WireGuard では無視されます。",
       "subId": "サブスクリプション ID",
       "online": "オンライン",
       "email": "メール",
@@ -906,6 +907,7 @@
       "delOrphansConfirmContent": "どのインバウンドにもアタッチされていないクライアントを、そのトラフィック記録とともにすべて削除します。元に戻せません。",
       "auth": "Auth",
       "hysteriaAuth": "Hysteria Auth",
+      "hysteriaAuthDesc": "Hysteria クライアントのみが使用する認証情報です。Trojan と Shadowsocks は代わりに「パスワード」フィールドを使用します。",
       "uuid": "UUID",
       "flow": "Flow",
       "vmessSecurity": "VMess セキュリティ",

+ 2 - 0
internal/web/translation/pt-BR.json

@@ -806,6 +806,7 @@
       "limitIpFail2banWindows": "O Fail2ban não está disponível no Windows, portanto o limite de IP não pode ser aplicado.",
       "limitIpDisabled": "O recurso de limite de IP está desativado neste servidor.",
       "password": "Senha",
+      "passwordDesc": "Usada apenas pelos clientes Trojan e Shadowsocks; ignorada para VLESS, VMess, Hysteria e WireGuard.",
       "subId": "ID da assinatura",
       "online": "Online",
       "email": "Email",
@@ -906,6 +907,7 @@
       "delOrphansConfirmContent": "Remove todos os clientes que não estão vinculados a nenhum inbound, junto com seu registro de tráfego. Não é possível desfazer.",
       "auth": "Auth",
       "hysteriaAuth": "Hysteria Auth",
+      "hysteriaAuthDesc": "Credencial usada apenas pelos clientes Hysteria. Trojan e Shadowsocks usam o campo \"Senha\" em vez disso.",
       "uuid": "UUID",
       "flow": "Flow",
       "vmessSecurity": "Segurança VMess",

+ 2 - 0
internal/web/translation/ru-RU.json

@@ -806,6 +806,7 @@
       "limitIpFail2banWindows": "Fail2ban недоступен в Windows, поэтому ограничение по IP не может быть применено.",
       "limitIpDisabled": "Функция ограничения по IP отключена на этом сервере.",
       "password": "Пароль",
+      "passwordDesc": "Используется только клиентами Trojan и Shadowsocks; игнорируется для VLESS, VMess, Hysteria и WireGuard.",
       "subId": "ID подписки",
       "online": "В сети",
       "email": "Email",
@@ -906,6 +907,7 @@
       "delOrphansConfirmContent": "Удаляются все клиенты, не привязанные ни к одному входящему, вместе с их записями трафика. Это действие нельзя отменить.",
       "auth": "Авторизация",
       "hysteriaAuth": "Hysteria Auth",
+      "hysteriaAuthDesc": "Учётные данные, используемые только клиентами Hysteria. Для Trojan и Shadowsocks используйте поле «Пароль».",
       "uuid": "UUID",
       "flow": "Flow",
       "vmessSecurity": "VMess Security",

+ 2 - 0
internal/web/translation/tr-TR.json

@@ -806,6 +806,7 @@
       "limitIpFail2banWindows": "Fail2ban Windows'ta kullanılamadığından IP sınırı uygulanamaz.",
       "limitIpDisabled": "IP sınırı özelliği bu sunucuda devre dışı.",
       "password": "Şifre",
+      "passwordDesc": "Yalnızca Trojan ve Shadowsocks istemcileri tarafından kullanılır; VLESS, VMess, Hysteria ve WireGuard için yok sayılır.",
       "subId": "Abonelik ID'si",
       "online": "Çevrimiçi",
       "email": "E-posta",
@@ -906,6 +907,7 @@
       "delOrphansConfirmContent": "Hiçbir gelen bağlantıya bağlı olmayan her kullanıcı, trafik kaydıyla birlikte silinir. Geri alınamaz.",
       "auth": "Auth",
       "hysteriaAuth": "Hysteria Auth",
+      "hysteriaAuthDesc": "Yalnızca Hysteria istemcilerinin kullandığı kimlik bilgisi. Trojan ve Shadowsocks bunun yerine \"Şifre\" alanını kullanır.",
       "uuid": "UUID",
       "flow": "Flow",
       "vmessSecurity": "VMess Güvenlik",

+ 2 - 0
internal/web/translation/uk-UA.json

@@ -806,6 +806,7 @@
       "limitIpFail2banWindows": "Fail2ban недоступний у Windows, тому обмеження за IP не може бути застосоване.",
       "limitIpDisabled": "Функцію обмеження за IP вимкнено на цьому сервері.",
       "password": "Пароль",
+      "passwordDesc": "Використовується лише клієнтами Trojan і Shadowsocks; ігнорується для VLESS, VMess, Hysteria та WireGuard.",
       "subId": "ID підписки",
       "online": "У мережі",
       "email": "Email",
@@ -906,6 +907,7 @@
       "delOrphansConfirmContent": "Видаляється кожен клієнт, не прив'язаний до жодного вхідного, разом із його записом трафіку. Цю дію неможливо скасувати.",
       "auth": "Авторизація",
       "hysteriaAuth": "Hysteria Auth",
+      "hysteriaAuthDesc": "Облікові дані, які використовують лише клієнти Hysteria. Для Trojan і Shadowsocks використовуйте поле «Пароль».",
       "uuid": "UUID",
       "flow": "Flow",
       "vmessSecurity": "Безпека VMess",

+ 2 - 0
internal/web/translation/vi-VN.json

@@ -806,6 +806,7 @@
       "limitIpFail2banWindows": "Fail2ban không khả dụng trên Windows nên không thể áp dụng giới hạn IP.",
       "limitIpDisabled": "Tính năng giới hạn IP đã bị tắt trên máy chủ này.",
       "password": "Mật khẩu",
+      "passwordDesc": "Chỉ được dùng bởi các client Trojan và Shadowsocks; bị bỏ qua đối với VLESS, VMess, Hysteria và WireGuard.",
       "subId": "ID đăng ký",
       "online": "Trực tuyến",
       "email": "Email",
@@ -906,6 +907,7 @@
       "delOrphansConfirmContent": "Gỡ tất cả khách hàng không được gắn vào bất kỳ inbound nào, cùng với bản ghi lưu lượng của họ. Không thể hoàn tác.",
       "auth": "Auth",
       "hysteriaAuth": "Hysteria Auth",
+      "hysteriaAuthDesc": "Thông tin xác thực chỉ dùng cho client Hysteria. Trojan và Shadowsocks dùng trường \"Mật khẩu\" thay thế.",
       "uuid": "UUID",
       "flow": "Flow",
       "vmessSecurity": "Bảo mật VMess",

+ 2 - 0
internal/web/translation/zh-CN.json

@@ -806,6 +806,7 @@
       "limitIpFail2banWindows": "Windows 上不支持 Fail2ban,无法实施 IP 限制。",
       "limitIpDisabled": "此服务器已禁用 IP 限制功能。",
       "password": "密码",
+      "passwordDesc": "仅 Trojan 和 Shadowsocks 客户端使用;VLESS、VMess、Hysteria 和 WireGuard 会忽略此项。",
       "subId": "订阅 ID",
       "online": "在线",
       "email": "邮箱",
@@ -906,6 +907,7 @@
       "delOrphansConfirmContent": "删除所有未关联到任何入站的客户端及其流量记录。该操作不可撤销。",
       "auth": "认证",
       "hysteriaAuth": "Hysteria 认证",
+      "hysteriaAuthDesc": "仅 Hysteria 客户端使用的凭据。Trojan 和 Shadowsocks 请改用上方的“密码”字段。",
       "uuid": "UUID",
       "flow": "Flow",
       "vmessSecurity": "VMess 加密",

+ 2 - 0
internal/web/translation/zh-TW.json

@@ -806,6 +806,7 @@
       "limitIpFail2banWindows": "Windows 上不支援 Fail2ban,無法實施 IP 限制。",
       "limitIpDisabled": "此伺服器已停用 IP 限制功能。",
       "password": "密碼",
+      "passwordDesc": "僅 Trojan 與 Shadowsocks 用戶端使用;VLESS、VMess、Hysteria 和 WireGuard 會忽略此項。",
       "subId": "訂閱 ID",
       "online": "上線",
       "email": "電子郵件",
@@ -906,6 +907,7 @@
       "delOrphansConfirmContent": "移除所有未關聯任何入站的客戶端,連同其流量紀錄一併刪除。此操作無法復原。",
       "auth": "認證",
       "hysteriaAuth": "Hysteria 認證",
+      "hysteriaAuthDesc": "僅 Hysteria 用戶端使用的憑證。Trojan 與 Shadowsocks 請改用上方的「密碼」欄位。",
       "uuid": "UUID",
       "flow": "Flow",
       "vmessSecurity": "VMess 加密",