'use client'; import Link from 'next/link'; import { useMemo, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useSearchParams } from 'next/navigation'; import { ArrowLeft, ChevronDown, ChevronRight, FileText, Loader2, Trash2, Upload, } from 'lucide-react'; import { toast } from 'sonner'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { EmptyStateWithBorder } from '@/components/layout/empty'; import { ErrorInline } from '@/components/layout/error'; import { LoadingStateWithBorder } from '@/components/layout/loading'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { type PagesDeployment, PagesService } from '@/lib/services/openflare'; import { formatDateTime } from '@/lib/utils'; import { DeploymentUploadDialog } from '../components/deployment-upload-dialog'; import { ProjectEditorDialog } from '../components/project-editor-dialog'; import { deploymentFilesQueryKey, deploymentsQueryKey, formatBytes, projectQueryKey, projectsQueryKey, } from '../components/pages-utils'; function DeploymentFilesPanel({ projectId, deployment, }: { projectId: number; deployment: PagesDeployment; }) { const filesQuery = useQuery({ queryKey: deploymentFilesQueryKey(projectId, deployment.id), queryFn: () => PagesService.listDeploymentFiles(deployment.id), }); if (filesQuery.isLoading) { return (

加载文件清单...

); } if (filesQuery.isError) { return (

{filesQuery.error instanceof Error ? filesQuery.error.message : '加载失败'}

); } const files = filesQuery.data ?? []; if (files.length === 0) { return (

暂无文件记录

); } return (
路径 大小 {files.map((file) => ( {file.path} {formatBytes(file.size)} ))}
); } export function PagesDetailPageClient() { const searchParams = useSearchParams(); const queryClient = useQueryClient(); const projectId = searchParams.get('id')?.trim() ?? ''; const parsedProjectId = Number(projectId); const [editorOpen, setEditorOpen] = useState(false); const [uploadOpen, setUploadOpen] = useState(false); const [expandedDeploymentId, setExpandedDeploymentId] = useState< number | null >(null); const [deleteProjectOpen, setDeleteProjectOpen] = useState(false); const [pendingDeploymentAction, setPendingDeploymentAction] = useState<{ type: 'activate' | 'delete'; deployment: PagesDeployment; } | null>(null); const enabled = projectId !== '' && Number.isFinite(parsedProjectId); const projectQuery = useQuery({ queryKey: projectQueryKey(projectId), queryFn: () => PagesService.getProject(parsedProjectId), enabled, }); const deploymentsQuery = useQuery({ queryKey: deploymentsQueryKey(parsedProjectId), queryFn: () => PagesService.listDeployments(parsedProjectId), enabled, }); const activateMutation = useMutation({ mutationFn: (deploymentId: number) => PagesService.activateDeployment(parsedProjectId, deploymentId), onSuccess: async () => { toast.success('部署已激活'); await Promise.all([ queryClient.invalidateQueries({ queryKey: deploymentsQueryKey(parsedProjectId), }), queryClient.invalidateQueries({ queryKey: projectQueryKey(projectId) }), queryClient.invalidateQueries({ queryKey: projectsQueryKey }), ]); setPendingDeploymentAction(null); }, onError: (error) => { toast.error(error instanceof Error ? error.message : '激活失败'); }, }); const deleteDeploymentMutation = useMutation({ mutationFn: (deploymentId: number) => PagesService.deleteDeployment(parsedProjectId, deploymentId), onSuccess: async () => { toast.success('部署已删除'); await Promise.all([ queryClient.invalidateQueries({ queryKey: deploymentsQueryKey(parsedProjectId), }), queryClient.invalidateQueries({ queryKey: projectQueryKey(projectId) }), queryClient.invalidateQueries({ queryKey: projectsQueryKey }), ]); setPendingDeploymentAction(null); }, onError: (error) => { toast.error(error instanceof Error ? error.message : '删除失败'); }, }); const deleteProjectMutation = useMutation({ mutationFn: () => PagesService.deleteProject(parsedProjectId), onSuccess: async () => { toast.success('项目已删除'); await queryClient.invalidateQueries({ queryKey: projectsQueryKey }); window.location.href = '/pages'; }, onError: (error) => { toast.error(error instanceof Error ? error.message : '删除失败'); }, }); const project = projectQuery.data; const deployments = useMemo( () => deploymentsQuery.data ?? [], [deploymentsQuery.data], ); if (!enabled) { return (
); } if (projectQuery.isLoading) { return (
); } if (projectQuery.isError) { return (
void projectQuery.refetch()} />
); } if (!project) { return (
); } return (

{project.name}

{project.slug} · {project.deployment_count} 个部署

状态

{project.enabled ? '已启用' : '已停用'}

当前激活

{project.active_deployment ? `#${project.active_deployment.deployment_number}` : '暂无'}

更新时间

{formatDateTime(project.updated_at)}

部署历史

部署不可变;激活后发布配置,Agent 才会拉取并切换静态资源。

{deploymentsQuery.isLoading ? ( ) : deployments.length === 0 ? ( ) : (
{deployments.map((deployment) => { const expanded = expandedDeploymentId === deployment.id; return (

#{deployment.deployment_number}

{deployment.status === 'active' ? ( 已激活 ) : null}

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

创建于 {formatDateTime(deployment.created_at)}

{expanded ? ( ) : null}
); })}
)}
删除 Pages 项目 确认删除项目 {project.name} 吗?此操作不可恢复。 取消 deleteProjectMutation.mutate()} > {deleteProjectMutation.isPending ? ( ) : ( '确认删除' )} { if (!open) setPendingDeploymentAction(null); }} > {pendingDeploymentAction?.type === 'activate' ? '激活部署' : '删除部署'} {pendingDeploymentAction?.type === 'activate' ? `确认激活部署 #${pendingDeploymentAction.deployment.deployment_number} 吗?` : `确认删除部署 #${pendingDeploymentAction?.deployment.deployment_number} 吗?`} 取消 { if (!pendingDeploymentAction) return; if (pendingDeploymentAction.type === 'activate') { activateMutation.mutate( pendingDeploymentAction.deployment.id, ); } else { deleteDeploymentMutation.mutate( pendingDeploymentAction.deployment.id, ); } }} > 确认
); }