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