diff --git a/.doc/system-design/project-control-center/01-personal-engineering-control-center-plan.md b/.doc/archive/design/2026-09-17-personal-engineering-control-center-plan.md
similarity index 99%
rename from .doc/system-design/project-control-center/01-personal-engineering-control-center-plan.md
rename to .doc/archive/design/2026-09-17-personal-engineering-control-center-plan.md
index a39f8b18..f1722db5 100644
--- a/.doc/system-design/project-control-center/01-personal-engineering-control-center-plan.md
+++ b/.doc/archive/design/2026-09-17-personal-engineering-control-center-plan.md
@@ -7,6 +7,8 @@ product_contract_source: ce-plan-bootstrap
execution: code
---
+> **已废弃(2026-09-17)**:旧版只读工程中控台(`cmd/engineering-dashboard` / `internal/engineeringdashboard`)代码已移除,由独立的可写中控台 Control Center 承接(见 `02-control-center-design.md`)。本文仅作历史证据保留。
+
# 个人工程中控台:实时项目看板与离线 HTML 快照
## Goal Capsule
diff --git a/.doc/ops/short-term/2026-09-17-control-center-server.md b/.doc/ops/short-term/2026-09-17-control-center-server.md
index b625ea83..6e0d3994 100644
--- a/.doc/ops/short-term/2026-09-17-control-center-server.md
+++ b/.doc/ops/short-term/2026-09-17-control-center-server.md
@@ -27,7 +27,7 @@ mutex: controlcenter
- 不接产品 sysadmin、不动 internal/server 路由
- 不做多仓库、不做远端 CI 集成
-- 不删旧 `internal/engineeringdashboard`(共存,对齐后另行弃用)
+- 旧 `internal/engineeringdashboard`:用户已授权移除(2026-09-17 执行:代码删除,设计文档归档 `.doc/archive/design/`,验收报告保留为历史证据)
- 不做 agent 自动推进阶段(任何推进必须人工批准)
## 步骤
diff --git a/.doc/status/FEATURE-MATRIX.md b/.doc/status/FEATURE-MATRIX.md
index 17087750..b6edb3bb 100644
--- a/.doc/status/FEATURE-MATRIX.md
+++ b/.doc/status/FEATURE-MATRIX.md
@@ -16,7 +16,7 @@
| 字段 | 含义 |
|------|------|
| **声明范围** | 本行冻结的能力边界;`done` 只对该边界负责 |
-| **状态** | `done` \| `partial` \| `planned` \| `deferred` \| `blocked` |
+| **状态** | `done` \| `partial` \| `planned` \| `deferred` \| `blocked` \| `removed`(已下线,仅留历史证据) |
| **发布关系** | 与当前首发/已签门禁的关系:`non_blocker` / `补证` / `加固` / `deferred_enhancement` |
| **关闭条件** | 何时可将本行升为 `done`(或关闭 `partial`);stub/SKIP/fallback 不得冒充 |
| **证据** | 可追溯到报告/commit/环境/schema;代码或 artifact 变化后须重验 |
@@ -68,7 +68,7 @@
| 可靠事件核心 | PG dispatcher、notify/advance/projection live、生产闭包 hook 退出、Phase2 选择性解耦已合入 | done | non_blocker | [E0A–E2](../test-reports/2026-09-15-event-e0a-production-scope-contract-test-report.md) 等;[复盘](../ops/research/2026-09-15-track-e-retrospective.md);[shared live](../test-reports/2026-09-15-event-notify-shared-live-test-report.md);[phase2 closeout](../test-reports/2026-09-15-phase2-closeout-test-report.md) | **before**:与 FAV off 混成 partial → **after**:FAV 独立行。选择性后续解耦按收益触发 | `architecture-docs` / `events` |
| Final Artifact Vault(FAV) | 不可变终态制品库;`FAV_DEGRADED_READ=false` | partial | deferred_enhancement | [Track E 复盘](../ops/research/2026-09-15-track-e-retrospective.md);[U7 报告](../test-reports/2026-09-16-fav-u7-incremental-test-report.md);[W6 shadow](../test-reports/2026-09-17-w6-fav-subscriber-shadow-test-report.md);[short-term](../ops/short-term/2026-09-17-w6-fav-subscriber-shadow.md) | **after W6**:subscriber `ObserveShadow` 只计算 intended outcome,不写副作用。仍**禁止**整支 merge;`FAV_DEGRADED_READ` 默认 false;**live 未放量**(staging→artifact→publication 各 ≥7 天观察窗) | `events` |
| 系统管理台 | 全屏系统级管理视图(总览/KB/特性开关/系统设置/工作流/角色/用户/文档 8 分组);`/api/v1/admin/system/*`;`system_settings` 白名单可写;吸收下线 AdminSettingsModal | done | non_blocker | [short-term](../ops/short-term/2026-09-17-system-admin-console.md);[测试报告](../test-reports/2026-09-17-system-admin-console-test-report.md);commit `f1404d5`/`0eaca61`、merge `dc7c31e`(dev);dev 冒烟 schema 49 + 四端点 + 写读回环 PASS | 二期:Playwright 实跑、细粒度 `system:*` 权限、设置审计日志、KB 运维动作、白名单项接消费方 | `web` + `sysadmin` |
-| 个人工程中控台 | 独立本地只读 CLI/`serve`/`export`;四轴状态;`.doc`/Git/Docker/`/ready` 聚合;不接产品后台 | done | deferred_enhancement(工具,非产品门禁) | [设计](../system-design/project-control-center/01-personal-engineering-control-center-plan.md);[报告](../test-reports/2026-09-17-engineering-dashboard-test-report.md) | 一期已交付。跨快照历史、远端 CI、富渲染另立后续 | `engineering-dashboard` |
+| 个人工程中控台(旧,已移除) | 原独立本地只读 CLI/`serve`/`export`;四轴状态;`.doc`/Git/Docker/`/ready` 聚合 | removed | 已被 Control Center 取代 | [设计(已归档)](../archive/design/2026-09-17-personal-engineering-control-center-plan.md);[报告](../test-reports/2026-09-17-engineering-dashboard-test-report.md) | 2026-09-17 代码移除(`cmd/engineering-dashboard`、`internal/engineeringdashboard`、`scripts/engineering-dashboard.sh`);能力由下一行 Control Center 承接 | `engineering-dashboard`(已删) |
| 个人中控台 Control Center | 独立 Go 服务(loopback,独立 pgvector 库):8 阶段流水线 + agent 协作 API(禁自动推进)+ 决策上报 + 租约/SSE + `.doc` 向量检索 + 导入导出 + 每日审查 + React SPA | partial | deferred_enhancement(个人工具,非产品门禁) | [设计](../system-design/project-control-center/02-control-center-design.md);[agent 接入规范](../system-design/project-control-center/agent-integration.md);[任务](../ops/short-term/2026-09-17-control-center-server.md);[报告](../test-reports/2026-09-17-control-center-test-report.md);commit `2be43ef`→`07a8e12`(P0–P5) | 后端 P0–P5 + 构建/单测/curl 冒烟已签。**未签**:前端人工走查(SPA 实机操作)。关闭:走查 PASS 后升 done;多仓库/远端 CI 另立 | `controlcenter` |
---
diff --git a/.doc/system-design/README.md b/.doc/system-design/README.md
index b74e53df..3336b29a 100644
--- a/.doc/system-design/README.md
+++ b/.doc/system-design/README.md
@@ -138,15 +138,17 @@
---
-### 8. 个人工程中控台
+### 8. 个人中控台(Control Center)
| 项 | 链接 |
|-----|------|
-| 设计与实施方案 | [project-control-center/01-personal-engineering-control-center-plan.md](project-control-center/01-personal-engineering-control-center-plan.md) |
-| 任务入口 | [../ops/short-term/2026-09-17-engineering-dashboard.md](../ops/short-term/2026-09-17-engineering-dashboard.md) |
-| 一句话总结 | 独立于产品后台的本地只读中控台,以同一不可变快照驱动实时项目看板、Agent JSON 和离线 HTML |
+| 设计与架构 SSOT | [project-control-center/02-control-center-design.md](project-control-center/02-control-center-design.md) |
+| Agent 接入规范 | [project-control-center/agent-integration.md](project-control-center/agent-integration.md) |
+| 任务入口 | [../ops/short-term/2026-09-17-control-center-server.md](../ops/short-term/2026-09-17-control-center-server.md) |
+| 一句话总结 | 独立于产品系统的个人项目管理中控台:8 阶段流水线(agent 禁自动推进)+ 决策上报 + 向量文档库 + 每日审查 + React SPA |
+| 旧版(已移除) | 只读工程中控台设计已归档:[../archive/design/2026-09-17-personal-engineering-control-center-plan.md](../archive/design/2026-09-17-personal-engineering-control-center-plan.md);任务:[../archive/ops/short-term/2026-09-17-engineering-dashboard.md](../archive/ops/short-term/2026-09-17-engineering-dashboard.md) |
-**核心边界**:计划、Git 实施、验证、环境实装四轴独立;冲突保留来源证据;不写文档、不操作 Git/Docker/部署/Herdr,不读取凭证。
+**核心边界**:个人工具、不接产品后台 sysadmin;agent 只能上报与 advance-request,阶段推进与模糊决策必须人工。
---
diff --git a/.doc/system-design/module-boundaries.yaml b/.doc/system-design/module-boundaries.yaml
index 38bd41f7..a650805a 100644
--- a/.doc/system-design/module-boundaries.yaml
+++ b/.doc/system-design/module-boundaries.yaml
@@ -275,7 +275,6 @@ imports:
- controlcenter/tasks
- controlcenter/webui
controlcenter/webui: []
- engineeringdashboard: []
table_owners:
document_records:
diff --git a/cmd/engineering-dashboard/main.go b/cmd/engineering-dashboard/main.go
deleted file mode 100644
index 43edb2a3..00000000
--- a/cmd/engineering-dashboard/main.go
+++ /dev/null
@@ -1,115 +0,0 @@
-package main
-
-import (
- "context"
- "fmt"
- "os"
- "path/filepath"
- "strings"
-
- "chat-agent-system/internal/engineeringdashboard"
-)
-
-func main() {
- if len(os.Args) < 2 {
- usage()
- os.Exit(2)
- }
- cmd := os.Args[1]
- args := os.Args[2:]
- switch cmd {
- case "serve":
- if err := runServe(args); err != nil {
- fmt.Fprintf(os.Stderr, "serve: %v\n", err)
- os.Exit(1)
- }
- case "export":
- if err := runExport(args); err != nil {
- fmt.Fprintf(os.Stderr, "export: %v\n", err)
- os.Exit(1)
- }
- case "help", "-h", "--help":
- usage()
- default:
- fmt.Fprintf(os.Stderr, "unknown command %q\n", cmd)
- usage()
- os.Exit(2)
- }
-}
-
-func usage() {
- fmt.Fprintf(os.Stderr, `engineering-dashboard — local read-only personal engineering control center
-
-Usage:
- engineering-dashboard serve [--repo PATH] [--addr 127.0.0.1:PORT]
- engineering-dashboard export [--repo PATH] [--output FILE]
-
-Does not write docs, operate git/docker, deploy, or control Herdr.
-`)
-}
-
-func parseFlags(args []string) (repo, addr, output string) {
- addr = "127.0.0.1:8787"
- output = "engineering-dashboard-snapshot.html"
- for i := 0; i < len(args); i++ {
- switch args[i] {
- case "--repo":
- i++
- if i < len(args) {
- repo = args[i]
- }
- case "--addr":
- i++
- if i < len(args) {
- addr = args[i]
- }
- case "--output":
- i++
- if i < len(args) {
- output = args[i]
- }
- }
- }
- return repo, addr, output
-}
-
-func runServe(args []string) error {
- repo, addr, _ := parseFlags(args)
- root, err := engineeringdashboard.ResolveRepoRoot(repo)
- if err != nil {
- return err
- }
- if !strings.HasPrefix(addr, "127.0.0.1:") && !strings.HasPrefix(addr, "localhost:") && !strings.HasPrefix(addr, "[::1]:") {
- return fmt.Errorf("addr must be loopback (got %s)", addr)
- }
- c := engineeringdashboard.NewCollector(root, engineeringdashboard.ExecRunner{})
- ctx := context.Background()
- if _, err := c.Refresh(ctx); err != nil {
- fmt.Fprintf(os.Stderr, "warning: initial refresh: %v\n", err)
- }
- srv, err := engineeringdashboard.NewServer(c)
- if err != nil {
- return err
- }
- fmt.Printf("engineering-dashboard listening on http://%s/?token=%s\n", addr, srv.Token)
- fmt.Printf("repo=%s\n", root)
- return engineeringdashboard.ListenAndServe(addr, srv.Handler())
-}
-
-func runExport(args []string) error {
- repo, _, output := parseFlags(args)
- root, err := engineeringdashboard.ResolveRepoRoot(repo)
- if err != nil {
- return err
- }
- if !filepath.IsAbs(output) {
- wd, _ := os.Getwd()
- output = filepath.Join(wd, output)
- }
- c := engineeringdashboard.NewCollector(root, engineeringdashboard.ExecRunner{})
- snap, err := c.Refresh(context.Background())
- if err != nil {
- return err
- }
- return engineeringdashboard.ExportHTML(*snap, output)
-}
diff --git a/internal/engineeringdashboard/assets/app.css b/internal/engineeringdashboard/assets/app.css
deleted file mode 100644
index 36febf7d..00000000
--- a/internal/engineeringdashboard/assets/app.css
+++ /dev/null
@@ -1,95 +0,0 @@
-:root {
- --bg: #0f1419;
- --panel: #1a222c;
- --text: #e7ecf1;
- --muted: #9aa7b5;
- --accent: #3d8bfd;
- --warn: #d4a017;
- --bad: #d64545;
- --ok: #3d9a6a;
- --border: #2b3642;
- --font: "IBM Plex Sans", "Noto Sans SC", sans-serif;
- --mono: "IBM Plex Mono", ui-monospace, monospace;
-}
-* { box-sizing: border-box; }
-body {
- margin: 0;
- font-family: var(--font);
- background: radial-gradient(1200px 600px at 10% -10%, #1b2735, var(--bg));
- color: var(--text);
- min-height: 100vh;
-}
-.skip {
- position: absolute; left: -9999px;
-}
-.skip:focus { left: 1rem; top: 1rem; background: #fff; color: #000; padding: .5rem; z-index: 9; }
-.top {
- display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1rem;
- padding: 1rem 1.25rem; border-bottom: 1px solid var(--border);
- background: rgba(26,34,44,.85); backdrop-filter: blur(6px);
-}
-.top h1 { margin: 0; font-size: 1.35rem; letter-spacing: .02em; }
-.meta { margin: .35rem 0 0; color: var(--muted); font-size: .85rem; font-family: var(--mono); }
-.banner { margin: 0; color: var(--warn); font-size: .85rem; max-width: 28rem; }
-.layout { display: grid; grid-template-columns: 16rem 1fr; min-height: calc(100vh - 5rem); }
-.nav-toggle { display: none; margin: .75rem; }
-nav {
- border-right: 1px solid var(--border); padding: 1rem; background: var(--panel);
-}
-nav ul { list-style: none; margin: 0; padding: 0; }
-nav li { margin: 0 0 .35rem; }
-.nav-btn {
- width: 100%; text-align: left; background: transparent; color: var(--text);
- border: 1px solid transparent; padding: .5rem .65rem; border-radius: .35rem; cursor: pointer;
-}
-.nav-btn.active, .nav-btn:hover, .nav-btn:focus-visible {
- border-color: var(--accent); background: #243041;
-}
-.search-label { display: block; margin-top: 1rem; font-size: .8rem; color: var(--muted); }
-#q {
- width: 100%; margin-top: .35rem; padding: .45rem .55rem; border-radius: .35rem;
- border: 1px solid var(--border); background: #10161d; color: var(--text);
-}
-.source-strip { margin-top: 1rem; font-size: .75rem; color: var(--muted); }
-.source-strip .chip {
- display: inline-block; margin: .15rem .2rem 0 0; padding: .15rem .4rem;
- border: 1px solid var(--border); border-radius: .25rem;
-}
-.chip.ok { border-color: var(--ok); }
-.chip.stale, .chip.degraded { border-color: var(--warn); color: var(--warn); }
-.chip.error { border-color: var(--bad); color: var(--bad); }
-main { padding: 1rem 1.25rem 2rem; overflow: auto; }
-.view-status { min-height: 1.25rem; color: var(--muted); font-size: .85rem; margin-bottom: .75rem; }
-.panel {
- background: rgba(26,34,44,.7); border: 1px solid var(--border); border-radius: .5rem;
- padding: 1rem; margin-bottom: 1rem;
-}
-.panel h2 { margin: 0 0 .75rem; font-size: 1.05rem; }
-.table-wrap { overflow-x: auto; }
-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
-th, td { text-align: left; padding: .45rem .5rem; border-bottom: 1px solid var(--border); vertical-align: top; }
-th { color: var(--muted); font-weight: 600; }
-.badge {
- display: inline-block; padding: .1rem .4rem; border-radius: .25rem;
- border: 1px solid var(--border); font-size: .75rem; margin-right: .25rem;
-}
-.badge.warn { border-color: var(--warn); color: var(--warn); }
-.badge.bad { border-color: var(--bad); color: var(--bad); }
-.pre {
- white-space: pre-wrap; word-break: break-word; font-family: var(--mono);
- font-size: .82rem; background: #0c1117; padding: .75rem; border-radius: .35rem;
- border: 1px solid var(--border); max-height: 28rem; overflow: auto;
-}
-.axes { display: grid; grid-template-columns: repeat(4, minmax(8rem, 1fr)); gap: .5rem; }
-.axis { border: 1px solid var(--border); border-radius: .35rem; padding: .5rem; font-size: .8rem; }
-.axis strong { display: block; color: var(--muted); margin-bottom: .25rem; }
-a, button.linkish {
- color: var(--accent); background: none; border: none; cursor: pointer; padding: 0; font: inherit;
-}
-@media (max-width: 860px) {
- .layout { grid-template-columns: 1fr; }
- .nav-toggle { display: inline-block; }
- nav { display: none; border-right: 0; border-bottom: 1px solid var(--border); }
- nav.open { display: block; }
- .axes { grid-template-columns: 1fr 1fr; }
-}
diff --git a/internal/engineeringdashboard/assets/app.js b/internal/engineeringdashboard/assets/app.js
deleted file mode 100644
index db7f347c..00000000
--- a/internal/engineeringdashboard/assets/app.js
+++ /dev/null
@@ -1,317 +0,0 @@
-(function () {
- function resolveToken() {
- const fromURL = new URL(window.location.href).searchParams.get("token");
- if (fromURL) return fromURL;
- const meta = document.querySelector('meta[name="engineering-dashboard-token"]');
- const fromMeta = meta && meta.getAttribute("content");
- if (fromMeta && fromMeta !== "__TOKEN__") return fromMeta;
- return "";
- }
- const token = resolveToken();
- const state = {
- snap: null,
- view: "overview",
- q: "",
- selectedDoc: "",
- scrollY: 0,
- };
-
- const el = {
- content: document.getElementById("content"),
- meta: document.getElementById("repo-meta"),
- sources: document.getElementById("source-strip"),
- status: document.getElementById("view-status"),
- q: document.getElementById("q"),
- navToggle: document.getElementById("nav-toggle"),
- nav: document.getElementById("nav"),
- };
-
- function qs() {
- const u = new URL(window.location.href);
- return u.searchParams;
- }
-
- function syncURL() {
- const u = new URL(window.location.href);
- if (token) u.searchParams.set("token", token);
- u.searchParams.set("view", state.view);
- if (state.q) u.searchParams.set("q", state.q); else u.searchParams.delete("q");
- if (state.selectedDoc) u.searchParams.set("doc", state.selectedDoc); else u.searchParams.delete("doc");
- history.replaceState(null, "", u.toString());
- }
-
- function loadURL() {
- const p = qs();
- state.view = p.get("view") || "overview";
- state.q = p.get("q") || "";
- state.selectedDoc = p.get("doc") || "";
- el.q.value = state.q;
- document.querySelectorAll(".nav-btn").forEach((b) => {
- b.classList.toggle("active", b.dataset.view === state.view);
- });
- }
-
- async function fetchSnapshot(force) {
- if (!token) {
- el.status.textContent = "缺少访问令牌";
- renderHardError("缺少 token:请使用服务启动时打印的完整 URL(含 ?token=)打开页面");
- return false;
- }
- el.status.textContent = state.snap ? "刷新中…" : "首次加载中…";
- const url = "/api/v1/snapshot?token=" + encodeURIComponent(token) + (force ? "&refresh=1" : "");
- const headers = {};
- if (state.snap && state.snap.revision_id) headers["If-None-Match"] = '"' + state.snap.revision_id + '"';
- const res = await fetch(url, { headers, credentials: "same-origin" });
- if (res.status === 304) {
- el.status.textContent = "已是最新 revision " + state.snap.revision_id;
- return false;
- }
- if (!res.ok) {
- el.status.textContent = "加载失败 HTTP " + res.status;
- if (!state.snap) renderHardError("无法获取快照");
- return false;
- }
- state.snap = await res.json();
- el.status.textContent = "generation " + state.snap.generation_id + " · revision " + state.snap.revision_id;
- return true;
- }
-
- function escapeText(s) {
- const d = document.createElement("div");
- d.textContent = s == null ? "" : String(s);
- return d.innerHTML;
- }
-
- function badge(text, cls) {
- return '' + escapeText(text) + "";
- }
-
- function renderMeta() {
- const s = state.snap;
- if (!s) return;
- el.meta.textContent = [
- s.repository.root,
- "branch=" + s.repository.branch,
- "HEAD=" + (s.repository.head || "").slice(0, 12),
- s.repository.dirty ? "dirty" : "clean",
- "at " + s.generated_at,
- ].join(" · ");
- el.sources.innerHTML = (s.sources || []).map((src) => {
- const cls = src.freshness === "stale" || src.status === "degraded" ? "stale" :
- src.status === "error" ? "error" : "ok";
- return '' + escapeText(src.name + ":" + src.status + "/" + src.freshness) + "";
- }).join("");
- }
-
- function matchQuery(text) {
- if (!state.q) return true;
- return String(text || "").toLowerCase().includes(state.q.toLowerCase());
- }
-
- function renderOverview() {
- const s = state.snap;
- const tasks = (s.tasks || []).filter((t) => ["planned", "in_progress", "blocked"].includes(t.doc_status) || matchQuery(t.title + t.path));
- const conflicts = s.conflicts || [];
- let html = ' 证据链焦点任务
';
- tasks.slice(0, 40).forEach((t) => {
- html += "任务 计划 Git 验证 实装 ";
- });
- html += "" + escapeText(t.title) + " ";
- html += "" + badge(t.doc_status) + (t.claim && t.claim.claimed ? badge("claimed") : badge("unclaimed", "warn")) + " ";
- html += "" + escapeText((t.git && t.git.branch) || "—") + " " + escapeText((t.git && t.git.cleanliness) || "") + " ";
- html += "" + escapeText((t.verification && t.verification.result) || "unknown") + " ";
- const dep = t.deployment || {};
- html += "" + escapeText(Object.keys(dep).map((k) => k + ":" + (dep[k].app_readiness || "unknown")).join(" ")) + " 漂移 / 冲突
';
- if (!conflicts.length) html += "";
- else {
- html += "" + conflicts.map((c) => "
";
- }
- html += "模块
';
- rows.forEach((m) => {
- html += "模块 状态 mutex ";
- });
- html += "" + escapeText(m.name) + " " + escapeText(m.status) + " " + escapeText(m.mutex) + " ' + escapeText(t.title) + "
";
- html += '
" + escapeText((t.claim && t.claim.owner) || "unassigned") + " · " + escapeText((t.claim && t.claim.agent_kind) || "") + " · activity=" + escapeText((t.claim && t.claim.activity) || "unknown") + "" + t.evidence_chain.map((p) => '").join("") + "
";
- }
- html += "
无匹配任务
"; - } - - function renderGit() { - const rows = (state.snap.git || []).filter((g) => matchQuery(g.name + (g.worktree || ""))); - let html = '| 分支 | ahead/behind | worktree | 备注 |
|---|---|---|---|
| " + escapeText(g.name) + (g.is_current ? " *" : "") + " | " + escapeText(g.ahead + "/" + g.behind) + " | " + escapeText(g.worktree || "") + " | " + escapeText(g.remote_note || "") + " |
" + badge("container:" + (e.facets && e.facets.container_status)) + badge("ready:" + (e.facets && e.facets.app_readiness)) + badge("services:" + (e.facets && e.facets.services_ok), e.facets && e.facets.services_ok === "partial" ? "warn" : "") + "
"; - if (e.drift_notes && e.drift_notes.length) { - html += "| 容器 | 状态 | health | image |
|---|---|---|---|
| " + escapeText(c.name) + " | " + escapeText(c.status) + " | " + escapeText(c.health) + " | " + escapeText(c.image) + " |
| 标题 | 路径 | 状态 |
|---|---|---|
| " + escapeText(d.path) + " | " + escapeText(d.status || "") + " |
未找到文档
"; - else { - html += ""; - html += '' + escapeText(msg) + "
无可用缓存快照。