diff --git a/Dockerfile b/Dockerfile index 6b22924..3103a68 100644 --- a/Dockerfile +++ b/Dockerfile @@ -75,6 +75,7 @@ RUN --mount=type=cache,target=/root/.cache/go-build \ FROM golang:1.24-alpine AS agent-builder ARG VERSION +ARG AGENT_VERSION ARG BUILD_TIME ARG TARGETARCH @@ -92,13 +93,13 @@ COPY agent/ ./ # Go 在 CGO_ENABLED=0 下可原生交叉编译,无需 QEMU,故一次性产出全部目标, # 与 router/agent_static.go 的下载清单保持一致,避免 ARM 主机下载 404。 // Logan.Li RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ - -ldflags="-s -w -X main.version=${VERSION} -X main.buildTime=${BUILD_TIME}" \ + -ldflags="-s -w -X main.version=${AGENT_VERSION} -X main.buildTime=${BUILD_TIME}" \ -o bin/aiops-agent-linux-amd64 . RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build \ - -ldflags="-s -w -X main.version=${VERSION} -X main.buildTime=${BUILD_TIME}" \ + -ldflags="-s -w -X main.version=${AGENT_VERSION} -X main.buildTime=${BUILD_TIME}" \ -o bin/aiops-agent-linux-arm64 . RUN CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build \ - -ldflags="-s -w -X main.version=${VERSION} -X main.buildTime=${BUILD_TIME}" \ + -ldflags="-s -w -X main.version=${AGENT_VERSION} -X main.buildTime=${BUILD_TIME}" \ -o bin/aiops-agent-windows-amd64.exe . # ==================== Stage 4: Runtime ==================== diff --git a/backend/main.go b/backend/main.go index cb246c7..31ac9d8 100644 --- a/backend/main.go +++ b/backend/main.go @@ -215,6 +215,8 @@ func main() { systask.Init(db) // 注册路由 + // 把编译期注入的版本号透传给 router,供 /agent/arch 返回真实版本。 // Logan.Li + router.AppVersion = Version router.Register(r, db, log, cfg, cache) // 用户定时任务调度(Linux 5 段 crontab)// Logan.Li diff --git a/backend/router/agent_static.go b/backend/router/agent_static.go index cb83a05..bb3e205 100644 --- a/backend/router/agent_static.go +++ b/backend/router/agent_static.go @@ -22,6 +22,10 @@ var agentBinaryFiles = map[string]string{ "windows-amd64": "aiops-agent-windows-amd64.exe", } +// AppVersion 由 main 在启动时注入,用于 /agent/arch 返回真实版本号; +// 此前该字段硬编码为 "1.0.0",发版后接口版本与实际不符。 // Logan.Li +var AppVersion = "dev" + // registerAgentDownloads 注册 Agent 安装脚本和二进制下载路由,避免维护 backend/agent 重复目录。 // Logan.Li func registerAgentDownloads(r *gin.Engine, log *zap.Logger) { paths := resolveAgentStaticPaths(log) @@ -34,7 +38,7 @@ func registerAgentDownloads(r *gin.Engine, log *zap.Logger) { } downloads := availableAgentDownloads(paths.binDir) c.JSON(http.StatusOK, gin.H{ - "version": "1.0.0", + "version": AppVersion, "downloads": downloads, "install": fmt.Sprintf("curl -fsSL %s/agent/install.sh | sudo bash -s -- --server %s --token ", base, base), }) diff --git a/docker-compose.yml b/docker-compose.yml index 0139c51..a939a28 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -61,6 +61,8 @@ services: dockerfile: Dockerfile args: VERSION: ${VERSION:-dev} + # AGENT_VERSION 与镜像版本解耦:纯后端/前端改动不必让所有主机重推 Agent。 // Logan.Li + AGENT_VERSION: ${AGENT_VERSION:-v1.0.2} BUILD_TIME: ${BUILD_TIME:-unknown} GIT_COMMIT: ${GIT_COMMIT:-unknown} container_name: aiops diff --git a/frontend/src/components/layout/MainLayout.tsx b/frontend/src/components/layout/MainLayout.tsx index 90a4b9b..b90a07c 100644 --- a/frontend/src/components/layout/MainLayout.tsx +++ b/frontend/src/components/layout/MainLayout.tsx @@ -9,6 +9,7 @@ import { authApi } from '@/api'; import type { JsonData } from '@/types/common'; import { useTranslation } from 'react-i18next'; import { SessionWatermark } from '@/components/common/SessionWatermark'; +import { useAppVersion } from '@/hooks/use-app-version'; import { shouldProtectPage } from '@/components/common/sensitivePageWatermark'; /** @@ -16,6 +17,8 @@ import { shouldProtectPage } from '@/components/common/sensitivePageWatermark'; * 侧栏 + 顶栏 + 面包屑 + 内容区,间距由 --layout-* 令牌驱动 // Logan.Li */ export function MainLayout() { + // 页脚版本号同样取自后端 /version。 // Logan.Li + const appVersion = useAppVersion(); const { t } = useTranslation(); const { sidebarCollapsed } = useThemeStore(); const token = useUserStore((s) => s.token); @@ -97,7 +100,7 @@ export function MainLayout() { diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx index bf7b7f5..285f06a 100644 --- a/frontend/src/components/layout/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar.tsx @@ -44,6 +44,7 @@ import { import { Button } from '@/components/ui/button'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { useAppVersion } from '@/hooks/use-app-version'; interface MenuItem { id: string; @@ -363,6 +364,8 @@ interface SidebarProps { } export function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) { + // 版本号从后端 /version 读取,避免发版后界面仍显示硬编码的旧版本。 // Logan.Li + const appVersion = useAppVersion(); const { t } = useTranslation(); const { sidebarCollapsed, toggleSidebar } = useThemeStore(); const { hasPermission } = usePermission(); @@ -433,7 +436,7 @@ export function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) { {!isCollapsed && (
- AIOps v1.0.0 + AIOps {appVersion || 'v1.0.0'}
)} diff --git a/frontend/src/hooks/use-app-version.ts b/frontend/src/hooks/use-app-version.ts new file mode 100644 index 0000000..4eea39c --- /dev/null +++ b/frontend/src/hooks/use-app-version.ts @@ -0,0 +1,58 @@ +import { useEffect, useState } from 'react'; + +/** + * 应用版本信息。 + * + * 版本号此前在后端 /version 已提供(含 build_time / git_commit), + * 但侧边栏与页脚硬编码了 v1.0.0,导致发版后界面显示陈旧。 + * 这里统一从 /version 读取并做模块级缓存,避免多处重复请求。 // Logan.Li + */ +export interface AppVersion { + version: string; + build_time?: string; + git_commit?: string; + mode?: string; +} + +// 模块级缓存:整个会话只请求一次。 +let cached: AppVersion | null = null; +let inflight: Promise | null = null; + +function fetchVersion(): Promise { + if (cached) return Promise.resolve(cached); + if (inflight) return inflight; + + // 注意:/version 挂在根路径,不带 /api/v1 前缀,故不能用统一 request 封装。 + inflight = fetch('/version') + .then((res) => (res.ok ? res.json() : null)) + .then((data) => { + if (data && typeof data.version === 'string') { + cached = data as AppVersion; + return cached; + } + return null; + }) + .catch(() => null) + .finally(() => { + inflight = null; + }); + + return inflight; +} + +/** 读取后端版本号;失败时返回空串,调用方负责兜底显示。 */ +export function useAppVersion(): string { + const [version, setVersion] = useState(cached?.version ?? ''); + + useEffect(() => { + let alive = true; + fetchVersion().then((v) => { + if (alive && v?.version) setVersion(v.version); + }); + return () => { + alive = false; + }; + }, []); + + return version; +}