mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 00:26:37 +08:00
158 lines
5.5 KiB
TypeScript
158 lines
5.5 KiB
TypeScript
'use client';
|
|
|
|
import { useQuery } from '@tanstack/react-query';
|
|
|
|
import { EmptyState } from '@/components/feedback/empty-state';
|
|
import { ErrorState } from '@/components/feedback/error-state';
|
|
import { LoadingState } from '@/components/feedback/loading-state';
|
|
import { AppModal } from '@/components/ui/app-modal';
|
|
import { StatusBadge } from '@/components/ui/status-badge';
|
|
import { getTlsCertificate } from '@/features/tls-certificates/api/tls-certificates';
|
|
import { getCertificateStatus, getErrorMessage } from '@/features/websites/utils';
|
|
import {
|
|
CodeBlock,
|
|
DangerButton,
|
|
PrimaryButton,
|
|
SecondaryButton,
|
|
} from '@/features/shared/components/resource-primitives';
|
|
import { formatDateTime } from '@/lib/utils/date';
|
|
|
|
interface CertificateDetailModalProps {
|
|
certificateId: number | null;
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
onEdit: () => void;
|
|
onDelete: () => void;
|
|
deleting?: boolean;
|
|
}
|
|
|
|
export function CertificateDetailModal({
|
|
certificateId,
|
|
isOpen,
|
|
onClose,
|
|
onEdit,
|
|
onDelete,
|
|
deleting = false,
|
|
}: CertificateDetailModalProps) {
|
|
const certificateQuery = useQuery({
|
|
queryKey: ['tls-certificates', 'detail', certificateId],
|
|
queryFn: () => getTlsCertificate(certificateId as number),
|
|
enabled: isOpen && certificateId !== null,
|
|
});
|
|
|
|
const certificate = certificateQuery.data;
|
|
const status = certificate ? getCertificateStatus(certificate) : null;
|
|
|
|
return (
|
|
<AppModal
|
|
isOpen={isOpen}
|
|
onClose={onClose}
|
|
title="证书详情"
|
|
description="查看证书元信息、备注以及当前保存的 PEM 内容。"
|
|
size="xl"
|
|
footer={
|
|
<div className="flex flex-wrap justify-end gap-3">
|
|
<SecondaryButton type="button" onClick={onClose}>
|
|
关闭
|
|
</SecondaryButton>
|
|
<PrimaryButton
|
|
type="button"
|
|
onClick={onEdit}
|
|
disabled={!certificate}
|
|
>
|
|
编辑证书
|
|
</PrimaryButton>
|
|
<DangerButton
|
|
type="button"
|
|
onClick={onDelete}
|
|
disabled={!certificate || deleting}
|
|
>
|
|
{deleting ? '删除中...' : '删除证书'}
|
|
</DangerButton>
|
|
</div>
|
|
}
|
|
>
|
|
{certificateQuery.isLoading ? (
|
|
<LoadingState />
|
|
) : certificateQuery.isError ? (
|
|
<ErrorState
|
|
title="证书详情加载失败"
|
|
description={getErrorMessage(certificateQuery.error)}
|
|
/>
|
|
) : !certificate ? (
|
|
<EmptyState
|
|
title="证书不存在"
|
|
description="当前证书可能已被删除。"
|
|
/>
|
|
) : (
|
|
<div className="space-y-6">
|
|
<div className="grid gap-4 md:grid-cols-2">
|
|
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
|
|
<p className="text-xs uppercase tracking-[0.18em] text-[var(--foreground-muted)]">
|
|
证书名称
|
|
</p>
|
|
<p className="mt-2 text-sm text-[var(--foreground-primary)]">
|
|
{certificate.name}
|
|
</p>
|
|
</div>
|
|
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
|
|
<p className="text-xs uppercase tracking-[0.18em] text-[var(--foreground-muted)]">
|
|
状态
|
|
</p>
|
|
<div className="mt-2">
|
|
{status ? (
|
|
<StatusBadge label={status.label} variant={status.variant} />
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
|
|
<p className="text-xs uppercase tracking-[0.18em] text-[var(--foreground-muted)]">
|
|
生效时间
|
|
</p>
|
|
<p className="mt-2 text-sm text-[var(--foreground-primary)]">
|
|
{formatDateTime(certificate.not_before)}
|
|
</p>
|
|
</div>
|
|
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
|
|
<p className="text-xs uppercase tracking-[0.18em] text-[var(--foreground-muted)]">
|
|
到期时间
|
|
</p>
|
|
<p className="mt-2 text-sm text-[var(--foreground-primary)]">
|
|
{formatDateTime(certificate.not_after)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
|
|
<p className="text-xs uppercase tracking-[0.18em] text-[var(--foreground-muted)]">
|
|
备注
|
|
</p>
|
|
<p className="mt-2 text-sm leading-6 text-[var(--foreground-primary)]">
|
|
{certificate.remark || '暂无备注'}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<div>
|
|
<p className="mb-2 text-sm font-medium text-[var(--foreground-primary)]">
|
|
证书 PEM
|
|
</p>
|
|
<CodeBlock className="max-h-56 overflow-y-auto whitespace-pre-wrap break-all">
|
|
{certificate.cert_pem}
|
|
</CodeBlock>
|
|
</div>
|
|
<div>
|
|
<p className="mb-2 text-sm font-medium text-[var(--foreground-primary)]">
|
|
私钥 PEM
|
|
</p>
|
|
<CodeBlock className="max-h-56 overflow-y-auto whitespace-pre-wrap break-all">
|
|
{certificate.key_pem}
|
|
</CodeBlock>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</AppModal>
|
|
);
|
|
}
|