Files
awoooi/docs/superpowers/plans/2026-04-08-sprint5-complete-solution.md

747 lines
26 KiB
Markdown
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.
# 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<string, React.ElementType> = {
'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 (
<div className={cn(
'bg-card border-[0.5px] rounded-[8px] px-3 py-2 min-w-[120px]',
'shadow-sm hover:shadow-md transition-shadow',
'font-body text-xs',
colorClass
)}>
<Handle type="target" position={Position.Left} className="!bg-border !w-1.5 !h-1.5" />
{/* 狀態燈 + 狀態文字 */}
<div className="flex items-center gap-1.5 mb-1">
<span className={cn('w-1.5 h-1.5 rounded-full', /* status bg */)} />
<span className="text-[8px] text-text3 uppercase tracking-wider">{data.statusText}</span>
</div>
{/* Icon + 服務名稱 */}
<div className="flex items-center gap-1.5">
<Icon className="w-3.5 h-3.5 opacity-60" />
<span className="font-semibold text-[11px]">{data.name}</span>
</div>
{/* 位置 + 類型 */}
<div className="text-[8px] text-text3 font-mono mt-0.5">
{data.port && `${data.host}:${data.port}`}
{data.latency && ` · ${data.latency}ms`}
</div>
<Handle type="source" position={Position.Right} className="!bg-border !w-1.5 !h-1.5" />
</div>
)
}, (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<string>, // 展開的群組 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 (
<AppLayout locale={params.locale}>
<ServiceTopology
mode="full" // 完整模式: 可展開所有群組
showControls={true} // 顯示 zoom/fit/篩選
showMiniMap={true} // 顯示迷你地圖
/>
</AppLayout>
)
}
```
---
#### WP3-2: 首頁 page.tsx 嵌入迷你拓撲
**修改檔案**: `apps/web/src/app/[locale]/page.tsx`
```typescript
// 整合方式:
// 1. 在現有 Metrics Strip 下方加入迷你拓撲 (收合模式)
// 2. 不移除現有 HostGrid — 兩者共存
// 3. 拓撲圖高度固定 300px不影響下方 Incident Feed
<ServiceTopology
mode="compact" // 收合模式: 只顯示群組
showControls={false} // 不顯示控制列
showMiniMap={false}
height={300}
/>
```
---
#### 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 可接受 |