'use client';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { 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 {
activateConfigVersion,
getConfigVersionDiff,
getConfigVersionPreview,
getConfigVersions,
publishConfigVersion,
} from '@/features/config-versions/api/config-versions';
import type {
ConfigDiffResult,
ConfigPreviewResult,
ConfigVersionItem,
SupportFile,
} from '@/features/config-versions/types';
import {
CodeBlock,
PrimaryButton,
SecondaryButton,
} from '@/features/shared/components/resource-primitives';
import { formatDateTime } from '@/lib/utils/date';
const versionsQueryKey = ['config-versions'];
type FeedbackState = {
tone: 'info' | 'success' | 'danger';
message: string;
};
function getErrorMessage(error: unknown) {
return error instanceof Error ? error.message : '请求失败,请稍后重试。';
}
function truncateChecksum(checksum: string) {
if (!checksum) {
return '—';
}
return checksum.length > 16 ? `${checksum.slice(0, 16)}...` : checksum;
}
function DiffList({ title, items }: { title: string; items: string[] }) {
return (
{title}
0 ? 'info' : 'warning'} />
{items.length > 0 ? (
{items.map((item) => (
{item}
))}
) : (
当前无相关变更。
)}
);
}
function SupportFilesList({ files }: { files: SupportFile[] }) {
if (files.length === 0) {
return 当前发布不需要额外支持文件。
;
}
return (
{files.map((file) => (
{file.path}
{file.content}
))}
);
}
function VersionDetailCard({
version,
}: {
version: ConfigVersionItem;
}) {
return (
}
>
Checksum
{version.checksum}
创建人
{version.created_by || '系统'}
创建时间
{formatDateTime(version.created_at)}
快照 JSON
{version.snapshot_json}
渲染结果
{version.rendered_config}
);
}
function PublishPreviewCard({
preview,
diff,
isPublishing,
onConfirm,
onCancel,
}: {
preview: ConfigPreviewResult;
diff: ConfigDiffResult;
isPublishing: boolean;
onConfirm: () => void;
onCancel: () => void;
}) {
return (
}
>
当前激活版本
{diff.active_version || '无'}
新增域名
{diff.added_domains.length}
删除域名
{diff.removed_domains.length}
修改域名
{diff.modified_domains.length}
渲染结果
Checksum:{preview.checksum}
{preview.rendered_config}
{isPublishing ? '发布中...' : '确认发布'}
取消预览
);
}
export function ConfigVersionsPage() {
const queryClient = useQueryClient();
const [feedback, setFeedback] = useState(null);
const [selectedVersion, setSelectedVersion] = useState(null);
const [publishPreview, setPublishPreview] = useState<{
preview: ConfigPreviewResult;
diff: ConfigDiffResult;
} | null>(null);
const [isPreviewLoading, setIsPreviewLoading] = useState(false);
const versionsQuery = useQuery({
queryKey: versionsQueryKey,
queryFn: getConfigVersions,
});
const publishMutation = useMutation({
mutationFn: publishConfigVersion,
onSuccess: async (version) => {
setFeedback({ tone: 'success', message: `发布成功,版本 ${version.version}` });
setPublishPreview(null);
setSelectedVersion(version);
await queryClient.invalidateQueries({ queryKey: versionsQueryKey });
},
onError: (error) => {
setFeedback({ tone: 'danger', message: getErrorMessage(error) });
},
});
const activateMutation = useMutation({
mutationFn: activateConfigVersion,
onSuccess: async (version) => {
setFeedback({ tone: 'success', message: `已激活版本 ${version.version}` });
setSelectedVersion(version);
await queryClient.invalidateQueries({ queryKey: versionsQueryKey });
},
onError: (error) => {
setFeedback({ tone: 'danger', message: getErrorMessage(error) });
},
});
const handleOpenPublishPreview = async () => {
setFeedback(null);
setIsPreviewLoading(true);
try {
const [preview, diff] = await Promise.all([getConfigVersionPreview(), getConfigVersionDiff()]);
setPublishPreview({ preview, diff });
} catch (error) {
setFeedback({ tone: 'danger', message: getErrorMessage(error) });
} finally {
setIsPreviewLoading(false);
}
};
const handleActivate = (version: ConfigVersionItem) => {
if (version.is_active) {
return;
}
if (!window.confirm(`确认激活版本 ${version.version} 吗?`)) {
return;
}
setFeedback(null);
activateMutation.mutate(version.id);
};
const versions = versionsQuery.data || [];
return (
{isPreviewLoading ? '加载预览中...' : '预览并发布'}
}
/>
{feedback ? : null}
{publishPreview ? (
publishMutation.mutate()}
onCancel={() => setPublishPreview(null)}
/>
) : (
1. 使用“预览并发布”查看新增、删除和修改的域名清单。
2. 确认渲染出的 Nginx 配置与支持文件内容符合预期。
3. 若需要回滚,可在下方历史版本列表中重新激活旧版本。
)}
{versionsQuery.isLoading ? (
) : versionsQuery.isError ? (
) : versions.length === 0 ? (
) : (
| 版本号 |
状态 |
创建人 |
Checksum |
创建时间 |
操作 |
{versions.map((version) => (
| {version.version} |
|
{version.created_by || '系统'} |
{truncateChecksum(version.checksum)}
|
{formatDateTime(version.created_at)}
|
setSelectedVersion(version)}
className='px-3 py-2 text-xs'
>
查看快照
{!version.is_active ? (
handleActivate(version)}
disabled={activateMutation.isPending}
className='px-3 py-2 text-xs'
>
重新激活
) : null}
|
))}
)}
{selectedVersion ? : null}
);
}