# 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 可接受 |