fix(waf): UA 检查说明改为问号悬浮提示

将屏蔽/匹配相关 FieldDescription 收敛为 CircleHelp Tooltip。
This commit is contained in:
ryan
2026-07-19 11:52:29 +08:00
parent 1d97ea69d0
commit 74106474ca
2 changed files with 77 additions and 64 deletions
@@ -25,9 +25,9 @@ it('hides match and block until UA check is enabled', () => {
const { rerender } = render( const { rerender } = render(
<NodeProperties node={node} ipGroups={[]} onChange={onChange} />, <NodeProperties node={node} ipGroups={[]} onChange={onChange} />,
); );
expect(screen.queryByLabelText('屏蔽常见爬虫 UA')).not.toBeInTheDocument(); expect(screen.queryByRole('switch', { name: /屏蔽常见爬虫/ })).not.toBeInTheDocument();
expect(screen.queryByLabelText('浏览器')).not.toBeInTheDocument(); expect(screen.queryByLabelText('浏览器')).not.toBeInTheDocument();
fireEvent.click(screen.getByLabelText('开启 UA 检查')); fireEvent.click(screen.getByRole('switch', { name: /开启 UA 检查/ }));
expect(onChange).toHaveBeenCalledWith( expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
config: expect.objectContaining({ require_ua: true }), config: expect.objectContaining({ require_ua: true }),
@@ -40,11 +40,10 @@ it('hides match and block until UA check is enabled', () => {
onChange={onChange} onChange={onChange}
/>, />,
); );
expect(screen.getByLabelText('屏蔽常见爬虫 UA')).toBeInTheDocument(); expect(screen.getByRole('switch', { name: /屏蔽常见爬虫/ })).toBeInTheDocument();
expect(screen.getByLabelText('屏蔽非正常 UA')).toBeInTheDocument(); expect(screen.getByRole('switch', { name: /屏蔽非正常/ })).toBeInTheDocument();
expect(screen.getByLabelText('屏蔽自定义 UA')).toBeInTheDocument(); expect(screen.getByRole('switch', { name: /屏蔽自定义/ })).toBeInTheDocument();
expect(screen.getByText(/浏览器或操作系统分类为 Bot/)).toBeInTheDocument(); expect(screen.getAllByLabelText('说明').length).toBeGreaterThan(0);
expect(screen.getByText(/浏览器分类为 Other 或 Unknown/)).toBeInTheDocument();
}); });
it('edits display name for configurable nodes', () => { it('edits display name for configurable nodes', () => {
@@ -1,4 +1,4 @@
import { Settings2 } from 'lucide-react'; import { CircleHelp, Settings2 } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@@ -27,6 +27,11 @@ import {
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
import { Switch } from '@/components/ui/switch'; import { Switch } from '@/components/ui/switch';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import type { WAFIPGroup, WAFRuleNode } from '@/lib/services/openflare'; import type { WAFIPGroup, WAFRuleNode } from '@/lib/services/openflare';
import { countryOptions, regionOptions, type GeoOption } from './geo-options'; import { countryOptions, regionOptions, type GeoOption } from './geo-options';
@@ -149,14 +154,13 @@ function PropertyFields({
orientation='horizontal' orientation='horizontal'
className='items-center justify-between' className='items-center justify-between'
> >
<div className='space-y-1'> <FieldLabel
<FieldLabel htmlFor={`${node.id}-require-ua`}> htmlFor={`${node.id}-require-ua`}
开启 UA 检查 className='flex items-center gap-1.5'
</FieldLabel> >
<FieldDescription> 开启 UA 检查
开启后如果请求头不携带 UA 将返回 False <FieldHelp tip='开启后如果请求头不携带 UA 将返回 False' />
</FieldDescription> </FieldLabel>
</div>
<Switch <Switch
id={`${node.id}-require-ua`} id={`${node.id}-require-ua`}
checked={node.config.require_ua} checked={node.config.require_ua}
@@ -170,30 +174,23 @@ function PropertyFields({
<> <>
<Separator /> <Separator />
<div className='space-y-3'> <div className='space-y-3'>
<div className='space-y-1'> <p className='flex items-center gap-1.5 text-xs font-medium text-muted-foreground'>
<p className='text-xs font-medium text-muted-foreground'> 屏蔽
屏蔽 <FieldHelp tip='命中返回 false,优先级高于匹配' />
</p> </p>
<FieldDescription>
命中返回 false,优先级高于匹配
</FieldDescription>
</div>
<Field <Field
orientation='horizontal' orientation='horizontal'
className='items-start justify-between gap-3' className='items-center justify-between gap-3'
> >
<div className='space-y-1'> <FieldLabel
<FieldLabel htmlFor={`${node.id}-block-bots`}> htmlFor={`${node.id}-block-bots`}
屏蔽常见爬虫 UA className='flex items-center gap-1.5'
</FieldLabel> >
<FieldDescription> 屏蔽常见爬虫 UA
浏览器或操作系统分类为 Bot(含 bot / spider / crawler / <FieldHelp tip='浏览器或操作系统分类为 Bot(含 bot / spider / crawler / slurp 等特征,如 Googlebot)' />
slurp 等特征,如 Googlebot) </FieldLabel>
</FieldDescription>
</div>
<Switch <Switch
id={`${node.id}-block-bots`} id={`${node.id}-block-bots`}
className='mt-0.5'
checked={node.config.block_common_bots} checked={node.config.block_common_bots}
onCheckedChange={(block_common_bots) => onCheckedChange={(block_common_bots) =>
onChange({ onChange({
@@ -205,20 +202,17 @@ function PropertyFields({
</Field> </Field>
<Field <Field
orientation='horizontal' orientation='horizontal'
className='items-start justify-between gap-3' className='items-center justify-between gap-3'
> >
<div className='space-y-1'> <FieldLabel
<FieldLabel htmlFor={`${node.id}-block-abnormal`}> htmlFor={`${node.id}-block-abnormal`}
屏蔽非正常 UA className='flex items-center gap-1.5'
</FieldLabel> >
<FieldDescription> 屏蔽非正常 UA
浏览器分类为 Other 或 Unknown(不含搜索引擎等爬虫 <FieldHelp tip='浏览器分类为 Other 或 Unknown(不含搜索引擎等爬虫 Bot,爬虫请用上方开关)' />
Bot,爬虫请用上方开关) </FieldLabel>
</FieldDescription>
</div>
<Switch <Switch
id={`${node.id}-block-abnormal`} id={`${node.id}-block-abnormal`}
className='mt-0.5'
checked={node.config.block_abnormal_ua} checked={node.config.block_abnormal_ua}
onCheckedChange={(block_abnormal_ua) => onCheckedChange={(block_abnormal_ua) =>
onChange({ onChange({
@@ -230,19 +224,17 @@ function PropertyFields({
</Field> </Field>
<Field <Field
orientation='horizontal' orientation='horizontal'
className='items-start justify-between gap-3' className='items-center justify-between gap-3'
> >
<div className='space-y-1'> <FieldLabel
<FieldLabel htmlFor={`${node.id}-block-custom`}> htmlFor={`${node.id}-block-custom`}
屏蔽自定义 UA className='flex items-center gap-1.5'
</FieldLabel> >
<FieldDescription> 屏蔽自定义 UA
原始 User-Agent 命中任一条正则时返回 false(Lua 模式语法) <FieldHelp tip='原始 User-Agent 命中任一条正则时返回 false(Lua 模式语法)' />
</FieldDescription> </FieldLabel>
</div>
<Switch <Switch
id={`${node.id}-block-custom`} id={`${node.id}-block-custom`}
className='mt-0.5'
checked={node.config.block_custom_ua} checked={node.config.block_custom_ua}
onCheckedChange={(block_custom_ua) => onCheckedChange={(block_custom_ua) =>
onChange({ onChange({
@@ -254,8 +246,12 @@ function PropertyFields({
</Field> </Field>
{node.config.block_custom_ua && ( {node.config.block_custom_ua && (
<Field> <Field>
<FieldLabel htmlFor={`${node.id}-custom-patterns`}> <FieldLabel
htmlFor={`${node.id}-custom-patterns`}
className='flex items-center gap-1.5'
>
自定义 UA 正则 自定义 UA 正则
<FieldHelp tip='每行一条 Lua 模式正则,命中任一条即 false;最多 32 条' />
</FieldLabel> </FieldLabel>
<Textarea <Textarea
id={`${node.id}-custom-patterns`} id={`${node.id}-custom-patterns`}
@@ -275,9 +271,6 @@ function PropertyFields({
}) })
} }
/> />
<FieldDescription>
每行一条 Lua 模式正则,命中任一条即 false;最多 32 条
</FieldDescription>
</Field> </Field>
)} )}
</div> </div>
@@ -287,8 +280,12 @@ function PropertyFields({
UA 匹配 UA 匹配
</p> </p>
<Field> <Field>
<FieldLabel htmlFor={`${node.id}-match-mode`}> <FieldLabel
htmlFor={`${node.id}-match-mode`}
className='flex items-center gap-1.5'
>
匹配模式 匹配模式
<FieldHelp tip='浏览器与操作系统两侧都有选择时生效' />
</FieldLabel> </FieldLabel>
<Select <Select
value={node.config.match_mode} value={node.config.match_mode}
@@ -312,9 +309,6 @@ function PropertyFields({
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>
</Select> </Select>
<FieldDescription>
浏览器与操作系统两侧都有选择时生效
</FieldDescription>
</Field> </Field>
<MultiSelect <MultiSelect
id={`${node.id}-browsers`} id={`${node.id}-browsers`}
@@ -431,6 +425,26 @@ function PropertyFields({
); );
} }
function FieldHelp({ tip }: { tip: string }) {
return (
<Tooltip>
<TooltipTrigger asChild>
<button
type='button'
className='inline-flex size-4 shrink-0 items-center justify-center text-muted-foreground transition-colors hover:text-foreground'
aria-label='说明'
onClick={(event) => event.preventDefault()}
>
<CircleHelp className='size-3.5' />
</button>
</TooltipTrigger>
<TooltipContent side='top' className='max-w-56 text-xs'>
{tip}
</TooltipContent>
</Tooltip>
);
}
function DisplayNameField({ function DisplayNameField({
node, node,
onChange, onChange,