'use client'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useMemo, useState } from 'react'; import { FileKey, Plus, RefreshCw, 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 { TlsCertificateItem } from '@/lib/services/openflare'; import { TlsCertificateService } from '@/lib/services/openflare'; import { formatDateTime } from '@/lib/utils'; import { CertificateApplyDialog } from '../websites/components/certificate-apply-dialog'; import { CertificateDetailDialog } from '../websites/components/certificate-detail-dialog'; import { CertificateEditorDialog } from '../websites/components/certificate-editor-dialog'; import { CertificateImportDialog } from '../websites/components/certificate-import-dialog'; import { WebsiteStatusBadge } from '../websites/components/status-badge'; import { getCertificateStatus, getErrorMessage, } from '../websites/components/website-utils'; const certificatesQueryKey = ['openflare', 'tls-certificates']; type CertificateApplyMode = 'edit-acme' | 'convert-upload'; export default function CertificatesPage() { const queryClient = useQueryClient(); const [importOpen, setImportOpen] = useState(false); const [applyOpen, setApplyOpen] = useState(false); const [detailOpen, setDetailOpen] = useState(false); const [editorOpen, setEditorOpen] = useState(false); const [deleteTarget, setDeleteTarget] = useState( null, ); const [selectedCertificateId, setSelectedCertificateId] = useState< number | null >(null); const [applyCertificate, setApplyCertificate] = useState(null); const [applyMode, setApplyMode] = useState('edit-acme'); const certificatesQuery = useQuery({ queryKey: certificatesQueryKey, queryFn: () => TlsCertificateService.list(), }); const deleteMutation = useMutation({ mutationFn: (id: number) => TlsCertificateService.deleteById(id), onSuccess: async () => { toast.success('证书已删除'); setDeleteTarget(null); await queryClient.invalidateQueries({ queryKey: certificatesQueryKey }); }, onError: (error) => toast.error(getErrorMessage(error)), }); const renewMutation = useMutation({ mutationFn: (id: number) => TlsCertificateService.renew(id), onSuccess: async (cert) => { toast.success(`证书 ${cert.name} 续期任务已提交`); await queryClient.invalidateQueries({ queryKey: certificatesQueryKey }); }, onError: (error) => toast.error(getErrorMessage(error)), }); const certificates = useMemo( () => certificatesQuery.data ?? [], [certificatesQuery.data], ); const handleOpenEditor = (certificate: TlsCertificateItem) => { if (certificate.provider === 'acme') { setApplyMode('edit-acme'); setApplyCertificate(certificate); setApplyOpen(true); } else { setSelectedCertificateId(certificate.id); setEditorOpen(true); } }; return (

证书

证书列表 展示证书有效期、备注和状态,支持查看详情、编辑内容或删除证书。 {certificatesQuery.isLoading ? ( ) : certificatesQuery.isError ? (
void certificatesQuery.refetch()} className='justify-center' />
) : certificates.length === 0 ? ( ) : (
{certificates.map((certificate) => { const status = getCertificateStatus(certificate); return (

{certificate.name}

生效:{formatDateTime(certificate.not_before)}

到期:{formatDateTime(certificate.not_after)}

来源: {certificate.provider === 'acme' ? 'ACME 申请' : '手动上传'}

{certificate.apply_status === 'applying' ? (

状态: {certificate.provider === 'upload' ? '转换申请中...' : '申请中...'}

) : null} {certificate.apply_status === 'error' ? (

状态: {certificate.provider === 'upload' ? '转换失败' : '申请失败'} ({certificate.apply_message})

) : null}

备注:{certificate.remark || '暂无备注'}

{certificate.provider === 'acme' ? ( ) : null}
); })}
)}
toast.success(`证书 ${certificate.name} 已导入`) } /> toast.success(`证书 ${certificate.name} 申请任务已提交`) } /> {applyCertificate ? ( { setApplyOpen(open); if (!open) setApplyCertificate(null); }} mode={applyMode} certificate={applyCertificate} onApplied={(certificate) => { setApplyCertificate(null); toast.success( applyMode === 'convert-upload' ? `证书 ${certificate.name} 转换申请已提交` : `证书 ${certificate.name} 配置已更新,重新申请中...`, ); }} /> ) : null} { setDetailOpen(false); const item = certificates.find((c) => c.id === selectedCertificateId); if (item) handleOpenEditor(item); }} onDelete={() => { const item = certificates.find((c) => c.id === selectedCertificateId); if (item) { setDetailOpen(false); setDeleteTarget(item); } }} deleting={deleteMutation.isPending} /> toast.success(`证书 ${certificate.name} 已更新`) } onConvert={(certificate) => { setEditorOpen(false); setApplyMode('convert-upload'); setApplyCertificate(certificate); setApplyOpen(true); }} /> !open && setDeleteTarget(null)} > 确认删除证书 确认删除证书 {deleteTarget?.name} 吗? 取消 deleteTarget && deleteMutation.mutate(deleteTarget.id) } > 删除
); }