'use client'; import Link from 'next/link'; import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; import {useMemo, useState} from 'react'; import {Globe, Plus, Trash2} from 'lucide-react'; import {toast} from 'sonner'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; 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 type {ManagedDomainItem} from '@/lib/services/openflare'; import {TlsCertificateService, WebsiteService} from '@/lib/services/openflare'; import {CertificateImportDialog} from './components/certificate-import-dialog'; import {WebsiteStatusBadge} from './components/status-badge'; import {WebsiteEditorDialog} from './components/website-editor-dialog'; import {buildCertificateLabel, getErrorMessage, getMatchTypeMeta,} from './components/website-utils'; const domainsQueryKey = ['openflare', 'managed-domains']; const certificatesQueryKey = ['openflare', 'tls-certificates']; export default function WebsitesPage() { const queryClient = useQueryClient(); const [editorOpen, setEditorOpen] = useState(false); const [importOpen, setImportOpen] = useState(false); const [preferredCertificateId, setPreferredCertificateId] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); const domainsQuery = useQuery({ queryKey: domainsQueryKey, queryFn: () => WebsiteService.list(), }); const certificatesQuery = useQuery({ queryKey: certificatesQueryKey, queryFn: () => TlsCertificateService.list(), }); const deleteMutation = useMutation({ mutationFn: (id: number) => WebsiteService.deleteById(id), onSuccess: async () => { toast.success('网站已删除'); setDeleteTarget(null); await queryClient.invalidateQueries({queryKey: domainsQueryKey}); }, onError: (error) => toast.error(getErrorMessage(error)), }); const domains = useMemo(() => domainsQuery.data ?? [], [domainsQuery.data]); const certificates = useMemo( () => certificatesQuery.data ?? [], [certificatesQuery.data], ); const certificateMap = useMemo( () => new Map(certificates.map((item) => [item.id, item])), [certificates], ); return (

网站

网站列表 查看网站绑定的证书、启用状态和更新时间。 {domainsQuery.isLoading ? ( ) : domainsQuery.isError ? (
void domainsQuery.refetch()} className="justify-center" />
) : domains.length === 0 ? ( ) : (
{domains.map((domain) => { const certificate = domain.cert_id ? (certificateMap.get(domain.cert_id) ?? null) : null; const matchType = getMatchTypeMeta(domain.domain); return (

{domain.domain}

{domain.remark || '暂无备注'}

绑定证书: {certificate ? buildCertificateLabel(certificate) : '未绑定证书'}

); })}
)}
setImportOpen(true)} onSaved={(_, mode) => { setPreferredCertificateId(null); toast.success(mode === 'create' ? '网站已创建' : '网站已更新'); }} /> { setPreferredCertificateId(certificate.id); toast.success(`证书 ${certificate.name} 已导入,可直接用于当前网站`); }} /> !open && setDeleteTarget(null)} > 确认删除网站 确认删除网站 {deleteTarget?.domain} 吗?此操作不可撤销。 取消 deleteTarget && deleteMutation.mutate(deleteTarget.id)} > 删除
); }