fix(iwooos): add security tool closure readback
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 3m55s
CD Pipeline / build-and-deploy (push) Has started running
CD Pipeline / post-deploy-checks (push) Has been cancelled

This commit is contained in:
ogt
2026-07-10 16:20:21 +08:00
parent d6e69b1a90
commit e513318e68
7 changed files with 1216 additions and 0 deletions

View File

@@ -46,6 +46,8 @@ import {
type IwoooSRuntimeSecurityReadbackResponse,
type IwoooSSecurityControlCoverageDomain,
type IwoooSSecurityControlCoverageResponse,
type IwoooSSecurityToolClosureReadbackResponse,
type IwoooSSecurityToolClosureTrack,
type IwoooSWazuhLiveMetadataGateItem,
type IwoooSWazuhLiveMetadataGateResponse,
type IwoooSWazuhAllowlistedCheckModeDryRunResponse,
@@ -518,6 +520,13 @@ type IwoooSManagerCockpitMetric = {
tone: 'steady' | 'warn' | 'locked'
}
type IwoooSSecurityToolClosureMetric = {
key: string
value: string
icon: typeof ShieldCheck
tone: 'steady' | 'warn' | 'locked'
}
type IwoooSImmediateVisualMeshNode = {
key: string
metric: string
@@ -16248,6 +16257,301 @@ function IwoooSManagerCockpit() {
)
}
function securityToolClosureTone(track: IwoooSSecurityToolClosureTrack): 'steady' | 'warn' | 'locked' {
if (track.missing_signal_count === 0) return 'steady'
if (track.ready_signal_count > 0) return 'warn'
return 'locked'
}
function IwoooSSecurityToolClosureBoard() {
const t = useTranslations('iwooos.securityToolClosure')
const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const }
const [data, setData] = useState<IwoooSSecurityToolClosureReadbackResponse | null>(null)
const [loading, setLoading] = useState(true)
const [failed, setFailed] = useState(false)
useEffect(() => {
let mounted = true
async function fetchToolClosure() {
setLoading(true)
setFailed(false)
try {
const payload = await apiClient.getIwoooSSecurityToolClosureReadback()
if (mounted) setData(payload)
} catch {
if (mounted) {
setData(null)
setFailed(true)
}
} finally {
if (mounted) setLoading(false)
}
}
fetchToolClosure()
return () => {
mounted = false
}
}, [])
const summary = data?.summary
const statusKey = loading ? 'loading' : failed || !data ? 'failed' : 'ready'
const tracks = data?.tool_tracks ?? []
const queue = data?.next_executable_queue ?? []
const metrics: IwoooSSecurityToolClosureMetric[] = [
{
key: 'closure',
value: summary ? `${summary.tool_closure_percent}%` : '...',
icon: ShieldCheck,
tone: summary && summary.tool_closure_percent > 0 ? 'warn' : 'locked',
},
{
key: 'tracks',
value: summary ? `${summary.partial_tool_track_count}/${summary.tool_track_count}` : '...',
icon: SearchCheck,
tone: summary && summary.partial_tool_track_count > 0 ? 'warn' : 'locked',
},
{
key: 'wazuh',
value: summary ? `${summary.wazuh_manager_registry_accepted_count}/${summary.wazuh_expected_host_scope_count}` : '...',
icon: Radar,
tone: summary && summary.wazuh_registry_complete_count > 0 ? 'steady' : 'warn',
},
{
key: 'alerts',
value: summary ? `${summary.alert_receipt_accepted_count}/${summary.alert_receipt_observed_count}` : '...',
icon: Bell,
tone: summary && summary.alert_receipt_accepted_count > 0 ? 'steady' : 'warn',
},
{
key: 'runtime',
value: summary ? String(summary.runtime_gate_count) : '...',
icon: Lock,
tone: 'locked',
},
]
return (
<section
data-testid="iwooos-security-tool-closure-board"
style={{ marginBottom: 14, maxWidth: '100%', overflow: 'hidden' }}
>
<div
style={{
...band,
padding: 14,
display: 'grid',
gap: 12,
background: '#f7faf8',
borderColor: '#cdded2',
}}
>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 260px), 1fr))',
gap: 10,
alignItems: 'stretch',
}}
>
<div style={{ minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, color: '#315f43', fontSize: 11, fontWeight: 800 }}>
<SearchCheck size={16} />
<span>{t('eyebrow')}</span>
</div>
<h2 style={{ margin: '6px 0 0', color: '#101513', fontSize: 20, lineHeight: 1.1, ...textWrap }}>
{t('title')}
</h2>
<p style={{ margin: '6px 0 0', color: '#4e5c52', fontSize: 12, lineHeight: 1.45, ...textWrap }}>
{t('subtitle')}
</p>
</div>
<div style={{ border: '0.5px solid #d8e5db', borderRadius: 8, background: '#fff', padding: 11, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
<span style={{ color: '#64746a', fontSize: 10, fontWeight: 800 }}>{t('statusLabel')}</span>
<ToneDot tone={statusKey === 'ready' ? 'warn' : 'locked'} />
</div>
<div style={{ marginTop: 7, color: statusKey === 'ready' ? '#9a5d1d' : '#7b2b2b', fontSize: 15, fontWeight: 800, lineHeight: 1.2, ...textWrap }}>
{t(`status.${statusKey}` as never)}
</div>
<div style={{ marginTop: 7, color: '#64746a', fontSize: 11, lineHeight: 1.35, ...textWrap }}>
{summary ? t('statusDetail', { ready: summary.total_ready_signal_count, required: summary.total_required_signal_count }) : t('statusDetailFallback')}
</div>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 120px), 1fr))', gap: 8 }}>
{metrics.map(item => {
const Icon = item.icon
return (
<div
key={item.key}
data-testid={`iwooos-security-tool-closure-metric-${item.key}`}
style={{
border: `0.5px solid ${item.tone === 'steady' ? '#cddfd5' : item.tone === 'warn' ? '#e2cbb4' : '#d9d6ce'}`,
borderRadius: 8,
background: item.tone === 'steady' ? '#f4fbf6' : item.tone === 'warn' ? '#fff8f1' : '#f8f7f2',
padding: 10,
minWidth: 0,
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 7 }}>
<span style={{ color: '#5f675f', fontSize: 10, fontWeight: 800, ...textWrap }}>
{t(`metrics.${item.key}` as never)}
</span>
<Icon size={15} color={toneColors[item.tone]} />
</div>
<div style={{ marginTop: 8, color: toneColors[item.tone], fontSize: 23, lineHeight: 1, fontWeight: 900 }}>
{item.value}
</div>
</div>
)
})}
</div>
<div
data-testid="iwooos-security-tool-closure-tracks"
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 210px), 1fr))',
gap: 8,
}}
>
{tracks.map(track => {
const tone = securityToolClosureTone(track)
const width = `${Math.min(Math.max(track.closure_percent, 0), 100)}%`
return (
<div
key={track.track_id}
data-testid={`iwooos-security-tool-closure-track-${track.track_id}`}
style={{
border: `0.5px solid ${tone === 'steady' ? '#cddfd5' : tone === 'warn' ? '#e2cbb4' : '#d9d6ce'}`,
borderRadius: 8,
background: '#fff',
padding: 10,
minWidth: 0,
display: 'grid',
gap: 8,
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
<div style={{ minWidth: 0 }}>
<div style={{ color: toneColors[tone], fontSize: 10, fontWeight: 900 }}>{track.priority}</div>
<div style={{ color: '#141413', fontSize: 12, fontWeight: 900, lineHeight: 1.2, ...textWrap }}>
{t(`tracks.${track.track_id}.label` as never)}
</div>
</div>
<ToneDot tone={tone} />
</div>
<div style={{ display: 'grid', gap: 5 }}>
<div style={{ height: 7, borderRadius: 999, background: '#edf1ee', overflow: 'hidden' }}>
<div style={{ width, height: '100%', background: toneColors[tone], borderRadius: 999 }} />
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, color: '#66706a', fontSize: 10, fontWeight: 800 }}>
<span>{track.ready_signal_count}/{track.required_signal_count}</span>
<span>{t(`states.${track.closure_state}` as never)}</span>
</div>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 5 }}>
{track.tool_ids.slice(0, 4).map(tool => (
<code
key={tool}
style={{
border: '0.5px solid #dce7df',
borderRadius: 999,
background: '#f6faf7',
color: '#355f43',
padding: '3px 6px',
fontSize: 9.5,
fontWeight: 800,
...textWrap,
}}
>
{tool}
</code>
))}
</div>
<div style={{ color: '#526058', fontSize: 10.5, lineHeight: 1.35, ...textWrap }}>
{t(`tracks.${track.track_id}.next` as never)}
</div>
</div>
)
})}
</div>
<div
data-testid="iwooos-security-tool-closure-next-queue"
style={{
border: '0.5px solid #d6e1d9',
borderRadius: 8,
background: '#fff',
padding: 10,
display: 'grid',
gap: 8,
minWidth: 0,
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 7, color: '#315f43', fontSize: 10, fontWeight: 900 }}>
<ListChecks size={14} />
<span>{t('queueLabel')}</span>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 190px), 1fr))', gap: 8 }}>
{queue.map(item => (
<div
key={`${item.queue_id}-${item.track_id}`}
style={{
border: '0.5px solid #e2dccb',
borderRadius: 8,
background: '#fffaf1',
padding: 9,
minWidth: 0,
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 7 }}>
<span style={{ color: '#9a5d1d', fontSize: 11, fontWeight: 900 }}>{item.queue_id}</span>
<Lock size={13} color={toneColors.locked} />
</div>
<div style={{ marginTop: 6, color: '#141413', fontSize: 12, fontWeight: 900, lineHeight: 1.2, ...textWrap }}>
{t(`queue.${item.queue_id}.title` as never)}
</div>
<div style={{ marginTop: 5, color: '#5f5b52', fontSize: 10.5, lineHeight: 1.35, ...textWrap }}>
{t(`queue.${item.queue_id}.next` as never)}
</div>
</div>
))}
</div>
</div>
<details style={{ border: '0.5px solid #d6e1d9', borderRadius: 8, background: '#fff', padding: '8px 10px' }}>
<summary style={{ cursor: 'pointer', color: '#64746a', fontSize: 10.5, fontWeight: 900 }}>
{t('boundaryTitle')}
</summary>
<div style={{ marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: 6 }}>
{(data?.boundary_markers ?? []).slice(0, 10).map(marker => (
<code
key={marker}
style={{
border: '0.5px solid #dce7df',
borderRadius: 8,
background: '#f6faf7',
color: '#355f43',
padding: '4px 6px',
fontSize: 10,
...textWrap,
}}
>
{marker}
</code>
))}
</div>
</details>
</div>
</section>
)
}
function IwoooSCommandRail() {
const t = useTranslations('iwooos.commandRail')
@@ -24200,6 +24504,7 @@ export default function IwoooSPage({ params }: { params: { locale: string } }) {
<IwoooSWorkspaceSwitcher active={workspaceView} onChange={setWorkspaceView} />
<IwoooSManagerCockpit />
<IwoooSSecurityToolClosureBoard />
<IwoooSWorkspacePanel active={workspaceView === 'overview'}>
<IwoooSCommandRail />

View File

@@ -742,6 +742,87 @@ export interface IwoooSSecurityControlCoverageResponse {
source_refs: string[]
}
export interface IwoooSSecurityToolClosureTrack {
track_id:
| 'wazuh_detection_response'
| 'supply_chain_sbom_scan'
| 'source_control_scorecard'
| 'k8s_policy_attestation'
| 'runtime_threat_detection'
| 'web_api_dast'
| 'normalized_detection_schema'
| 'ai_agent_controlled_apply'
priority: 'P0' | 'P1'
label: string
tool_ids: string[]
closure_state:
| 'readback_ready_runtime_gate_still_closed'
| 'partial_readback_missing_verifier'
| 'required_not_closed'
closure_percent: number
ready_signal_count: number
required_signal_count: number
missing_signal_count: number
runtime_gate_open: boolean
active_response_authorized: boolean
host_write_authorized: boolean
secret_value_collection_allowed: boolean
next_executable_action: string
required_verifier: string
source_refs: string[]
}
export interface IwoooSSecurityToolClosureQueueItem {
queue_id: string
track_id: IwoooSSecurityToolClosureTrack['track_id']
state: string
next_action: string
ready_signal_count?: number
runtime_gate_open: boolean
}
export interface IwoooSSecurityToolClosureReadbackResponse {
schema_version: 'iwooos_security_tool_closure_readback_v1'
status: string
mode: string
summary: {
source_readback_count: number
tool_track_count: number
p0_tool_track_count: number
closed_tool_track_count: number
partial_tool_track_count: number
missing_tool_track_count: number
total_ready_signal_count: number
total_required_signal_count: number
tool_closure_percent: number
wazuh_expected_host_scope_count: number
wazuh_manager_registry_accepted_count: number
wazuh_registry_complete_count: number
wazuh_fim_vulnerability_alert_closed_count: number
alert_receipt_observed_count: number
alert_receipt_accepted_count: number
alert_receipt_source_status: string
controlled_apply_preflight_ready_count: number
owner_review_packet_accepted_count: number
allowlisted_dry_run_result_ref_count: number
supply_chain_scan_closed_count: number
runtime_detection_closed_count: number
web_api_dast_closed_count: number
normalized_detection_schema_closed_count: number
runtime_gate_count: number
host_write_authorized_count: number
active_response_authorized_count: number
secret_value_collection_allowed_count: number
}
tool_tracks: IwoooSSecurityToolClosureTrack[]
next_executable_queue: IwoooSSecurityToolClosureQueueItem[]
acceptance_definition: string[]
boundary_markers: string[]
boundaries: Record<string, boolean>
no_false_green_rules: string[]
source_refs: string[]
}
export interface IwoooSHighValueConfigControlCoverageCategory {
category_id: string
label: string
@@ -945,6 +1026,11 @@ export const apiClient = {
return handleResponse<IwoooSSecurityControlCoverageResponse>(res)
},
async getIwoooSSecurityToolClosureReadback() {
const res = await fetch(`${API_BASE_URL}/iwooos/security-tool-closure-readback`, { cache: 'no-store' })
return handleResponse<IwoooSSecurityToolClosureReadbackResponse>(res)
},
async getIwoooSHighValueConfigControlCoverage() {
const res = await fetch(`${API_BASE_URL}/iwooos/high-value-config-control-coverage`, { cache: 'no-store' })
return handleResponse<IwoooSHighValueConfigControlCoverageResponse>(res)