From 271152054fde6f2024905985cd99947ce8df5e72 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 18 Jun 2026 10:46:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E8=A6=96=E8=A6=BA=E5=8C=96?= =?UTF-8?q?=E6=B2=BB=E7=90=86=E8=87=AA=E5=8B=95=E5=8C=96=E7=9B=A4=E9=BB=9E?= =?UTF-8?q?=E9=A6=96=E5=B1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/messages/en.json | 63 ++++ apps/web/messages/zh-TW.json | 63 ++++ .../tabs/automation-inventory-tab.tsx | 334 ++++++++++++++++++ 3 files changed, 460 insertions(+) diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 9ac442a6..5de75a7f 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -3524,6 +3524,69 @@ "cd_login_banner_observed_os_only": "僅 CD banner 觀察" } }, + "visualOps": { + "title": "自動化戰情視覺總覽", + "subtitle": "{current} → {next};自動化 {overall}%;決策支援 {support}%。先看流程與 Gate,再下鑽證據。", + "flowTitle": "AI 自動化流程", + "flowBadge": "{stages} 階段", + "gateTitle": "Gate / 阻塞矩陣", + "weighted": "決策支援 {value}%", + "focusTitle": "本輪優先焦點", + "focusDetail": "先補 Runner {runner}、Live 證據 {evidence}、修復候選複核 {candidate}、人工批准任務 {approval};未通過前不開 runtime gate。", + "badges": { + "readOnly": "只讀營運台", + "runtimeGate": "正式寫入 {value}", + "approval": "需批准 {value}" + }, + "stages": { + "intake": { + "label": "告警 / 服務收件", + "detail": "健康目標中 {actions} 個仍需處置。" + }, + "evidence": { + "label": "MCP / 來源證據", + "detail": "仍缺 {missing} 個 live evidence 綁定。" + }, + "candidate": { + "label": "修復候選", + "detail": "{review} 個待 owner 複核;{blocked} 個被 allowlist / policy 阻擋。" + }, + "approval": { + "label": "人工 Gate", + "detail": "共 {total} 個任務邊界,未批准不會執行。" + }, + "verifier": { + "label": "執行讀回 / Verifier", + "detail": "{blocked} 個 release readback 仍阻擋。" + }, + "learning": { + "label": "KM / PlayBook 學習", + "detail": "{gates} 個 learning gate 等 owner review。" + } + }, + "gates": { + "notification": { + "label": "Failure-only 通知", + "detail": "成功降噪 {quiet};需處置 {action};立即升級 {escalation}。" + }, + "repairCandidate": { + "label": "修復候選完整度", + "detail": "待 owner review {review};verifier plan {verifier}。" + }, + "approval": { + "label": "批准邊界", + "detail": "{blocked} 個批准項仍是明確 false。" + }, + "execution": { + "label": "正式執行寫入", + "detail": "讀回 {readback};阻擋 {blocked};0 代表尚未開 live write。" + }, + "learning": { + "label": "PlayBook / KM 回寫", + "detail": "候選 {candidates};learning gate {gates}。" + } + } + }, "overview": { "title": "決策指揮摘要", "mode": "只讀決策支援", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 9ac442a6..5de75a7f 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -3524,6 +3524,69 @@ "cd_login_banner_observed_os_only": "僅 CD banner 觀察" } }, + "visualOps": { + "title": "自動化戰情視覺總覽", + "subtitle": "{current} → {next};自動化 {overall}%;決策支援 {support}%。先看流程與 Gate,再下鑽證據。", + "flowTitle": "AI 自動化流程", + "flowBadge": "{stages} 階段", + "gateTitle": "Gate / 阻塞矩陣", + "weighted": "決策支援 {value}%", + "focusTitle": "本輪優先焦點", + "focusDetail": "先補 Runner {runner}、Live 證據 {evidence}、修復候選複核 {candidate}、人工批准任務 {approval};未通過前不開 runtime gate。", + "badges": { + "readOnly": "只讀營運台", + "runtimeGate": "正式寫入 {value}", + "approval": "需批准 {value}" + }, + "stages": { + "intake": { + "label": "告警 / 服務收件", + "detail": "健康目標中 {actions} 個仍需處置。" + }, + "evidence": { + "label": "MCP / 來源證據", + "detail": "仍缺 {missing} 個 live evidence 綁定。" + }, + "candidate": { + "label": "修復候選", + "detail": "{review} 個待 owner 複核;{blocked} 個被 allowlist / policy 阻擋。" + }, + "approval": { + "label": "人工 Gate", + "detail": "共 {total} 個任務邊界,未批准不會執行。" + }, + "verifier": { + "label": "執行讀回 / Verifier", + "detail": "{blocked} 個 release readback 仍阻擋。" + }, + "learning": { + "label": "KM / PlayBook 學習", + "detail": "{gates} 個 learning gate 等 owner review。" + } + }, + "gates": { + "notification": { + "label": "Failure-only 通知", + "detail": "成功降噪 {quiet};需處置 {action};立即升級 {escalation}。" + }, + "repairCandidate": { + "label": "修復候選完整度", + "detail": "待 owner review {review};verifier plan {verifier}。" + }, + "approval": { + "label": "批准邊界", + "detail": "{blocked} 個批准項仍是明確 false。" + }, + "execution": { + "label": "正式執行寫入", + "detail": "讀回 {readback};阻擋 {blocked};0 代表尚未開 live write。" + }, + "learning": { + "label": "PlayBook / KM 回寫", + "detail": "候選 {candidates};learning gate {gates}。" + } + } + }, "overview": { "title": "決策指揮摘要", "mode": "只讀決策支援", diff --git a/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx b/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx index fc0e3270..a4b27367 100644 --- a/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx +++ b/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx @@ -454,6 +454,105 @@ function SummaryTile({ ) } +function FlowStageTile({ + index, + label, + value, + detail, + icon, + tone = 'neutral', +}: { + index: string + label: string + value: string + detail: string + icon: ReactNode + tone?: 'ok' | 'warn' | 'danger' | 'neutral' +}) { + const color = toneColor(tone) + return ( +
+
+ {icon} +
+
+
+ + {label} + + + {index} + +
+ + {value} + + + {detail} + +
+
+ ) +} + +function GateMatrixRow({ + label, + value, + detail, + tone = 'neutral', +}: { + label: string + value: string + detail: string + tone?: 'ok' | 'warn' | 'danger' | 'neutral' +}) { + const color = toneColor(tone) + return ( +
+
+ + {label} + + + {detail} + +
+ + {value} + +
+ ) +} + export function AutomationInventoryTab() { const t = useTranslations('governance.automationInventory') const [snapshot, setSnapshot] = useState(null) @@ -2155,6 +2254,7 @@ export function AutomationInventoryTab() { const runtimeSecrets = runtimeSurface?.rollups.secret_surface_ids.length ?? 0 const runtimeLiveMissing = runtimeSurface?.rollups.live_check_missing_surface_ids.length ?? 0 const runtimeBoundComponents = runtimeSurface?.rollups.source_components_with_runtime_binding ?? 0 + const runtimeSurfaceComponentTotal = runtimeSurface?.rollups.total_source_components ?? 0 const giteaRunnerActions = giteaHealth.rollups.workflow_ids_requiring_runner_attestation.length const giteaQuietPolicies = giteaHealth.rollups.notification_contracts_quiet_success_count const observabilityActions = observabilityMatrix.rollups.surface_ids_requiring_action.length @@ -3813,6 +3913,122 @@ export function AutomationInventoryTab() { const blockedApprovals = Object.entries(snapshot.approval_boundaries) .filter(([, allowed]) => allowed === false) .map(([key]) => key) + const visualFlowStages: Array<{ + key: string + label: string + value: string + detail: string + tone: 'ok' | 'warn' | 'danger' | 'neutral' + icon: ReactNode + }> = [ + { + key: 'intake', + label: t('visualOps.stages.intake.label'), + value: String(serviceHealthGapMatrix.rollups.total_targets), + detail: t('visualOps.stages.intake.detail', { actions: serviceHealthActions }), + tone: serviceHealthActions > 0 ? 'warn' : 'ok', + icon: , + }, + { + key: 'evidence', + label: t('visualOps.stages.evidence.label'), + value: `${runtimeBoundComponents}/${runtimeSurfaceComponentTotal}`, + detail: t('visualOps.stages.evidence.detail', { missing: runtimeLiveMissing }), + tone: runtimeLiveMissing > 0 ? 'warn' : 'ok', + icon: , + }, + { + key: 'candidate', + label: t('visualOps.stages.candidate.label'), + value: `${candidateDryRunEvidenceCount}/${candidateDryRunCount}`, + detail: t('visualOps.stages.candidate.detail', { + review: candidateDryRunNeedsReview, + blocked: candidateDryRunAllowlistBlocked + candidateDryRunPolicyBlocked, + }), + tone: candidateDryRunNeedsReview > 0 || candidateDryRunPolicyBlocked > 0 ? 'warn' : 'ok', + icon: , + }, + { + key: 'approval', + label: t('visualOps.stages.approval.label'), + value: String(explicitApprovalTaskCount), + detail: t('visualOps.stages.approval.detail', { total: taskBoundaryCount }), + tone: explicitApprovalTaskCount > 0 ? 'warn' : 'ok', + icon: , + }, + { + key: 'verifier', + label: t('visualOps.stages.verifier.label'), + value: `${resultCaptureReleaseReadbacks}`, + detail: t('visualOps.stages.verifier.detail', { blocked: resultCaptureReleaseReadbackBlocked }), + tone: resultCaptureReleaseReadbackBlocked > 0 ? 'warn' : 'ok', + icon: , + }, + { + key: 'learning', + label: t('visualOps.stages.learning.label'), + value: `${learningWritebackLiveWrites}/${learningWritebackLanes}`, + detail: t('visualOps.stages.learning.detail', { gates: learningWritebackApprovals }), + tone: learningWritebackApprovals > 0 ? 'warn' : 'ok', + icon: , + }, + ] + const visualGateRows: Array<{ + key: string + label: string + value: string + detail: string + tone: 'ok' | 'warn' | 'danger' | 'neutral' + }> = [ + { + key: 'notification', + label: t('visualOps.gates.notification.label'), + value: String(serviceHealthNotificationAllowedCount), + detail: t('visualOps.gates.notification.detail', { + quiet: serviceHealthSuppressedSuccess, + action: serviceHealthActionRequiredRules, + escalation: serviceHealthImmediateEscalations, + }), + tone: serviceHealthNotificationAllowedCount === 0 ? 'ok' : 'danger', + }, + { + key: 'repairCandidate', + label: t('visualOps.gates.repairCandidate.label'), + value: `${candidateDryRunEvidenceCount}/${candidateDryRunCount}`, + detail: t('visualOps.gates.repairCandidate.detail', { + review: candidateDryRunNeedsReview, + verifier: candidateDryRunVerifierPlans, + }), + tone: candidateDryRunEvidenceCount < candidateDryRunCount ? 'warn' : 'ok', + }, + { + key: 'approval', + label: t('visualOps.gates.approval.label'), + value: `${explicitApprovalTaskCount}/${taskBoundaryCount}`, + detail: t('visualOps.gates.approval.detail', { blocked: blockedApprovals.length }), + tone: explicitApprovalTaskCount > 0 ? 'warn' : 'ok', + }, + { + key: 'execution', + label: t('visualOps.gates.execution.label'), + value: String(resultCaptureReleaseReadbackLiveWrites), + detail: t('visualOps.gates.execution.detail', { + readback: resultCaptureReleaseReadbacks, + blocked: resultCaptureReleaseReadbackBlocked, + }), + tone: resultCaptureReleaseReadbackLiveWrites === 0 ? 'ok' : 'danger', + }, + { + key: 'learning', + label: t('visualOps.gates.learning.label'), + value: String(matchedPlaybookUpdated), + detail: t('visualOps.gates.learning.detail', { + candidates: matchedPlaybookCandidates, + gates: matchedPlaybookGates, + }), + tone: matchedPlaybookGates > 0 ? 'warn' : 'ok', + }, + ] const statusLabel = (value: string) => { try { @@ -4016,6 +4232,121 @@ export function AutomationInventoryTab() { ]} /> + +
+
+
+
+ +
+
+ + {t('visualOps.title')} + + + {t('visualOps.subtitle', { + current: snapshot.program_status.current_task_id, + next: snapshot.program_status.next_task_id, + overall: backlogProgressPercent, + support: decisionSupportCoverage, + })} + +
+
+
+ + + +
+
+ +
+
+
+ {t('visualOps.flowTitle')} + +
+
+ {visualFlowStages.map((stage, index) => ( + + ))} +
+
+ +
+
+
+ {t('visualOps.gateTitle')} + +
+ {visualGateRows.map(row => ( + + ))} +
+ +
+
+ {t('visualOps.focusTitle')} + + {decisionSupportCoverage}% + +
+ + {t('visualOps.focusDetail', { + runner: giteaRunnerActions, + evidence: runtimeLiveMissing, + candidate: candidateDryRunNeedsReview, + approval: explicitApprovalTaskCount, + })} + +
+ = 70 ? 'ok' : completedTaskCoverage >= 45 ? 'warn' : 'danger'} + /> + = 80 ? 'ok' : runtimeBindingCoverage >= 50 ? 'warn' : 'danger'} + /> +
+
+
+
+
+
+
@@ -15387,6 +15718,9 @@ export function AutomationInventoryTab() { } @media (max-width: 900px) { + .automation-inventory-visual-grid, + .automation-inventory-stage-grid, + .automation-inventory-visual-factor-grid, .automation-inventory-kpi-grid, .automation-inventory-command-grid, .automation-inventory-summary-grid,