#!/usr/bin/env python3 # -*- coding: utf-8 -*- """外部工具診斷頁 payload 組裝服務。 Route 只負責 HTTP glue;Metabase、Grist、Webcrumbs 的狀態文案、 替代入口與 shared-ui host data 在這裡集中組裝。 """ from __future__ import annotations from urllib.parse import urlparse from config import ( GRIST_URL, METABASE_URL, SYSTEM_VERSION, WEBCRUMBS_ASSET_UPSTREAM_URL, WEBCRUMBS_BASE_URL, WEBCRUMBS_ENABLED, WEBCRUMBS_PLUGIN_BASE_URL, WEBCRUMBS_RUNTIME_URL, WEBCRUMBS_RUNTIME_VERSION, ) from services.logger_manager import SystemLogger from services.webcrumbs_host_data_service import build_webcrumbs_marketplace_host_data sys_log = SystemLogger("ExternalToolPayloadService").get_logger() def _safe_launch_url(configured_url, bridge_path): """回傳安全的站內啟動 URL,避免導覽跳到其他專案。""" candidate = (configured_url or "").strip() if not candidate or candidate.rstrip("/") == bridge_path.rstrip("/"): return "" if candidate.startswith("/") and not candidate.startswith("//"): return candidate parsed = urlparse(candidate) if parsed.scheme in {"http", "https"} and parsed.hostname == "mo.wooo.work": if parsed.path.rstrip("/") == bridge_path.rstrip("/"): return "" return candidate return "" def _configured_url_label(configured_url): candidate = (configured_url or "").strip() if not candidate: return "未設定" parsed = urlparse(candidate) if parsed.scheme in {"http", "https"} and parsed.hostname != "mo.wooo.work": return f"{parsed.hostname}(已由入口攔截)" return candidate def _is_blocked_external_url(configured_url): candidate = (configured_url or "").strip() if not candidate or candidate.startswith("/") and not candidate.startswith("//"): return False parsed = urlparse(candidate) return parsed.scheme in {"http", "https"} and parsed.hostname != "mo.wooo.work" def build_webcrumbs_auth_required_seed_data(): """Return a non-sensitive host data shape when auth is not proven.""" return { "marketSnapshot": [ { "name": "MOMO/PChome host data requires authentication", "price": "not_available", "change_pct": "not_available", "freshness_status": "auth_required", } ], "aiCandidate": { "ticker": "-", "name": "MOMO/PChome host data locked", "thesis": "Webcrumbs runtime 已接入,但真實 SKU、價格與候選摘要需要登入 session 或 X-Internal-Key;未授權時不顯示 fallback demo 數字,也不顯示正式比價資料。", "confidence_score": "not_available", "risk_level": "auth_required", "release_status": "blocked", "evidence_refs": ["auth_required"], "updated_at": SYSTEM_VERSION, }, "metadata": { "source": "auth_required", "row_count": 0, "writes_database": False, "calls_llm": False, "fetches_external": False, }, } def build_webcrumbs_seed_data(limit: int = 5): """Build safe Webcrumbs host data for page seed and read-only API.""" try: return build_webcrumbs_marketplace_host_data(limit=limit) except Exception as exc: sys_log.warning(f"[Webcrumbs] host data build skipped: {exc}") return { "marketSnapshot": [ { "name": "MOMO/PChome data source unavailable", "price": "not_available", "change_pct": "not_available", "freshness_status": "diagnostic_unavailable", } ], "aiCandidate": { "ticker": "-", "name": "MOMO/PChome host data unavailable", "thesis": "Webcrumbs runtime 已接入,但只讀比價摘要暫時不可用;本頁不顯示 fallback demo 數字。", "confidence_score": "not_available", "risk_level": "source_unavailable", "release_status": "blocked", "evidence_refs": ["competitor_intel_repository_unavailable"], "updated_at": SYSTEM_VERSION, }, } def _metabase_payload(): launch_url = _safe_launch_url(METABASE_URL, "/metabase") return { "key": "metabase", "eyebrow": "Analytics Bridge", "title": "自訂圖表入口", "status_label": "待接上 BI 服務" if not launch_url else "可開啟", "summary": "這裡是 momo-pro 的 Metabase 安全入口。導覽先停在系統內部,不再出現空白頁或錯站外跳。", "detail": "目前正式主機沒有啟用 Metabase / Grist 的 bi profile 容器,Gateway 也沒有可用 proxy;因此先提供可用的分析替代入口與設定診斷。", "launch_href": launch_url, "launch_label": "開啟 Metabase", "configured_label": _configured_url_label(METABASE_URL), "checks": [ {"label": "導覽路由", "value": "/metabase", "state": "ok"}, {"label": "跨站保護", "value": "已鎖在 momo-pro", "state": "ok"}, {"label": "BI 服務", "value": "尚未接入 proxy", "state": "warn"}, ], "actions": [ {"label": "月份總表", "href": "/monthly_summary_analysis", "icon": "fas fa-table"}, {"label": "成長分析", "href": "/growth_analysis", "icon": "fas fa-arrow-trend-up"}, {"label": "當日業績", "href": "/daily_sales", "icon": "fas fa-calendar-day"}, ], } def _webcrumbs_payload(include_host_data: bool = True): runtime_ready = bool(WEBCRUMBS_ENABLED and WEBCRUMBS_RUNTIME_URL) launch_url = WEBCRUMBS_BASE_URL if WEBCRUMBS_BASE_URL else "" plugin_base = (WEBCRUMBS_PLUGIN_BASE_URL or "").rstrip("/") plugin_previews = [] if plugin_base: plugin_previews = [ { "label": "Market ticker strip", "uri": f"{plugin_base}/finance.market-ticker-strip/0.1.0", }, { "label": "AI candidate card", "uri": f"{plugin_base}/finance.ai-candidate-card/0.1.0", }, ] return { "key": "webcrumbs", "eyebrow": "Shared UI Runtime", "title": "Webcrumbs 共用 UI Runtime", "status_label": "Runtime 已接入" if runtime_ready else "Runtime 未啟用", "summary": "Webcrumbs 已接到 momo-pro 全站 shell,正式頁面透過同源 asset proxy 載入 shared-ui loader 與版本化外掛。", "detail": "工具入口可開啟 Webcrumbs/Open Design;正式頁面載入則走 /webcrumbs-assets/,避免跨域憑證或官方 @latest 造成生產頁面不穩。", "launch_href": launch_url, "launch_label": "開啟 Webcrumbs", "configured_label": WEBCRUMBS_RUNTIME_URL or "未設定", "checks": [ {"label": "導覽路由", "value": "/webcrumbs", "state": "ok"}, { "label": "Runtime", "value": "已載入全站 shell" if runtime_ready else "未啟用或 URL 無效", "state": "ok" if runtime_ready else "warn", }, { "label": "版本", "value": WEBCRUMBS_RUNTIME_VERSION or "未指定", "state": "ok" if WEBCRUMBS_RUNTIME_VERSION else "warn", }, { "label": "Plugin Base", "value": WEBCRUMBS_PLUGIN_BASE_URL or "未設定", "state": "ok" if WEBCRUMBS_PLUGIN_BASE_URL else "warn", }, { "label": "Asset Proxy", "value": WEBCRUMBS_ASSET_UPSTREAM_URL or "未設定", "state": "ok" if WEBCRUMBS_ASSET_UPSTREAM_URL else "warn", }, ], "actions": [ {"label": "商品看板", "href": "/", "icon": "fas fa-border-all"}, {"label": "比價覆核", "href": "/?filter=pchome_review", "icon": "fas fa-scale-balanced"}, {"label": "AI 觀測台", "href": "/observability/overview", "icon": "fas fa-satellite-dish"}, ], "plugin_previews": plugin_previews, "plugin_preview_uris": [preview["uri"] for preview in plugin_previews], "plugin_seed_data": ( build_webcrumbs_seed_data(limit=5) if include_host_data else build_webcrumbs_auth_required_seed_data() ), } def _grist_payload(): launch_url = _safe_launch_url(GRIST_URL, "/grist") blocked_external = _is_blocked_external_url(GRIST_URL) return { "key": "grist", "eyebrow": "Data Collaboration", "title": "資料協作入口", "status_label": "錯鏈已攔截" if blocked_external else ("可開啟" if launch_url else "待接上協作服務"), "summary": "資料協作入口已回到 momo-pro 內部,不會再連到其他專案站台。", "detail": "Grist 的正式 momo-pro 專案隔離尚未接上 Gateway;在完成專屬服務前,本頁會提供資料作業入口與目前設定狀態。", "launch_href": launch_url, "launch_label": "開啟 Grist", "configured_label": _configured_url_label(GRIST_URL), "checks": [ {"label": "導覽路由", "value": "/grist", "state": "ok"}, {"label": "錯站攔截", "value": "已攔截外部錯鏈" if blocked_external else "未偵測錯鏈", "state": "ok"}, {"label": "協作服務", "value": "尚未接入 proxy", "state": "warn"}, ], "actions": [ {"label": "月份總表", "href": "/monthly_summary_analysis", "icon": "fas fa-table"}, {"label": "雲端匯入", "href": "/auto_import", "icon": "fas fa-download"}, {"label": "業績分析", "href": "/sales_analysis", "icon": "fas fa-chart-bar"}, ], } def build_external_tool_payload(kind, include_host_data: bool = True): """建立外部工具診斷頁 payload。""" if kind == "metabase": return _metabase_payload() if kind == "webcrumbs": return _webcrumbs_payload(include_host_data=include_host_data) return _grist_payload()