fix(iwooos): compact security cockpit
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 1m9s
CD Pipeline / build-and-deploy (push) Successful in 5m19s
CD Pipeline / post-deploy-checks (push) Successful in 5m43s
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 1m9s
CD Pipeline / build-and-deploy (push) Successful in 5m19s
CD Pipeline / post-deploy-checks (push) Successful in 5m43s
This commit is contained in:
@@ -21829,6 +21829,7 @@
|
||||
"domainStatusLabel": "狀態",
|
||||
"p0Title": "P0 優先順序",
|
||||
"p0Intro": "以下是下一階段真正能降低即時風險的順序;每一項都需要脫敏證據與 owner acceptance,不能靠 UI 可見或一般批准跳過。",
|
||||
"p0More": "Show remaining {count} P0 items and evidence requirements",
|
||||
"requiredEvidenceLabel": "需要證據",
|
||||
"boundaryTitle": "不可假綠燈規則",
|
||||
"status": {
|
||||
@@ -21876,6 +21877,16 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "為什麼還沒有完成資安管理閉環",
|
||||
"p0Queue": "Next P0 queue",
|
||||
"toolRail": "External security tools",
|
||||
"aiLoop": "AI automation loop",
|
||||
"rootCauseDetails": "Show root causes and closure gates",
|
||||
"cockpit": {
|
||||
"findingCount": "Findings",
|
||||
"p0Count": "P0",
|
||||
"toolCount": "Tools",
|
||||
"aiStageCount": "AI stages"
|
||||
},
|
||||
"diagnosis": {
|
||||
"controlPlaneRuntimeGap": "控制面已建立,runtime 防護閉環未完成",
|
||||
"unknown": "需要重新讀回診斷狀態"
|
||||
|
||||
@@ -21829,6 +21829,7 @@
|
||||
"domainStatusLabel": "狀態",
|
||||
"p0Title": "P0 優先順序",
|
||||
"p0Intro": "以下是下一階段真正能降低即時風險的順序;每一項都需要脫敏證據與 owner acceptance,不能靠 UI 可見或一般批准跳過。",
|
||||
"p0More": "展開其餘 {count} 個 P0 與證據要求",
|
||||
"requiredEvidenceLabel": "需要證據",
|
||||
"boundaryTitle": "不可假綠燈規則",
|
||||
"status": {
|
||||
@@ -21876,6 +21877,16 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "為什麼還沒有完成資安管理閉環",
|
||||
"p0Queue": "下一步 P0 queue",
|
||||
"toolRail": "外部資安工具軌",
|
||||
"aiLoop": "AI 自動化閉環",
|
||||
"rootCauseDetails": "展開根因與 closure 條件",
|
||||
"cockpit": {
|
||||
"findingCount": "根因",
|
||||
"p0Count": "P0",
|
||||
"toolCount": "工具",
|
||||
"aiStageCount": "AI 階段"
|
||||
},
|
||||
"diagnosis": {
|
||||
"controlPlaneRuntimeGap": "控制面已建立,runtime 防護閉環未完成",
|
||||
"unknown": "需要重新讀回診斷狀態"
|
||||
|
||||
@@ -8770,6 +8770,37 @@ function IwoooSSecurityControlCoverageBoard() {
|
||||
]
|
||||
const domains = data?.domains ?? []
|
||||
const p0Actions = data?.p0_next_actions ?? []
|
||||
const orderedP0Actions = review?.priority_work_items ?? p0Actions
|
||||
const p0Preview = orderedP0Actions.slice(0, 4)
|
||||
const p0Remainder = orderedP0Actions.slice(4)
|
||||
const toolTracks = review?.security_tool_integration_matrix ?? []
|
||||
const aiLoopStages = review?.ai_automation_closure_loop ?? []
|
||||
const cockpitStats = review ? [
|
||||
{
|
||||
key: 'findingCount',
|
||||
value: String(review.root_cause_summary.length),
|
||||
icon: FileWarning,
|
||||
tone: 'warn' as const,
|
||||
},
|
||||
{
|
||||
key: 'p0Count',
|
||||
value: String(review.priority_work_items.length),
|
||||
icon: ListChecks,
|
||||
tone: 'warn' as const,
|
||||
},
|
||||
{
|
||||
key: 'toolCount',
|
||||
value: String(toolTracks.length),
|
||||
icon: SearchCheck,
|
||||
tone: 'steady' as const,
|
||||
},
|
||||
{
|
||||
key: 'aiStageCount',
|
||||
value: String(aiLoopStages.length),
|
||||
icon: Workflow,
|
||||
tone: 'steady' as const,
|
||||
},
|
||||
] : []
|
||||
|
||||
return (
|
||||
<section
|
||||
@@ -8835,48 +8866,167 @@ function IwoooSSecurityControlCoverageBoard() {
|
||||
data-testid="iwooos-security-professional-review-strip"
|
||||
style={{
|
||||
marginTop: 14,
|
||||
border: '0.5px solid #e0d8bf',
|
||||
border: '0.5px solid #1f3d35',
|
||||
borderRadius: 8,
|
||||
background: '#fff',
|
||||
background: '#101816',
|
||||
padding: 12,
|
||||
display: 'grid',
|
||||
gap: 10,
|
||||
gap: 12,
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
|
||||
<AlertTriangle size={16} color="#8a5b1f" />
|
||||
<span style={{ fontSize: 12, color: '#6a5730', fontWeight: 800, ...textWrap }}>
|
||||
<ShieldAlert size={17} color="#f0b35a" />
|
||||
<span style={{ fontSize: 12, color: '#f7f3e8', fontWeight: 800, ...textWrap }}>
|
||||
{t('review.title')}
|
||||
</span>
|
||||
</div>
|
||||
<span style={{ fontSize: 11, color: '#8a5b1f', fontWeight: 700, ...textWrap }}>
|
||||
<span style={{ fontSize: 11, color: '#f0b35a', fontWeight: 800, ...textWrap }}>
|
||||
{t(`review.diagnosis.${securityReviewDiagnosisKey(review.completion_diagnosis.diagnosis)}` as never)}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 230px), 1fr))', gap: 8 }}>
|
||||
{reviewFindings.map(finding => (
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 116px), 1fr))', gap: 8 }}>
|
||||
{cockpitStats.map(item => {
|
||||
const Icon = item.icon
|
||||
return (
|
||||
<div
|
||||
key={item.key}
|
||||
style={{
|
||||
border: '0.5px solid rgba(232, 238, 225, 0.18)',
|
||||
borderRadius: 8,
|
||||
background: 'rgba(255, 255, 255, 0.06)',
|
||||
padding: 9,
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: '#cbd8cc', fontWeight: 700, ...textWrap }}>
|
||||
{t(`review.cockpit.${item.key}` as never)}
|
||||
</span>
|
||||
<Icon size={15} color={toneColors[item.tone]} />
|
||||
</div>
|
||||
<div style={{ marginTop: 7, fontSize: 22, lineHeight: 1, color: '#f7f3e8', fontWeight: 900 }}>
|
||||
{item.value}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 260px), 1fr))', gap: 10 }}>
|
||||
<div style={{ border: '0.5px solid rgba(232, 238, 225, 0.18)', borderRadius: 8, padding: 10, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 7, color: '#cbd8cc', fontSize: 11, fontWeight: 800 }}>
|
||||
<ListChecks size={15} color="#f0b35a" />
|
||||
{t('review.p0Queue')}
|
||||
</div>
|
||||
<div style={{ marginTop: 8, display: 'grid', gap: 6 }}>
|
||||
{p0Preview.map(action => (
|
||||
<div
|
||||
key={action.priority}
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '46px minmax(0, 1fr)',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
border: '0.5px solid rgba(240, 179, 90, 0.25)',
|
||||
borderRadius: 8,
|
||||
background: 'rgba(240, 179, 90, 0.08)',
|
||||
padding: '7px 8px',
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
<span style={{ color: '#f0b35a', fontSize: 11, fontWeight: 900 }}>{action.priority}</span>
|
||||
<span style={{ color: '#f7f3e8', fontSize: 12, fontWeight: 800, ...textWrap }}>{action.title}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gap: 8, minWidth: 0 }}>
|
||||
<div style={{ border: '0.5px solid rgba(232, 238, 225, 0.18)', borderRadius: 8, padding: 10, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 7, color: '#cbd8cc', fontSize: 11, fontWeight: 800 }}>
|
||||
<SearchCheck size={15} color="#86c89a" />
|
||||
{t('review.toolRail')}
|
||||
</div>
|
||||
<div style={{ marginTop: 8, display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
||||
{toolTracks.slice(0, 8).map(tool => (
|
||||
<span
|
||||
key={tool.tool_id}
|
||||
style={{
|
||||
border: '0.5px solid rgba(134, 200, 154, 0.35)',
|
||||
borderRadius: 999,
|
||||
color: '#dff5e4',
|
||||
background: 'rgba(134, 200, 154, 0.12)',
|
||||
padding: '4px 7px',
|
||||
fontSize: 10,
|
||||
fontWeight: 800,
|
||||
...textWrap,
|
||||
}}
|
||||
>
|
||||
{tool.tool_id}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ border: '0.5px solid rgba(232, 238, 225, 0.18)', borderRadius: 8, padding: 10, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 7, color: '#cbd8cc', fontSize: 11, fontWeight: 800 }}>
|
||||
<Workflow size={15} color="#8db7f2" />
|
||||
{t('review.aiLoop')}
|
||||
</div>
|
||||
<div style={{ marginTop: 8, display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(62px, 1fr))', gap: 6 }}>
|
||||
{aiLoopStages.map(stage => (
|
||||
<div
|
||||
key={stage.stage_id}
|
||||
style={{
|
||||
border: '0.5px solid rgba(141, 183, 242, 0.28)',
|
||||
borderRadius: 8,
|
||||
color: '#e6eefc',
|
||||
background: 'rgba(141, 183, 242, 0.1)',
|
||||
padding: '6px 7px',
|
||||
fontSize: 10,
|
||||
fontWeight: 800,
|
||||
textAlign: 'center',
|
||||
...textWrap,
|
||||
}}
|
||||
>
|
||||
{stage.stage_id}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<details style={{ border: '0.5px solid rgba(232, 238, 225, 0.18)', borderRadius: 8, padding: '8px 10px' }}>
|
||||
<summary style={{ cursor: 'pointer', fontSize: 11, color: '#cbd8cc', fontWeight: 800 }}>
|
||||
{t('review.rootCauseDetails')}
|
||||
</summary>
|
||||
<div style={{ marginTop: 8, display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 230px), 1fr))', gap: 8 }}>
|
||||
{reviewFindings.map(finding => (
|
||||
<div
|
||||
key={finding.finding_id}
|
||||
style={{
|
||||
border: '0.5px solid #e7debf',
|
||||
border: '0.5px solid rgba(240, 179, 90, 0.26)',
|
||||
borderRadius: 8,
|
||||
background: '#fffaf1',
|
||||
background: 'rgba(240, 179, 90, 0.08)',
|
||||
padding: 10,
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 7, minWidth: 0 }}>
|
||||
<span style={{ fontSize: 11, color: '#8a5b1f', fontWeight: 800 }}>{finding.severity}</span>
|
||||
<span style={{ fontSize: 12, color: '#141413', fontWeight: 800, ...textWrap }}>{finding.title}</span>
|
||||
<span style={{ fontSize: 11, color: '#f0b35a', fontWeight: 800 }}>{finding.severity}</span>
|
||||
<span style={{ fontSize: 12, color: '#f7f3e8', fontWeight: 800, ...textWrap }}>{finding.title}</span>
|
||||
</div>
|
||||
<p style={{ fontSize: 11, color: '#5d5036', lineHeight: 1.45, margin: '7px 0 0', ...textWrap }}>
|
||||
<p style={{ fontSize: 11, color: '#cbd8cc', lineHeight: 1.45, margin: '7px 0 0', ...textWrap }}>
|
||||
{finding.required_closure}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -8952,18 +9102,43 @@ function IwoooSSecurityControlCoverageBoard() {
|
||||
{t('p0Intro')}
|
||||
</p>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 240px), 1fr))', gap: 8 }}>
|
||||
{p0Actions.map(action => (
|
||||
<div key={action.priority} style={{ border: '0.5px solid #e0d8bf', borderRadius: 8, background: '#fff', padding: 11, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
|
||||
<span style={{ fontSize: 11, color: '#8a5b1f', fontWeight: 800 }}>{action.priority}</span>
|
||||
<span style={{ fontSize: 12, color: '#141413', fontWeight: 700, ...textWrap }}>{action.title}</span>
|
||||
{p0Preview.map(action => (
|
||||
<div key={action.priority} style={{ border: '0.5px solid #e0d8bf', borderRadius: 8, background: '#fff', padding: 11, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
|
||||
<span style={{ fontSize: 11, color: '#8a5b1f', fontWeight: 800 }}>{action.priority}</span>
|
||||
<span style={{ fontSize: 12, color: '#141413', fontWeight: 700, ...textWrap }}>{action.title}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p style={{ fontSize: 11, color: '#5d5036', lineHeight: 1.45, margin: '7px 0 0', ...textWrap }}>
|
||||
{t('requiredEvidenceLabel')}:{action.required_evidence}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
{p0Remainder.length > 0 ? (
|
||||
<details
|
||||
style={{
|
||||
marginTop: 8,
|
||||
border: '0.5px solid #e0d8bf',
|
||||
borderRadius: 8,
|
||||
background: '#fff',
|
||||
padding: '8px 10px',
|
||||
}}
|
||||
>
|
||||
<summary style={{ cursor: 'pointer', fontSize: 12, color: '#6a5730', fontWeight: 800 }}>
|
||||
{t('p0More', { count: p0Remainder.length })}
|
||||
</summary>
|
||||
<div style={{ marginTop: 8, display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 240px), 1fr))', gap: 8 }}>
|
||||
{p0Remainder.map(action => (
|
||||
<div key={action.priority} style={{ border: '0.5px solid #e0d8bf', borderRadius: 8, background: '#fffaf1', padding: 10, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
|
||||
<span style={{ fontSize: 11, color: '#8a5b1f', fontWeight: 800 }}>{action.priority}</span>
|
||||
<span style={{ fontSize: 12, color: '#141413', fontWeight: 700, ...textWrap }}>{action.title}</span>
|
||||
</div>
|
||||
<p style={{ fontSize: 11, color: '#5d5036', lineHeight: 1.45, margin: '7px 0 0', ...textWrap }}>
|
||||
{t('requiredEvidenceLabel')}:{action.required_evidence}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</details>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<details
|
||||
|
||||
Reference in New Issue
Block a user