diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index c86fc91a..25270479 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -63,6 +63,7 @@ "drift": "漂移偵測", "neuralCommand": "神經指揮中心", "commandCenter": "指令中心", + "delivery": "交付閉環", "observability": "可觀測性", "automation": "自動化", "operations": "營運", @@ -84,6 +85,82 @@ "iwooos": "IwoooS", "iwooosSecurityCompliance": "IwoooS 安全合規" }, + "delivery": { + "eyebrow": "AWOOOI Delivery", + "title": "交付閉環工作台", + "subtitle": "把目前真正會推進交付的主線集中在同一頁:乾淨 release、GitHub 私有備援、Gitea / CI/CD、runtime surface、資料與備份。這裡只顯示能推動下一步的狀態,不再把文件數量當成完成度。", + "states": { + "loading": "讀取中", + "ready": "資料鏈正常", + "partial": "部分資料待補", + "noData": "尚未回讀" + }, + "actions": { + "refresh": "重新整理" + }, + "sources": { + "0": { "error": "狀態清理資料未回讀" }, + "1": { "error": "GitHub 備援資料未回讀" }, + "2": { "error": "Gitea / runner 資料未回讀" }, + "3": { "error": "Runtime surface 資料未回讀" }, + "4": { "error": "Backup readiness 資料未回讀" } + }, + "metrics": { + "loaded": "資料來源", + "loadedDetail": "只計入正式 API 成功回讀的來源。", + "completion": "平均進度", + "completionDetail": "以本頁五條交付主線計算。", + "blockers": "高風險阻擋", + "blockersDetail": "只統計會影響交付決策的阻擋。", + "execution": "執行授權", + "executionDetail": "本頁只讀,不執行 runtime 或 remote write。" + }, + "sections": { + "lanes": "交付主線", + "lanesDetail": "每張卡只回答完成度、阻擋數、下一步入口。", + "next": "下一步焦點", + "nextDetail": "只列需要處理的主線,不列文件清單。", + "boundary": "保留硬邊界", + "boundaryDetail": "這些仍需明確授權,但不得阻擋低風險 coding / UI / test。" + }, + "lanes": { + "release": { + "title": "乾淨 release 工作流", + "description": "把可交付的變更切出乾淨分支與可驗證提交,避免髒分支整包推送。", + "metric": "阻擋 gate {blocked}" + }, + "github": { + "title": "GitHub 私有備援", + "description": "所有備援 repo 必須私有,公開可讀或未驗證 private 都不能標綠。", + "metric": "已驗證 {verified}/{total}" + }, + "gitea": { + "title": "Gitea / CI-CD", + "description": "確認 workflow、runner label、通知與 dev / prod 發版線是真實可跑。", + "metric": "workflow {count}" + }, + "runtime": { + "title": "Runtime surface", + "description": "把產品、網站、服務與部署面映射到實際 runtime,不再停在文件描述。", + "metric": "surface {total}" + }, + "backup": { + "title": "資料與備份", + "description": "資料庫、類資料庫、備份與還原演練必須能支撐一鍵上雲與獨立部署。", + "metric": "readiness row {rows}" + } + }, + "boundaries": { + "secret": "不收 secret value、token、private key、cookie 或 private clone credential。", + "production": "不直接改 production runtime、public gateway、Nginx、Docker、K8s 或 firewall。", + "repo": "不直接建立 GitHub repo、改 visibility、sync refs、force push 或 trigger workflow。", + "data": "不直接做資料庫、backup、restore 或 migration 寫操作。", + "security": "不啟動 Wazuh / Kali active response、active scan 或 host containment。" + }, + "errors": { + "title": "部分資料沒有回讀" + } + }, "observabilityCommand": { "eyebrow": "AWOOOI 可觀測性指揮面板", "title": "主機、專案、網站、服務與工具全域監控", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index c86fc91a..25270479 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -63,6 +63,7 @@ "drift": "漂移偵測", "neuralCommand": "神經指揮中心", "commandCenter": "指令中心", + "delivery": "交付閉環", "observability": "可觀測性", "automation": "自動化", "operations": "營運", @@ -84,6 +85,82 @@ "iwooos": "IwoooS", "iwooosSecurityCompliance": "IwoooS 安全合規" }, + "delivery": { + "eyebrow": "AWOOOI Delivery", + "title": "交付閉環工作台", + "subtitle": "把目前真正會推進交付的主線集中在同一頁:乾淨 release、GitHub 私有備援、Gitea / CI/CD、runtime surface、資料與備份。這裡只顯示能推動下一步的狀態,不再把文件數量當成完成度。", + "states": { + "loading": "讀取中", + "ready": "資料鏈正常", + "partial": "部分資料待補", + "noData": "尚未回讀" + }, + "actions": { + "refresh": "重新整理" + }, + "sources": { + "0": { "error": "狀態清理資料未回讀" }, + "1": { "error": "GitHub 備援資料未回讀" }, + "2": { "error": "Gitea / runner 資料未回讀" }, + "3": { "error": "Runtime surface 資料未回讀" }, + "4": { "error": "Backup readiness 資料未回讀" } + }, + "metrics": { + "loaded": "資料來源", + "loadedDetail": "只計入正式 API 成功回讀的來源。", + "completion": "平均進度", + "completionDetail": "以本頁五條交付主線計算。", + "blockers": "高風險阻擋", + "blockersDetail": "只統計會影響交付決策的阻擋。", + "execution": "執行授權", + "executionDetail": "本頁只讀,不執行 runtime 或 remote write。" + }, + "sections": { + "lanes": "交付主線", + "lanesDetail": "每張卡只回答完成度、阻擋數、下一步入口。", + "next": "下一步焦點", + "nextDetail": "只列需要處理的主線,不列文件清單。", + "boundary": "保留硬邊界", + "boundaryDetail": "這些仍需明確授權,但不得阻擋低風險 coding / UI / test。" + }, + "lanes": { + "release": { + "title": "乾淨 release 工作流", + "description": "把可交付的變更切出乾淨分支與可驗證提交,避免髒分支整包推送。", + "metric": "阻擋 gate {blocked}" + }, + "github": { + "title": "GitHub 私有備援", + "description": "所有備援 repo 必須私有,公開可讀或未驗證 private 都不能標綠。", + "metric": "已驗證 {verified}/{total}" + }, + "gitea": { + "title": "Gitea / CI-CD", + "description": "確認 workflow、runner label、通知與 dev / prod 發版線是真實可跑。", + "metric": "workflow {count}" + }, + "runtime": { + "title": "Runtime surface", + "description": "把產品、網站、服務與部署面映射到實際 runtime,不再停在文件描述。", + "metric": "surface {total}" + }, + "backup": { + "title": "資料與備份", + "description": "資料庫、類資料庫、備份與還原演練必須能支撐一鍵上雲與獨立部署。", + "metric": "readiness row {rows}" + } + }, + "boundaries": { + "secret": "不收 secret value、token、private key、cookie 或 private clone credential。", + "production": "不直接改 production runtime、public gateway、Nginx、Docker、K8s 或 firewall。", + "repo": "不直接建立 GitHub repo、改 visibility、sync refs、force push 或 trigger workflow。", + "data": "不直接做資料庫、backup、restore 或 migration 寫操作。", + "security": "不啟動 Wazuh / Kali active response、active scan 或 host containment。" + }, + "errors": { + "title": "部分資料沒有回讀" + } + }, "observabilityCommand": { "eyebrow": "AWOOOI 可觀測性指揮面板", "title": "主機、專案、網站、服務與工具全域監控", diff --git a/apps/web/src/app/[locale]/delivery/page.tsx b/apps/web/src/app/[locale]/delivery/page.tsx new file mode 100644 index 00000000..c2d39761 --- /dev/null +++ b/apps/web/src/app/[locale]/delivery/page.tsx @@ -0,0 +1,555 @@ +'use client' + +import { useEffect, useMemo, useState } from 'react' +import Link from 'next/link' +import { useTranslations } from 'next-intl' +import { + AlertTriangle, + ArrowRight, + CheckCircle2, + GitBranch, + HardDrive, + Lock, + PackageCheck, + RefreshCw, + Rocket, + Server, +} from 'lucide-react' +import { AppLayout } from '@/components/layout' +import { GlassCard } from '@/components/ui/glass-card' +import { + apiClient, + type AwoooIStatusCleanupDashboardSnapshot, + type BackupDrReadinessMatrixSnapshot, + type GiteaWorkflowRunnerHealthSnapshot, + type GithubTargetPrivateBackupEvidenceGateSnapshot, + type RuntimeSurfaceInventorySnapshot, +} from '@/lib/api-client' + +type DeliveryTone = 'ok' | 'warn' | 'danger' | 'neutral' + +interface DeliveryData { + statusCleanup: AwoooIStatusCleanupDashboardSnapshot | null + github: GithubTargetPrivateBackupEvidenceGateSnapshot | null + gitea: GiteaWorkflowRunnerHealthSnapshot | null + runtime: RuntimeSurfaceInventorySnapshot | null + backup: BackupDrReadinessMatrixSnapshot | null +} + +interface DeliveryLane { + id: string + title: string + description: string + percent: number + status: string + metric: string + blockerCount: number + href: string + tone: DeliveryTone + Icon: typeof Rocket +} + +const EMPTY_DATA: DeliveryData = { + statusCleanup: null, + github: null, + gitea: null, + runtime: null, + backup: null, +} + +const clampPercent = (value: number | null | undefined) => Math.max(0, Math.min(100, Math.round(value ?? 0))) + +const toneColor = (tone: DeliveryTone) => { + if (tone === 'ok') return '#2f7d54' + if (tone === 'warn') return '#9a6a22' + if (tone === 'danger') return '#b2432d' + return '#706f68' +} + +const toneBackground = (tone: DeliveryTone) => { + if (tone === 'ok') return 'rgba(47, 125, 84, 0.10)' + if (tone === 'warn') return 'rgba(154, 106, 34, 0.10)' + if (tone === 'danger') return 'rgba(178, 67, 45, 0.10)' + return 'rgba(112, 111, 104, 0.10)' +} + +const resolveTone = (blockerCount: number, percent: number): DeliveryTone => { + if (blockerCount > 0) return 'danger' + if (percent < 80) return 'warn' + return 'ok' +} + +function StatusPill({ tone, label }: { tone: DeliveryTone; label: string }) { + return ( + + {label} + + ) +} + +function ProgressBar({ percent, tone }: { percent: number; tone: DeliveryTone }) { + return ( +