Files
ewoooc/services/external_tool_payload_service.py
OoO 353465d38a
All checks were successful
CD Pipeline / deploy (push) Successful in 1m6s
V10.515 收緊 Webcrumbs host data 授權
2026-05-31 20:59:47 +08:00

246 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""外部工具診斷頁 payload 組裝服務。
Route 只負責 HTTP glueMetabase、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()