From 8788c720e42ae7d1771400c94775f5d8149bcf53 Mon Sep 17 00:00:00 2001 From: OG T Date: Wed, 8 Apr 2026 12:22:05 +0800 Subject: [PATCH] =?UTF-8?q?docs(plan):=20Sprint=205=20=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E8=A7=A3=E6=B1=BA=E6=96=B9=E6=A1=88=20=E2=80=94=20=E8=88=87?= =?UTF-8?q?=E7=8F=BE=E6=9C=89=E6=9E=B6=E6=A7=8B=E6=95=B4=E5=90=88=E7=9A=84?= =?UTF-8?q?=E7=B4=B0=E5=8C=96=E5=AF=A6=E6=96=BD=E8=A8=88=E7=95=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-04-08-sprint5-complete-solution.md | 746 ++++++++++++++++++ 1 file changed, 746 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-08-sprint5-complete-solution.md diff --git a/docs/superpowers/plans/2026-04-08-sprint5-complete-solution.md b/docs/superpowers/plans/2026-04-08-sprint5-complete-solution.md new file mode 100644 index 000000000..93cdf734a --- /dev/null +++ b/docs/superpowers/plans/2026-04-08-sprint5-complete-solution.md @@ -0,0 +1,746 @@ +# Sprint 5 指令中心重設計 — 完整解決方案與整合計畫 + +> **建立**: 2026-04-08 (台北) +> **建立者**: Claude Code +> **狀態**: 📋 待統帥審核 +> **ADR**: ADR-060 React Flow + elkjs (已批准) + +--- + +## 一、現有架構與資料流完整地圖 + +### 1.1 資料從哪裡來? + +``` +┌─────────────────────────────────────────────────────────┐ +│ 資料源 (SSOT) │ +│ ops/monitoring/service-registry.yaml (717行) │ +│ 定義: 6 K8s + 10 Docker + 1 Systemd + 6 Nodes + │ +│ 6 Pages + 8 API + 4 AI = 41 個受監控實體 │ +│ + 15 個告警模板 + 9 個自動修復動作 │ +└────────────────────────┬────────────────────────────────┘ + │ + ┌─────────────┼─────────────────┐ + ▼ ▼ ▼ +┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ +│ Prometheus │ │ Alertmanager │ │ Blackbox Exporter │ +│ 22 targets │ │ 71 rules │ │ HTTP/TCP probes │ +└──────┬───────┘ └──────┬───────┘ └────────┬──────────┘ + │ │ │ + ▼ ▼ ▼ +┌──────────────────────────────────────────────────────┐ +│ 後端 API 層 │ +│ apps/api/src/services/host_aggregator.py (500+行) │ +│ HostAggregator.fetch_all() → 4 主機並行 TCP/HTTP 探測│ +│ 回傳: AggregatedStatus {hosts, services, metrics} │ +└────────────────────────┬─────────────────────────────┘ + │ + ┌─────────────┼─────────────────┐ + ▼ ▼ ▼ +┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ +│ GET │ │ GET │ │ GET │ +│ /dashboard │ │ /dashboard/ │ │ /dashboard/hosts │ +│ (聚合資料) │ │ stream (SSE) │ │ (4主機概覽) │ +│ 389行 │ │ 每30秒推送 │ │ │ +└──────┬───────┘ └──────┬───────┘ └────────┬──────────┘ + │ │ │ + ▼ ▼ ▼ +┌──────────────────────────────────────────────────────┐ +│ 前端 Store 層 │ +│ apps/web/src/stores/dashboard.store.ts (449行) │ +│ Zustand: Host[] + HostService[] + HostMetrics │ +│ SSE 自動重連 (exponential backoff) │ +└────────────────────────┬─────────────────────────────┘ + │ + ┌─────────────┼─────────────────┐ + ▼ ▼ ▼ +┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ +│ page.tsx │ │ topology/ │ │ neural-command/ │ +│ (首頁) │ │ page.tsx │ │ page.tsx │ +│ HostGrid │ │ (103行,簡單) │ │ (4元件,1052行) │ +│ MetricsStrip │ │ │ │ │ +└──────────────┘ └──────────────┘ └──────────────────┘ +``` + +### 1.2 現有 API 回傳的完整資料結構 + +```typescript +// GET /api/v1/dashboard 回傳: +interface DashboardResponse { + timestamp: string + environment: string + mock_mode: boolean + overall_status: 'healthy' | 'degraded' | 'unhealthy' + hosts: HostStatusResponse[] // 4 個主機 + alerts_count: number + pending_approvals: number +} + +interface HostStatusResponse { + ip: string // "192.168.0.110" + name: string // "DevOps 金庫" + role: string // "devops" | "security" | "k3s" | "ai_web" + status: string // "healthy" | "degraded" | "unhealthy" | "unreachable" + services: ServiceStatus[] // 每主機的服務清單 + metrics: HostMetricsResponse // CPU/Memory/Disk/Load/Baseline + last_check: string +} + +interface ServiceStatus { + name: string // "PostgreSQL" + status: 'up' | 'down' | 'degraded' + port: number // 5432 + latency_ms: number // 2.3 + error: string | null +} + +interface HostMetricsResponse { + cpu_percent: number // 45.2 + memory_percent: number // 62.8 + disk_percent: number // 71.0 + load_avg_1m: number // 1.23 + uptime_hours: number // 720 + cpu_baseline: BaselineResponse // {baseline: 50, std: 10, sigma: -0.48} + memory_baseline: BaselineResponse +} +``` + +**結論: 拓撲圖需要的 90% 資料已經在 API 裡了。** + +### 1.3 缺什麼?需要擴充什麼? + +| 需要的資料 | 現有來源 | 缺口 | 解決方案 | +|-----------|---------|------|---------| +| 主機清單 + 狀態 | ✅ `/dashboard` hosts | — | 直接用 | +| 每主機的服務 | ✅ `/dashboard` hosts[].services | — | 直接用 | +| CPU/Memory 指標 | ✅ `/dashboard` hosts[].metrics | — | 直接用 | +| 即時更新 | ✅ `/dashboard/stream` SSE | — | 直接用 | +| **服務間依賴關係** | ❌ 不存在 | **缺** | **新增靜態定義** | +| **群組分類 (拓撲層級)** | ⚠️ role 欄位只有 4 種 | **部分** | **擴充 role + group** | +| **AI 介入狀態** | ⚠️ 沒有即時 field | **缺** | **擴充 SSE 事件** | +| **K8s Pod 明細** | ⚠️ 只有 service 級 | **缺** | **新增 k3s_monitor 整合** | + +--- + +## 二、完整解決方案 — 分四個工作包 + +### 工作包 1: 前端拓撲元件 (核心) + +**目標**: 建立 React Flow + elkjs 的嵌套群組拓撲圖元件 + +#### WP1-1: 安裝依賴 + +```bash +cd apps/web +npm install @xyflow/react elkjs +``` + +**影響檔案**: +- `apps/web/package.json` (新增 2 deps) +- `apps/web/package-lock.json` + +**驗證**: +```bash +cd apps/web && node -e "require('@xyflow/react'); require('elkjs'); console.log('OK')" +``` + +--- + +#### WP1-2: 建立元件目錄 + +**新增檔案清單** (8 個新檔案): + +| 檔案 | 行數(預估) | 功能 | +|------|-----------|------| +| `src/components/topology/index.ts` | 10 | 匯出 | +| `src/components/topology/ServiceTopology.tsx` | 150 | 主元件: ReactFlow + Controls + MiniMap | +| `src/components/topology/nodes/GroupNode.tsx` | 120 | 群組節點: 展開/收合 + 摘要資訊 | +| `src/components/topology/nodes/ServiceNode.tsx` | 80 | 服務節點: 狀態燈 + 名稱 + 指標 (memo) | +| `src/components/topology/edges/TopologyEdge.tsx` | 60 | 自定義邊線: 漸層 + 動畫 | +| `src/components/topology/hooks/useTopologyData.ts` | 100 | 從 dashboard.store → nodes/edges 轉換 | +| `src/components/topology/hooks/useElkLayout.ts` | 80 | elkjs 嵌套佈局計算 | +| `src/components/topology/topology.css` | 30 | 邊線動畫 keyframes | + +**總新增**: ~630 行 + +--- + +#### WP1-3: ServiceNode.tsx 詳細設計 + +```typescript +// 與現有架構的整合點: +// 1. 讀取 dashboard.store.ts 的 HostService 型別 (已定義) +// 2. 色彩來自 tailwind.config.ts 的 status.* (已定義) +// 3. Icon 來自 lucide-react (已安裝) +// 4. 字型 DM Mono / JetBrains Mono (已載入) + +import { memo } from 'react' +import { Handle, Position, type NodeProps } from '@xyflow/react' +import { Server, Database, Globe, Brain, Shield, Box } from 'lucide-react' +import { cn } from '@/lib/utils' + +// 服務類型 → Lucide icon 映射 +const SERVICE_ICONS: Record = { + 'PostgreSQL': Database, + 'Redis': Database, + 'Ollama': Brain, + 'OpenClaw': Brain, + 'Harbor': Box, + 'Sentry': Shield, + 'Nginx': Globe, + 'K3s API': Server, + // ... 完整映射 +} + +// 狀態 → tailwind.config.ts 色彩 +const STATUS_COLORS = { + up: 'border-status-healthy text-status-healthy', // #22C55E + down: 'border-status-severe text-status-severe', // #FF3300 + degraded: 'border-status-warning text-status-warning', // #F59E0B + healthy: 'border-status-healthy text-status-healthy', + thinking: 'border-status-thinking text-status-thinking', // #8B5CF6 +} + +export const ServiceNode = memo(({ data }: NodeProps) => { + const Icon = SERVICE_ICONS[data.name] || Server + const colorClass = STATUS_COLORS[data.status] || 'border-border' + + return ( +
+ + + {/* 狀態燈 + 狀態文字 */} +
+ + {data.statusText} +
+ + {/* Icon + 服務名稱 */} +
+ + {data.name} +
+ + {/* 位置 + 類型 */} +
+ {data.port && `${data.host}:${data.port}`} + {data.latency && ` · ${data.latency}ms`} +
+ + +
+ ) +}, (prev, next) => { + // 自定義比較: 只在狀態/指標變化時 re-render + return prev.data.status === next.data.status + && prev.data.latency === next.data.latency + && prev.data.cpu === next.data.cpu +}) +``` + +**與現有架構的整合**: +- `HostService` 型別來自 `dashboard.store.ts` L28-34 (已定義) +- `cn()` 來自 `@/lib/utils` (已存在) +- `status.*` 色彩來自 `tailwind.config.ts` L39-45 (已定義) +- `lucide-react` 已安裝 (v0.577.0) + +--- + +#### WP1-4: GroupNode.tsx 詳細設計 + +```typescript +// 與現有架構的整合: +// 1. Host 型別來自 dashboard.store.ts (已定義) +// 2. HostGrid 的 statusColor() 邏輯可複用 +// 3. useTranslations('topology') 用既有 i18n key + +// 群組節點 = 4 主機 (110/112/120/188) + K3s + 外部 +// 收合時: 顯示群組名稱 + 服務數 + 健康摘要 +// 展開時: 內部節點由 elkjs 自動排列 + +// 群組分類 (對應 service-registry.yaml nodes[].role): +const GROUP_STYLES = { + devops: { border: 'border-blue-400/30', bg: 'bg-blue-50/30', label: '基礎設施' }, + security: { border: 'border-red-400/30', bg: 'bg-red-50/30', label: 'Security' }, + k3s: { border: 'border-purple-400/30',bg: 'bg-purple-50/30', label: 'K3s 叢集' }, + ai_web: { border: 'border-orange-400/30',bg: 'bg-orange-50/30', label: 'AI/數據中心' }, + external: { border: 'border-amber-400/30', bg: 'bg-amber-50/30', label: '外部服務' }, +} +``` + +--- + +#### WP1-5: useTopologyData.ts 詳細設計 + +```typescript +// 關鍵: 從現有 dashboard.store 讀取 Host[],轉換為 React Flow nodes/edges + +import { useDashboardStore } from '@/stores/dashboard.store' +import type { Node, Edge } from '@xyflow/react' + +export function useTopologyData() { + // 直接讀取現有 store — 不需要新的 API 呼叫! + const hosts = useDashboardStore(s => s.hosts) + const overallStatus = useDashboardStore(s => s.overallStatus) + + // 轉換 Host[] → React Flow Node[] + const { nodes, edges } = useMemo(() => { + const nodes: Node[] = [] + const edges: Edge[] = [] + + // Step 1: 建立群組節點 (每個主機 = 一個群組) + hosts.forEach(host => { + nodes.push({ + id: `group-${host.ip}`, + type: 'groupNode', + data: { + label: host.name, // "DevOps 金庫" + role: host.role, // "devops" + status: host.status, // "healthy" + serviceCount: host.services.length, + healthySvc: host.services.filter(s => s.status === 'up').length, + metrics: host.metrics, // CPU/Memory 完整指標 + }, + position: { x: 0, y: 0 }, // elkjs 會計算 + }) + + // Step 2: 建立服務節點 (每個服務 = 群組內的子節點) + host.services.forEach(svc => { + nodes.push({ + id: `svc-${host.ip}-${svc.name}`, + type: 'serviceNode', + parentId: `group-${host.ip}`, // elkjs 嵌套! + data: { + name: svc.name, + status: svc.status, + port: svc.port, + latency: svc.latency_ms, + host: host.ip, + }, + position: { x: 0, y: 0 }, + }) + }) + }) + + // Step 3: 服務間依賴邊線 (靜態定義) + DEPENDENCY_MAP.forEach(([from, to]) => { + edges.push({ + id: `edge-${from}-${to}`, + source: from, + target: to, + type: 'topologyEdge', + }) + }) + + return { nodes, edges } + }, [hosts]) + + return { nodes, edges, overallStatus } +} +``` + +**與現有架構的整合**: +- `useDashboardStore` 來自 `stores/dashboard.store.ts` (已存在) +- `Host`, `HostService` 型別已定義 +- **不需要新的 API 呼叫** — 直接用 store 裡的資料 + +--- + +#### WP1-6: 服務間依賴關係定義 + +```typescript +// 靜態依賴定義 (對應 host_aggregator.py HOST_CONFIGS) +// 資料來源: service-registry.yaml + ConfigMap 環境變數 + +// 格式: [source_node_id, target_node_id] +export const DEPENDENCY_MAP: [string, string][] = [ + // K3s Pod 間依賴 + ['svc-K3s-awoooi-web', 'svc-K3s-awoooi-api'], + ['svc-K3s-awoooi-api', 'svc-192.168.0.188-PostgreSQL'], + ['svc-K3s-awoooi-api', 'svc-192.168.0.188-Redis'], + ['svc-K3s-awoooi-api', 'svc-192.168.0.188-OpenClaw'], + ['svc-K3s-awoooi-worker', 'svc-192.168.0.188-PostgreSQL'], + ['svc-K3s-awoooi-worker', 'svc-192.168.0.188-Redis'], + ['svc-K3s-awoooi-worker', 'svc-192.168.0.188-Ollama'], + + // AI 服務依賴 + ['svc-192.168.0.188-OpenClaw', 'svc-192.168.0.188-Ollama'], + ['svc-192.168.0.188-OpenClaw', 'svc-192.168.0.188-Redis'], + + // 監控工具依賴 + ['svc-192.168.0.188-SigNoz', 'svc-192.168.0.188-ClickHouse'], + + // DevOps 依賴 + ['svc-K3s-awoooi-api', 'svc-192.168.0.110-Sentry'], + ['svc-K3s-awoooi-web', 'svc-192.168.0.110-Sentry'], +] + +// 這些依賴來自: +// 1. ConfigMap OLLAMA_URL → API 依賴 Ollama +// 2. ConfigMap OPENCLAW_URL → API 依賴 OpenClaw +// 3. config.py DATABASE_URL → API 依賴 PostgreSQL +// 4. config.py REDIS_URL → API 依賴 Redis +// 5. Sentry SDK → API/Web 依賴 Sentry +``` + +--- + +#### WP1-7: useElkLayout.ts 詳細設計 + +```typescript +import ELK from 'elkjs/lib/elk.bundled' + +const elk = new ELK() + +export async function computeElkLayout( + nodes: Node[], + edges: Edge[], + expanded: Set, // 展開的群組 ID +): Promise<{ nodes: Node[], edges: Edge[] }> { + + // 建立 elk graph (嵌套結構) + const elkGraph = { + id: 'root', + layoutOptions: { + 'elk.algorithm': 'layered', + 'elk.direction': 'RIGHT', + 'elk.spacing.nodeNode': '30', + 'elk.layered.spacing.nodeNodeBetweenLayers': '60', + 'elk.edgeRouting': 'ORTHOGONAL', + }, + children: buildElkChildren(nodes, expanded), + edges: edges.map(e => ({ + id: e.id, + sources: [e.source], + targets: [e.target], + })), + } + + const layout = await elk.layout(elkGraph) + + // 將 elk 計算的座標寫回 React Flow nodes + return applyElkPositions(nodes, layout) +} +``` + +--- + +### 工作包 2: 後端 API 擴充 + +**目標**: 擴充 dashboard API 支援拓撲圖需要的額外資料 + +#### WP2-1: 擴充 DashboardResponse 加入 K8s Pod 明細 + +**修改檔案**: `apps/api/src/api/v1/dashboard.py` + +```python +# 新增: K8s Pod 狀態 (來自 k3s_monitor_service) +class PodStatusResponse(BaseModel): + name: str + namespace: str + status: str # Running, Pending, CrashLoopBackOff + restart_count: int + cpu_millicores: int | None + memory_mb: int | None + +class DashboardResponse(BaseModel): + # ... 現有欄位保留 ... + k8s_pods: list[PodStatusResponse] = [] # 新增 + ai_diagnosing: list[str] = [] # 新增: OpenClaw 正在分析的服務名稱 +``` + +**與現有架構整合**: +- `k3s_monitor_service.py` (9,077行) 已有完整的 Pod 查詢邏輯 +- 只需在 `aggregated_to_response()` 中加入 k3s 資料 + +--- + +#### WP2-2: 新增 AI 診斷狀態到 SSE + +**修改檔案**: `apps/api/src/api/v1/dashboard.py` 的 `dashboard_update_loop()` + +```python +# 在 SSE 推送中加入 AI 診斷狀態 +# 來源: 從 Redis working memory 讀取正在分析的 incident +async def get_ai_diagnosing_services() -> list[str]: + """取得 OpenClaw 正在分析的服務名稱""" + from src.services.incident_service import get_incident_service + incidents = await get_incident_service().list_active_incidents() + return [i.affected_services[0] for i in incidents if i.affected_services] +``` + +--- + +### 工作包 3: 頁面整合 + +#### WP3-1: topology/page.tsx 全面重寫 + +**修改檔案**: `apps/web/src/app/[locale]/topology/page.tsx` +**當前**: 103 行 (簡單 host list) +**目標**: ~200 行 (ServiceTopology + 篩選器 + 控制列) + +```typescript +// 整合方式: +// 1. 保留 AppLayout wrapper (現有 layout 不動) +// 2. 替換內容為 ServiceTopology 元件 +// 3. 加入篩選器 (按群組/狀態) +// 4. 保留現有 i18n key (topology.*) + +import { ServiceTopology } from '@/components/topology' + +export default function TopologyPage({ params }) { + return ( + + + + ) +} +``` + +--- + +#### WP3-2: 首頁 page.tsx 嵌入迷你拓撲 + +**修改檔案**: `apps/web/src/app/[locale]/page.tsx` + +```typescript +// 整合方式: +// 1. 在現有 Metrics Strip 下方加入迷你拓撲 (收合模式) +// 2. 不移除現有 HostGrid — 兩者共存 +// 3. 拓撲圖高度固定 300px,不影響下方 Incident Feed + + +``` + +--- + +#### WP3-3: i18n 擴充 + +**修改檔案**: `messages/zh-TW.json`, `messages/en.json` + +```json +{ + "topology": { + // 保留現有 key + "title": "拓撲圖", + "subtitle": "服務依賴與健康狀態", + // 新增 + "expandAll": "全部展開", + "collapseAll": "全部收合", + "showAnomaliesOnly": "只看異常", + "groupInfra": "基礎設施 (.110)", + "groupAI": "AI/數據中心 (.188)", + "groupK3s": "K3s 叢集", + "groupExternal": "外部服務", + "healthy": "健康", + "warning": "警告", + "critical": "異常", + "aiDiagnosing": "AI 診斷中", + "servicesCount": "{count} 個服務", + "allHealthy": "全部健康", + "expandGroup": "展開群組", + "collapseGroup": "收合群組", + "dependencies": "依賴關係", + "noData": "無拓撲資料" + } +} +``` + +--- + +### 工作包 4: Tailwind 設計系統擴充 + +**修改檔案**: `apps/web/tailwind.config.ts` + +```typescript +// 新增動畫 (在現有 animation 區塊下方): +animation: { + // ... 現有 11 種動畫保留 ... + 'edge-flow': 'edge-flow 1.5s linear infinite', + 'ai-pulse': 'ai-pulse 2s ease-in-out infinite', + 'node-glow': 'node-glow 2s ease-in-out infinite', +}, +keyframes: { + // ... 現有 keyframes 保留 ... + 'edge-flow': { + 'to': { 'stroke-dashoffset': '-12' }, + }, + 'ai-pulse': { + '0%, 100%': { 'box-shadow': '0 0 0 0 rgba(74, 144, 217, 0.2)' }, + '50%': { 'box-shadow': '0 0 0 8px rgba(74, 144, 217, 0)' }, + }, + 'node-glow': { + '0%, 100%': { opacity: '1' }, + '50%': { opacity: '0.5' }, + }, +} +``` + +--- + +## 三、完整檔案影響清單 + +### 新增檔案 (8 個) + +| # | 檔案 | 行數 | 功能 | +|---|------|------|------| +| 1 | `components/topology/index.ts` | 10 | 匯出 | +| 2 | `components/topology/ServiceTopology.tsx` | 150 | 主元件 | +| 3 | `components/topology/nodes/GroupNode.tsx` | 120 | 群組節點 | +| 4 | `components/topology/nodes/ServiceNode.tsx` | 80 | 服務節點 | +| 5 | `components/topology/edges/TopologyEdge.tsx` | 60 | 自定義邊線 | +| 6 | `components/topology/hooks/useTopologyData.ts` | 100 | 資料轉換 | +| 7 | `components/topology/hooks/useElkLayout.ts` | 80 | 佈局引擎 | +| 8 | `components/topology/topology.css` | 30 | 動畫 CSS | + +**新增總計: ~630 行** + +### 修改檔案 (6 個) + +| # | 檔案 | 改動幅度 | 內容 | +|---|------|---------|------| +| 1 | `apps/web/package.json` | +2 行 | 新增依賴 | +| 2 | `apps/web/tailwind.config.ts` | +15 行 | 新增動畫 | +| 3 | `apps/web/src/app/[locale]/topology/page.tsx` | 重寫 (~200行) | 整合 ServiceTopology | +| 4 | `apps/web/src/app/[locale]/page.tsx` | +10 行 | 嵌入迷你拓撲 | +| 5 | `apps/api/src/api/v1/dashboard.py` | +30 行 | K8s Pod + AI 狀態 | +| 6 | `messages/zh-TW.json` + `en.json` | +20 行 | i18n key | + +### 不修改的檔案 (穩定層) + +| 檔案 | 原因 | +|------|------| +| `dashboard.store.ts` | 拓撲直接讀取現有 store,不需改 | +| `host_aggregator.py` | 現有探測邏輯不動 | +| `service-registry.yaml` | SSOT 不需改 | +| `AppLayout/Header/Sidebar` | 拓撲頁掛在現有 layout 下 | +| `HostGrid` | 首頁保留,拓撲頁獨立 | +| `CI/CD workflows` | 新程式碼走現有 pipeline | + +--- + +## 四、細化實施步驟 (15 步) + +### Phase 1: 安裝 + 骨架 (2小時) + +| Step | 動作 | 驗證 | +|------|------|------| +| 1.1 | `npm install @xyflow/react elkjs` | import 成功 | +| 1.2 | 建立 `components/topology/` 目錄結構 | ls 確認 8 檔案 | +| 1.3 | tailwind.config.ts 加入 3 個動畫 | build 無錯 | + +### Phase 2: 核心元件 (4小時) + +| Step | 動作 | 驗證 | +|------|------|------| +| 2.1 | 實作 ServiceNode.tsx (memo + Tailwind + Lucide) | 單一節點渲染正確 | +| 2.2 | 實作 GroupNode.tsx (展開/收合 + 摘要) | 群組展開/收合切換 | +| 2.3 | 實作 TopologyEdge.tsx (漸層 + CSS 動畫) | 三種邊線樣式 | +| 2.4 | 實作 useElkLayout.ts (elkjs 嵌套) | 20 節點正確佈局 | + +### Phase 3: 資料整合 (3小時) + +| Step | 動作 | 驗證 | +|------|------|------| +| 3.1 | 實作 useTopologyData.ts (store → nodes/edges) | hosts 正確轉換 | +| 3.2 | 定義 DEPENDENCY_MAP (靜態依賴) | 邊線正確連接 | +| 3.3 | 實作 ServiceTopology.tsx (組裝全部) | 完整拓撲渲染 | + +### Phase 4: 頁面整合 (2小時) + +| Step | 動作 | 驗證 | +|------|------|------| +| 4.1 | topology/page.tsx 全面重寫 | 拓撲頁完整功能 | +| 4.2 | page.tsx 嵌入迷你拓撲 | 首頁有拓撲區塊 | +| 4.3 | i18n 擴充 | 所有文字有翻譯 | + +### Phase 5: 後端擴充 (2小時) + +| Step | 動作 | 驗證 | +|------|------|------| +| 5.1 | dashboard.py 加入 K8s Pod + AI 狀態 | curl 回傳新欄位 | +| 5.2 | SSE 推送加入 AI 診斷事件 | SSE 有新事件類型 | + +### Phase 6: 驗收 (1小時) + +| Step | 動作 | 驗證 | +|------|------|------| +| 6.1 | TypeScript 編譯 (`tsc --noEmit`) | 零錯誤 | +| 6.2 | 生產建置 (`pnpm build`) | 建置成功 | +| 6.3 | 視覺驗收 (群組展開/收合/動畫) | 統帥確認 | + +--- + +## 五、前置條件 — 先 PoC 還是直接實作? + +### 選項 A: 先做 PoC (建議) + +``` +Day 1: PoC (React Flow + elkjs 基本嵌套群組) + → 部署到內網 http://192.168.0.188:8765/poc-topology.html + → 統帥實際操作確認展開/收合/佈局效果 + +Day 2: 基於 PoC 回饋 → 製作 3+ 設計稿 + → 統帥選擇佈局比例/面板模式 + +Day 3-7: 正式實作 (Phase 1-6) +``` + +### 選項 B: 直接實作 + +``` +Day 1-2: Phase 1-2 (安裝+核心元件) +Day 3: Phase 3 (資料整合) +Day 4: Phase 4 (頁面整合) +Day 5: Phase 5-6 (後端+驗收) +``` + +--- + +## 六、需要統帥決策的事項 + +| # | 決策項 | 選項 | 影響 | +|---|--------|------|------| +| **D1** | 先 PoC 還是直接實作? | A: 先 PoC / B: 直接做 | 時程差 1-2 天 | +| **D2** | 拓撲圖放在哪裡? | A: 替代首頁 / B: 首頁+拓撲頁並存 / C: 只在拓撲頁 | 影響 page.tsx | +| **D3** | K3s Pod 要不要展示? | A: 只到服務級 / B: 展開到 Pod 級 | 影響資料量 | +| **D4** | 依賴關係定義方式 | A: 靜態 TypeScript / B: 後端 API 動態 | 影響維護方式 | + +--- + +## 七、與統帥截圖概念的對應 + +| 統帥截圖元素 | 實作方式 | 對應 WP | +|-------------|---------|---------| +| 神經拓撲圖 (6節點) | ServiceTopology + GroupNode | WP1 | +| KPI 橫條 (4指標) | 現有 Metrics Strip (不動) | — | +| 待核准任務 | 現有 `/approvals/pending` API | — | +| 處置統計 (4色) | Sprint 4 已實作 | — | +| 活動串流 | 現有 SSE `/dashboard/stream` | — | +| 底部控制列 | ServiceTopology controls prop | WP1 | + +--- + +## 八、風險矩陣 + +| 風險 | 機率 | 影響 | 緩解 | +|------|------|------|------| +| elkjs 佈局效果不如預期 | 中 | 高 | **先做 PoC 驗證** | +| 節點數 100+ DOM 效能 | 低 | 中 | React.memo + 收合群組 | +| 依賴關係定義維護 | 中 | 低 | 先靜態,未來 OTEL | +| 與現有首頁衝突 | 低 | 低 | 拓撲圖是新增元件 | +| build 大小增加 | 低 | 低 | +270KB gzip 可接受 |