feat(iwooos): expose wazuh managed host coverage readback
Some checks failed
Code Review / ai-code-review (push) Successful in 17s
CD Pipeline / tests (push) Successful in 1m39s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-27 14:43:09 +08:00
parent 9dffbf8100
commit 4ed96a83a5
9 changed files with 666 additions and 11 deletions

View File

@@ -44,6 +44,7 @@ import {
type IwoooSSecurityControlCoverageResponse,
type IwoooSWazuhLiveMetadataGateItem,
type IwoooSWazuhLiveMetadataGateResponse,
type IwoooSWazuhManagedHostCoverageResponse,
type IwoooSWazuhOwnerEvidencePreflightItem,
type IwoooSWazuhOwnerEvidencePreflightResponse,
} from '@/lib/api-client'
@@ -2433,13 +2434,6 @@ const wazuhOwnerEvidencePreflightBoundaries = [
'not_authorization=true',
] as const
const wazuhManagedHostCoverageSummary = [
{ key: 'scope', value: '6', icon: Server, tone: 'warn' },
{ key: 'directActive', value: '2', icon: Activity, tone: 'warn' },
{ key: 'coverageGap', value: '4', icon: FileWarning, tone: 'locked' },
{ key: 'registry', value: '0', icon: Lock, tone: 'locked' },
] as const
const wazuhManagedHostCoverageItems: WazuhManagedHostCoverageItem[] = [
{ key: 'registryTruth', check: 'HC-1', state: '接受 0', icon: Lock, tone: 'locked' },
{ key: 'coreTransport', check: 'HC-2', state: '2 有連線', icon: Activity, tone: 'warn' },
@@ -9481,6 +9475,74 @@ function IwoooSWazuhOwnerEvidencePreflightBoard() {
function IwoooSWazuhManagedHostCoverageBoard() {
const t = useTranslations('iwooos.wazuhManagedHostCoverage')
const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const }
const [data, setData] = useState<IwoooSWazuhManagedHostCoverageResponse | null>(null)
const [loading, setLoading] = useState(true)
const [failed, setFailed] = useState(false)
useEffect(() => {
let mounted = true
async function loadCoverage() {
setLoading(true)
setFailed(false)
try {
const payload = await apiClient.getIwoooSWazuhManagedHostCoverage()
if (mounted) {
setData(payload)
}
} catch {
if (mounted) {
setData(null)
setFailed(true)
}
} finally {
if (mounted) {
setLoading(false)
}
}
}
loadCoverage()
return () => {
mounted = false
}
}, [])
const summary = data?.summary
const summaryItems = [
{
key: 'scope',
value: summary ? String(summary.expected_host_scope_count) : loading ? '...' : '6',
icon: Server,
tone: 'warn',
},
{
key: 'directActive',
value: summary ? String(summary.direct_agent_active_observed_count) : loading ? '...' : '2',
icon: Activity,
tone: 'warn',
},
{
key: 'coverageGap',
value: summary ? String(summary.manager_registry_gap_count) : loading ? '...' : '6',
icon: FileWarning,
tone: 'locked',
},
{
key: 'registry',
value: summary ? String(summary.manager_registry_accepted_count) : loading ? '...' : '0',
icon: Lock,
tone: 'locked',
},
] as const
const boundaryMarkers = data?.boundary_markers?.length
? data.boundary_markers
: loading
? [t('loadingBoundary')]
: wazuhManagedHostCoverageBoundaries
const hostMatrix = data?.host_scope_matrix ?? []
const statusText = loading ? t('status.loading') : failed ? t('status.failed') : t('status.ready')
const statusTone: 'steady' | 'warn' | 'locked' = loading || failed ? 'warn' : 'locked'
return (
<section
@@ -9498,10 +9560,14 @@ function IwoooSWazuhManagedHostCoverageBoard() {
<p style={{ fontSize: 12, color: '#655638', margin: '6px 0 0', lineHeight: 1.55, ...textWrap }}>
{t('subtitle')}
</p>
<div style={{ marginTop: 8, display: 'inline-flex', alignItems: 'center', gap: 8, color: toneColors[statusTone], fontSize: 12, fontWeight: 700, ...textWrap }}>
<ToneDot tone={statusTone} />
{statusText}
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(126px, 1fr))', gap: 8 }}>
{wazuhManagedHostCoverageSummary.map(item => {
{summaryItems.map(item => {
const Icon = item.icon
return (
<div key={item.key} style={{ border: '0.5px solid #ded6c5', borderRadius: 8, padding: 12, background: '#fff' }}>
@@ -9568,6 +9634,69 @@ function IwoooSWazuhManagedHostCoverageBoard() {
})}
</div>
<div
data-testid="iwooos-wazuh-managed-host-coverage-matrix"
style={{
marginTop: 14,
border: '0.5px solid #ded6c5',
borderRadius: 8,
background: '#fff',
padding: 12,
minWidth: 0,
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, flexWrap: 'wrap' }}>
<div style={{ color: '#6b5631', fontSize: 12, fontWeight: 700 }}>{t('matrixTitle')}</div>
<div style={{ color: '#655638', fontSize: 11, fontWeight: 700, ...textWrap }}>
{t('matrixSubtitle')}
</div>
</div>
<div
style={{
marginTop: 10,
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 230px), 1fr))',
gap: 8,
}}
>
{(hostMatrix.length ? hostMatrix : []).map(item => (
<div
key={item.node_id}
style={{
border: '0.5px solid #ded6c5',
borderRadius: 8,
background: item.manager_registry_accepted ? '#f7faf5' : '#fbfaf6',
padding: 10,
minWidth: 0,
...textWrap,
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
<code style={{ fontSize: 11, color: '#6b5631', fontWeight: 700, overflowWrap: 'anywhere' }}>{item.node_id}</code>
<span style={{ color: toneColors.locked, fontSize: 11, fontWeight: 700 }}>
{t('matrixAcceptedLabel')}0
</span>
</div>
<div style={{ fontSize: 12, color: '#141413', fontWeight: 700, marginTop: 8 }}>{item.role}</div>
<p style={{ fontSize: 11, color: '#655638', lineHeight: 1.45, margin: '6px 0 0', ...textWrap }}>
{item.readback_status_label}
</p>
<p style={{ fontSize: 11, color: '#7a5d2f', lineHeight: 1.45, margin: '6px 0 0', ...textWrap }}>
{t('matrixNextGateLabel')}{item.next_gate_label}
</p>
<code style={{ display: 'block', marginTop: 6, fontSize: 10.5, color: '#655638', overflowWrap: 'anywhere' }}>
{item.next_gate}
</code>
</div>
))}
{!hostMatrix.length && (
<div style={{ border: '0.5px solid #ded6c5', borderRadius: 8, background: '#fbfaf6', padding: 10, color: '#655638', fontSize: 12 }}>
{loading ? t('matrixLoading') : t('matrixFallback')}
</div>
)}
</div>
</div>
<details
data-testid="iwooos-wazuh-managed-host-coverage-boundaries"
style={{
@@ -9585,7 +9714,7 @@ function IwoooSWazuhManagedHostCoverageBoard() {
{t('boundaryIntro')}
</p>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(230px, 1fr))', gap: 6 }}>
{wazuhManagedHostCoverageBoundaries.map(item => (
{boundaryMarkers.map(item => (
<code
key={item}
style={{

View File

@@ -268,6 +268,56 @@ export interface IwoooSWazuhOwnerEvidencePreflightResponse {
no_false_green_rules: string[]
}
export interface IwoooSWazuhManagedHostCoverageHost {
node_id: string
role: string
readback_status: string
readback_status_label: string
next_gate: string
next_gate_label: string
manager_registry_accepted: boolean
}
export interface IwoooSWazuhManagedHostCoverageResponse {
schema_version: 'iwooos_wazuh_managed_host_coverage_readback_v1'
status: string
mode: string
source_refs: string[]
summary: {
expected_host_scope_count: number
manager_service_active_observed_count: number
manager_api_unauthenticated_response_count: number
manager_transport_established_connection_count: number
direct_agent_active_observed_count: number
direct_agent_transport_observed_count: number
direct_agent_missing_or_no_transport_count: number
ssh_readback_blocked_count: number
manager_registry_accepted_count: number
manager_registry_gap_count: number
dashboard_api_degraded_observed_count: number
live_metadata_env_enabled_count: number
active_response_authorized_count: number
host_write_authorized_count: number
agent_reenroll_authorized_count: number
agent_restart_authorized_count: number
runtime_gate_count: number
host_scope_matrix_count: number
required_evidence_before_green_count: number
required_evidence_accepted_count: number
}
host_scope_matrix: IwoooSWazuhManagedHostCoverageHost[]
required_evidence_before_green: Array<{
evidence_id: string
accepted: boolean
}>
operator_interpretation: string[]
forbidden_completion_claims: string[]
forbidden_actions: string[]
boundary_markers: string[]
boundaries: Record<string, boolean>
no_false_green_rules: string[]
}
export interface IwoooSSecurityControlCoverageDomain {
domain_id:
| 'high_value_asset_control'
@@ -508,6 +558,11 @@ export const apiClient = {
return handleResponse<IwoooSWazuhOwnerEvidencePreflightResponse>(res)
},
async getIwoooSWazuhManagedHostCoverage() {
const res = await fetch(`${API_BASE_URL}/iwooos/wazuh-managed-host-coverage`, { cache: 'no-store' })
return handleResponse<IwoooSWazuhManagedHostCoverageResponse>(res)
},
async getIwoooSSecurityControlCoverage() {
const res = await fetch(`${API_BASE_URL}/iwooos/security-control-coverage`, { cache: 'no-store' })
return handleResponse<IwoooSSecurityControlCoverageResponse>(res)