'use client'; import {useEffect, useMemo, useState} from 'react'; import {zodResolver} from '@hookform/resolvers/zod'; import {Search} from 'lucide-react'; import {useForm} from 'react-hook-form'; import {z} from 'zod'; import {Button} from '@/components/ui/button'; import {Checkbox} from '@/components/ui/checkbox'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import {Input} from '@/components/ui/input'; import {Label} from '@/components/ui/label'; import {Textarea} from '@/components/ui/textarea'; import {cn} from '@/lib/utils'; import type {WAFIPGroup} from '@/lib/services/openflare'; import {type CountryOption, normalizeItems, type RuleDimension, type RuleListType, textToList,} from './helpers'; const ruleEntrySchema = z .object({ listType: z.enum(['whitelist', 'blacklist']), dimension: z.enum(['ip', 'ip_group', 'country']), ipValue: z.string(), ipGroupIDs: z.array(z.number()), countryValues: z.array(z.string()), }) .superRefine((value, context) => { if (value.dimension === 'ip') { if (textToList(value.ipValue).length === 0) { context.addIssue({ code: z.ZodIssueCode.custom, path: ['ipValue'], message: '请输入至少一个 IP 或 IP 段', }); } return; } if (value.dimension === 'ip_group') { if (value.ipGroupIDs.length === 0) { context.addIssue({ code: z.ZodIssueCode.custom, path: ['ipGroupIDs'], message: '请选择至少一个 IP 组', }); } return; } if (normalizeItems(value.countryValues).length === 0) { context.addIssue({ code: z.ZodIssueCode.custom, path: ['countryValues'], message: '请选择至少一个国家/地区', }); } }); export type RuleEntryFormValues = z.infer; const defaultRuleEntryValues: RuleEntryFormValues = { listType: 'blacklist', dimension: 'ip', ipValue: '', ipGroupIDs: [], countryValues: [], }; interface RuleEntryDialogProps { open: boolean; countryOptions: CountryOption[]; ipGroups: WAFIPGroup[]; onOpenChange: (open: boolean) => void; onSubmit: (values: RuleEntryFormValues) => void; } export function RuleEntryDialog({ open, countryOptions, ipGroups, onOpenChange, onSubmit, }: RuleEntryDialogProps) { const [keyword, setKeyword] = useState(''); const form = useForm({ resolver: zodResolver(ruleEntrySchema), defaultValues: defaultRuleEntryValues, }); const listType = form.watch('listType'); const dimension = form.watch('dimension'); const ipValue = form.watch('ipValue'); const ipGroupIDs = form.watch('ipGroupIDs'); const countryValues = form.watch('countryValues'); useEffect(() => { if (!open) return; form.reset(defaultRuleEntryValues); setKeyword(''); }, [form, open]); useEffect(() => { if (!open) return; setKeyword(''); }, [dimension, open]); const selectedCountrySet = useMemo( () => new Set(countryValues), [countryValues], ); const selectedIPGroupSet = useMemo( () => new Set(ipGroupIDs), [ipGroupIDs], ); const filteredCountries = useMemo(() => { const normalized = keyword.trim().toLowerCase(); return countryOptions .filter((option) => !normalized || option.searchText.includes(normalized)) .sort((left, right) => { const leftSelected = selectedCountrySet.has(left.code) ? 1 : 0; const rightSelected = selectedCountrySet.has(right.code) ? 1 : 0; return rightSelected - leftSelected || left.code.localeCompare(right.code); }); }, [countryOptions, keyword, selectedCountrySet]); const toggleCountry = (code: string) => { const values = selectedCountrySet.has(code) ? countryValues.filter((item) => item !== code) : normalizeItems([...countryValues, code]); form.setValue('countryValues', values, { shouldValidate: true }); }; const toggleIPGroup = (id: number) => { const values = selectedIPGroupSet.has(id) ? ipGroupIDs.filter((item) => item !== id) : [...ipGroupIDs, id].sort((left, right) => left - right); form.setValue('ipGroupIDs', values, { shouldValidate: true }); }; const typeLabel = listType === 'blacklist' ? '黑名单' : '白名单'; const dimensionLabel = dimension === 'ip' ? 'IP' : dimension === 'ip_group' ? 'IP 组' : '地域'; const handleSubmit = form.handleSubmit((values) => { onSubmit(values); }); return ( 添加{typeLabel}规则 当前准备新增 {dimensionLabel} 维度的{typeLabel}项。
{[ { value: 'blacklist', label: '黑名单' }, { value: 'whitelist', label: '白名单' }, ].map((option) => ( ))}
{[ { value: 'ip', label: 'IP' }, { value: 'ip_group', label: 'IP 组' }, { value: 'country', label: '地域' }, ].map((option) => ( ))}
{dimension === 'ip' ? (