'use client'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { ArrowLeft, Cloud, Loader2, Plus, RefreshCw, Settings, Trash2, } from 'lucide-react'; import Link from 'next/link'; import { useParams } from 'next/navigation'; import { useState } from 'react'; import { toast } from 'sonner'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card'; import { Switch } from '@/components/ui/switch'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { ErrorInline } from '@/components/layout/error'; import { LoadingStateWithBorder } from '@/components/layout/loading'; import { CloudflareService, cloudflareQueryKey, NodeService, type CloudflareGroupPayload, } from '@/lib/services/openflare'; import { getErrorMessage } from '../../../websites/components/website-utils'; import { GroupDialog } from '../../components/group-dialog'; import { MemberAddDialog } from '../../components/member-add-dialog'; export function CloudflareGroupDetailPageClient() { const params = useParams<{ id: string }>(); const groupID = Number(params.id); const queryClient = useQueryClient(); const [editOpen, setEditOpen] = useState(false); const [addOpen, setAddOpen] = useState(false); const detailQuery = useQuery({ queryKey: [...cloudflareQueryKey, 'groups', groupID], queryFn: () => CloudflareService.getGroup(groupID), enabled: Number.isInteger(groupID) && groupID > 0, refetchInterval: 5000, }); const domainsQuery = useQuery({ queryKey: [...cloudflareQueryKey, 'domains', 'available'], queryFn: () => CloudflareService.listAvailableDomains(), }); const nodesQuery = useQuery({ queryKey: ['openflare', 'nodes'], queryFn: () => NodeService.listNodes(), }); const invalidate = async () => queryClient.invalidateQueries({ queryKey: cloudflareQueryKey }); const updateGroupMutation = useMutation({ mutationFn: (payload: CloudflareGroupPayload) => CloudflareService.updateGroup(groupID, payload), onSuccess: async () => { toast.success('分组配置已更新'); setEditOpen(false); await invalidate(); }, onError: (error) => toast.error(getErrorMessage(error)), }); const addMutation = useMutation({ mutationFn: async ({ domainIDs, proxied, }: { domainIDs: number[]; proxied: boolean; }) => { const results = await Promise.allSettled( domainIDs.map((domainID) => CloudflareService.createMember(groupID, { zone_domain_id: domainID, proxied, }), ), ); const failed = results.filter((r) => r.status === 'rejected'); const succeeded = results.length - failed.length; return { succeeded, failed: failed.length, total: results.length }; }, onSuccess: async ({ succeeded, failed, total }) => { if (failed === 0) { toast.success( total === 1 ? '域名已加入并排队同步' : `已添加 ${succeeded} 个域名并排队同步`, ); } else if (succeeded === 0) { toast.error(`添加失败:${failed} 个域名未能加入`); } else { toast.warning(`部分成功:${succeeded} 个已加入,${failed} 个失败`); } setAddOpen(false); await invalidate(); }, onError: (error) => toast.error(getErrorMessage(error)), }); const proxiedMutation = useMutation({ mutationFn: ({ memberID, proxied, }: { memberID: number; proxied: boolean; }) => CloudflareService.updateMember(groupID, memberID, proxied), onSuccess: async () => { toast.success('橙云设置已更新并排队同步'); await invalidate(); }, onError: (error) => toast.error(getErrorMessage(error)), }); const syncMutation = useMutation({ mutationFn: (memberID: number) => CloudflareService.syncMember(groupID, memberID), onSuccess: () => toast.success('成员同步任务已入队'), onError: (error) => toast.error(getErrorMessage(error)), }); const removeMutation = useMutation({ mutationFn: (memberID: number) => CloudflareService.removeMember(groupID, memberID), onSuccess: async () => { toast.success('成员及远端 A 记录已删除'); await invalidate(); }, onError: (error) => toast.error(getErrorMessage(error)), }); if (detailQuery.isLoading) return (
暂无域名成员。
) : (