diff --git a/config.py b/config.py index 0007471..d0f285b 100644 --- a/config.py +++ b/config.py @@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.120" +SYSTEM_VERSION = "V10.121" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/scripts/check_observability_ui.py b/scripts/check_observability_ui.py index 9f87805..51de3c3 100644 --- a/scripts/check_observability_ui.py +++ b/scripts/check_observability_ui.py @@ -25,6 +25,7 @@ WEB_CSS_PATH = Path("web/static/css/observability-system.css") SHELL_PATH = Path("templates/components/_ewoooc_shell.html") BASE_PATH = Path("templates/ewoooc_base.html") ROUTE_PATH = Path("routes/admin_observability_routes.py") +OVERVIEW_PATH = Path("templates/admin/observability_overview.html") @dataclass(frozen=True) @@ -136,6 +137,23 @@ FORBIDDEN_BASE_PATTERNS = [ ), ] +FORBIDDEN_OVERVIEW_COPY = [ + "AI Observability Command Room", + "Risk Signals", + "AI Calls / 24h", + "Host Cascade", + "AI Runtime", + "Learning Loop", + ">Command<", + ">Runtime<", + ">Quality<", + "RAG hits", + "Cache hits", + "30d episodes", + "MCP calls", + "force-throttle", +] + def line_number(text: str, index: int) -> int: return text.count("\n", 0, index) + 1 @@ -240,6 +258,21 @@ def scan_base_topbar() -> list[str]: return findings +def scan_overview_copy() -> list[str]: + path = ROOT / OVERVIEW_PATH + if not path.exists(): + return [f"{OVERVIEW_PATH}: missing required overview page"] + + text = path.read_text(encoding="utf-8") + findings: list[str] = [] + for snippet in FORBIDDEN_OVERVIEW_COPY: + if snippet in text: + findings.append( + f"{OVERVIEW_PATH}: legacy English overview copy `{snippet}` must be localized to the V2 workbench language" + ) + return findings + + def scan_shell() -> list[str]: path = ROOT / SHELL_PATH if not path.exists(): @@ -324,6 +357,7 @@ def main() -> int: findings.extend(scan_css()) findings.extend(scan_shell()) findings.extend(scan_base_topbar()) + findings.extend(scan_overview_copy()) findings.extend(scan_nav_contract()) if findings: @@ -337,6 +371,7 @@ def main() -> int: print(f"- css guardrails checked: {len(REQUIRED_CSS_SNIPPETS)}") print(f"- sidebar/nav guardrails checked: {len(REQUIRED_SHELL_SNIPPETS)}") print(f"- base/topbar guardrails checked: {len(REQUIRED_BASE_SNIPPETS) + len(FORBIDDEN_BASE_PATTERNS)}") + print(f"- overview copy guardrails checked: {len(FORBIDDEN_OVERVIEW_COPY)}") print(f"- nav contract checked: {len(OBSERVABILITY_PAGES)} pages") return 0 diff --git a/static/css/observability-system.css b/static/css/observability-system.css index 7cb5183..5eba739 100644 --- a/static/css/observability-system.css +++ b/static/css/observability-system.css @@ -1975,7 +1975,7 @@ .ppt-title ) { color: var(--obs-ink) !important; - font-family: var(--momo-font-family, "Inter", "Noto Sans TC", system-ui, sans-serif) !important; + font-family: var(--momo-font-display, "JetBrains Mono", "Noto Sans TC", ui-monospace, monospace) !important; font-size: var(--obs-title-size) !important; font-weight: 800 !important; letter-spacing: 0 !important; diff --git a/templates/admin/observability_overview.html b/templates/admin/observability_overview.html index 05e4178..8b31607 100644 --- a/templates/admin/observability_overview.html +++ b/templates/admin/observability_overview.html @@ -8,7 +8,7 @@ --obs-ink: var(--momo-ink, #302720); --obs-muted: var(--momo-muted, #8b8077); --obs-paper: var(--momo-paper, #fff8ef); - --obs-card: rgba(255, 252, 246, 0.92); + --obs-card: var(--momo-bg-surface, #faf7f0); --obs-line: rgba(86, 64, 48, 0.14); --obs-accent: var(--momo-accent, #c96442); --obs-accent-soft: rgba(201, 100, 66, 0.12); @@ -17,28 +17,31 @@ --obs-red: #b94b45; --obs-blue: #4f6f8f; color: var(--obs-ink); + font-family: var(--momo-font-family, "Inter", "Noto Sans TC", system-ui, sans-serif); } .obs-hero { position: relative; overflow: hidden; border: 1px solid var(--obs-line); - border-radius: 28px; - padding: clamp(1.35rem, 3vw, 2.3rem); - background: - radial-gradient(circle at 12% 18%, rgba(201, 100, 66, 0.2), transparent 28%), - radial-gradient(circle at 84% 12%, rgba(79, 111, 143, 0.18), transparent 30%), - linear-gradient(135deg, #fff8ed 0%, #f7eadb 48%, #fffdf8 100%); - box-shadow: 0 22px 55px rgba(70, 46, 28, 0.1); + border-radius: var(--momo-radius-lg, 8px); + padding: clamp(1.05rem, 2.2vw, 1.65rem); + background-color: var(--momo-bg-surface, #faf7f0); + background-image: var(--obs-dot); + background-size: 13px 13px; + box-shadow: var(--momo-shadow-md, 0 2px 8px rgba(42, 37, 32, 0.06)); } .obs-hero::after { content: ""; position: absolute; - inset: auto -8% -42% 42%; - height: 260px; - background: repeating-linear-gradient(90deg, rgba(201, 100, 66, 0.1) 0 1px, transparent 1px 18px); - transform: rotate(-7deg); + inset: auto 1rem 1rem auto; + width: 8.5rem; + height: 8.5rem; + border: 1px solid color-mix(in srgb, var(--obs-accent) 22%, transparent); + background-image: var(--obs-dot); + background-size: 10px 10px; + opacity: 0.46; pointer-events: none; } @@ -46,30 +49,36 @@ display: inline-flex; align-items: center; gap: 0.5rem; - padding: 0.42rem 0.72rem; + padding: 0.36rem 0.58rem; border: 1px solid rgba(201, 100, 66, 0.22); - border-radius: 999px; - background: rgba(255, 255, 255, 0.58); + border-radius: var(--momo-radius-lg, 8px); + background: var(--momo-bg-elevated, #fdfaf3); color: var(--obs-accent); - font-size: 0.78rem; - letter-spacing: 0.12em; - text-transform: uppercase; + font-family: var(--momo-font-family-mono, "JetBrains Mono", ui-monospace, monospace); + font-size: var(--momo-text-label, 0.6875rem); + font-weight: 800; + letter-spacing: 0.06em; + } + + .obs-kicker-date { + color: var(--obs-muted); } .obs-title { - margin: 0.85rem 0 0.4rem; + margin: 0.7rem 0 0.35rem; max-width: 820px; - font-family:'Noto Sans TC','Inter',sans-serif; - font-size: clamp(1.9rem, 3.2vw, 2.75rem); - line-height: 0.95; - letter-spacing: -0.055em; + font-family: var(--momo-font-display, "JetBrains Mono", "Noto Sans TC", ui-monospace, monospace); + font-size: var(--obs-title-size, 1.8rem); + line-height: 1.18; + letter-spacing: 0; + font-weight: 800; } .obs-lede { max-width: 760px; margin: 0; color: var(--obs-muted); - font-size: 1rem; + font-size: var(--momo-text-body, 0.875rem); line-height: 1.75; } @@ -83,28 +92,30 @@ .obs-signal { position: relative; z-index: 1; - min-height: 112px; - padding: 1rem; + min-height: 104px; + padding: 0.92rem; border: 1px solid var(--obs-line); - border-radius: 20px; - background: rgba(255, 255, 255, 0.66); + border-radius: var(--momo-radius-lg, 8px); + background: var(--momo-bg-surface, #faf7f0); backdrop-filter: blur(8px); } .obs-signal-label, .obs-section-eyebrow, .obs-route-code { - color: var(--obs-muted); - font-size: 0.72rem; - letter-spacing: 0.11em; - text-transform: uppercase; + color: color-mix(in srgb, var(--obs-accent) 76%, var(--obs-muted)); + font-family: var(--momo-font-family-mono, "JetBrains Mono", ui-monospace, monospace); + font-size: var(--momo-text-label, 0.6875rem); + font-weight: 800; + letter-spacing: 0.06em; } .obs-signal-value { margin-top: 0.45rem; - font-size: clamp(1.65rem, 3vw, 2.35rem); - font-weight: 800; - letter-spacing: -0.04em; + font-family: var(--momo-font-family-mono, "JetBrains Mono", ui-monospace, monospace); + font-size: var(--obs-value-size, 1.85rem); + font-weight: 850; + letter-spacing: 0; } .obs-signal-note { @@ -122,9 +133,9 @@ .obs-panel { border: 1px solid var(--obs-line); - border-radius: 24px; + border-radius: var(--momo-radius-lg, 8px); background: var(--obs-card); - box-shadow: 0 14px 36px rgba(70, 46, 28, 0.07); + box-shadow: var(--momo-shadow-md, 0 2px 8px rgba(42, 37, 32, 0.06)); } .obs-panel-head { @@ -137,9 +148,9 @@ .obs-panel-title { margin: 0.18rem 0 0; - font-size: 1.1rem; + font-size: var(--momo-text-title, 1.0625rem); font-weight: 800; - letter-spacing: -0.02em; + letter-spacing: 0; } .obs-panel-body { @@ -158,13 +169,13 @@ align-items: center; padding: 0.9rem; border: 1px solid var(--obs-line); - border-radius: 18px; - background: rgba(255, 255, 255, 0.62); + border-radius: var(--momo-radius-lg, 8px); + background: var(--momo-bg-elevated, #fdfaf3); } - .obs-host-card.is-good { border-left: 5px solid var(--obs-green); } - .obs-host-card.is-warn { border-left: 5px solid var(--obs-amber); } - .obs-host-card.is-bad { border-left: 5px solid var(--obs-red); } + .obs-host-card.is-good { border-left: 4px solid var(--obs-green); } + .obs-host-card.is-warn { border-left: 4px solid var(--obs-amber); } + .obs-host-card.is-bad { border-left: 4px solid var(--obs-red); } .obs-host-top { display: flex; @@ -178,9 +189,10 @@ } .obs-host-pct { + font-family: var(--momo-font-family-mono, "JetBrains Mono", ui-monospace, monospace); font-size: 1.35rem; font-weight: 850; - letter-spacing: -0.04em; + letter-spacing: 0; } .obs-host-meta, @@ -230,7 +242,7 @@ align-items: center; gap: 0.35rem; padding: 0.32rem 0.55rem; - border-radius: 999px; + border-radius: var(--momo-radius-lg, 8px); background: var(--obs-accent-soft); color: var(--obs-accent); font-size: 0.76rem; @@ -251,15 +263,16 @@ .obs-mini { padding: 0.85rem; border: 1px solid var(--obs-line); - border-radius: 18px; - background: rgba(255, 255, 255, 0.58); + border-radius: var(--momo-radius-lg, 8px); + background: var(--momo-bg-elevated, #fdfaf3); } .obs-mini strong { display: block; margin-top: 0.25rem; + font-family: var(--momo-font-family-mono, "JetBrains Mono", ui-monospace, monospace); font-size: 1.45rem; - letter-spacing: -0.04em; + letter-spacing: 0; } .obs-status-good { color: var(--obs-green); } @@ -273,8 +286,8 @@ gap: 0.45rem; padding: 0.58rem 0.78rem; border: 1px solid rgba(201, 100, 66, 0.25); - border-radius: 999px; - background: rgba(255, 255, 255, 0.65); + border-radius: var(--momo-radius-lg, 8px); + background: var(--momo-bg-elevated, #fdfaf3); color: var(--obs-accent); text-decoration: none; font-size: 0.82rem; @@ -298,16 +311,18 @@ .obs-route-group { border: 1px solid var(--obs-line); - border-radius: 24px; + border-radius: var(--momo-radius-lg, 8px); padding: 1rem; - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.74), rgba(255, 248, 239, 0.82)); + background-color: var(--momo-bg-surface, #faf7f0); + background-image: var(--obs-dot); + background-size: 13px 13px; } .obs-route-group h3 { margin: 0.25rem 0 0.85rem; - font-size: 1.1rem; + font-size: var(--momo-text-title, 1.0625rem); font-weight: 850; + letter-spacing: 0; } .obs-route-list { @@ -322,10 +337,10 @@ align-items: center; padding: 0.72rem; border: 1px solid transparent; - border-radius: 16px; + border-radius: var(--momo-radius-lg, 8px); color: var(--obs-ink); text-decoration: none; - background: rgba(255, 255, 255, 0.58); + background: var(--momo-bg-elevated, #fdfaf3); transition: border-color 160ms ease, transform 160ms ease, background 160ms ease; } @@ -341,7 +356,7 @@ place-items: center; width: 2rem; height: 2rem; - border-radius: 12px; + border-radius: 6px; background: var(--obs-accent-soft); color: var(--obs-accent); } @@ -403,32 +418,32 @@
- AI Observability Command Room · {{ today }} + 01 指揮總覽 · {{ today }}

AI 觀測戰情室

- 這裡是 momo-pro 私有 AI 中樞的第一入口:三主機、AI 呼叫、RAG 學習、MCP、AIOps、預算與 PPT 視覺審核全部收斂到同一張作戰圖。數字只讀正式資料來源;沒有資料時顯示診斷空狀態,不用假 KPI 撐場面。 + 私有 AI 中樞的第一入口:三主機、AI 呼叫、RAG 學習、MCP、AIOps、預算與 PPT 視覺審核收斂到同一張工作台。所有數字只讀正式資料來源;缺資料時呈現可診斷空狀態。

-
Risk Signals
+
即時風險
{{ risk_count }}
主機、預算、錯誤率、待審核的即時風險數
-
AI Calls / 24h
+
24 小時呼叫
{{ "{:,}".format(ai.total) if ai else '—' }}
-
Token {{ "{:,}".format(ai.tokens) if ai else '—' }}
+
權杖量:{{ "{:,}".format(ai.tokens) if ai else '—' }}
-
Cost
+
成本水位
${{ "%.2f"|format(ai.cost_24h) if ai else '0.00' }}
當月累計 ${{ "%.2f"|format(summary.month_cost|default(0)) }}
-
RAG Hit Rate
+
RAG 命中率
{{ "%.1f"|format(ai.rag_rate) if ai else '—' }}{% if ai %}%{% endif %}
-
Cache {{ "%.0f"|format(ai.cache_rate) if ai else '—' }}{% if ai %}%{% endif %}
+
快取命中 {{ "%.0f"|format(ai.cache_rate) if ai else '—' }}{% if ai %}%{% endif %}
@@ -438,7 +453,7 @@
-
Host Cascade
+
02 主機級聯

三主機生命線

主機健康 @@ -453,7 +468,7 @@ {{ h.label }} {{ "%.1f"|format(h.uptime_pct) }}%
-
{{ h.up }}/{{ h.total }} probes · 平均 {{ h.avg_ms }} ms · 24h 視窗
+
{{ h.up }}/{{ h.total }} 次探測 · 平均 {{ h.avg_ms }} ms · 24 小時視窗
{% if host_sparkline.get(h.label) %} @@ -468,7 +483,7 @@ {% else %}
- host_health_probes 無資料。請確認 migration 029 與 scheduler probe job 是否已啟動。 + 主機探測尚無資料。請確認第 029 號資料遷移與排程探測任務是否已啟動。
{% endif %}
@@ -478,7 +493,7 @@
-
Budget Guard
+
預算守門

預算告警

處理預算 @@ -509,7 +524,7 @@
-
AI Runtime
+
AI 呼叫

呼叫品質

詳情 @@ -519,8 +534,8 @@
錯誤率{{ "%.1f"|format(ai.error_rate) }}%
失敗{{ ai.errors }}
-
RAG hits{{ ai.rag_hits }}
-
Cache hits{{ ai.cache_hits }}
+
RAG 命中{{ ai.rag_hits }}
+
快取命中{{ ai.cache_hits }}
{% else %}
ai_calls 無 24h 資料。
@@ -531,7 +546,7 @@
-
Learning Loop
+
學習閉環

RAG 學習閘

審核 @@ -540,7 +555,7 @@ {% if summary.episodes %}
待審核{{ summary.episodes.pending }}
-
30d episodes{{ summary.episodes.total_30d }}
+
30 日樣本{{ summary.episodes.total_30d }}
已晉升{{ summary.episodes.approved_30d }}
晉升率{{ "%.0f"|format(summary.episodes.approval_rate) }}%
@@ -553,7 +568,7 @@
-
AIOps / MCP / PPT
+
自動化後勤

自動化後勤

@@ -561,7 +576,7 @@
AIOps 未解{{ summary.aiops.incidents_open if summary.aiops else '—' }}
自癒成功率{{ "%.0f"|format(summary.aiops.heal_rate) if summary.aiops else '—' }}{% if summary.aiops %}%{% endif %}
-
MCP calls{{ "{:,}".format(summary.mcp.total) if summary.mcp else '—' }}
+
MCP 呼叫{{ "{:,}".format(summary.mcp.total) if summary.mcp else '—' }}
PPT 通過率{{ "%.0f"|format(summary.ppt.pass_rate) if summary.ppt and summary.ppt.total > 0 else '—' }}{% if summary.ppt and summary.ppt.total > 0 %}%{% endif %}
@@ -571,7 +586,7 @@
-
Command
+
03 戰情指揮

戰情室

-
Quality
+
RAG 品質

RAG 與品質

RAG 晉升審核Promotion Gate 與人工審核。07 @@ -603,7 +618,7 @@

- 資料來源:host_health_probes / ai_calls / ai_call_budgets / learning_episodes / rag_query_log / mcp_calls / incidents / heal_logs / ppt_audit_results。 + 資料來源:主機探測、AI 呼叫、預算、學習事件、RAG 查詢、MCP 呼叫、事件與自癒、PPT 審核。

@@ -636,7 +651,7 @@ legend: { display: false }, tooltip: { displayColors: false, - callbacks: { label: c => `${c.label}: ${c.parsed.y.toFixed(0)}% uptime` } + callbacks: { label: c => `${c.label}: ${c.parsed.y.toFixed(0)}% 可用率` } } }, scales: { diff --git a/web/static/css/observability-system.css b/web/static/css/observability-system.css index 7cb5183..5eba739 100644 --- a/web/static/css/observability-system.css +++ b/web/static/css/observability-system.css @@ -1975,7 +1975,7 @@ .ppt-title ) { color: var(--obs-ink) !important; - font-family: var(--momo-font-family, "Inter", "Noto Sans TC", system-ui, sans-serif) !important; + font-family: var(--momo-font-display, "JetBrains Mono", "Noto Sans TC", ui-monospace, monospace) !important; font-size: var(--obs-title-size) !important; font-weight: 800 !important; letter-spacing: 0 !important;