feat(web): add IwoooS topology atlas
All checks were successful
CD Pipeline / tests (push) Successful in 1m21s
Code Review / ai-code-review (push) Successful in 12s
CD Pipeline / build-and-deploy (push) Successful in 3m38s
CD Pipeline / post-deploy-checks (push) Successful in 2m28s

This commit is contained in:
Your Name
2026-06-01 10:02:51 +08:00
parent 07000d532c
commit 86fe36dc55
7 changed files with 1286 additions and 1 deletions

View File

@@ -5447,6 +5447,113 @@
"boundaryTitle": "視覺模型邊界",
"boundaryIntro": "這張圖是首屏理解模型,不是操作面板;所有高風險動作仍需人工批准與後續 runtime gate。"
},
"topologyAtlas": {
"eyebrow": "專業架構與拓樸圖譜",
"title": "用圖譜看攻擊面、資產關係與證據流",
"subtitle": "把主流資安產品常見的 graph、attack path、blast radius 與 evidence lane 濃縮成四個可切換視角;少文字、多圖表,仍維持 Gate 0。",
"tabsLabel": "架構拓樸圖譜視角",
"mapLabel": "圖譜視角",
"panelLabels": {
"evidence": "目前可見",
"next": "下一步",
"locked": "仍鎖住"
},
"lenses": {
"architecture": {
"title": "架構分層",
"mapTitle": "Code → Asset → Host → Evidence → Gate",
"detail": "用五層結構看 IwoooS產品與網站、版本來源、Kali / 開發主機、監控與 AwoooP、最後才是執行閘。",
"evidence": "8 類產品 / 網站、3 台主機、6 條工具鏈已進入同一張只讀圖譜。",
"next": "把 S4.9 owner response 與脫敏證據接成可驗證節點。",
"locked": "架構圖不是 runtime 授權,不代表可以掃描或修復。"
},
"topology": {
"title": "主機拓樸",
"mapTitle": "112 / 111 / 168 Observe-only Fabric",
"detail": "把 Kali 112、開發主機 111 / 168、監控工具與 AwoooP 真相鏈放在同一張拓樸圖,而不是分散在長文件裡。",
"evidence": "目前只呈現觀測窗口、證據位置與人工 gate沒有執行 SSH、掃描或主機設定變更。",
"next": "等 runtime gate 與掃描範圍批准後,才把 read-only evidence 轉入受控探測。",
"locked": "host_change_authorized=falsescan_authorized=false。"
},
"attackSurface": {
"title": "攻擊面路徑",
"mapTitle": "External Surface → Source Control → Runtime Boundary",
"detail": "把公開入口、產品、版本來源與 Gate 0 串成攻擊面圖;目標是優先看清可被利用的關聯,不先提高限制。",
"evidence": "目前可看見 8 類資產與 S4.9 版本來源卡點,但 blast radius 仍維持 0避免誤導成已完成攻防驗證。",
"next": "先完成 GitHub primary / Gitea owner evidence再讓風險路徑有可信來源。",
"locked": "source_control_mutation_authorized=falseGitHub primary switch 未授權。"
},
"evidenceFlow": {
"title": "證據流",
"mapTitle": "Monitoring → AwoooP Truth Chain → Human Gate",
"detail": "用 evidence lane 表示資料如何被收集、脫敏、審查與回寫;這比單純列文件更接近 SOC / XDR 的操作體驗。",
"evidence": "AwoooP 跨 Session 狀態已接線IwoooS 只讀鏡像與 progress guard 已有證據。",
"next": "補 reviewer 接受紀錄與 owner decision record才可能進入下一個 runtime gate。",
"locked": "active_runtime_gate_count=0沒有任何自動執行按鈕。"
}
},
"nodes": {
"productSurface": {
"title": "產品 / 網站"
},
"sourceControl": {
"title": "版本來源"
},
"kali": {
"title": "Kali 112"
},
"devHosts": {
"title": "開發主機"
},
"monitoring": {
"title": "監控 / 工具"
},
"awooopTruth": {
"title": "AwoooP 真相鏈"
},
"runtimeGate": {
"title": "Gate 0"
}
},
"layers": {
"externalSurface": {
"title": "外部資產面",
"body": "公開網站、產品入口與 VibeWork 先進入可理解範圍。"
},
"codeSupply": {
"title": "版本來源面",
"body": "GitHub primary / Gitea 遷移仍等 S4.9 證據。"
},
"hostFabric": {
"title": "主機拓樸面",
"body": "112、111、168 維持 observe-only 顯示。"
},
"evidenceOps": {
"title": "證據營運面",
"body": "監控、KM、MCP、Ansible 與 AwoooP 對齊。"
},
"gateControl": {
"title": "執行閘面",
"body": "runtime gate、掃描與修復仍全部鎖住。"
}
},
"charts": {
"contextDepth": {
"label": "關聯深度",
"body": "已把 code-to-runtime 的理解路徑壓成四段。"
},
"blastRadius": {
"label": "爆炸半徑",
"body": "未授權探測前維持 0不誤導成已驗證攻擊半徑。"
},
"evidenceFreshness": {
"label": "證據新鮮度",
"body": "目前真正卡點仍是 S4.9 owner evidence。"
}
},
"boundaryTitle": "圖譜邊界",
"boundaryIntro": "這張圖是專業可視化與理解層,不是掃描拓樸、不是自動修復、不是 GitHub / Gitea 切換授權。"
},
"gateRadar": {
"eyebrow": "執行閘雷達",
"title": "一眼看懂哪裡可前進、哪裡不能碰",

View File

@@ -5447,6 +5447,113 @@
"boundaryTitle": "視覺模型邊界",
"boundaryIntro": "這張圖是首屏理解模型,不是操作面板;所有高風險動作仍需人工批准與後續 runtime gate。"
},
"topologyAtlas": {
"eyebrow": "專業架構與拓樸圖譜",
"title": "用圖譜看攻擊面、資產關係與證據流",
"subtitle": "把主流資安產品常見的 graph、attack path、blast radius 與 evidence lane 濃縮成四個可切換視角;少文字、多圖表,仍維持 Gate 0。",
"tabsLabel": "架構拓樸圖譜視角",
"mapLabel": "圖譜視角",
"panelLabels": {
"evidence": "目前可見",
"next": "下一步",
"locked": "仍鎖住"
},
"lenses": {
"architecture": {
"title": "架構分層",
"mapTitle": "Code → Asset → Host → Evidence → Gate",
"detail": "用五層結構看 IwoooS產品與網站、版本來源、Kali / 開發主機、監控與 AwoooP、最後才是執行閘。",
"evidence": "8 類產品 / 網站、3 台主機、6 條工具鏈已進入同一張只讀圖譜。",
"next": "把 S4.9 owner response 與脫敏證據接成可驗證節點。",
"locked": "架構圖不是 runtime 授權,不代表可以掃描或修復。"
},
"topology": {
"title": "主機拓樸",
"mapTitle": "112 / 111 / 168 Observe-only Fabric",
"detail": "把 Kali 112、開發主機 111 / 168、監控工具與 AwoooP 真相鏈放在同一張拓樸圖,而不是分散在長文件裡。",
"evidence": "目前只呈現觀測窗口、證據位置與人工 gate沒有執行 SSH、掃描或主機設定變更。",
"next": "等 runtime gate 與掃描範圍批准後,才把 read-only evidence 轉入受控探測。",
"locked": "host_change_authorized=falsescan_authorized=false。"
},
"attackSurface": {
"title": "攻擊面路徑",
"mapTitle": "External Surface → Source Control → Runtime Boundary",
"detail": "把公開入口、產品、版本來源與 Gate 0 串成攻擊面圖;目標是優先看清可被利用的關聯,不先提高限制。",
"evidence": "目前可看見 8 類資產與 S4.9 版本來源卡點,但 blast radius 仍維持 0避免誤導成已完成攻防驗證。",
"next": "先完成 GitHub primary / Gitea owner evidence再讓風險路徑有可信來源。",
"locked": "source_control_mutation_authorized=falseGitHub primary switch 未授權。"
},
"evidenceFlow": {
"title": "證據流",
"mapTitle": "Monitoring → AwoooP Truth Chain → Human Gate",
"detail": "用 evidence lane 表示資料如何被收集、脫敏、審查與回寫;這比單純列文件更接近 SOC / XDR 的操作體驗。",
"evidence": "AwoooP 跨 Session 狀態已接線IwoooS 只讀鏡像與 progress guard 已有證據。",
"next": "補 reviewer 接受紀錄與 owner decision record才可能進入下一個 runtime gate。",
"locked": "active_runtime_gate_count=0沒有任何自動執行按鈕。"
}
},
"nodes": {
"productSurface": {
"title": "產品 / 網站"
},
"sourceControl": {
"title": "版本來源"
},
"kali": {
"title": "Kali 112"
},
"devHosts": {
"title": "開發主機"
},
"monitoring": {
"title": "監控 / 工具"
},
"awooopTruth": {
"title": "AwoooP 真相鏈"
},
"runtimeGate": {
"title": "Gate 0"
}
},
"layers": {
"externalSurface": {
"title": "外部資產面",
"body": "公開網站、產品入口與 VibeWork 先進入可理解範圍。"
},
"codeSupply": {
"title": "版本來源面",
"body": "GitHub primary / Gitea 遷移仍等 S4.9 證據。"
},
"hostFabric": {
"title": "主機拓樸面",
"body": "112、111、168 維持 observe-only 顯示。"
},
"evidenceOps": {
"title": "證據營運面",
"body": "監控、KM、MCP、Ansible 與 AwoooP 對齊。"
},
"gateControl": {
"title": "執行閘面",
"body": "runtime gate、掃描與修復仍全部鎖住。"
}
},
"charts": {
"contextDepth": {
"label": "關聯深度",
"body": "已把 code-to-runtime 的理解路徑壓成四段。"
},
"blastRadius": {
"label": "爆炸半徑",
"body": "未授權探測前維持 0不誤導成已驗證攻擊半徑。"
},
"evidenceFreshness": {
"label": "證據新鮮度",
"body": "目前真正卡點仍是 S4.9 owner evidence。"
}
},
"boundaryTitle": "圖譜邊界",
"boundaryIntro": "這張圖是專業可視化與理解層,不是掃描拓樸、不是自動修復、不是 GitHub / Gitea 切換授權。"
},
"gateRadar": {
"eyebrow": "執行閘雷達",
"title": "一眼看懂哪裡可前進、哪裡不能碰",

View File

@@ -9,17 +9,25 @@ import {
Activity,
AlertTriangle,
Bell,
Boxes,
CheckCircle2,
ClipboardCheck,
Cloud,
Code2,
Database,
Clock3,
FileText,
FileWarning,
GitBranch,
ListChecks,
Lock,
Network,
Radar,
Route,
SearchCheck,
Server,
ShieldCheck,
Workflow,
} from 'lucide-react'
import Link from 'next/link'
import { useTranslations } from 'next-intl'
@@ -188,6 +196,39 @@ type IwoooSImmediateVisualMeshNode = {
tone: 'steady' | 'warn' | 'locked'
}
type IwoooSTopologyAtlasMode = 'architecture' | 'topology' | 'attackSurface' | 'evidenceFlow'
type IwoooSTopologyAtlasLens = {
key: IwoooSTopologyAtlasMode
metric: string
icon: typeof ShieldCheck
tone: 'steady' | 'warn' | 'locked'
}
type IwoooSTopologyAtlasNode = {
key: string
metric: string
x: number
y: number
icon: typeof ShieldCheck
tone: 'steady' | 'warn' | 'locked'
}
type IwoooSTopologyAtlasLayer = {
key: string
metric: string
icon: typeof ShieldCheck
tone: 'steady' | 'warn' | 'locked'
}
type IwoooSTopologyAtlasChart = {
key: string
value: string
percent: number
icon: typeof ShieldCheck
tone: 'steady' | 'warn' | 'locked'
}
type IwoooSGateRadarMode = 'visible' | 'blocker' | 'review' | 'locked'
type IwoooSGateRadarLane = {
@@ -2363,6 +2404,74 @@ const iwooosImmediateVisualMeshBoundaries = [
'not_authorization=true',
] as const
const iwooosTopologyAtlasLenses: IwoooSTopologyAtlasLens[] = [
{ key: 'architecture', metric: '5層', icon: Boxes, tone: 'steady' },
{ key: 'topology', metric: '3主機', icon: Network, tone: 'warn' },
{ key: 'attackSurface', metric: '8面', icon: Route, tone: 'warn' },
{ key: 'evidenceFlow', metric: 'Gate 0', icon: Workflow, tone: 'locked' },
]
const iwooosTopologyAtlasNodes: IwoooSTopologyAtlasNode[] = [
{ key: 'productSurface', metric: '8', x: 14, y: 30, icon: Boxes, tone: 'steady' },
{ key: 'sourceControl', metric: 'S4.9', x: 35, y: 18, icon: Code2, tone: 'warn' },
{ key: 'kali', metric: '112', x: 58, y: 30, icon: Server, tone: 'warn' },
{ key: 'devHosts', metric: '111 / 168', x: 77, y: 50, icon: Network, tone: 'warn' },
{ key: 'monitoring', metric: '6', x: 37, y: 66, icon: Radar, tone: 'steady' },
{ key: 'awooopTruth', metric: '已接線', x: 62, y: 78, icon: Workflow, tone: 'steady' },
{ key: 'runtimeGate', metric: '0', x: 88, y: 24, icon: Lock, tone: 'locked' },
]
const iwooosTopologyAtlasEdges = [
['productSurface', 'sourceControl'],
['sourceControl', 'kali'],
['sourceControl', 'monitoring'],
['kali', 'devHosts'],
['monitoring', 'awooopTruth'],
['awooopTruth', 'runtimeGate'],
] as const
const iwooosTopologyAtlasCompactPositions: Record<string, { x: number; y: number }> = {
productSurface: { x: 28, y: 24 },
sourceControl: { x: 72, y: 24 },
kali: { x: 28, y: 43 },
devHosts: { x: 72, y: 43 },
monitoring: { x: 28, y: 63 },
awooopTruth: { x: 72, y: 63 },
runtimeGate: { x: 50, y: 82 },
}
const iwooosTopologyAtlasLayers: IwoooSTopologyAtlasLayer[] = [
{ key: 'externalSurface', metric: '8', icon: Cloud, tone: 'steady' },
{ key: 'codeSupply', metric: 'S4.9', icon: GitBranch, tone: 'warn' },
{ key: 'hostFabric', metric: '3', icon: Server, tone: 'warn' },
{ key: 'evidenceOps', metric: '6', icon: Database, tone: 'steady' },
{ key: 'gateControl', metric: '0', icon: Lock, tone: 'locked' },
]
const iwooosTopologyAtlasCharts: IwoooSTopologyAtlasChart[] = [
{ key: 'contextDepth', value: '4-hop', percent: 74, icon: Route, tone: 'steady' },
{ key: 'blastRadius', value: '0', percent: 0, icon: Lock, tone: 'locked' },
{ key: 'evidenceFreshness', value: 'S4.9', percent: 61, icon: ClipboardCheck, tone: 'warn' },
]
const iwooosTopologyAtlasBoundaries = [
'iwooos_topology_atlas_first_layer=true',
'iwooos_topology_atlas_lens_count=4',
'iwooos_topology_atlas_node_count=7',
'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_atlas_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',
'runtime_execution_authorized=false',
'active_runtime_gate_count=0',
'action_buttons_allowed=false',
'not_authorization=true',
] as const
const iwooosGateRadarLanes: IwoooSGateRadarLane[] = [
{ key: 'visible', metric: '8/8', score: 86, icon: ShieldCheck, tone: 'steady' },
{ key: 'blocker', metric: 'S4.9', score: 61, icon: FileWarning, tone: 'warn' },
@@ -9531,6 +9640,374 @@ function IwoooSImmediateVisualMeshBoard() {
)
}
function IwoooSTopologyAtlasBoard() {
const t = useTranslations('iwooos.topologyAtlas')
const [activeKey, setActiveKey] = useState<IwoooSTopologyAtlasMode>('architecture')
const activeLens = iwooosTopologyAtlasLenses.find(item => item.key === activeKey) ?? iwooosTopologyAtlasLenses[0]
const ActiveIcon = activeLens.icon
const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const }
const atlasCanvasRef = useRef<HTMLDivElement>(null)
const [isCompactAtlas, setIsCompactAtlas] = useState(false)
const topologyAtlasNodes = iwooosTopologyAtlasNodes.map(item => {
if (!isCompactAtlas) return item
const compactPosition = iwooosTopologyAtlasCompactPositions[item.key]
return compactPosition ? { ...item, ...compactPosition } : item
})
const nodeByKey = new Map(topologyAtlasNodes.map(item => [item.key, item]))
useEffect(() => {
const node = atlasCanvasRef.current
if (!node) return
const updateLayout = () => setIsCompactAtlas(node.getBoundingClientRect().width < 520)
updateLayout()
const observer = new ResizeObserver(updateLayout)
observer.observe(node)
return () => observer.disconnect()
}, [])
return (
<section
data-testid="iwooos-topology-atlas-board"
style={{ marginBottom: 14, maxWidth: '100%', overflow: 'hidden' }}
>
<div style={{ ...band, padding: 14, background: '#f7f8fb', borderColor: '#cfd8e3' }}>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 280px), 1fr))',
gap: 10,
alignItems: 'end',
marginBottom: 12,
}}
>
<div style={{ minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, color: '#2f4f67', fontSize: 12, fontWeight: 700 }}>
<Network size={17} color="#315f7d" />
{t('eyebrow')}
</div>
<h2 style={{ fontSize: 17, lineHeight: 1.25, margin: '7px 0 0', color: '#141413' }}>{t('title')}</h2>
<p style={{ fontSize: 12, lineHeight: 1.55, color: '#3f5b6f', margin: '6px 0 0', ...textWrap }}>
{t('subtitle')}
</p>
</div>
<div
role="tablist"
aria-label={t('tabsLabel')}
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 128px), 1fr))',
gap: 7,
minWidth: 0,
}}
>
{iwooosTopologyAtlasLenses.map(item => {
const Icon = item.icon
const selected = activeKey === item.key
return (
<button
key={item.key}
type="button"
role="tab"
aria-selected={selected}
data-testid={`iwooos-topology-atlas-mode-${item.key}`}
onClick={() => setActiveKey(item.key)}
style={{
border: selected ? `1px solid ${toneColors[item.tone]}` : '0.5px solid #d9e0e7',
borderRadius: 8,
padding: 10,
background: selected ? '#fff' : '#fbfcfd',
color: '#141413',
cursor: 'pointer',
textAlign: 'left',
minWidth: 0,
boxShadow: selected ? '0 8px 22px rgba(30, 58, 79, 0.08)' : 'none',
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
<Icon size={15} color={toneColors[item.tone]} />
<span style={{ color: toneColors[item.tone], fontSize: 12, fontWeight: 700 }}>{item.metric}</span>
</div>
<div style={{ fontSize: 12, lineHeight: 1.25, marginTop: 8, fontWeight: 700, ...textWrap }}>
{t(`lenses.${item.key}.title` as never)}
</div>
</button>
)
})}
</div>
</div>
<div
data-testid="iwooos-topology-atlas-canvas"
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 320px), 1fr))',
gap: 10,
alignItems: 'stretch',
}}
>
<div
ref={atlasCanvasRef}
style={{
position: 'relative',
minHeight: isCompactAtlas ? 430 : 342,
border: '0.5px solid #172331',
borderRadius: 8,
overflow: 'hidden',
background:
'linear-gradient(135deg, #0d141b 0%, #111b23 52%, #18232d 100%)',
color: '#f8fbfd',
}}
>
<svg
aria-hidden
viewBox="0 0 100 86"
preserveAspectRatio="none"
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', opacity: 0.95 }}
>
<defs>
<pattern id="iwooos-topology-grid" width="10" height="10" patternUnits="userSpaceOnUse">
<path d="M 10 0 L 0 0 0 10" fill="none" stroke="rgba(255,255,255,0.06)" strokeWidth="0.5" />
</pattern>
<linearGradient id="iwooos-topology-path" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stopColor="#58a27d" />
<stop offset="55%" stopColor="#d19455" />
<stop offset="100%" stopColor="#a7a6a0" />
</linearGradient>
</defs>
<rect width="100" height="86" fill="url(#iwooos-topology-grid)" />
<circle cx="58" cy="43" r="31" fill="none" stroke="rgba(255,255,255,0.08)" strokeWidth="1" />
<circle cx="58" cy="43" r="20" fill="none" stroke="rgba(255,255,255,0.12)" strokeWidth="0.8" />
{iwooosTopologyAtlasEdges.map(([fromKey, toKey]) => {
const from = nodeByKey.get(fromKey)
const to = nodeByKey.get(toKey)
if (!from || !to) return null
return (
<line
key={`${fromKey}-${toKey}`}
x1={from.x}
y1={from.y}
x2={to.x}
y2={to.y}
stroke="url(#iwooos-topology-path)"
strokeWidth="1.35"
strokeDasharray={to.tone === 'locked' ? '3 3' : undefined}
strokeLinecap="round"
opacity={to.tone === 'locked' ? 0.62 : 0.86}
/>
)
})}
</svg>
<div style={{ position: 'absolute', left: 16, top: 14, right: 16, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
<div style={{ minWidth: 0 }}>
<div style={{ fontSize: 11, color: '#b8c7d3', fontWeight: 700 }}>{t('mapLabel')}</div>
<div style={{ fontSize: 18, lineHeight: 1.15, color: '#fff', marginTop: 4, fontWeight: 700, ...textWrap }}>
{t(`lenses.${activeLens.key}.mapTitle` as never)}
</div>
</div>
<div
style={{
width: 58,
height: 58,
borderRadius: 58,
border: '1px solid rgba(255,255,255,0.22)',
display: 'grid',
placeItems: 'center',
background: `conic-gradient(${toneColors[activeLens.tone]} 0 245deg, rgba(255,255,255,0.16) 245deg 360deg)`,
boxShadow: 'inset 0 0 0 13px rgba(13,20,27,0.72)',
flex: '0 0 auto',
}}
>
<ActiveIcon size={20} color="#fffdf8" />
</div>
</div>
{topologyAtlasNodes.map(node => {
const Icon = node.icon
const active = activeKey === 'architecture' || (activeKey === 'topology' && ['kali', 'devHosts', 'monitoring'].includes(node.key)) || (activeKey === 'attackSurface' && ['productSurface', 'sourceControl', 'runtimeGate'].includes(node.key)) || (activeKey === 'evidenceFlow' && ['monitoring', 'awooopTruth', 'runtimeGate'].includes(node.key))
return (
<div
key={node.key}
data-testid={`iwooos-topology-atlas-node-${node.key}`}
style={{
position: 'absolute',
left: `${node.x}%`,
top: `${node.y}%`,
transform: 'translate(-50%, -50%)',
minWidth: 86,
maxWidth: 116,
border: `1px solid ${active ? toneColors[node.tone] : 'rgba(255,255,255,0.2)'}`,
borderRadius: 8,
padding: 8,
background: active ? 'rgba(255,255,255,0.95)' : 'rgba(255,255,255,0.78)',
color: '#141413',
boxShadow: active ? '0 12px 24px rgba(0,0,0,0.18)' : 'none',
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 7 }}>
<Icon size={15} color={toneColors[node.tone]} />
<span style={{ color: toneColors[node.tone], fontSize: 11, fontWeight: 700 }}>{node.metric}</span>
</div>
<div style={{ fontSize: 10.5, lineHeight: 1.25, marginTop: 6, color: '#2f3a40', fontWeight: 700, ...textWrap }}>
{t(`nodes.${node.key}.title` as never)}
</div>
</div>
)
})}
</div>
<div style={{ display: 'grid', gap: 8, minWidth: 0 }}>
<div
data-testid="iwooos-topology-atlas-active-panel"
style={{
border: '0.5px solid #d9e0e7',
borderRadius: 8,
background: '#fff',
padding: 12,
minWidth: 0,
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, color: toneColors[activeLens.tone], fontSize: 12, fontWeight: 700 }}>
<ActiveIcon size={16} />
{activeLens.metric}
</div>
<h3 style={{ margin: '7px 0 0', fontSize: 15, lineHeight: 1.25, color: '#141413' }}>
{t(`lenses.${activeLens.key}.title` as never)}
</h3>
<p style={{ margin: '7px 0 0', fontSize: 12, lineHeight: 1.55, color: '#41576a', ...textWrap }}>
{t(`lenses.${activeLens.key}.detail` as never)}
</p>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 130px), 1fr))',
gap: 7,
marginTop: 10,
}}
>
{(['evidence', 'next', 'locked'] as const).map(key => (
<div key={key} style={{ border: '0.5px solid #e4e9ee', borderRadius: 8, background: '#fbfcfd', padding: 9, minWidth: 0 }}>
<div style={{ fontSize: 10, color: '#6b7a86', fontWeight: 700 }}>{t(`panelLabels.${key}`)}</div>
<div style={{ marginTop: 5, fontSize: 11, lineHeight: 1.45, color: '#263640', ...textWrap }}>
{t(`lenses.${activeLens.key}.${key}` as never)}
</div>
</div>
))}
</div>
</div>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 138px), 1fr))',
gap: 8,
}}
>
{iwooosTopologyAtlasLayers.map(item => {
const Icon = item.icon
return (
<div key={item.key} style={{ border: '0.5px solid #d9e0e7', borderRadius: 8, background: '#fff', padding: 10, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
<Icon size={15} color={toneColors[item.tone]} />
<span style={{ color: toneColors[item.tone], fontSize: 12, fontWeight: 700 }}>{item.metric}</span>
</div>
<div style={{ fontSize: 11.5, lineHeight: 1.3, marginTop: 8, color: '#1f2d35', fontWeight: 700, ...textWrap }}>
{t(`layers.${item.key}.title` as never)}
</div>
<div style={{ fontSize: 10.5, lineHeight: 1.35, color: '#64737d', marginTop: 5, ...textWrap }}>
{t(`layers.${item.key}.body` as never)}
</div>
</div>
)
})}
</div>
</div>
</div>
<div
data-testid="iwooos-topology-atlas-technical-charts"
style={{
marginTop: 10,
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 220px), 1fr))',
gap: 8,
}}
>
{iwooosTopologyAtlasCharts.map(item => {
const Icon = item.icon
return (
<div key={item.key} style={{ border: '0.5px solid #d9e0e7', borderRadius: 8, background: '#fff', padding: 10, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 7, minWidth: 0 }}>
<Icon size={15} color={toneColors[item.tone]} />
<span style={{ fontSize: 11, color: '#536674', fontWeight: 700, ...textWrap }}>
{t(`charts.${item.key}.label` as never)}
</span>
</div>
<span style={{ color: toneColors[item.tone], fontSize: 12, fontWeight: 700 }}>{item.value}</span>
</div>
<div style={{ marginTop: 8, height: 8, borderRadius: 8, background: '#edf1f4', overflow: 'hidden' }}>
<div
style={{
width: `${item.percent}%`,
height: '100%',
borderRadius: 8,
background: toneColors[item.tone],
}}
/>
</div>
<p style={{ margin: '7px 0 0', fontSize: 10.5, lineHeight: 1.4, color: '#63727c', ...textWrap }}>
{t(`charts.${item.key}.body` as never)}
</p>
</div>
)
})}
</div>
<details
data-testid="iwooos-topology-atlas-boundaries"
style={{
marginTop: 10,
border: '0.5px solid #d9e0e7',
borderRadius: 8,
background: '#fff',
padding: '7px 10px',
}}
>
<summary style={{ cursor: 'pointer', fontSize: 12, fontWeight: 700, color: '#3f5b6f' }}>
{t('boundaryTitle')}
</summary>
<p style={{ fontSize: 11, color: '#3f5b6f', lineHeight: 1.45, margin: '8px 0', ...textWrap }}>
{t('boundaryIntro')}
</p>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(230px, 1fr))', gap: 6 }}>
{iwooosTopologyAtlasBoundaries.map(item => (
<code
key={item}
style={{
border: '0.5px solid #e4e9ee',
borderRadius: 8,
padding: '6px 8px',
color: '#41576a',
fontSize: 11,
lineHeight: 1.4,
background: '#f7f8fb',
overflowWrap: 'anywhere',
}}
>
{item}
</code>
))}
</div>
</details>
</div>
</section>
)
}
function IwoooSGateRadarBoard() {
const t = useTranslations('iwooos.gateRadar')
const [activeKey, setActiveKey] = useState<IwoooSGateRadarMode>('visible')
@@ -14655,6 +15132,7 @@ export default function IwoooSPage({ params }: { params: { locale: string } }) {
<IwoooSFocusDeckBoard />
<IwoooSImmediateVisualMeshBoard />
<IwoooSTopologyAtlasBoard />
<IwoooSGateRadarBoard />
<IwoooSCommandMapBoard />
<IwoooSFirstProgressUnlockPathBoard />