'use client'; import { useEffect } from 'react'; import { zodResolver } from '@hookform/resolvers/zod'; import { useQuery } from '@tanstack/react-query'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { Button } from '@/components/ui/button'; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, } from '@/components/ui/sheet'; import { Switch } from '@/components/ui/switch'; import type { ProxyRouteItem } from '@/lib/services/openflare'; import { ProxyRouteService, ZoneService, zoneQueryKey, } from '@/lib/services/openflare'; import { listAllZoneDomains, parseOriginUrl } from './helpers'; import { ZoneDomainSelector } from './zone-domain-selector'; const createProxyRouteSchema = z .object({ site_name: z.string().trim().max(255, '站点标识不能超过 255 个字符'), zone_domain_ids: z .array(z.number().int().positive()) .min(1, '请至少选择一个域名'), origin_url: z.string().trim().min(1, '请输入上游地址'), enabled: z.boolean(), }) .superRefine((value, context) => { try { const parsed = new URL(value.origin_url); if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { context.addIssue({ code: z.ZodIssueCode.custom, path: ['origin_url'], message: '上游地址必须以 http:// 或 https:// 开头', }); } } catch { context.addIssue({ code: z.ZodIssueCode.custom, path: ['origin_url'], message: '上游地址格式不合法', }); } }); type CreateProxyRouteFormValues = z.infer; const defaultValues: CreateProxyRouteFormValues = { site_name: '', zone_domain_ids: [], origin_url: '', enabled: true, }; interface ProxyRouteCreateSheetProps { open: boolean; onOpenChange: (open: boolean) => void; onCreated: (route: ProxyRouteItem) => void; } export function ProxyRouteCreateSheet({ open, onOpenChange, onCreated, }: ProxyRouteCreateSheetProps) { const form = useForm({ resolver: zodResolver(createProxyRouteSchema), defaultValues, }); const zonesQuery = useQuery({ queryKey: zoneQueryKey, queryFn: () => ZoneService.list(), enabled: open, }); const domainsQuery = useQuery({ queryKey: [...zoneQueryKey, 'all-domains'], queryFn: () => listAllZoneDomains(), enabled: open, }); useEffect(() => { if (!open) { form.reset(defaultValues); } }, [form, open]); const handleSubmit = form.handleSubmit(async (values) => { const origin = parseOriginUrl(values.origin_url.trim()); const selectedDomains = (domainsQuery.data ?? []).filter((domain) => values.zone_domain_ids.includes(domain.id), ); const primaryDomain = selectedDomains[0]?.domain ?? ''; const hasCert = selectedDomains.some((domain) => domain.cert_id != null); try { const route = await ProxyRouteService.create({ site_name: values.site_name.trim() || primaryDomain, zone_domain_ids: values.zone_domain_ids, origin_id: null, origin_url: values.origin_url.trim(), origin_scheme: origin.scheme, origin_address: origin.address, origin_port: origin.port, origin_uri: origin.uri, origin_host: '', upstreams: [], enabled: values.enabled, enable_https: hasCert, redirect_http: false, limit_conn_per_server: 0, limit_conn_per_ip: 0, limit_rate: '', cache_enabled: false, cache_policy: 'url', cache_rules: [], custom_headers: [], basic_auth_enabled: false, upstream_type: 'direct', }); form.reset(defaultValues); onOpenChange(false); onCreated(route); } catch (error) { form.setError('root', { message: error instanceof Error ? error.message : '创建失败,请稍后重试', }); } }); return ( 新建规则 从已注册的 Zone 域名中选择绑定关系,创建后可继续配置缓存和限流等高级选项。
( 站点标识 可选,留空时会自动使用首个域名。 )} /> ( 绑定域名 { await domainsQuery.refetch(); }} /> 勾选已登记域名,或使用「快捷新增域名」创建后自动勾选。 )} /> ( 上游地址 )} /> (
启用站点 关闭后会保留配置,但不会参与发布。
)} /> {form.formState.errors.root ? (

{form.formState.errors.root.message}

) : null}
); }