'use client'; import Link from 'next/link'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useMemo, useState } from 'react'; import { EmptyState } from '@/components/feedback/empty-state'; import { ErrorState } from '@/components/feedback/error-state'; import { InlineMessage } from '@/components/feedback/inline-message'; import { LoadingState } from '@/components/feedback/loading-state'; import { PageHeader } from '@/components/layout/page-header'; import { AppCard } from '@/components/ui/app-card'; import { StatusBadge } from '@/components/ui/status-badge'; import { deleteTlsCertificate, getTlsCertificates, } from '@/features/tls-certificates/api/tls-certificates'; import type { TlsCertificateItem } from '@/features/tls-certificates/types'; import { CertificateDetailModal } from '@/features/websites/components/certificate-detail-modal'; import { CertificateEditorModal } from '@/features/websites/components/certificate-editor-modal'; import { CertificateImportModal } from '@/features/websites/components/certificate-import-modal'; import { getCertificateStatus, getErrorMessage } from '@/features/websites/utils'; import { DangerButton, PrimaryButton, SecondaryButton, } from '@/features/shared/components/resource-primitives'; import { formatDateTime } from '@/lib/utils/date'; type FeedbackState = { tone: 'info' | 'success' | 'danger'; message: string; }; const certificatesQueryKey = ['tls-certificates', 'list'] as const; export function TlsCertificatesPage() { const queryClient = useQueryClient(); const [feedback, setFeedback] = useState(null); const [isImportOpen, setIsImportOpen] = useState(false); const [selectedCertificateId, setSelectedCertificateId] = useState< number | null >(null); const [isDetailOpen, setIsDetailOpen] = useState(false); const [isEditorOpen, setIsEditorOpen] = useState(false); const certificatesQuery = useQuery({ queryKey: certificatesQueryKey, queryFn: getTlsCertificates, }); const deleteCertificateMutation = useMutation({ mutationFn: deleteTlsCertificate, onSuccess: async () => { setFeedback({ tone: 'success', message: '证书已删除。' }); await queryClient.invalidateQueries({ queryKey: ['tls-certificates'] }); }, onError: (error) => { setFeedback({ tone: 'danger', message: getErrorMessage(error) }); }, }); const certificates = useMemo( () => certificatesQuery.data ?? [], [certificatesQuery.data], ); const handleDeleteCertificate = (certificate: TlsCertificateItem) => { if (!window.confirm(`确认删除证书 ${certificate.name} 吗?`)) { return; } setFeedback(null); deleteCertificateMutation.mutate(certificate.id); }; const handleOpenCertificateDetail = (certificate: TlsCertificateItem) => { setSelectedCertificateId(certificate.id); setIsDetailOpen(true); }; const handleOpenCertificateEditor = (certificate: TlsCertificateItem) => { setSelectedCertificateId(certificate.id); setIsEditorOpen(true); }; return ( <>
返回网站 void queryClient.invalidateQueries({ queryKey: ['tls-certificates'], }) } > 刷新证书 setIsImportOpen(true)}> 添加证书
} /> {feedback ? ( ) : null} {certificatesQuery.isLoading ? ( ) : certificatesQuery.isError ? ( ) : certificates.length === 0 ? ( ) : (
{certificates.map((certificate) => { const status = getCertificateStatus(certificate); return (

{certificate.name}

生效:{formatDateTime(certificate.not_before)}

到期:{formatDateTime(certificate.not_after)}

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

handleOpenCertificateDetail(certificate)} className="px-3 py-2 text-xs" > 查看 handleOpenCertificateEditor(certificate)} className="px-3 py-2 text-xs" > 编辑 handleDeleteCertificate(certificate)} disabled={deleteCertificateMutation.isPending} className="px-3 py-2 text-xs" > 删除
); })}
)}
{isImportOpen ? ( setIsImportOpen(false)} onImported={(certificate) => { setFeedback({ tone: 'success', message: `证书 ${certificate.name} 已导入。`, }); }} /> ) : null} {isDetailOpen ? ( setIsDetailOpen(false)} onEdit={() => { setIsDetailOpen(false); setIsEditorOpen(true); }} onDelete={() => { const certificate = certificates.find( (item) => item.id === selectedCertificateId, ); if (certificate) { setIsDetailOpen(false); handleDeleteCertificate(certificate); } }} deleting={deleteCertificateMutation.isPending} /> ) : null} {isEditorOpen ? ( setIsEditorOpen(false)} onSaved={(certificate) => { setFeedback({ tone: 'success', message: `证书 ${certificate.name} 已更新。`, }); }} /> ) : null} ); }