feat: 顶栏显示后端应用版本号

后端 /api/health 返回 BuildVersion(由 ldflags 注入的 main.version),
前端顶栏设置按钮左侧展示 v<版本>。
This commit is contained in:
Autumn-27 committed 2026-08-18 11:27:01 -04:00
1 parent ff3d41ea35
commit 82c6d4a06b
4 files changed
+26 -2

No files matched your search

+4
View File
@@ -48,6 +48,10 @@ func main() {
)
flag.Parse()
// hand the build version to the server package so GET /api/health can report it
// to the frontend top bar.
server.BuildVersion = version
printBanner(*addr)
// capture backend logs into the in-memory sink (still to stderr) so the /logs
+6 -1
View File
@@ -27,6 +27,11 @@ import (
"github.com/Autumn-27/norma/transcript"
)
// BuildVersion is the backend application version, injected from cmd/artex at
// startup (which in turn gets it from -ldflags "-X main.version=<tag>").
// Defaults to "dev" for local builds. Exposed to the frontend via GET /api/health.
var BuildVersion = "dev"
// Server exposes the ARTEX backend over a JSON HTTP API for the shadcn/ui
// frontend.
type Server struct {
@@ -741,7 +746,7 @@ func (s *Server) Handler() http.Handler {
// --- handlers ---
func (s *Server) health(w http.ResponseWriter, r *http.Request) {
writeJSON(w, 200, map[string]any{"ok": true, "service": "artex"})
writeJSON(w, 200, map[string]any{"ok": true, "service": "artex", "version": BuildVersion})
}
func (s *Server) stats(w http.ResponseWriter, r *http.Request) {
@@ -1,11 +1,12 @@
"use client";
import type { ReactNode } from "react";
import { type ReactNode, useEffect, useState } from "react";
import { usePathname } from "next/navigation";
import { Separator } from "@/components/ui/separator";
import { SidebarTrigger } from "@/components/ui/sidebar";
import { api } from "@/lib/api";
import { cn } from "@/lib/utils";
import { useCurrentUser } from "@/hooks/use-current-user";
@@ -29,6 +30,14 @@ function isFullBleed(pathname: string) {
export function MainContent({ children }: { children: ReactNode }) {
const currentUser = useCurrentUser();
const pathname = usePathname();
const [version, setVersion] = useState("");
useEffect(() => {
api
.health()
.then((h) => setVersion(h.version ?? ""))
.catch(() => setVersion(""));
}, []);
if (isFullBleed(pathname)) {
return <>{children}</>;
@@ -52,6 +61,9 @@ export function MainContent({ children }: { children: ReactNode }) {
<SearchDialog />
</div>
<div className="flex items-center gap-2">
{version && (
<span className="text-muted-foreground text-xs font-medium tabular-nums">版本·v{version}</span>
)}
<LayoutControls />
<ThemeSwitcher />
<AccountSwitcher users={[currentUser]} />
+3
View File
@@ -99,6 +99,9 @@ const arr = <T>(x: T[] | null | undefined): T[] => x ?? [];
const tq = (task?: string, sep: "?" | "&" = "?") => (task ? `${sep}task=${encodeURIComponent(task)}` : "");
export const api = {
// 后端应用版本号(release 时由 ldflags 注入,默认 "dev")。
health: () => get<{ ok: boolean; service: string; version: string }>("/health"),
// ---- auth ----
authStatus: () => get<{ initialized: boolean }>("/auth/status"),
login: (username: string, password: string) =>