Merge remote-tracking branch 'origin/main' into codex/agent99-alert-control-20260710
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m36s
CD Pipeline / build-and-deploy (push) Successful in 4m58s
CD Pipeline / post-deploy-checks (push) Has been cancelled
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m36s
CD Pipeline / build-and-deploy (push) Successful in 4m58s
CD Pipeline / post-deploy-checks (push) Has been cancelled
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
|
||||
|
||||
@@ -84,12 +84,12 @@ spec:
|
||||
- name: AWOOOI_BUILD_COMMIT_SHA
|
||||
# 2026-06-29 Codex: CD rewrites this to the deployed image tag so
|
||||
# production deploy readback does not rely on a stale static snapshot.
|
||||
value: "3b1c659f14c82eb6128cb234e3aab2744c5034fa"
|
||||
value: "a049c909e270a225300b6780f079472c47ec149d"
|
||||
- name: AWOOOI_DESIRED_API_IMAGE_TAG
|
||||
# 2026-06-30 Codex: CD rewrites this alongside AWOOOI_BUILD_COMMIT_SHA.
|
||||
# Production readback compares runtime image truth against this
|
||||
# GitOps desired tag instead of doing a slow Gitea raw fetch.
|
||||
value: "3b1c659f14c82eb6128cb234e3aab2744c5034fa"
|
||||
value: "a049c909e270a225300b6780f079472c47ec149d"
|
||||
- name: DATABASE_POOL_SIZE
|
||||
# 2026-07-01 Codex: production role `awoooi` currently has a low
|
||||
# connection limit. Keep API pool conservative until DB role
|
||||
|
||||
@@ -41,7 +41,7 @@ resources:
|
||||
images:
|
||||
- name: 192.168.0.110:5000/library/api:IMAGE_TAG_PLACEHOLDER
|
||||
newName: 192.168.0.110:5000/awoooi/api
|
||||
newTag: 3b1c659f14c82eb6128cb234e3aab2744c5034fa
|
||||
newTag: a049c909e270a225300b6780f079472c47ec149d
|
||||
- name: 192.168.0.110:5000/library/web:IMAGE_TAG_PLACEHOLDER
|
||||
newName: 192.168.0.110:5000/awoooi/web
|
||||
newTag: 3b1c659f14c82eb6128cb234e3aab2744c5034fa
|
||||
newTag: a049c909e270a225300b6780f079472c47ec149d
|
||||
|
||||
Reference in New Issue
Block a user