From f5141f4f42ee923fd0cfcccc369f0da6bcc5b4dc Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 1 Jun 2026 12:32:22 +0800 Subject: [PATCH] feat(web): add IwoooS intelligence deck --- apps/web/messages/en.json | 46 ++- apps/web/messages/zh-TW.json | 42 +++ apps/web/src/app/[locale]/iwooos/page.tsx | 296 +++++++++++++++++- docs/LOGBOOK.md | 28 ++ .../iwooos-posture-projection.snapshot.json | 80 ++++- ...ecurity-mirror-status-rollup.snapshot.json | 12 + .../security-mirror-progress-guard.py | 146 +++++++++ 7 files changed, 642 insertions(+), 8 deletions(-) diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index b7e4782a9..a6430e43e 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -4182,9 +4182,9 @@ "claimBlocked": "不可宣稱全自動修復", "claimReason": "原因:{reason}", "mapTitle": "Automation Flow Map", - "mapSubtitle": "依照真實 truth-chain gate,把告警到 Operator 可見性切成 8 個節點。", + "mapSubtitle": "依照真實 truth-chain gate,把告警到可見性的流程畫成 8 個節點。", "statusCount": "{status} {count}", - "attentionTitle": "優先瓶頸", + "attentionTitle": "優先處理", "attentionAction": "下一步:{action}", "heatmapTitle": "Gate Evidence Heatmap", "bottleneckTitle": "目前瓶頸", @@ -5569,6 +5569,48 @@ } } }, + "intelligenceDeck": { + "eyebrow": "圖譜情報面板", + "title": "把關聯、路徑、半徑與時序放在同一層", + "subtitle": "參考 XDR / CNAPP 常見的調查體驗:先用可點選情報卡看資安脈絡,再下鑽到節點與證據,不把頁面變成長文字清單。", + "selectorLabel": "切換圖譜情報", + "ringLabel": "目前可信度", + "fields": { + "signal": "訊號", + "interpretation": "判讀", + "next": "下一步" + }, + "items": { + "assetContext": { + "title": "資產關聯", + "signal": "8 類產品、網站與新專案已進入同一個資產面。", + "interpretation": "使用者可以先看哪些入口被納管,而不是從文件裡找清單。", + "next": "補上 owner evidence 後,資產圖才能變成可信驗收範圍。", + "ring": "關聯已可讀;尚未代表完成驗收。" + }, + "attackPath": { + "title": "攻擊路徑", + "signal": "4 條路徑可從版本來源、主機與 Gate 0 互相切換。", + "interpretation": "目前是 exposure mapping,不是滲透測試或主動掃描結果。", + "next": "等待範圍與維護窗口批准後,才進入受控探測。", + "ring": "路徑已成形;執行授權仍為 0。" + }, + "blastRadius": { + "title": "影響半徑", + "signal": "Kali、開發主機與 runtime gate 已形成半徑邊界。", + "interpretation": "未探測前半徑維持 0,避免把推測當成已驗證風險。", + "next": "先完成人工批准,再用受控證據更新半徑。", + "ring": "半徑故意鎖在 0,避免誤導。" + }, + "evidenceTimeline": { + "title": "證據時序", + "signal": "監控、AwoooP 真相鏈與人工閘門已接成證據時序。", + "interpretation": "使用者能看見目前卡在 S4.9,而不是以為 UI 沒有進度。", + "next": "補審查者接受紀錄與負責人回覆。", + "ring": "時序已接線;等待 S4.9 證據。" + } + } + }, "nodeDrilldown": { "eyebrow": "節點下鑽", "selectorLabel": "切換拓樸節點", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index ca6040ee8..a6430e43e 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -5569,6 +5569,48 @@ } } }, + "intelligenceDeck": { + "eyebrow": "圖譜情報面板", + "title": "把關聯、路徑、半徑與時序放在同一層", + "subtitle": "參考 XDR / CNAPP 常見的調查體驗:先用可點選情報卡看資安脈絡,再下鑽到節點與證據,不把頁面變成長文字清單。", + "selectorLabel": "切換圖譜情報", + "ringLabel": "目前可信度", + "fields": { + "signal": "訊號", + "interpretation": "判讀", + "next": "下一步" + }, + "items": { + "assetContext": { + "title": "資產關聯", + "signal": "8 類產品、網站與新專案已進入同一個資產面。", + "interpretation": "使用者可以先看哪些入口被納管,而不是從文件裡找清單。", + "next": "補上 owner evidence 後,資產圖才能變成可信驗收範圍。", + "ring": "關聯已可讀;尚未代表完成驗收。" + }, + "attackPath": { + "title": "攻擊路徑", + "signal": "4 條路徑可從版本來源、主機與 Gate 0 互相切換。", + "interpretation": "目前是 exposure mapping,不是滲透測試或主動掃描結果。", + "next": "等待範圍與維護窗口批准後,才進入受控探測。", + "ring": "路徑已成形;執行授權仍為 0。" + }, + "blastRadius": { + "title": "影響半徑", + "signal": "Kali、開發主機與 runtime gate 已形成半徑邊界。", + "interpretation": "未探測前半徑維持 0,避免把推測當成已驗證風險。", + "next": "先完成人工批准,再用受控證據更新半徑。", + "ring": "半徑故意鎖在 0,避免誤導。" + }, + "evidenceTimeline": { + "title": "證據時序", + "signal": "監控、AwoooP 真相鏈與人工閘門已接成證據時序。", + "interpretation": "使用者能看見目前卡在 S4.9,而不是以為 UI 沒有進度。", + "next": "補審查者接受紀錄與負責人回覆。", + "ring": "時序已接線;等待 S4.9 證據。" + } + } + }, "nodeDrilldown": { "eyebrow": "節點下鑽", "selectorLabel": "切換拓樸節點", diff --git a/apps/web/src/app/[locale]/iwooos/page.tsx b/apps/web/src/app/[locale]/iwooos/page.tsx index 391c55e85..d921bba1f 100644 --- a/apps/web/src/app/[locale]/iwooos/page.tsx +++ b/apps/web/src/app/[locale]/iwooos/page.tsx @@ -198,6 +198,7 @@ type IwoooSImmediateVisualMeshNode = { type IwoooSTopologyAtlasMode = 'architecture' | 'topology' | 'attackSurface' | 'evidenceFlow' type IwoooSTopologyPathKey = 'externalToGate' | 'sourceToHost' | 'kaliToDev' | 'evidenceToGate' +type IwoooSTopologyInsightKey = 'assetContext' | 'attackPath' | 'blastRadius' | 'evidenceTimeline' type IwoooSTopologyNodeKey = | 'productSurface' | 'sourceControl' @@ -256,6 +257,17 @@ type IwoooSTopologyPathExplorerPath = { tone: 'steady' | 'warn' | 'locked' } +type IwoooSTopologyInsightItem = { + key: IwoooSTopologyInsightKey + metric: string + percent: number + lensKey: IwoooSTopologyAtlasMode + pathKey: IwoooSTopologyPathKey + nodes: readonly IwoooSTopologyNodeKey[] + icon: typeof ShieldCheck + tone: 'steady' | 'warn' | 'locked' +} + type IwoooSGateRadarMode = 'visible' | 'blocker' | 'review' | 'locked' type IwoooSGateRadarLane = { @@ -2579,6 +2591,49 @@ const iwooosTopologyPathExplorerPaths: IwoooSTopologyPathExplorerPath[] = [ }, ] +const iwooosTopologyInsightDeck: IwoooSTopologyInsightItem[] = [ + { + key: 'assetContext', + metric: '8資產', + percent: 68, + lensKey: 'architecture', + pathKey: 'externalToGate', + nodes: ['productSurface', 'sourceControl', 'monitoring'], + icon: Boxes, + tone: 'steady', + }, + { + key: 'attackPath', + metric: '4路徑', + percent: 61, + lensKey: 'attackSurface', + pathKey: 'sourceToHost', + nodes: ['sourceControl', 'kali', 'devHosts', 'runtimeGate'], + icon: Route, + tone: 'warn', + }, + { + key: 'blastRadius', + metric: '0驗證', + percent: 0, + lensKey: 'attackSurface', + pathKey: 'kaliToDev', + nodes: ['kali', 'devHosts', 'runtimeGate'], + icon: Lock, + tone: 'locked', + }, + { + key: 'evidenceTimeline', + metric: 'S4.9', + percent: 54, + lensKey: 'evidenceFlow', + pathKey: 'evidenceToGate', + nodes: ['monitoring', 'awooopTruth', 'runtimeGate'], + icon: Clock3, + tone: 'warn', + }, +] + const iwooosTopologyAtlasBoundaries = [ 'iwooos_topology_atlas_first_layer=true', 'iwooos_topology_atlas_lens_count=4', @@ -2589,15 +2644,20 @@ const iwooosTopologyAtlasBoundaries = [ 'iwooos_topology_path_explorer_path_count=4', 'iwooos_topology_path_explorer_default_path=externalToGate', 'iwooos_topology_path_explorer_interactive_path_allowed=true', + 'iwooos_topology_intelligence_deck_count=4', + 'iwooos_topology_intelligence_default_item=assetContext', + 'iwooos_topology_intelligence_interactive_item_allowed=true', 'iwooos_topology_atlas_layer_count=5', 'iwooos_topology_atlas_technical_chart_count=3', 'iwooos_topology_atlas_interactive_lens_allowed=true', 'iwooos_topology_atlas_execution_action_buttons_allowed=false', 'iwooos_topology_drilldown_execution_action_buttons_allowed=false', 'iwooos_topology_path_explorer_execution_action_buttons_allowed=false', + 'iwooos_topology_intelligence_execution_action_buttons_allowed=false', 'iwooos_topology_atlas_runtime_gate_count=0', 'iwooos_topology_drilldown_runtime_gate_count=0', 'iwooos_topology_path_explorer_runtime_gate_count=0', + 'iwooos_topology_intelligence_runtime_gate_count=0', 'iwooos_topology_atlas_scan_authorized=false', 'iwooos_topology_atlas_host_change_authorized=false', 'iwooos_topology_atlas_source_control_mutation_authorized=false', @@ -2607,6 +2667,9 @@ const iwooosTopologyAtlasBoundaries = [ 'iwooos_topology_path_explorer_scan_authorized=false', 'iwooos_topology_path_explorer_host_change_authorized=false', 'iwooos_topology_path_explorer_source_control_mutation_authorized=false', + 'iwooos_topology_intelligence_scan_authorized=false', + 'iwooos_topology_intelligence_host_change_authorized=false', + 'iwooos_topology_intelligence_source_control_mutation_authorized=false', 'runtime_execution_authorized=false', 'active_runtime_gate_count=0', 'action_buttons_allowed=false', @@ -9786,12 +9849,15 @@ function IwoooSTopologyAtlasBoard() { const [activeKey, setActiveKey] = useState('architecture') const [activeNodeKey, setActiveNodeKey] = useState('productSurface') const [activePathKey, setActivePathKey] = useState('externalToGate') + const [activeInsightKey, setActiveInsightKey] = useState('assetContext') const activeLens = iwooosTopologyAtlasLenses.find(item => item.key === activeKey) ?? iwooosTopologyAtlasLenses[0] const activeNode = iwooosTopologyDrilldownNodes.find(item => item.key === activeNodeKey) ?? iwooosTopologyDrilldownNodes[0] const activePath = iwooosTopologyPathExplorerPaths.find(item => item.key === activePathKey) ?? iwooosTopologyPathExplorerPaths[0] + const activeInsight = iwooosTopologyInsightDeck.find(item => item.key === activeInsightKey) ?? iwooosTopologyInsightDeck[0] const ActiveIcon = activeLens.icon const ActiveNodeIcon = activeNode.icon const ActivePathIcon = activePath.icon + const ActiveInsightIcon = activeInsight.icon const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const } const atlasCanvasRef = useRef(null) const [isCompactAtlas, setIsCompactAtlas] = useState(false) @@ -9807,12 +9873,19 @@ function IwoooSTopologyAtlasBoard() { if (toKey === activeNodeKey) connectedNodeKeys.add(fromKey) }) const activePathNodeKeys = new Set(activePath.nodes) + const activeInsightNodeKeys = new Set(activeInsight.nodes) const activePathEdgeKeys = new Set( activePath.nodes.slice(0, -1).map((fromKey, index) => { const toKey = activePath.nodes[index + 1] return `${fromKey}-${toKey}` }), ) + const activeInsightEdgeKeys = new Set( + activeInsight.nodes.slice(0, -1).map((fromKey, index) => { + const toKey = activeInsight.nodes[index + 1] + return `${fromKey}-${toKey}` + }), + ) useEffect(() => { const node = atlasCanvasRef.current @@ -9945,6 +10018,7 @@ function IwoooSTopologyAtlasBoard() { if (!from || !to) return null const selectedPath = fromKey === activeNodeKey || toKey === activeNodeKey const exploredPath = activePathEdgeKeys.has(`${fromKey}-${toKey}`) + const insightPath = activeInsightEdgeKeys.has(`${fromKey}-${toKey}`) return ( ) })} @@ -9992,6 +10066,7 @@ function IwoooSTopologyAtlasBoard() { const selectedNode = node.key === activeNodeKey const connectedNode = connectedNodeKeys.has(node.key) const exploredNode = activePathNodeKeys.has(node.key) + const insightNode = activeInsightNodeKeys.has(node.key) return ( + ) + })} + + +
+ {activeInsight.nodes.map((nodeKey, index) => { + const insightNode = iwooosTopologyDrilldownNodes.find(item => item.key === nodeKey) ?? iwooosTopologyDrilldownNodes[0] + const Icon = insightNode.icon + return ( + + ) + })} +
+ +
+
+ {(['signal', 'interpretation', 'next'] as const).map(key => ( +
+
+ {t(`intelligenceDeck.fields.${key}`)} +
+
+ {t(`intelligenceDeck.items.${activeInsight.key}.${key}` as never)} +
+
+ ))} +
+ +
+ {[0, 1, 2].map(index => ( +
+ ))} +
+
+
+ {t('intelligenceDeck.ringLabel')} +
+
+ {activeInsight.percent}% +
+
+ {t(`intelligenceDeck.items.${activeInsight.key}.ring` as never)} +
+
+
+
+
+
+
None: "s2_148_iwooos_professional_topology_atlas", "s2_149_iwooos_topology_node_drilldown", "s2_150_iwooos_topology_path_explorer", + "s2_151_iwooos_topology_intelligence_deck", ] assert_equal( "progress_delta_ledger.delta_ids", @@ -1610,6 +1611,20 @@ def validate(root: Path) -> None: "iwooos_projection.summary.topology_path_explorer_interactive_path_allowed", iwooos_projection["summary"]["topology_path_explorer_interactive_path_allowed"], ) + assert_equal( + "iwooos_projection.summary.topology_intelligence_deck_count", + iwooos_projection["summary"]["topology_intelligence_deck_count"], + 4, + ) + assert_equal( + "iwooos_projection.summary.topology_intelligence_default_item", + iwooos_projection["summary"]["topology_intelligence_default_item"], + "assetContext", + ) + assert_true( + "iwooos_projection.summary.topology_intelligence_interactive_item_allowed", + iwooos_projection["summary"]["topology_intelligence_interactive_item_allowed"], + ) assert_equal( "iwooos_projection.summary.topology_atlas_layer_count", iwooos_projection["summary"]["topology_atlas_layer_count"], @@ -1643,6 +1658,11 @@ def validate(root: Path) -> None: iwooos_projection["summary"]["topology_path_explorer_runtime_gate_count"], 0, ) + assert_equal( + "iwooos_projection.summary.topology_intelligence_runtime_gate_count", + iwooos_projection["summary"]["topology_intelligence_runtime_gate_count"], + 0, + ) assert_true( "iwooos_projection.summary.gate_radar_first_layer", iwooos_projection["summary"]["gate_radar_first_layer"], @@ -12043,6 +12063,10 @@ def validate(root: Path) -> None: 'data-testid="iwooos-topology-atlas-path-explorer-selector"', 'data-testid="iwooos-topology-atlas-path-explorer-sequence"', 'data-testid="iwooos-topology-atlas-path-explorer-summary"', + 'data-testid="iwooos-topology-atlas-intelligence-deck"', + 'data-testid="iwooos-topology-atlas-intelligence-selector"', + 'data-testid="iwooos-topology-atlas-intelligence-trail"', + 'data-testid="iwooos-topology-atlas-blast-rings"', 'data-testid="iwooos-topology-atlas-node-drilldown"', 'data-testid="iwooos-topology-atlas-node-drilldown-metrics"', 'data-testid="iwooos-topology-atlas-node-drilldown-selector"', @@ -12051,13 +12075,16 @@ def validate(root: Path) -> None: "IwoooSTopologyAtlasBoard", "IwoooSTopologyAtlasMode", "IwoooSTopologyPathKey", + "IwoooSTopologyInsightKey", "IwoooSTopologyNodeKey", "IwoooSTopologyDrilldownNode", "IwoooSTopologyPathExplorerPath", + "IwoooSTopologyInsightItem", "iwooosTopologyAtlasLenses", "iwooosTopologyAtlasNodes", "iwooosTopologyDrilldownNodes", "iwooosTopologyPathExplorerPaths", + "iwooosTopologyInsightDeck", "iwooosTopologyAtlasLayers", "iwooosTopologyAtlasCharts", "iwooosTopologyAtlasBoundaries", @@ -12096,15 +12123,20 @@ def validate(root: Path) -> None: "iwooos_topology_path_explorer_path_count=4", "iwooos_topology_path_explorer_default_path=externalToGate", "iwooos_topology_path_explorer_interactive_path_allowed=true", + "iwooos_topology_intelligence_deck_count=4", + "iwooos_topology_intelligence_default_item=assetContext", + "iwooos_topology_intelligence_interactive_item_allowed=true", "iwooos_topology_atlas_layer_count=5", "iwooos_topology_atlas_technical_chart_count=3", "iwooos_topology_atlas_interactive_lens_allowed=true", "iwooos_topology_atlas_execution_action_buttons_allowed=false", "iwooos_topology_drilldown_execution_action_buttons_allowed=false", "iwooos_topology_path_explorer_execution_action_buttons_allowed=false", + "iwooos_topology_intelligence_execution_action_buttons_allowed=false", "iwooos_topology_atlas_runtime_gate_count=0", "iwooos_topology_drilldown_runtime_gate_count=0", "iwooos_topology_path_explorer_runtime_gate_count=0", + "iwooos_topology_intelligence_runtime_gate_count=0", "iwooos_topology_atlas_scan_authorized=false", "iwooos_topology_atlas_host_change_authorized=false", "iwooos_topology_atlas_source_control_mutation_authorized=false", @@ -12114,6 +12146,9 @@ def validate(root: Path) -> None: "iwooos_topology_path_explorer_scan_authorized=false", "iwooos_topology_path_explorer_host_change_authorized=false", "iwooos_topology_path_explorer_source_control_mutation_authorized=false", + "iwooos_topology_intelligence_scan_authorized=false", + "iwooos_topology_intelligence_host_change_authorized=false", + "iwooos_topology_intelligence_source_control_mutation_authorized=false", "runtime_execution_authorized=false", "active_runtime_gate_count=0", "action_buttons_allowed=false", @@ -12659,6 +12694,73 @@ def validate(root: Path) -> None: f"iwooos_projection.topology_path_explorer_paths.{path_id}.not_authorization", item["not_authorization"], ) + expected_topology_intelligence_deck_ids = [ + "assetContext", + "attackPath", + "blastRadius", + "evidenceTimeline", + ] + expected_topology_intelligence_sequences = { + "assetContext": ["productSurface", "sourceControl", "monitoring"], + "attackPath": ["sourceControl", "kali", "devHosts", "runtimeGate"], + "blastRadius": ["kali", "devHosts", "runtimeGate"], + "evidenceTimeline": ["monitoring", "awooopTruth", "runtimeGate"], + } + topology_intelligence_deck = iwooos_projection["topology_intelligence_deck"] + assert_equal( + "iwooos_projection.topology_intelligence_deck.ids", + [item["insight_id"] for item in topology_intelligence_deck], + expected_topology_intelligence_deck_ids, + ) + assert_equal( + "iwooos_projection.topology_intelligence_deck.display_order", + [item["display_order"] for item in topology_intelligence_deck], + list(range(1, len(expected_topology_intelligence_deck_ids) + 1)), + ) + for item in topology_intelligence_deck: + insight_id = item["insight_id"] + assert_equal( + f"iwooos_projection.topology_intelligence_deck.{insight_id}.display_mode", + item["display_mode"], + "professional_graph_intelligence_deck", + ) + assert_equal( + f"iwooos_projection.topology_intelligence_deck.{insight_id}.node_sequence", + item["node_sequence"], + expected_topology_intelligence_sequences[insight_id], + ) + assert_true( + f"iwooos_projection.topology_intelligence_deck.{insight_id}.interactive_item_allowed", + item["interactive_item_allowed"], + ) + assert_false( + f"iwooos_projection.topology_intelligence_deck.{insight_id}.execution_action_button_allowed", + item["execution_action_button_allowed"], + ) + assert_false( + f"iwooos_projection.topology_intelligence_deck.{insight_id}.scan_authorized", + item["scan_authorized"], + ) + assert_false( + f"iwooos_projection.topology_intelligence_deck.{insight_id}.host_change_authorized", + item["host_change_authorized"], + ) + assert_false( + f"iwooos_projection.topology_intelligence_deck.{insight_id}.source_control_mutation_authorized", + item["source_control_mutation_authorized"], + ) + assert_false( + f"iwooos_projection.topology_intelligence_deck.{insight_id}.runtime_gate_opened", + item["runtime_gate_opened"], + ) + assert_false( + f"iwooos_projection.topology_intelligence_deck.{insight_id}.runtime_execution_authorized", + item["runtime_execution_authorized"], + ) + assert_true( + f"iwooos_projection.topology_intelligence_deck.{insight_id}.not_authorization", + item["not_authorization"], + ) expected_gate_radar_lane_ids = [ "visible", "blocker", @@ -13066,6 +13168,50 @@ def validate(root: Path) -> None: list(web_messages_en["iwooos"]["topologyAtlas"]["pathExplorer"]["paths"][key].keys()), field, ) + for key in ["eyebrow", "title", "subtitle", "selectorLabel", "ringLabel", "fields", "items"]: + assert_contains( + "web_messages.zh-TW.iwooos.topologyAtlas.intelligenceDeck.keys", + list(web_messages_zh["iwooos"]["topologyAtlas"]["intelligenceDeck"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.topologyAtlas.intelligenceDeck.keys", + list(web_messages_en["iwooos"]["topologyAtlas"]["intelligenceDeck"].keys()), + key, + ) + for key in ["signal", "interpretation", "next"]: + assert_contains( + "web_messages.zh-TW.iwooos.topologyAtlas.intelligenceDeck.fields", + list(web_messages_zh["iwooos"]["topologyAtlas"]["intelligenceDeck"]["fields"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.topologyAtlas.intelligenceDeck.fields", + list(web_messages_en["iwooos"]["topologyAtlas"]["intelligenceDeck"]["fields"].keys()), + key, + ) + for key in ["assetContext", "attackPath", "blastRadius", "evidenceTimeline"]: + assert_contains( + "web_messages.zh-TW.iwooos.topologyAtlas.intelligenceDeck.items", + list(web_messages_zh["iwooos"]["topologyAtlas"]["intelligenceDeck"]["items"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.topologyAtlas.intelligenceDeck.items", + list(web_messages_en["iwooos"]["topologyAtlas"]["intelligenceDeck"]["items"].keys()), + key, + ) + for field in ["title", "signal", "interpretation", "next", "ring"]: + assert_contains( + f"web_messages.zh-TW.iwooos.topologyAtlas.intelligenceDeck.items.{key}", + list(web_messages_zh["iwooos"]["topologyAtlas"]["intelligenceDeck"]["items"][key].keys()), + field, + ) + assert_contains( + f"web_messages.en.iwooos.topologyAtlas.intelligenceDeck.items.{key}", + list(web_messages_en["iwooos"]["topologyAtlas"]["intelligenceDeck"]["items"][key].keys()), + field, + ) for key in ["eyebrow", "selectorLabel", "fields", "nodes"]: assert_contains( "web_messages.zh-TW.iwooos.topologyAtlas.nodeDrilldown.keys",