'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 {Textarea} from '@/components/ui/textarea'; 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(), remark: z.string().max(255, '备注不能超过 255 个字符'), }) .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, remark: '', }; 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, remark: values.remark.trim(), upstream_type: 'direct', }); form.reset(defaultValues); onOpenChange(false); onCreated(route); } catch (error) { form.setError('root', { message: error instanceof Error ? error.message : '创建失败,请稍后重试', }); } }); return ( 新建规则 从已注册的 Zone 域名中选择绑定关系,创建后可继续配置缓存和限流等高级选项。
( 站点标识 可选,留空时会自动使用首个域名。 )} /> ( 绑定域名 域名与证书在 Zone 中维护;此处只选择本站点要绑定的 FQDN。 )} /> ( 上游地址 )} /> (
启用站点 关闭后会保留配置,但不会参与发布。
)} /> ( 备注