'use client'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { AlertTriangle, Cloud, Plus, RefreshCw, Settings, Trash2, } from 'lucide-react'; import Link from 'next/link'; import { useState } from 'react'; import { toast } from 'sonner'; import { GroupDialog } from '@/app/(main)/cloudflare/components/group-dialog'; import { SyncTasksPanel } from '@/app/(main)/cloudflare/components/sync-tasks-panel'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card'; import { EmptyStateWithBorder } from '@/components/layout/empty'; import { ErrorInline } from '@/components/layout/error'; import { LoadingStateWithBorder } from '@/components/layout/loading'; import { CloudflareService, cloudflareQueryKey, NodeService, type CloudflareGroup, type CloudflareGroupPayload, } from '@/lib/services/openflare'; import { getErrorMessage } from '../websites/components/website-utils'; export default function CloudflarePage() { const queryClient = useQueryClient(); const [createOpen, setCreateOpen] = useState(false); const [deleteTarget, setDeleteTarget] = useState( null, ); const overviewQuery = useQuery({ queryKey: [...cloudflareQueryKey, 'overview'], queryFn: () => CloudflareService.getOverview(), }); const groupsQuery = useQuery({ queryKey: [...cloudflareQueryKey, 'groups'], queryFn: () => CloudflareService.listGroups(), }); const nodesQuery = useQuery({ queryKey: ['openflare', 'nodes'], queryFn: () => NodeService.listNodes(), }); const invalidate = async () => queryClient.invalidateQueries({ queryKey: cloudflareQueryKey }); const createMutation = useMutation({ mutationFn: (payload: CloudflareGroupPayload) => CloudflareService.createGroup(payload), onSuccess: async () => { toast.success('指向分组已创建'); setCreateOpen(false); await invalidate(); }, onError: (error) => toast.error(getErrorMessage(error)), }); const syncMutation = useMutation({ mutationFn: (id: number) => CloudflareService.syncGroup(id), onSuccess: async () => { toast.success('分组同步任务已入队'); await queryClient.invalidateQueries({ queryKey: [...cloudflareQueryKey, 'sync-executions'], }); }, onError: (error) => toast.error(getErrorMessage(error)), }); const deleteMutation = useMutation({ mutationFn: (id: number) => CloudflareService.deleteGroup(id), onSuccess: async () => { toast.success('指向分组及远端记录已删除'); setDeleteTarget(null); await invalidate(); }, onError: (error) => toast.error(getErrorMessage(error)), }); const overview = overviewQuery.data; return (

Cloudflare

{overviewQuery.isLoading ? ( ) : overviewQuery.isError ? ( void overviewQuery.refetch()} /> ) : !overview?.connection.ready ? ( Cloudflare 连接尚未就绪 请先导入现有 Cloudflare DNS 账号,或录入独立 API Token 并完成连接测试。 ) : null}

指向分组

管理 Cloudflare A 记录对应的节点与域名成员。

{groupsQuery.isLoading ? ( ) : groupsQuery.isError ? ( void groupsQuery.refetch()} /> ) : (groupsQuery.data ?? []).length === 0 ? ( setCreateOpen(true)} /> ) : (
{(groupsQuery.data ?? []).map((group) => (
{group.name} {group.active_node.name} · {group.active_node.ip}
{group.enabled ? '已启用' : '已停用'}

成员 {group.member_count} 个 · 新成员默认 {group.default_proxied ? '开启' : '关闭'}橙云

))}
)}
createMutation.mutate(payload)} /> !open && setDeleteTarget(null)} > 删除指向分组 将删除 {deleteTarget?.name} 的全部成员及本模块管理的远端 A 记录。此操作不可撤销。 取消 deleteTarget && deleteMutation.mutate(deleteTarget.id) } > 确认删除
); }