fix(waf): UA 检查开启后才显示匹配与屏蔽并补充说明

未开启 require_ua 时隐藏匹配/屏蔽区块;爬虫与非正常 UA 开关增加分类提示。
This commit is contained in:
ryan
2026-07-19 11:38:18 +08:00
parent 28eef0bbcd
commit 7476c86976
2 changed files with 147 additions and 102 deletions
@@ -5,7 +5,7 @@ import type { WAFIPGroup, WAFRuleNode } from '@/lib/services/openflare';
import { NodeProperties } from './node-properties'; import { NodeProperties } from './node-properties';
it('toggles UA check switches and match mode', () => { it('hides match and block until UA check is enabled', () => {
const node: WAFRuleNode = { const node: WAFRuleNode = {
id: 'ua', id: 'ua',
type: 'ua_check', type: 'ua_check',
@@ -20,17 +20,31 @@ it('toggles UA check switches and match mode', () => {
}, },
}; };
const onChange = vi.fn(); const onChange = vi.fn();
render(<NodeProperties node={node} ipGroups={[]} onChange={onChange} />); const { rerender } = render(
<NodeProperties node={node} ipGroups={[]} onChange={onChange} />,
);
expect(screen.queryByLabelText('屏蔽常见爬虫 UA')).not.toBeInTheDocument();
expect(screen.queryByLabelText('浏览器')).not.toBeInTheDocument();
fireEvent.click(screen.getByLabelText('开启 UA 检查')); fireEvent.click(screen.getByLabelText('开启 UA 检查'));
expect(onChange).toHaveBeenCalledWith( expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
config: expect.objectContaining({ require_ua: true }), config: expect.objectContaining({ require_ua: true }),
}), }),
); );
rerender(
<NodeProperties
node={{ ...node, config: { ...node.config, require_ua: true } }}
ipGroups={[]}
onChange={onChange}
/>,
);
expect(screen.getByLabelText('屏蔽常见爬虫 UA')).toBeInTheDocument(); expect(screen.getByLabelText('屏蔽常见爬虫 UA')).toBeInTheDocument();
expect(screen.getByLabelText('屏蔽非正常 UA')).toBeInTheDocument(); expect(screen.getByLabelText('屏蔽非正常 UA')).toBeInTheDocument();
expect( expect(
screen.getByText('命中返回 false,优先级高于匹配'), screen.getByText(/浏览器或操作系统分类为 Bot/),
).toBeInTheDocument();
expect(
screen.getByText(/浏览器分类为 Bot、Other 或 Unknown/),
).toBeInTheDocument(); ).toBeInTheDocument();
}); });
@@ -154,7 +154,7 @@ function PropertyFields({
开启 UA 检查 开启 UA 检查
</FieldLabel> </FieldLabel>
<FieldDescription> <FieldDescription>
开启后如果请求头不携带 UA 返回 False 开启后如果请求头不携带 UA 返回 False;并可配置匹配与屏蔽
</FieldDescription> </FieldDescription>
</div> </div>
<Switch <Switch
@@ -166,18 +166,30 @@ function PropertyFields({
/> />
</Field> </Field>
</div> </div>
{node.config.require_ua && (
<>
<Separator /> <Separator />
<div className='space-y-3'> <div className='space-y-3'>
<p className='text-xs font-medium text-muted-foreground'>UA 匹配</p> <p className='text-xs font-medium text-muted-foreground'>
UA 匹配
</p>
<Field> <Field>
<FieldLabel htmlFor={`${node.id}-match-mode`}>匹配模式</FieldLabel> <FieldLabel htmlFor={`${node.id}-match-mode`}>
匹配模式
</FieldLabel>
<Select <Select
value={node.config.match_mode} value={node.config.match_mode}
onValueChange={(match_mode: 'and' | 'or') => onValueChange={(match_mode: 'and' | 'or') =>
onChange({ ...node, config: { ...node.config, match_mode } }) onChange({
...node,
config: { ...node.config, match_mode },
})
} }
> >
<SelectTrigger id={`${node.id}-match-mode`} className='w-full'> <SelectTrigger
id={`${node.id}-match-mode`}
className='w-full'
>
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -224,18 +236,29 @@ function PropertyFields({
<Separator /> <Separator />
<div className='space-y-3'> <div className='space-y-3'>
<div className='space-y-1'> <div className='space-y-1'>
<p className='text-xs font-medium text-muted-foreground'>屏蔽</p> <p className='text-xs font-medium text-muted-foreground'>
<FieldDescription>命中返回 false,优先级高于匹配</FieldDescription> 屏蔽
</p>
<FieldDescription>
命中返回 false,优先级高于匹配
</FieldDescription>
</div> </div>
<Field <Field
orientation='horizontal' orientation='horizontal'
className='items-center justify-between' className='items-start justify-between gap-3'
> >
<div className='space-y-1'>
<FieldLabel htmlFor={`${node.id}-block-bots`}> <FieldLabel htmlFor={`${node.id}-block-bots`}>
屏蔽常见爬虫 UA 屏蔽常见爬虫 UA
</FieldLabel> </FieldLabel>
<FieldDescription>
浏览器或操作系统分类为 Bot(含 bot / spider / crawler /
slurp 等特征,如 Googlebot)
</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({
@@ -247,13 +270,19 @@ function PropertyFields({
</Field> </Field>
<Field <Field
orientation='horizontal' orientation='horizontal'
className='items-center justify-between' className='items-start justify-between gap-3'
> >
<div className='space-y-1'>
<FieldLabel htmlFor={`${node.id}-block-abnormal`}> <FieldLabel htmlFor={`${node.id}-block-abnormal`}>
屏蔽非正常 UA 屏蔽非正常 UA
</FieldLabel> </FieldLabel>
<FieldDescription>
浏览器分类为 Bot、Other 或 Unknown(无法识别为已知浏览器)
</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({
@@ -264,6 +293,8 @@ function PropertyFields({
/> />
</Field> </Field>
</div> </div>
</>
)}
</FieldGroup> </FieldGroup>
); );
if (node.type === 'pow') if (node.type === 'pow')