[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
This commit is contained in:
ecgang
2026-07-07 05:43:09 -07:00
committed by GitHub
parent ad7a0f8164
commit 6e75938c61
15 changed files with 99 additions and 2 deletions

View File

@@ -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))} />

View File

@@ -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.',
);
});
});
});