'use client'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useState, type FormEvent } from 'react'; import { AppCard } from '@/components/ui/app-card'; import { activatePagesDeployment, createPagesProject, deletePagesDeployment, deletePagesProject, getPagesDeployments, getPagesProjects, uploadPagesDeployment, } from '@/features/pages/api/pages'; import type { PagesProject } from '@/features/pages/types'; import { DangerButton, PrimaryButton, ResourceField, ResourceInput, SecondaryButton, ToggleField, } from '@/features/shared/components/resource-primitives'; const projectsQueryKey = ['pages-projects']; function formatBytes(value: number) { if (value < 1024) { return `${value} B`; } if (value < 1024 * 1024) { return `${(value / 1024).toFixed(1)} KiB`; } return `${(value / 1024 / 1024).toFixed(1)} MiB`; } function formatDate(value?: string | null) { if (!value) { return '未激活'; } return new Date(value).toLocaleString(); } export function PagesPage() { const queryClient = useQueryClient(); const [name, setName] = useState(''); const [slug, setSlug] = useState(''); const [description, setDescription] = useState(''); const [spaFallbackEnabled, setSpaFallbackEnabled] = useState(false); const projectsQuery = useQuery({ queryKey: projectsQueryKey, queryFn: getPagesProjects, }); const createMutation = useMutation({ mutationFn: () => createPagesProject({ name, slug, description, enabled: true, spa_fallback_enabled: spaFallbackEnabled, }), onSuccess: () => { setName(''); setSlug(''); setDescription(''); setSpaFallbackEnabled(false); queryClient.invalidateQueries({ queryKey: projectsQueryKey }); }, }); function handleCreate(event: FormEvent) { event.preventDefault(); createMutation.mutate(); } return (

OpenFlare Pages

边缘静态站点托管

创建 Pages 项目,上传已构建的 zip 静态资源包,然后在规则中选择 Pages 项目作为上游。发布后 Agent 会拉取部署包并在边缘节点本地服务静态文件。

setName(event.target.value)} required /> setSlug(event.target.value)} /> setDescription(event.target.value)} />
{createMutation.isPending ? '创建中...' : '创建项目'}
{createMutation.error ? (

{createMutation.error.message}

) : null}
{projectsQuery.isLoading ? ( 正在加载 Pages 项目... ) : projectsQuery.error ? (

{projectsQuery.error.message}

) : (projectsQuery.data ?? []).length === 0 ? (

还没有 Pages 项目。先创建一个项目,再上传静态资源包。

) : ( (projectsQuery.data ?? []).map((project) => ( )) )}
); } function PagesProjectCard({ project }: { project: PagesProject }) { const queryClient = useQueryClient(); const [file, setFile] = useState(null); const [entryFile, setEntryFile] = useState('index.html'); const deploymentsQuery = useQuery({ queryKey: ['pages-deployments', project.id], queryFn: () => getPagesDeployments(project.id), }); const uploadMutation = useMutation({ mutationFn: () => { if (!file) { throw new Error('请选择 zip 文件'); } return uploadPagesDeployment(project.id, file, entryFile); }, onSuccess: () => { setFile(null); queryClient.invalidateQueries({ queryKey: ['pages-deployments', project.id], }); queryClient.invalidateQueries({ queryKey: projectsQueryKey }); }, }); const activateMutation = useMutation({ mutationFn: (deploymentId: number) => activatePagesDeployment(project.id, deploymentId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['pages-deployments', project.id], }); queryClient.invalidateQueries({ queryKey: projectsQueryKey }); }, }); const deleteDeploymentMutation = useMutation({ mutationFn: (deploymentId: number) => deletePagesDeployment(project.id, deploymentId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['pages-deployments', project.id], }); queryClient.invalidateQueries({ queryKey: projectsQueryKey }); }, }); const deleteProjectMutation = useMutation({ mutationFn: () => deletePagesProject(project.id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: projectsQueryKey }); }, }); return ( { if (window.confirm(`确认删除 Pages 项目 ${project.name} 吗?`)) { deleteProjectMutation.mutate(); } }} > 删除项目 } >

当前激活部署

{project.active_deployment ? `#${project.active_deployment.deployment_number} · ${project.active_deployment.checksum.slice(0, 12)}` : '暂无激活部署'}

激活时间:{formatDate(project.active_deployment?.activated_at)}

setFile(event.target.files?.[0] ?? null)} /> setEntryFile(event.target.value)} /> uploadMutation.mutate()} > {uploadMutation.isPending ? '上传中...' : '上传部署'} {uploadMutation.error ? (

{uploadMutation.error.message}

) : null}

部署历史

{deploymentsQuery.isLoading ? (

加载中...

) : (deploymentsQuery.data ?? []).length === 0 ? (

暂无部署。上传 zip 后再激活部署。

) : (
{(deploymentsQuery.data ?? []).map((deployment) => (

#{deployment.deployment_number}{' '} {deployment.status === 'active' ? '· 已激活' : ''}

{deployment.checksum.slice(0, 16)} ·{' '} {deployment.file_count} files ·{' '} {formatBytes(deployment.total_size)}

{ if ( window.confirm( `确认激活部署 #${deployment.deployment_number} 吗?`, ) ) { activateMutation.mutate(deployment.id); } }} > 激活 { if ( window.confirm( `确认删除部署 #${deployment.deployment_number} 吗?`, ) ) { deleteDeploymentMutation.mutate(deployment.id); } }} > 删除
))}
)}
); }