mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-11 01:36:37 +08:00
[功能] 添加获取配置版本详情的API,优化配置版本管理逻辑
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"openflare/service"
|
"openflare/service"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetConfigVersions godoc
|
// GetConfigVersions godoc
|
||||||
@@ -30,6 +31,39 @@ func GetConfigVersions(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetConfigVersion godoc
|
||||||
|
// @Summary Get config version detail
|
||||||
|
// @Tags ConfigVersions
|
||||||
|
// @Produce json
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Param id path int true "Version ID"
|
||||||
|
// @Success 200 {object} map[string]interface{}
|
||||||
|
// @Failure 400 {object} map[string]interface{}
|
||||||
|
// @Router /api/config-versions/{id} [get]
|
||||||
|
func GetConfigVersion(c *gin.Context) {
|
||||||
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||||
|
if err != nil || id == 0 {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "invalid id",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
version, err := service.GetConfigVersionDetail(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
|
"message": "",
|
||||||
|
"data": version,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// GetActiveConfigVersion godoc
|
// GetActiveConfigVersion godoc
|
||||||
// @Summary Get active config version
|
// @Summary Get active config version
|
||||||
// @Tags ConfigVersions
|
// @Tags ConfigVersions
|
||||||
@@ -137,7 +171,7 @@ func ActivateConfigVersion(c *gin.Context) {
|
|||||||
if err != nil || id == 0 {
|
if err != nil || id == 0 {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": "无效的参数",
|
"message": "invalid id",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,15 @@ package model
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
type ConfigVersionSummary struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Checksum string `json:"checksum"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
CreatedBy string `json:"created_by"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
type ConfigVersion struct {
|
type ConfigVersion struct {
|
||||||
ID uint `json:"id" gorm:"primaryKey"`
|
ID uint `json:"id" gorm:"primaryKey"`
|
||||||
Version string `json:"version" gorm:"uniqueIndex;size:32;not null"`
|
Version string `json:"version" gorm:"uniqueIndex;size:32;not null"`
|
||||||
@@ -15,8 +24,11 @@ type ConfigVersion struct {
|
|||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListConfigVersions() (versions []*ConfigVersion, err error) {
|
func ListConfigVersionSummaries() (versions []*ConfigVersionSummary, err error) {
|
||||||
err = DB.Order("id desc").Find(&versions).Error
|
err = DB.Model(&ConfigVersion{}).
|
||||||
|
Select("id", "version", "checksum", "is_active", "created_by", "created_at").
|
||||||
|
Order("id desc").
|
||||||
|
Find(&versions).Error
|
||||||
return versions, err
|
return versions, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
configVersionRoute.GET("/active", controller.GetActiveConfigVersion)
|
configVersionRoute.GET("/active", controller.GetActiveConfigVersion)
|
||||||
configVersionRoute.GET("/preview", controller.PreviewConfigVersion)
|
configVersionRoute.GET("/preview", controller.PreviewConfigVersion)
|
||||||
configVersionRoute.GET("/diff", controller.DiffConfigVersion)
|
configVersionRoute.GET("/diff", controller.DiffConfigVersion)
|
||||||
|
configVersionRoute.GET("/:id", controller.GetConfigVersion)
|
||||||
configVersionRoute.POST("/publish", controller.PublishConfigVersion)
|
configVersionRoute.POST("/publish", controller.PublishConfigVersion)
|
||||||
configVersionRoute.POST("/:id/activate", controller.ActivateConfigVersion)
|
configVersionRoute.POST("/:id/activate", controller.ActivateConfigVersion)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,11 +127,33 @@ func TestPhase1PublishLifecycle(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resp = performJSONRequest(t, engine, token, http.MethodGet, "/api/config-versions/", nil)
|
resp = performJSONRequest(t, engine, token, http.MethodGet, "/api/config-versions/", nil)
|
||||||
var versions []model.ConfigVersion
|
var versions []map[string]any
|
||||||
decodeResponseData(t, resp, &versions)
|
decodeResponseData(t, resp, &versions)
|
||||||
if len(versions) != 2 {
|
if len(versions) != 2 {
|
||||||
t.Fatalf("expected 2 versions, got %d", len(versions))
|
t.Fatalf("expected 2 versions, got %d", len(versions))
|
||||||
}
|
}
|
||||||
|
if _, ok := versions[0]["snapshot_json"]; ok {
|
||||||
|
t.Fatal("expected config version list to omit snapshot_json")
|
||||||
|
}
|
||||||
|
if _, ok := versions[0]["main_config"]; ok {
|
||||||
|
t.Fatal("expected config version list to omit main_config")
|
||||||
|
}
|
||||||
|
if _, ok := versions[0]["rendered_config"]; ok {
|
||||||
|
t.Fatal("expected config version list to omit rendered_config")
|
||||||
|
}
|
||||||
|
if _, ok := versions[0]["support_files_json"]; ok {
|
||||||
|
t.Fatal("expected config version list to omit support_files_json")
|
||||||
|
}
|
||||||
|
|
||||||
|
detailResp := performJSONRequest(t, engine, token, http.MethodGet, "/api/config-versions/"+toString(version2.ID), nil)
|
||||||
|
var versionDetail model.ConfigVersion
|
||||||
|
decodeResponseData(t, detailResp, &versionDetail)
|
||||||
|
if versionDetail.ID != version2.ID {
|
||||||
|
t.Fatalf("expected config version detail %d, got %d", version2.ID, versionDetail.ID)
|
||||||
|
}
|
||||||
|
if versionDetail.SnapshotJSON == "" || versionDetail.MainConfig == "" || versionDetail.RenderedConfig == "" {
|
||||||
|
t.Fatal("expected config version detail endpoint to include full payload")
|
||||||
|
}
|
||||||
|
|
||||||
activeResp := performJSONRequest(t, engine, token, http.MethodGet, "/api/config-versions/active", nil)
|
activeResp := performJSONRequest(t, engine, token, http.MethodGet, "/api/config-versions/active", nil)
|
||||||
var activeVersion model.ConfigVersion
|
var activeVersion model.ConfigVersion
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ type ConfigPreviewResult struct {
|
|||||||
RouteCount int `json:"route_count"`
|
RouteCount int `json:"route_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConfigVersionSummary = model.ConfigVersionSummary
|
||||||
|
|
||||||
|
type ConfigVersionDetail = model.ConfigVersion
|
||||||
|
|
||||||
type ConfigDiffResult struct {
|
type ConfigDiffResult struct {
|
||||||
ActiveVersion string `json:"active_version,omitempty"`
|
ActiveVersion string `json:"active_version,omitempty"`
|
||||||
AddedDomains []string `json:"added_domains"`
|
AddedDomains []string `json:"added_domains"`
|
||||||
@@ -158,11 +162,15 @@ var requiredMainConfigTemplatePlaceholders = []string{
|
|||||||
"{{OpenRestyRouteConfigInclude}}",
|
"{{OpenRestyRouteConfigInclude}}",
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListConfigVersions() ([]*model.ConfigVersion, error) {
|
func ListConfigVersions() ([]*ConfigVersionSummary, error) {
|
||||||
return model.ListConfigVersions()
|
return model.ListConfigVersionSummaries()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetActiveConfigVersion() (*model.ConfigVersion, error) {
|
func GetConfigVersionDetail(id uint) (*ConfigVersionDetail, error) {
|
||||||
|
return model.GetConfigVersionByID(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetActiveConfigVersion() (*ConfigVersionDetail, error) {
|
||||||
return model.GetActiveConfigVersion()
|
return model.GetActiveConfigVersion()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
import { apiRequest } from '@/lib/api/client';
|
import { apiRequest } from '@/lib/api/client';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ConfigDiffResult,
|
ConfigDiffResult,
|
||||||
ConfigPreviewResult,
|
ConfigPreviewResult,
|
||||||
ConfigVersionItem,
|
ConfigVersionDetail,
|
||||||
} from '@/features/config-versions/types';
|
ConfigVersionSummary,
|
||||||
|
} from '@/features/config-versions/types';
|
||||||
export function getConfigVersions() {
|
|
||||||
return apiRequest<ConfigVersionItem[]>('/config-versions/');
|
export function getConfigVersions() {
|
||||||
}
|
return apiRequest<ConfigVersionSummary[]>('/config-versions/');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getConfigVersion(id: number) {
|
||||||
|
return apiRequest<ConfigVersionDetail>(`/config-versions/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getActiveConfigVersion() {
|
||||||
|
return apiRequest<ConfigVersionDetail>('/config-versions/active');
|
||||||
|
}
|
||||||
|
|
||||||
export function getConfigVersionPreview() {
|
export function getConfigVersionPreview() {
|
||||||
return apiRequest<ConfigPreviewResult>('/config-versions/preview');
|
return apiRequest<ConfigPreviewResult>('/config-versions/preview');
|
||||||
@@ -18,14 +27,14 @@ export function getConfigVersionDiff() {
|
|||||||
return apiRequest<ConfigDiffResult>('/config-versions/diff');
|
return apiRequest<ConfigDiffResult>('/config-versions/diff');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function publishConfigVersion() {
|
export function publishConfigVersion() {
|
||||||
return apiRequest<ConfigVersionItem>('/config-versions/publish', {
|
return apiRequest<ConfigVersionDetail>('/config-versions/publish', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function activateConfigVersion(id: number) {
|
export function activateConfigVersion(id: number) {
|
||||||
return apiRequest<ConfigVersionItem>(`/config-versions/${id}/activate`, {
|
return apiRequest<ConfigVersionDetail>(`/config-versions/${id}/activate`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-9
@@ -1,20 +1,41 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { ErrorState } from '@/components/feedback/error-state';
|
||||||
|
import { LoadingState } from '@/components/feedback/loading-state';
|
||||||
import { AppModal } from '@/components/ui/app-modal';
|
import { AppModal } from '@/components/ui/app-modal';
|
||||||
import type { ConfigVersionItem } from '@/features/config-versions/types';
|
import { getConfigVersion } from '@/features/config-versions/api/config-versions';
|
||||||
|
import type { ConfigVersionSummary } from '@/features/config-versions/types';
|
||||||
import {
|
import {
|
||||||
CodeBlock,
|
CodeBlock,
|
||||||
SecondaryButton,
|
SecondaryButton,
|
||||||
} from '@/features/shared/components/resource-primitives';
|
} from '@/features/shared/components/resource-primitives';
|
||||||
import { formatDateTime } from '@/lib/utils/date';
|
import { formatDateTime } from '@/lib/utils/date';
|
||||||
|
|
||||||
|
function getErrorMessage(error: unknown) {
|
||||||
|
return error instanceof Error ? error.message : '请求失败,请稍后重试。';
|
||||||
|
}
|
||||||
|
|
||||||
export function ConfigVersionSnapshotModal({
|
export function ConfigVersionSnapshotModal({
|
||||||
version,
|
version,
|
||||||
onClose,
|
onClose,
|
||||||
}: {
|
}: {
|
||||||
version: ConfigVersionItem | null;
|
version: ConfigVersionSummary | null;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}) {
|
}) {
|
||||||
|
const versionDetailQuery = useQuery({
|
||||||
|
queryKey: ['config-versions', 'detail', version?.id ?? 0],
|
||||||
|
queryFn: () => {
|
||||||
|
if (!version) {
|
||||||
|
throw new Error('missing config version');
|
||||||
|
}
|
||||||
|
return getConfigVersion(version.id);
|
||||||
|
},
|
||||||
|
enabled: Boolean(version?.id),
|
||||||
|
});
|
||||||
|
const versionDetail = versionDetailQuery.data ?? null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppModal
|
<AppModal
|
||||||
isOpen={Boolean(version)}
|
isOpen={Boolean(version)}
|
||||||
@@ -30,7 +51,14 @@ export function ConfigVersionSnapshotModal({
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{version ? (
|
{!version ? null : versionDetailQuery.isLoading && !versionDetail ? (
|
||||||
|
<LoadingState />
|
||||||
|
) : versionDetailQuery.isError ? (
|
||||||
|
<ErrorState
|
||||||
|
title="配置版本详情加载失败"
|
||||||
|
description={getErrorMessage(versionDetailQuery.error)}
|
||||||
|
/>
|
||||||
|
) : versionDetail ? (
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<div className="grid gap-4 md:grid-cols-3">
|
<div className="grid gap-4 md:grid-cols-3">
|
||||||
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
|
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
|
||||||
@@ -38,7 +66,7 @@ export function ConfigVersionSnapshotModal({
|
|||||||
Checksum
|
Checksum
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-2 text-sm break-all text-[var(--foreground-primary)]">
|
<p className="mt-2 text-sm break-all text-[var(--foreground-primary)]">
|
||||||
{version.checksum}
|
{versionDetail.checksum}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
|
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
|
||||||
@@ -46,7 +74,7 @@ export function ConfigVersionSnapshotModal({
|
|||||||
创建人
|
创建人
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-2 text-sm text-[var(--foreground-primary)]">
|
<p className="mt-2 text-sm text-[var(--foreground-primary)]">
|
||||||
{version.created_by || '系统'}
|
{versionDetail.created_by || '系统'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
|
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
|
||||||
@@ -54,7 +82,7 @@ export function ConfigVersionSnapshotModal({
|
|||||||
创建时间
|
创建时间
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-2 text-sm text-[var(--foreground-primary)]">
|
<p className="mt-2 text-sm text-[var(--foreground-primary)]">
|
||||||
{formatDateTime(version.created_at)}
|
{formatDateTime(versionDetail.created_at)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -64,23 +92,25 @@ export function ConfigVersionSnapshotModal({
|
|||||||
快照 JSON
|
快照 JSON
|
||||||
</p>
|
</p>
|
||||||
<CodeBlock className="max-h-96 whitespace-pre-wrap">
|
<CodeBlock className="max-h-96 whitespace-pre-wrap">
|
||||||
{version.snapshot_json}
|
{versionDetail.snapshot_json}
|
||||||
</CodeBlock>
|
</CodeBlock>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="mb-2 text-sm font-semibold text-[var(--foreground-primary)]">
|
<p className="mb-2 text-sm font-semibold text-[var(--foreground-primary)]">
|
||||||
主配置
|
主配置
|
||||||
</p>
|
</p>
|
||||||
<CodeBlock className="max-h-96 whitespace-pre-wrap">
|
<CodeBlock className="max-h-96 whitespace-pre-wrap">
|
||||||
{version.main_config}
|
{versionDetail.main_config}
|
||||||
</CodeBlock>
|
</CodeBlock>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="mb-2 text-sm font-semibold text-[var(--foreground-primary)]">
|
<p className="mb-2 text-sm font-semibold text-[var(--foreground-primary)]">
|
||||||
路由配置
|
路由配置
|
||||||
</p>
|
</p>
|
||||||
<CodeBlock className="max-h-[32rem] whitespace-pre-wrap">
|
<CodeBlock className="max-h-[32rem] whitespace-pre-wrap">
|
||||||
{version.rendered_config}
|
{versionDetail.rendered_config}
|
||||||
</CodeBlock>
|
</CodeBlock>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { AppCard } from '@/components/ui/app-card';
|
|||||||
import { StatusBadge } from '@/components/ui/status-badge';
|
import { StatusBadge } from '@/components/ui/status-badge';
|
||||||
import {
|
import {
|
||||||
activateConfigVersion,
|
activateConfigVersion,
|
||||||
|
getActiveConfigVersion,
|
||||||
getConfigVersionDiff,
|
getConfigVersionDiff,
|
||||||
getConfigVersionPreview,
|
getConfigVersionPreview,
|
||||||
getConfigVersions,
|
getConfigVersions,
|
||||||
@@ -22,7 +23,8 @@ import type {
|
|||||||
ConfigOptionDiffItem,
|
ConfigOptionDiffItem,
|
||||||
ConfigDiffResult,
|
ConfigDiffResult,
|
||||||
ConfigPreviewResult,
|
ConfigPreviewResult,
|
||||||
ConfigVersionItem,
|
ConfigVersionDetail,
|
||||||
|
ConfigVersionSummary,
|
||||||
SupportFile,
|
SupportFile,
|
||||||
} from '@/features/config-versions/types';
|
} from '@/features/config-versions/types';
|
||||||
import {
|
import {
|
||||||
@@ -168,14 +170,20 @@ function OptionDiffTable({ items }: { items: ConfigOptionDiffItem[] }) {
|
|||||||
function PublishPreviewCard({
|
function PublishPreviewCard({
|
||||||
preview,
|
preview,
|
||||||
diff,
|
diff,
|
||||||
activeVersion,
|
activeVersionMeta,
|
||||||
|
activeVersionDetail,
|
||||||
|
isActiveVersionDetailLoading,
|
||||||
|
activeVersionDetailError,
|
||||||
isPublishing,
|
isPublishing,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
onCancel,
|
onCancel,
|
||||||
}: {
|
}: {
|
||||||
preview: ConfigPreviewResult;
|
preview: ConfigPreviewResult;
|
||||||
diff: ConfigDiffResult;
|
diff: ConfigDiffResult;
|
||||||
activeVersion: ConfigVersionItem | null;
|
activeVersionMeta: ConfigVersionSummary | null;
|
||||||
|
activeVersionDetail: ConfigVersionDetail | null;
|
||||||
|
isActiveVersionDetailLoading: boolean;
|
||||||
|
activeVersionDetailError: string | null;
|
||||||
isPublishing: boolean;
|
isPublishing: boolean;
|
||||||
onConfirm: () => void;
|
onConfirm: () => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
@@ -263,18 +271,29 @@ function PublishPreviewCard({
|
|||||||
<OptionDiffTable items={diff.changed_option_details} />
|
<OptionDiffTable items={diff.changed_option_details} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{diff.main_config_changed && activeVersion ? (
|
{diff.main_config_changed && activeVersionMeta ? (
|
||||||
<div className="grid gap-5 xl:grid-cols-2">
|
<div className="grid gap-5 xl:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-2 flex flex-wrap items-center justify-between gap-3">
|
<div className="mb-2 flex flex-wrap items-center justify-between gap-3">
|
||||||
<p className="text-sm font-semibold text-[var(--foreground-primary)]">
|
<p className="text-sm font-semibold text-[var(--foreground-primary)]">
|
||||||
Current Active Main Config
|
Current Active Main Config
|
||||||
</p>
|
</p>
|
||||||
<StatusBadge label={activeVersion.version} variant="info" />
|
<StatusBadge label={activeVersionMeta.version} variant="info" />
|
||||||
</div>
|
</div>
|
||||||
<CodeBlock className="max-h-[32rem] whitespace-pre-wrap">
|
{isActiveVersionDetailLoading ? (
|
||||||
{activeVersion.main_config}
|
<LoadingState />
|
||||||
</CodeBlock>
|
) : activeVersionDetailError ? (
|
||||||
|
<InlineMessage tone="danger" message={activeVersionDetailError} />
|
||||||
|
) : activeVersionDetail ? (
|
||||||
|
<CodeBlock className="max-h-[32rem] whitespace-pre-wrap">
|
||||||
|
{activeVersionDetail.main_config}
|
||||||
|
</CodeBlock>
|
||||||
|
) : (
|
||||||
|
<InlineMessage
|
||||||
|
tone="info"
|
||||||
|
message="当前激活版本详情暂不可用。"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-2 flex flex-wrap items-center justify-between gap-3">
|
<div className="mb-2 flex flex-wrap items-center justify-between gap-3">
|
||||||
@@ -406,6 +425,14 @@ export function ConfigVersionsPage() {
|
|||||||
() => versions.find((item) => item.id === selectedVersionId) ?? null,
|
() => versions.find((item) => item.id === selectedVersionId) ?? null,
|
||||||
[selectedVersionId, versions],
|
[selectedVersionId, versions],
|
||||||
);
|
);
|
||||||
|
const shouldLoadActiveVersionDetail = Boolean(
|
||||||
|
activeVersion?.id && publishPreview?.diff.main_config_changed,
|
||||||
|
);
|
||||||
|
const activeVersionDetailQuery = useQuery({
|
||||||
|
queryKey: ['config-versions', 'active-detail', activeVersion?.id ?? 0],
|
||||||
|
queryFn: getActiveConfigVersion,
|
||||||
|
enabled: shouldLoadActiveVersionDetail,
|
||||||
|
});
|
||||||
|
|
||||||
const publishMutation = useMutation({
|
const publishMutation = useMutation({
|
||||||
mutationFn: publishConfigVersion,
|
mutationFn: publishConfigVersion,
|
||||||
@@ -455,7 +482,7 @@ export function ConfigVersionsPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleActivate = (version: ConfigVersionItem) => {
|
const handleActivate = (version: ConfigVersionSummary) => {
|
||||||
if (version.is_active) {
|
if (version.is_active) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -493,7 +520,14 @@ export function ConfigVersionsPage() {
|
|||||||
<PublishPreviewCard
|
<PublishPreviewCard
|
||||||
preview={publishPreview.preview}
|
preview={publishPreview.preview}
|
||||||
diff={publishPreview.diff}
|
diff={publishPreview.diff}
|
||||||
activeVersion={activeVersion}
|
activeVersionMeta={activeVersion}
|
||||||
|
activeVersionDetail={activeVersionDetailQuery.data ?? null}
|
||||||
|
isActiveVersionDetailLoading={activeVersionDetailQuery.isLoading}
|
||||||
|
activeVersionDetailError={
|
||||||
|
activeVersionDetailQuery.isError
|
||||||
|
? getErrorMessage(activeVersionDetailQuery.error)
|
||||||
|
: null
|
||||||
|
}
|
||||||
isPublishing={publishMutation.isPending}
|
isPublishing={publishMutation.isPending}
|
||||||
onConfirm={() => publishMutation.mutate()}
|
onConfirm={() => publishMutation.mutate()}
|
||||||
onCancel={() => setPublishPreview(null)}
|
onCancel={() => setPublishPreview(null)}
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
export interface ConfigVersionItem {
|
export interface ConfigVersionSummary {
|
||||||
id: number;
|
id: number;
|
||||||
version: string;
|
version: string;
|
||||||
snapshot_json: string;
|
|
||||||
main_config: string;
|
|
||||||
rendered_config: string;
|
|
||||||
support_files_json: string;
|
|
||||||
checksum: string;
|
checksum: string;
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
created_by: string;
|
created_by: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ConfigVersionDetail extends ConfigVersionSummary {
|
||||||
|
snapshot_json: string;
|
||||||
|
main_config: string;
|
||||||
|
rendered_config: string;
|
||||||
|
support_files_json: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SupportFile {
|
export interface SupportFile {
|
||||||
path: string;
|
path: string;
|
||||||
content: string;
|
content: string;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { AppCard } from '@/components/ui/app-card';
|
|||||||
import { StatusBadge } from '@/components/ui/status-badge';
|
import { StatusBadge } from '@/components/ui/status-badge';
|
||||||
import { getConfigVersions } from '@/features/config-versions/api/config-versions';
|
import { getConfigVersions } from '@/features/config-versions/api/config-versions';
|
||||||
import { ConfigVersionSnapshotModal } from '@/features/config-versions/components/config-version-snapshot-modal';
|
import { ConfigVersionSnapshotModal } from '@/features/config-versions/components/config-version-snapshot-modal';
|
||||||
import type { ConfigVersionItem } from '@/features/config-versions/types';
|
import type { ConfigVersionSummary } from '@/features/config-versions/types';
|
||||||
import { getApplyLogs } from '@/features/apply-logs/api/apply-logs';
|
import { getApplyLogs } from '@/features/apply-logs/api/apply-logs';
|
||||||
import {
|
import {
|
||||||
deleteNode,
|
deleteNode,
|
||||||
@@ -416,7 +416,7 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const activeConfigVersion = useMemo<ConfigVersionItem | null>(() => {
|
const activeConfigVersion = useMemo<ConfigVersionSummary | null>(() => {
|
||||||
return (
|
return (
|
||||||
(configVersionsQuery.data ?? []).find((item) => item.is_active) ?? null
|
(configVersionsQuery.data ?? []).find((item) => item.is_active) ?? null
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user