fix(updater): 自动定位宿主机 Compose 目录并发布 v1.3.6
CI / verify (push) Canceled after 0s

This commit is contained in:
2026-09-28 11:10:14 +08:00
parent 3f23610e59
commit d154aef807
13 changed files with 235 additions and 44 deletions
+16
View File
@@ -2,6 +2,22 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [v1.3.6] - 2026-09-28
### 修复
- **自动定位宿主机 Compose 目录**:当 updater 容器内的 `/workspace` 没有挂载到真实 Compose 目录时,不再直接失败。updater 会从 `fonu` 容器的 `com.docker.compose.project.working_dir` / `project.config_files` 标签读取宿主机路径,并通过临时 helper 容器挂载真实目录执行 `docker compose`。`FONU_UPDATE_HOST_DIR` 仍可作为显式覆盖
- 示例页验证标识更新为 `v1.3.6-example-page`
### 变更
- 版本号提升到 1.3.6;`web/package.json` 与 `web/package-lock.json` 同步对齐
- `AgentVersion` 保持 0.12.8
### 测试
- `internal/updater`:覆盖 Compose 文件直挂载与宿主机目录 helper 模式
## [v1.3.5] - 2026-09-28 ## [v1.3.5] - 2026-09-28
### 新增 ### 新增
+1 -1
View File
@@ -1 +1 @@
1.3.5 1.3.6
+11 -8
View File
@@ -12,14 +12,17 @@ import (
func main() { func main() {
server, err := updater.NewServer(updater.Config{ server, err := updater.NewServer(updater.Config{
SocketPath: envOr("FONU_UPDATER_SOCKET", "/run/fonu-updater/updater.sock"), SocketPath: envOr("FONU_UPDATER_SOCKET", "/run/fonu-updater/updater.sock"),
ProjectDir: envOr("FONU_UPDATE_PROJECT_DIR", "/workspace"), ProjectDir: envOr("FONU_UPDATE_PROJECT_DIR", "/workspace"),
ComposeFile: envOr("FONU_UPDATE_COMPOSE_FILE", "docker-compose.yml"), ComposeFile: envOr("FONU_UPDATE_COMPOSE_FILE", "docker-compose.yml"),
Service: envOr("FONU_UPDATE_SERVICE", "fonu"), Service: envOr("FONU_UPDATE_SERVICE", "fonu"),
Mode: envOr("FONU_UPDATE_MODE", "local"), Mode: envOr("FONU_UPDATE_MODE", "local"),
RepoURL: envOr("FONU_UPDATE_REPO", ""), RepoURL: envOr("FONU_UPDATE_REPO", ""),
Branch: envOr("FONU_UPDATE_BRANCH", "main"), Branch: envOr("FONU_UPDATE_BRANCH", "main"),
AppURL: envOr("FONU_UPDATE_APP_URL", "http://fonu:6893"), AppURL: envOr("FONU_UPDATE_APP_URL", "http://fonu:6893"),
HostProjectDir: envOr("FONU_UPDATE_HOST_DIR", ""),
SelfImage: envOr("FONU_UPDATE_SELF_IMAGE", ""),
DockerSocket: envOr("FONU_UPDATE_DOCKER_SOCK", "/var/run/docker.sock"),
}, nil) }, nil)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
+3
View File
@@ -81,6 +81,9 @@ services:
FONU_UPDATE_REPO: http://45.207.223.190:10082/mrlee/-Fonu.git FONU_UPDATE_REPO: http://45.207.223.190:10082/mrlee/-Fonu.git
FONU_UPDATE_BRANCH: main FONU_UPDATE_BRANCH: main
FONU_UPDATE_APP_URL: http://fonu:6893 FONU_UPDATE_APP_URL: http://fonu:6893
FONU_UPDATE_HOST_DIR: ${FONU_UPDATE_HOST_DIR:-}
FONU_UPDATE_SELF_IMAGE: fonu-updater:${FONU_VERSION:-local}
FONU_UPDATE_DOCKER_SOCK: ${DOCKER_SOCK:-/var/run/docker.sock}
TZ: ${TZ:-Asia/Shanghai} TZ: ${TZ:-Asia/Shanghai}
volumes: volumes:
+3
View File
@@ -63,6 +63,9 @@ services:
FONU_UPDATE_SERVICE: fonu FONU_UPDATE_SERVICE: fonu
FONU_UPDATE_MODE: local FONU_UPDATE_MODE: local
FONU_UPDATE_APP_URL: http://fonu:6893 FONU_UPDATE_APP_URL: http://fonu:6893
FONU_UPDATE_HOST_DIR: ${FONU_UPDATE_HOST_DIR:-}
FONU_UPDATE_SELF_IMAGE: fonu-updater:${FONU_VERSION:-local}
FONU_UPDATE_DOCKER_SOCK: ${DOCKER_SOCK:-/var/run/docker.sock}
TZ: ${TZ:-Asia/Shanghai} TZ: ${TZ:-Asia/Shanghai}
volumes: volumes:
+2 -2
View File
@@ -1,7 +1,7 @@
# Fonu 文档索引 # Fonu 文档索引
> 维护约定:新增方案 / 审查文档时在下方登记;方案落地后在「状态」列标注实施结果,并给已被推翻的描述加时效说明。 > 维护约定:新增方案 / 审查文档时在下方登记;方案落地后在「状态」列标注实施结果,并给已被推翻的描述加时效说明。
> 最近更新:2026-09-28(v1.3.5) > 最近更新:2026-09-28(v1.3.6)
## 方案与设计(本目录) ## 方案与设计(本目录)
@@ -9,7 +9,7 @@
| --- | --- | --- | | --- | --- | --- |
| `project-analysis-2026-09-27.md` | 全项目详细分析(v1.2.19):架构、模块、数据、API、前端、链路、风险与改进路线 | 当前版本快照 | | `project-analysis-2026-09-27.md` | 全项目详细分析(v1.2.19):架构、模块、数据、API、前端、链路、风险与改进路线 | 当前版本快照 |
| `improvement-roadmap-2026-09-27.md` | P0-P3 改进路线实施方案:逐项改法、测试、验收、依赖、发布与回滚 | P0-1、P0-3 已完成,其余待实施 | | `improvement-roadmap-2026-09-27.md` | P0-P3 改进路线实施方案:逐项改法、测试、验收、依赖、发布与回滚 | P0-1、P0-3 已完成,其余待实施 |
| `system-upgrade.md` | Docker Compose 一键升级:sidecar 架构、首次部署、安全边界、回滚、故障排查与验证页 | 已实施(v1.3.5) | | `system-upgrade.md` | Docker Compose 一键升级:sidecar 架构、首次部署、安全边界、回滚、故障排查与验证页 | 已实施(v1.3.6) |
| `project-audit-2026-09-24.md` | 全项目审查:P0/P1/P2 分级,含精确行号与逐条修法 | **进行中**——P0 已随 v1.2.7 完成,P1 实施中 | | `project-audit-2026-09-24.md` | 全项目审查:P0/P1/P2 分级,含精确行号与逐条修法 | **进行中**——P0 已随 v1.2.7 完成,P1 实施中 |
| `frp-server-list-merge-plan.md` | FRP 服务端列表并入「公网服务端」页(方案 A,四步) | 步 1/2 已完成(v1.2.6);步 3 文案收尾、步 4 死样式清理未做 | | `frp-server-list-merge-plan.md` | FRP 服务端列表并入「公网服务端」页(方案 A,四步) | 步 1/2 已完成(v1.2.6);步 3 文案收尾、步 4 死样式清理未做 |
| `agent-capability-plan.md` | fonu-agent 能力扩展(P0/P1/P2/P3) | 已实施(agent 0.11.0);P3 三项不做 | | `agent-capability-plan.md` | fonu-agent 能力扩展(P0/P1/P2/P3) | 已实施(agent 0.11.0);P3 三项不做 |
+10 -8
View File
@@ -94,7 +94,7 @@ docker compose -f docker-compose.hub.yml up -d --build
| `FONU_UPDATE_BRANCH` | `git` 模式下的分支,默认 `main` | | `FONU_UPDATE_BRANCH` | `git` 模式下的分支,默认 `main` |
| `FONU_UPDATE_APP_URL` | 健康检查地址,默认 `http://fonu:6893` | | `FONU_UPDATE_APP_URL` | 健康检查地址,默认 `http://fonu:6893` |
| `DOCKER_SOCK` | 宿主机 Docker Socket,默认 `/var/run/docker.sock` | | `DOCKER_SOCK` | 宿主机 Docker Socket,默认 `/var/run/docker.sock` |
| `FONU_UPDATE_HOST_DIR` | 宿主机上 Compose 文件所在目录;Docker UI / fnOS 场景下建议显式设置 | | `FONU_UPDATE_HOST_DIR` | 宿主机上 Compose 文件所在目录;留空时自动从 Compose 容器标签发现,设置后作为显式覆盖 |
## 6. 前端行为 ## 6. 前端行为
@@ -160,13 +160,15 @@ docker compose up -d --build
### 升级时报 `/workspace/docker-compose.hub.yml: no such file or directory` ### 升级时报 `/workspace/docker-compose.hub.yml: no such file or directory`
说明 updater 容器没有挂载到真实的 Compose 项目目录。先查询运行中 `fonu` 容器的 Compose 工作目录: v1.3.6 起 updater 会自动从 `fonu` 容器的 Compose 标签读取宿主机目录,并通过 helper 容器执行 Compose。如果自动发现仍失败,可以显式设置目录:
先查询运行中 `fonu` 容器的 Compose 工作目录:
```bash ```bash
docker inspect fonu --format '{{ index .Config.Labels "com.docker.compose.project.working_dir" }}' docker inspect fonu --format '{{ index .Config.Labels "com.docker.compose.project.working_dir" }}'
``` ```
把输出目录写入 Compose 同层 `.env`: 把输出目录写入 Compose 同层 `.env` 作为覆盖:
```dotenv ```dotenv
FONU_UPDATE_HOST_DIR=/实际/的/Compose/目录 FONU_UPDATE_HOST_DIR=/实际/的/Compose/目录
@@ -178,7 +180,7 @@ FONU_UPDATE_HOST_DIR=/实际/的/Compose/目录
docker compose -f /实际/的/Compose/目录/docker-compose.hub.yml up -d --build updater docker compose -f /实际/的/Compose/目录/docker-compose.hub.yml up -d --build updater
``` ```
状态接口此时会明确提示 Compose 文件缺失,修复挂载后按钮才会重新显示。 状态接口会明确提示 Compose 文件缺失,修复挂载或设置覆盖后按钮才会重新显示。
### `docker-compose.hub.yml` 模式无法读取远端版本 ### `docker-compose.hub.yml` 模式无法读取远端版本
@@ -226,16 +228,16 @@ FONU_VERSION=<旧版本> docker compose up -d --no-deps --force-recreate fonu
## 10. 在线升级验证 ## 10. 在线升级验证
示例页面用于验证升级结果。v1.3.5 的验证流程: 示例页面用于验证升级结果。v1.3.6 的验证流程:
1. 确认当前线上实例为 v1.3.0,且 `fonu-updater` sidecar 已运行。 1. 确认当前线上实例为 v1.3.0,且 `fonu-updater` sidecar 已运行。
2. 确认远端 `main` 分支的 `VERSION` 为 `1.3.5`。 2. 确认远端 `main` 分支的 `VERSION` 为 `1.3.6`。
3. 在侧栏看到「一键升级」后点击并确认。 3. 在侧栏看到「一键升级」后点击并确认。
4. 等待升级弹窗显示成功,页面自动刷新。 4. 等待升级弹窗显示成功,页面自动刷新。
5. 打开侧栏「示例页面」,确认: 5. 打开侧栏「示例页面」,确认:
- 页面路由为 `/example`; - 页面路由为 `/example`;
- 页面显示的版本为 `1.3.5`; - 页面显示的版本为 `1.3.6`;
- 页面显示验证标识 `v1.3.5-example-page`; - 页面显示验证标识 `v1.3.6-example-page`;
- 「升级验证」显示为「通过」。 - 「升级验证」显示为「通过」。
如果示例页面可见且版本正确,说明镜像构建、容器重建、后端版本注入和前端静态资源更新均已生效。 如果示例页面可见且版本正确,说明镜像构建、容器重建、后端版本注入和前端静态资源更新均已生效。
+46 -2
View File
@@ -22,7 +22,7 @@ func (s *Server) runUpdate(ctx context.Context) error {
s.state.Phase = PhaseRunning s.state.Phase = PhaseRunning
s.state.Message = "正在准备升级" s.state.Message = "正在准备升级"
s.mu.Unlock() s.mu.Unlock()
if err := s.ensureComposeFile(); err != nil { if err := s.ensureComposeFile(ctx); err != nil {
return err return err
} }
@@ -316,11 +316,55 @@ func imageTag(image string) string {
} }
func (s *Server) compose(ctx context.Context, version string, args ...string) (string, error) { func (s *Server) compose(ctx context.Context, version string, args ...string) (string, error) {
fullArgs := append([]string{"compose", "-f", s.composePath}, args...) plan, err := s.resolveComposePlan(ctx)
if err != nil {
return "", err
}
if plan.helper {
return s.composeViaHelper(ctx, plan, version, args...)
}
fullArgs := append([]string{"compose", "-f", plan.containerCompose}, args...)
env := []string{"FONU_VERSION=" + version} env := []string{"FONU_VERSION=" + version}
return runCommand(ctx, s.cfg.ProjectDir, env, "docker", fullArgs...) return runCommand(ctx, s.cfg.ProjectDir, env, "docker", fullArgs...)
} }
func (s *Server) composeViaHelper(ctx context.Context, plan composePlan, version string, args ...string) (string, error) {
image, err := s.selfImage(ctx)
if err != nil {
return "", err
}
helperArgs := []string{
"run", "--rm", "--entrypoint", "docker",
"-v", s.cfg.DockerSocket + ":/var/run/docker.sock",
"-v", plan.hostProjectDir + ":/workspace",
"-w", "/workspace",
"-e", "FONU_VERSION=" + version,
image,
"compose", "-f", plan.helperComposePath,
}
helperArgs = append(helperArgs, args...)
return runCommand(ctx, "", nil, "docker", helperArgs...)
}
func (s *Server) selfImage(ctx context.Context) (string, error) {
if s.cfg.SelfImage != "" {
return s.cfg.SelfImage, nil
}
hostname, err := os.Hostname()
if err != nil {
return "", err
}
output, err := runCommand(ctx, "", nil, "docker", "inspect", "--format", "{{.Config.Image}}", hostname)
if err != nil {
return "", fmt.Errorf("无法识别 updater 镜像:%w", err)
}
image := strings.TrimSpace(output)
if image == "" {
return "", fmt.Errorf("无法识别 updater 镜像")
}
return image, nil
}
func runCommand(ctx context.Context, dir string, extraEnv []string, name string, args ...string) (string, error) { func runCommand(ctx context.Context, dir string, extraEnv []string, name string, args ...string) (string, error) {
command := exec.CommandContext(ctx, name, args...) command := exec.CommandContext(ctx, name, args...)
command.Dir = dir command.Dir = dir
+112 -16
View File
@@ -22,16 +22,19 @@ var serviceNamePattern = regexp.MustCompile(`^[a-zA-Z0-9_.-]+$`)
// Config 是 fonu-updater sidecar 的运行配置。 // Config 是 fonu-updater sidecar 的运行配置。
type Config struct { type Config struct {
SocketPath string SocketPath string
ProjectDir string ProjectDir string
ComposeFile string ComposeFile string
Service string Service string
Mode string Mode string
RepoURL string RepoURL string
Branch string Branch string
AppURL string AppURL string
BuildTimeout time.Duration HostProjectDir string
HealthTimeout time.Duration SelfImage string
DockerSocket string
BuildTimeout time.Duration
HealthTimeout time.Duration
} }
type Server struct { type Server struct {
@@ -44,6 +47,14 @@ type Server struct {
lastChecked time.Time lastChecked time.Time
} }
type composePlan struct {
helper bool
containerCompose string
hostProjectDir string
hostComposePath string
helperComposePath string
}
// NewServer 校验 sidecar 配置;参数不正确时拒绝启动。 // NewServer 校验 sidecar 配置;参数不正确时拒绝启动。
func NewServer(cfg Config, logger *slog.Logger) (*Server, error) { func NewServer(cfg Config, logger *slog.Logger) (*Server, error) {
cfg.SocketPath = strings.TrimSpace(cfg.SocketPath) cfg.SocketPath = strings.TrimSpace(cfg.SocketPath)
@@ -68,6 +79,11 @@ func NewServer(cfg Config, logger *slog.Logger) (*Server, error) {
if strings.TrimSpace(cfg.AppURL) == "" { if strings.TrimSpace(cfg.AppURL) == "" {
cfg.AppURL = "http://fonu:6893" cfg.AppURL = "http://fonu:6893"
} }
if strings.TrimSpace(cfg.DockerSocket) == "" {
cfg.DockerSocket = "/var/run/docker.sock"
}
cfg.HostProjectDir = strings.TrimSpace(cfg.HostProjectDir)
cfg.SelfImage = strings.TrimSpace(cfg.SelfImage)
if cfg.BuildTimeout <= 0 { if cfg.BuildTimeout <= 0 {
cfg.BuildTimeout = 30 * time.Minute cfg.BuildTimeout = 30 * time.Minute
} }
@@ -162,15 +178,95 @@ func (s *Server) handleApply(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusAccepted, status) writeJSON(w, http.StatusAccepted, status)
} }
func (s *Server) ensureComposeFile() error { func (s *Server) ensureComposeFile(ctx context.Context) error {
_, err := s.resolveComposePlan(ctx)
return err
}
func (s *Server) resolveComposePlan(ctx context.Context) (composePlan, error) {
info, err := os.Stat(s.composePath) info, err := os.Stat(s.composePath)
if err == nil && !info.IsDir() {
return composePlan{containerCompose: s.composePath}, nil
}
if err == nil && info.IsDir() {
return composePlan{}, fmt.Errorf("Compose 路径是目录而不是文件:%s", s.composePath)
}
hostDir, hostComposePath, discoverErr := s.discoverHostCompose(ctx)
if discoverErr == nil {
helperPath, pathErr := helperComposePath(hostDir, hostComposePath)
if pathErr == nil {
return composePlan{
helper: true,
hostProjectDir: hostDir,
hostComposePath: hostComposePath,
helperComposePath: helperPath,
}, nil
}
}
return composePlan{}, fmt.Errorf("找不到 Compose 文件 %s,且无法自动定位宿主机目录;请设置 FONU_UPDATE_HOST_DIR", s.composePath)
}
func (s *Server) discoverHostCompose(ctx context.Context) (string, string, error) {
if s.cfg.HostProjectDir != "" {
if !filepath.IsAbs(s.cfg.HostProjectDir) {
return "", "", fmt.Errorf("FONU_UPDATE_HOST_DIR 必须是绝对路径")
}
return s.cfg.HostProjectDir, filepath.Join(s.cfg.HostProjectDir, filepath.Base(s.cfg.ComposeFile)), nil
}
workingDir, _ := s.dockerLabel(ctx, "com.docker.compose.project.working_dir")
configFiles, _ := s.dockerLabel(ctx, "com.docker.compose.project.config_files")
hostDir := strings.TrimSpace(workingDir)
hostCompose := chooseComposeFile(configFiles, filepath.Base(s.cfg.ComposeFile))
if hostCompose == "" {
return "", "", fmt.Errorf("无法从容器标签读取 Compose 文件")
}
if hostDir == "" {
hostDir = filepath.Dir(hostCompose)
}
if !filepath.IsAbs(hostDir) || !filepath.IsAbs(hostCompose) {
return "", "", fmt.Errorf("容器标签中的 Compose 路径不是绝对路径")
}
return filepath.Clean(hostDir), filepath.Clean(hostCompose), nil
}
func (s *Server) dockerLabel(ctx context.Context, label string) (string, error) {
format := fmt.Sprintf(`{{ index .Config.Labels %q }}`, label)
checkCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
output, err := runCommand(checkCtx, "", nil, "docker", "inspect", "--format", format, s.cfg.Service)
if err != nil { if err != nil {
return fmt.Errorf("找不到 Compose 文件 %s;请确认 updater 的 /workspace 挂载,或在 .env 设置 FONU_UPDATE_HOST_DIR 指向 Compose 所在目录", s.composePath) return "", err
} }
if info.IsDir() { return strings.TrimSpace(output), nil
return fmt.Errorf("Compose 路径是目录而不是文件:%s", s.composePath) }
func chooseComposeFile(configFiles, expectedBase string) string {
for _, item := range strings.Split(configFiles, ",") {
path := strings.TrimSpace(item)
if path == "" {
continue
}
if filepath.Base(path) == expectedBase {
return path
}
} }
return nil for _, item := range strings.Split(configFiles, ",") {
if path := strings.TrimSpace(item); path != "" {
return path
}
}
return ""
}
func helperComposePath(hostDir, hostCompose string) (string, error) {
rel, err := filepath.Rel(hostDir, hostCompose)
if err != nil {
return "", err
}
if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
return "", fmt.Errorf("Compose 文件不在宿主机项目目录内")
}
return filepath.Join("/workspace", rel), nil
} }
// Status 返回当前版本、远端版本和任务状态。 // Status 返回当前版本、远端版本和任务状态。
@@ -182,7 +278,7 @@ func (s *Server) Status(ctx context.Context) Status {
return status return status
} }
s.mu.Unlock() s.mu.Unlock()
if err := s.ensureComposeFile(); err != nil { if err := s.ensureComposeFile(ctx); err != nil {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
s.state.UpdateAvailable = false s.state.UpdateAvailable = false
+26 -2
View File
@@ -1,6 +1,7 @@
package updater package updater
import ( import (
"context"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@@ -49,14 +50,37 @@ func TestEnsureComposeFile(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := server.ensureComposeFile(); err == nil { if err := server.ensureComposeFile(context.Background()); err == nil {
t.Fatal("缺少 compose 文件时应返回错误") t.Fatal("缺少 compose 文件时应返回错误")
} }
path := filepath.Join(dir, "docker-compose.yml") path := filepath.Join(dir, "docker-compose.yml")
if err := os.WriteFile(path, []byte("services: {}\n"), 0o600); err != nil { if err := os.WriteFile(path, []byte("services: {}\n"), 0o600); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := server.ensureComposeFile(); err != nil { if err := server.ensureComposeFile(context.Background()); err != nil {
t.Fatalf("存在 compose 文件时不应报错: %v", err) t.Fatalf("存在 compose 文件时不应报错: %v", err)
} }
} }
func TestResolveComposePlanUsesHostProjectDir(t *testing.T) {
containerDir := t.TempDir()
hostDir := t.TempDir()
server, err := NewServer(Config{
ProjectDir: containerDir,
ComposeFile: "docker-compose.hub.yml",
HostProjectDir: hostDir,
}, nil)
if err != nil {
t.Fatal(err)
}
plan, err := server.resolveComposePlan(context.Background())
if err != nil {
t.Fatal(err)
}
if !plan.helper {
t.Fatal("容器内看不到 Compose 文件时应使用 helper 模式")
}
if plan.helperComposePath != filepath.Join("/workspace", "docker-compose.hub.yml") {
t.Fatalf("helper compose 路径不正确: %s", plan.helperComposePath)
}
}
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "fonu-web", "name": "fonu-web",
"version": "1.3.5", "version": "1.3.6",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "fonu-web", "name": "fonu-web",
"version": "1.3.5", "version": "1.3.6",
"dependencies": { "dependencies": {
"@codemirror/commands": "^6.11.1", "@codemirror/commands": "^6.11.1",
"@codemirror/lang-javascript": "^6.2.5", "@codemirror/lang-javascript": "^6.2.5",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "fonu-web", "name": "fonu-web",
"private": true, "private": true,
"version": "1.3.5", "version": "1.3.6",
"packageManager": "npm@10.9.4", "packageManager": "npm@10.9.4",
"type": "module", "type": "module",
"scripts": { "scripts": {
+2 -2
View File
@@ -8,7 +8,7 @@
</template> </template>
</PageHeader> </PageHeader>
<n-alert type="success" :bordered="false" class="example-alert" title="v1.3.5 示例页面验证标识已生效"> <n-alert type="success" :bordered="false" class="example-alert" title="v1.3.6 示例页面验证标识已生效">
当前页面由 v{{ version }} 提供,验证标识为 <span class="mono">{{ verificationMarker }}</span>。 当前页面由 v{{ version }} 提供,验证标识为 <span class="mono">{{ verificationMarker }}</span>。
如果你是在升级后首次看到这行内容,说明前端构建、镜像重建和服务重启均已成功。 如果你是在升级后首次看到这行内容,说明前端构建、镜像重建和服务重启均已成功。
</n-alert> </n-alert>
@@ -77,7 +77,7 @@ const message = useMessage()
const version = ref(__APP_VERSION__) const version = ref(__APP_VERSION__)
const checkedAt = ref('') const checkedAt = ref('')
const refreshing = ref(false) const refreshing = ref(false)
const verificationMarker = 'v1.3.5-example-page' const verificationMarker = 'v1.3.6-example-page'
async function refreshVersion() { async function refreshVersion() {
refreshing.value = true refreshing.value = true