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": "狀態",
|
"domainStatusLabel": "狀態",
|
||||||
"p0Title": "P0 優先順序",
|
"p0Title": "P0 優先順序",
|
||||||
"p0Intro": "以下是下一階段真正能降低即時風險的順序;每一項都需要脫敏證據與 owner acceptance,不能靠 UI 可見或一般批准跳過。",
|
"p0Intro": "以下是下一階段真正能降低即時風險的順序;每一項都需要脫敏證據與 owner acceptance,不能靠 UI 可見或一般批准跳過。",
|
||||||
|
"p0More": "Show remaining {count} P0 items and evidence requirements",
|
||||||
"requiredEvidenceLabel": "需要證據",
|
"requiredEvidenceLabel": "需要證據",
|
||||||
"boundaryTitle": "不可假綠燈規則",
|
"boundaryTitle": "不可假綠燈規則",
|
||||||
"status": {
|
"status": {
|
||||||
@@ -21876,6 +21877,16 @@
|
|||||||
},
|
},
|
||||||
"review": {
|
"review": {
|
||||||
"title": "為什麼還沒有完成資安管理閉環",
|
"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": {
|
"diagnosis": {
|
||||||
"controlPlaneRuntimeGap": "控制面已建立,runtime 防護閉環未完成",
|
"controlPlaneRuntimeGap": "控制面已建立,runtime 防護閉環未完成",
|
||||||
"unknown": "需要重新讀回診斷狀態"
|
"unknown": "需要重新讀回診斷狀態"
|
||||||
|
|||||||
@@ -21829,6 +21829,7 @@
|
|||||||
"domainStatusLabel": "狀態",
|
"domainStatusLabel": "狀態",
|
||||||
"p0Title": "P0 優先順序",
|
"p0Title": "P0 優先順序",
|
||||||
"p0Intro": "以下是下一階段真正能降低即時風險的順序;每一項都需要脫敏證據與 owner acceptance,不能靠 UI 可見或一般批准跳過。",
|
"p0Intro": "以下是下一階段真正能降低即時風險的順序;每一項都需要脫敏證據與 owner acceptance,不能靠 UI 可見或一般批准跳過。",
|
||||||
|
"p0More": "展開其餘 {count} 個 P0 與證據要求",
|
||||||
"requiredEvidenceLabel": "需要證據",
|
"requiredEvidenceLabel": "需要證據",
|
||||||
"boundaryTitle": "不可假綠燈規則",
|
"boundaryTitle": "不可假綠燈規則",
|
||||||
"status": {
|
"status": {
|
||||||
@@ -21876,6 +21877,16 @@
|
|||||||
},
|
},
|
||||||
"review": {
|
"review": {
|
||||||
"title": "為什麼還沒有完成資安管理閉環",
|
"title": "為什麼還沒有完成資安管理閉環",
|
||||||
|
"p0Queue": "下一步 P0 queue",
|
||||||
|
"toolRail": "外部資安工具軌",
|
||||||
|
"aiLoop": "AI 自動化閉環",
|
||||||
|
"rootCauseDetails": "展開根因與 closure 條件",
|
||||||
|
"cockpit": {
|
||||||
|
"findingCount": "根因",
|
||||||
|
"p0Count": "P0",
|
||||||
|
"toolCount": "工具",
|
||||||
|
"aiStageCount": "AI 階段"
|
||||||
|
},
|
||||||
"diagnosis": {
|
"diagnosis": {
|
||||||
"controlPlaneRuntimeGap": "控制面已建立,runtime 防護閉環未完成",
|
"controlPlaneRuntimeGap": "控制面已建立,runtime 防護閉環未完成",
|
||||||
"unknown": "需要重新讀回診斷狀態"
|
"unknown": "需要重新讀回診斷狀態"
|
||||||
|
|||||||
@@ -8770,6 +8770,37 @@ function IwoooSSecurityControlCoverageBoard() {
|
|||||||
]
|
]
|
||||||
const domains = data?.domains ?? []
|
const domains = data?.domains ?? []
|
||||||
const p0Actions = data?.p0_next_actions ?? []
|
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 (
|
return (
|
||||||
<section
|
<section
|
||||||
@@ -8835,48 +8866,167 @@ function IwoooSSecurityControlCoverageBoard() {
|
|||||||
data-testid="iwooos-security-professional-review-strip"
|
data-testid="iwooos-security-professional-review-strip"
|
||||||
style={{
|
style={{
|
||||||
marginTop: 14,
|
marginTop: 14,
|
||||||
border: '0.5px solid #e0d8bf',
|
border: '0.5px solid #1f3d35',
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
background: '#fff',
|
background: '#101816',
|
||||||
padding: 12,
|
padding: 12,
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gap: 10,
|
gap: 12,
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
|
||||||
<AlertTriangle size={16} color="#8a5b1f" />
|
<ShieldAlert size={17} color="#f0b35a" />
|
||||||
<span style={{ fontSize: 12, color: '#6a5730', fontWeight: 800, ...textWrap }}>
|
<span style={{ fontSize: 12, color: '#f7f3e8', fontWeight: 800, ...textWrap }}>
|
||||||
{t('review.title')}
|
{t('review.title')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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)}
|
{t(`review.diagnosis.${securityReviewDiagnosisKey(review.completion_diagnosis.diagnosis)}` as never)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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
|
<div
|
||||||
key={finding.finding_id}
|
key={finding.finding_id}
|
||||||
style={{
|
style={{
|
||||||
border: '0.5px solid #e7debf',
|
border: '0.5px solid rgba(240, 179, 90, 0.26)',
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
background: '#fffaf1',
|
background: 'rgba(240, 179, 90, 0.08)',
|
||||||
padding: 10,
|
padding: 10,
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 7, 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: 11, color: '#f0b35a', fontWeight: 800 }}>{finding.severity}</span>
|
||||||
<span style={{ fontSize: 12, color: '#141413', fontWeight: 800, ...textWrap }}>{finding.title}</span>
|
<span style={{ fontSize: 12, color: '#f7f3e8', fontWeight: 800, ...textWrap }}>{finding.title}</span>
|
||||||
</div>
|
</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}
|
{finding.required_closure}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</details>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
@@ -8952,18 +9102,43 @@ function IwoooSSecurityControlCoverageBoard() {
|
|||||||
{t('p0Intro')}
|
{t('p0Intro')}
|
||||||
</p>
|
</p>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 240px), 1fr))', gap: 8 }}>
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 240px), 1fr))', gap: 8 }}>
|
||||||
{p0Actions.map(action => (
|
{p0Preview.map(action => (
|
||||||
<div key={action.priority} style={{ border: '0.5px solid #e0d8bf', borderRadius: 8, background: '#fff', padding: 11, minWidth: 0 }}>
|
<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 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
|
||||||
<span style={{ fontSize: 11, color: '#8a5b1f', fontWeight: 800 }}>{action.priority}</span>
|
<span style={{ fontSize: 11, color: '#8a5b1f', fontWeight: 800 }}>{action.priority}</span>
|
||||||
<span style={{ fontSize: 12, color: '#141413', fontWeight: 700, ...textWrap }}>{action.title}</span>
|
<span style={{ fontSize: 12, color: '#141413', fontWeight: 700, ...textWrap }}>{action.title}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p style={{ fontSize: 11, color: '#5d5036', lineHeight: 1.45, margin: '7px 0 0', ...textWrap }}>
|
))}
|
||||||
{t('requiredEvidenceLabel')}:{action.required_evidence}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
<details
|
<details
|
||||||
|
|||||||
Reference in New Issue
Block a user