From dafe53425916f90bd9eb1dc91802aca8ea1f00bc Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 18 Jun 2026 15:20:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(web):=20=E5=9C=A8=E5=AF=A9=E6=89=B9?= =?UTF-8?q?=E4=BD=87=E5=88=97=E9=A1=AF=E7=A4=BA=E8=B3=87=E7=94=A2=E6=B2=89?= =?UTF-8?q?=E6=BE=B1=E7=9F=A9=E9=99=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/messages/en.json | 18 ++++ apps/web/messages/zh-TW.json | 18 ++++ .../app/[locale]/awooop/approvals/page.tsx | 97 ++++++++++++++++++- 3 files changed, 132 insertions(+), 1 deletion(-) diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 2d71200c..6c343eb7 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -9718,6 +9718,24 @@ "gate5Projection": "Gate 5 投影", "executorHandoffPending": "等待 executor handoff" }, + "assetLedger": { + "column": "資產沉澱", + "title": "資產沉澱", + "summary": "完成 {ready} / 卡點 {blocked}", + "boundary": "只讀推導;不代表已寫入 KM、更新 PlayBook trust、套用腳本 / 排程或執行 verifier。", + "items": { + "km": "KM", + "playbook": "PlayBook", + "script": "腳本", + "schedule": "排程", + "verifier": "Verifier" + }, + "values": { + "ready": "ready", + "pending": "pending", + "blocked": "blocked" + } + }, "columns": { "runId": "執行 ID", "projectId": "專案 ID", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 2d71200c..6c343eb7 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -9718,6 +9718,24 @@ "gate5Projection": "Gate 5 投影", "executorHandoffPending": "等待 executor handoff" }, + "assetLedger": { + "column": "資產沉澱", + "title": "資產沉澱", + "summary": "完成 {ready} / 卡點 {blocked}", + "boundary": "只讀推導;不代表已寫入 KM、更新 PlayBook trust、套用腳本 / 排程或執行 verifier。", + "items": { + "km": "KM", + "playbook": "PlayBook", + "script": "腳本", + "schedule": "排程", + "verifier": "Verifier" + }, + "values": { + "ready": "ready", + "pending": "pending", + "blocked": "blocked" + } + }, "columns": { "runId": "執行 ID", "projectId": "專案 ID", diff --git a/apps/web/src/app/[locale]/awooop/approvals/page.tsx b/apps/web/src/app/[locale]/awooop/approvals/page.tsx index 6e8b0c2c..4b5b8f9d 100644 --- a/apps/web/src/app/[locale]/awooop/approvals/page.tsx +++ b/apps/web/src/app/[locale]/awooop/approvals/page.tsx @@ -291,6 +291,95 @@ function RemediationEvidenceCell({ summary }: { summary?: RemediationSummary | n ); } +type ApprovalAssetTone = "ready" | "pending" | "blocked"; + +function assetToneClass(tone: ApprovalAssetTone) { + if (tone === "ready") return "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]"; + if (tone === "blocked") return "border-[#e2a29b] bg-[#fff0ef] text-[#9f2f25]"; + return "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]"; +} + +function countLabel(value: number) { + return value > 0 ? String(value) : "--"; +} + +function ApprovalAutomationAssetLedger({ approval }: { approval: Approval }) { + const t = useTranslations("awooop.approvals.assetLedger"); + const chain = approval.awooop_status_chain; + const summary = approval.remediation_summary; + const kmCount = chain?.evidence?.knowledge_entries ?? 0; + const playbookCount = (chain?.execution?.playbook_ids?.length ?? 0) + (chain?.execution?.playbook_paths?.length ?? 0); + const ansible = chain?.execution?.ansible; + const scriptCount = (ansible?.candidate_count ?? 0) + (ansible?.check_mode_total ?? 0) + (ansible?.apply_total ?? 0); + const correlation = chain?.source_refs?.correlation; + const scheduleCount = (correlation?.provider_event_total ?? 0) + + (correlation?.direct_ref_total ?? 0) + + (correlation?.candidate_total ?? 0) + + (correlation?.applied_link_total ?? 0); + const verification = String(chain?.verification ?? "").toLowerCase(); + const verifierReady = verification.includes("verified") || verification.includes("success"); + const verifierBlocked = verification.includes("degraded") || verification.includes("fail"); + const route = String(summary?.latest_route ?? ""); + const noRepairCandidate = route.includes("fallback") || route.includes("no_action") || chain?.blockers?.some((item) => item.includes("playbook") || item.includes("candidate")); + + const items = [ + { + key: "km", + tone: kmCount > 0 ? "ready" : chain?.needs_human ? "blocked" : "pending", + value: countLabel(kmCount), + }, + { + key: "playbook", + tone: playbookCount > 0 ? "ready" : noRepairCandidate ? "blocked" : "pending", + value: countLabel(playbookCount), + }, + { + key: "script", + tone: scriptCount > 0 ? "ready" : summary?.has_mcp_investigation ? "pending" : "blocked", + value: countLabel(scriptCount), + }, + { + key: "schedule", + tone: scheduleCount > 0 ? "ready" : "pending", + value: countLabel(scheduleCount), + }, + { + key: "verifier", + tone: verifierReady ? "ready" : verifierBlocked ? "blocked" : "pending", + value: verifierReady ? t("values.ready") : verifierBlocked ? t("values.blocked") : t("values.pending"), + }, + ] satisfies Array<{ key: string; tone: ApprovalAssetTone; value: string }>; + const readyCount = items.filter((item) => item.tone === "ready").length; + const blockedCount = items.filter((item) => item.tone === "blocked").length; + + return ( +
+
+ {t("title")} + 0 ? "border-[#e2a29b] bg-[#fff0ef] text-[#9f2f25]" : "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]" + )}> + {t("summary", { ready: readyCount, blocked: blockedCount })} + +
+
+ {items.map((item) => ( +
+
+ {t(`items.${item.key}` as never)} + {item.value} +
+
+ ))} +
+

+ {t("boundary")} +

+
+ ); +} + function LegacyHitlBacklogPanel({ approvals, loading, @@ -591,6 +680,9 @@ function ApprovalRow({ approval }: { approval: Approval }) { + + + @@ -1519,6 +1611,9 @@ export default function ApprovalsPage() { {tEvidence("sourceFlow.column")} + + {t("assetLedger.column")} + {tStatusChain("title")} @@ -1534,7 +1629,7 @@ export default function ApprovalsPage() { {loading ? ( Array.from({ length: 5 }).map((_, i) => ( - {Array.from({ length: 9 }).map((_, j) => ( + {Array.from({ length: 10 }).map((_, j) => (