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
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:
@@ -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 />
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user