26 KiB
26 KiB
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 回傳的完整資料結構
// 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: 安裝依賴
cd apps/web
npm install @xyflow/react elkjs
影響檔案:
apps/web/package.json(新增 2 deps)apps/web/package-lock.json
驗證:
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 詳細設計
// 與現有架構的整合點:
// 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.tsL28-34 (已定義)cn()來自@/lib/utils(已存在)status.*色彩來自tailwind.config.tsL39-45 (已定義)lucide-react已安裝 (v0.577.0)
WP1-4: GroupNode.tsx 詳細設計
// 與現有架構的整合:
// 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 詳細設計
// 關鍵: 從現有 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: 服務間依賴關係定義
// 靜態依賴定義 (對應 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 詳細設計
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
# 新增: 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()
# 在 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 + 篩選器 + 控制列)
// 整合方式:
// 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
// 整合方式:
// 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
{
"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
// 新增動畫 (在現有 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 可接受 |