diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 4500b1ec..650b038f 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -808,12 +808,19 @@ }, "topology": { "title": "Topology", - "subtitle": "Host architecture view", + "subtitle": "Service dependencies & health status", "noHosts": "No host data available", "fetchError": "Failed to load host data", "services": "Services", "cpu": "CPU", - "ram": "RAM" + "ram": "RAM", + "groupInfra": "Infrastructure", + "groupSecurity": "Security", + "groupK3s": "K3s Cluster", + "groupAiData": "AI/Data Center", + "allHealthy": "All Healthy", + "warning": "Warning", + "healthy": "Healthy" }, "notifications": { "title": "Notifications", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index bbbb5562..74a03094 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -809,12 +809,19 @@ }, "topology": { "title": "拓撲圖", - "subtitle": "主機架構視圖", + "subtitle": "服務依賴與健康狀態", "noHosts": "目前無主機資料", "fetchError": "無法取得主機資料", "services": "服務", "cpu": "CPU", - "ram": "RAM" + "ram": "RAM", + "groupInfra": "基礎設施", + "groupSecurity": "安全中心", + "groupK3s": "K3s 叢集", + "groupAiData": "AI/數據中心", + "allHealthy": "全部健康", + "warning": "異常", + "healthy": "健康" }, "notifications": { "title": "通知", diff --git a/apps/web/src/components/topology/ServiceTopology.tsx b/apps/web/src/components/topology/ServiceTopology.tsx index ece151b9..7e5faa83 100644 --- a/apps/web/src/components/topology/ServiceTopology.tsx +++ b/apps/web/src/components/topology/ServiceTopology.tsx @@ -29,6 +29,7 @@ import { } from '@xyflow/react' import '@xyflow/react/dist/style.css' +import { useTranslations } from 'next-intl' import { useTopologyData } from './hooks/useTopologyData' import { ServiceNode } from './nodes/ServiceNode' import { GroupNode } from './nodes/GroupNode' @@ -72,6 +73,7 @@ export function ServiceTopology({ showMiniMap = false, height = '100%', }: ServiceTopologyProps) { + const t = useTranslations('dashboard') const { nodes, edges, hostCount, serviceCount, healthyCount, warningCount } = useTopologyData() // 空狀態 @@ -82,9 +84,9 @@ export function ServiceTopology({ height: typeof height === 'number' ? height : '100%', color: '#87867f', fontSize: 13, }}> -
🌐
-
等待主機資料...
-
Dashboard API 連線中
+
--
+
{t('waitingHostData')}
+
{t('dashboardConnecting')}
) } @@ -99,10 +101,10 @@ export function ServiceTopology({ background: 'rgba(250,249,243,0.9)', padding: '4px 10px', borderRadius: 6, border: '0.5px solid #e0ddd4', }}> - {hostCount} 主機 - {serviceCount} 服務 - {healthyCount} 健康 - {warningCount > 0 && {warningCount} 異常} + {hostCount} {t('hostsLabel')} + {serviceCount} + {healthyCount} + {warningCount > 0 && {warningCount}} = { - devops: { label: '基礎設施', color: '#3B82F6', borderColor: 'rgba(59,130,246,0.25)', bgColor: 'rgba(59,130,246,0.02)' }, - security: { label: '安全中心', color: '#cc2200', borderColor: 'rgba(204,34,0,0.25)', bgColor: 'rgba(204,34,0,0.02)' }, - k3s: { label: 'K3s 叢集', color: '#A855F7', borderColor: 'rgba(168,85,247,0.25)', bgColor: 'rgba(168,85,247,0.02)' }, - ai_web: { label: 'AI/數據中心', color: '#F97316', borderColor: 'rgba(249,115,22,0.25)', bgColor: 'rgba(249,115,22,0.02)' }, +// C2: 群組標籤用 key 而非硬編碼中文(i18n 在元件層處理) +const GROUP_CONFIG: Record = { + devops: { labelKey: 'infra', color: '#3B82F6', borderColor: 'rgba(59,130,246,0.25)', bgColor: 'rgba(59,130,246,0.02)' }, + security: { labelKey: 'security', color: '#cc2200', borderColor: 'rgba(204,34,0,0.25)', bgColor: 'rgba(204,34,0,0.02)' }, + k3s: { labelKey: 'k3s', color: '#A855F7', borderColor: 'rgba(168,85,247,0.25)', bgColor: 'rgba(168,85,247,0.02)' }, + ai_web: { labelKey: 'aiData', color: '#F97316', borderColor: 'rgba(249,115,22,0.25)', bgColor: 'rgba(249,115,22,0.02)' }, } // ============================================================================= @@ -85,7 +86,8 @@ export function useTopologyData(): TopologyData { type: 'groupNode', position: { x: (hostIdx % 2) * 350, y: Math.floor(hostIdx / 2) * 300 }, data: { - label: `${config.label} (${host.ip.split('.').pop()})`, + labelKey: config.labelKey, + ipSuffix: host.ip.split('.').pop(), role: host.role, status: host.status, ip: host.ip, diff --git a/apps/web/src/components/topology/nodes/GroupNode.tsx b/apps/web/src/components/topology/nodes/GroupNode.tsx index 67e02eba..03547c20 100644 --- a/apps/web/src/components/topology/nodes/GroupNode.tsx +++ b/apps/web/src/components/topology/nodes/GroupNode.tsx @@ -13,8 +13,15 @@ import { memo, type CSSProperties } from 'react' import { type NodeProps } from '@xyflow/react' +import { useTranslations } from 'next-intl' + +// C2: 群組標籤 i18n +const GROUP_LABELS: Record = { + infra: 'groupInfra', security: 'groupSecurity', k3s: 'groupK3s', aiData: 'groupAiData', +} function GroupNodeInner({ data }: NodeProps) { + const t = useTranslations('topology') const d = data as Record const borderColor = d.borderColor || '#e0ddd4' const bgColor = d.bgColor || 'rgba(0,0,0,0.01)' @@ -39,15 +46,15 @@ function GroupNodeInner({ data }: NodeProps) { {/* 群組標題 */}
- {d.label} + {t(GROUP_LABELS[d.labelKey] || 'groupInfra')} (.{d.ipSuffix})
{/* 摘要 */}
- {totalSvc} 服務 · {hasWarning - ? ⚠ {totalSvc - healthySvc} 異常 - : ✓ 全部健康 + {totalSvc} {t('services')} · {hasWarning + ? {totalSvc - healthySvc} {t('warning')} + : {t('allHealthy')} }