feat(iwooos): expose security source health
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
Bot,
|
||||
Boxes,
|
||||
CheckCircle2,
|
||||
Database,
|
||||
RefreshCw,
|
||||
Radar,
|
||||
ShieldAlert,
|
||||
@@ -41,6 +42,7 @@ type Copy = {
|
||||
priority: string;
|
||||
audit: string;
|
||||
aiTrace: string;
|
||||
sources: string;
|
||||
sameRunMissing: string;
|
||||
unavailable: string;
|
||||
refresh: string;
|
||||
@@ -65,6 +67,7 @@ const COPY: Record<"zh" | "en", Copy> = {
|
||||
priority: "優先處理",
|
||||
audit: "稽核軌跡",
|
||||
aiTrace: "AI 軌跡",
|
||||
sources: "資料源",
|
||||
sameRunMissing: "尚無同 run 閉環證據",
|
||||
unavailable: "Live readback 無法取得",
|
||||
refresh: "重新整理資安控制平面",
|
||||
@@ -87,6 +90,7 @@ const COPY: Record<"zh" | "en", Copy> = {
|
||||
priority: "Priorities",
|
||||
audit: "Audit trail",
|
||||
aiTrace: "AI trace",
|
||||
sources: "Sources",
|
||||
sameRunMissing: "No same-run closure evidence",
|
||||
unavailable: "Live readback unavailable",
|
||||
refresh: "Refresh security control plane",
|
||||
@@ -388,7 +392,7 @@ export function SecurityAssetControlPlaneCockpit({
|
||||
<div className="grid gap-3 lg:grid-cols-[1fr_1.35fr]">
|
||||
<div className="border border-[#d8d3c7] bg-white p-3">
|
||||
<div className="mb-2 text-[11px] font-semibold text-[#4f4d47]">{copy.audit}</div>
|
||||
<div className="grid grid-cols-3 gap-2 text-center">
|
||||
<div className="grid grid-cols-4 gap-2 text-center">
|
||||
<div>
|
||||
<div className="font-mono text-sm font-semibold text-[#141413]">
|
||||
{payload.security_audit.automation_operation_count_24h}
|
||||
@@ -407,7 +411,30 @@ export function SecurityAssetControlPlaneCockpit({
|
||||
</div>
|
||||
<div className="mt-1 text-[9px] text-[#77736a]">{copy.aiTrace}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-mono text-sm font-semibold text-[#141413]">
|
||||
{payload.summary.ready_source_count}/{payload.summary.source_count}
|
||||
</div>
|
||||
<div className="mt-1 text-[9px] text-[#77736a]">{copy.sources}</div>
|
||||
</div>
|
||||
</div>
|
||||
{payload.summary.unavailable_source_count > 0 ? (
|
||||
<div className="mt-3 flex flex-wrap gap-1 border-t border-[#ebe8df] pt-2">
|
||||
{payload.source_health
|
||||
.filter((source) => source.status !== "ready")
|
||||
.slice(0, 4)
|
||||
.map((source) => (
|
||||
<span
|
||||
key={source.source_id}
|
||||
className="inline-flex min-w-0 items-center gap-1 border border-[#e2a29b] bg-[#fff2f0] px-1.5 py-0.5 font-mono text-[9px] text-[#9f2f25]"
|
||||
title={source.reason_code ?? source.source_id}
|
||||
>
|
||||
<Database className="h-3 w-3 shrink-0" aria-hidden="true" />
|
||||
<span className="max-w-32 truncate">{source.source_id}</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{!payload.ai_automation.same_run_closed_loop_proven ? (
|
||||
<div className="mt-3 flex items-center gap-1.5 border-t border-[#ebe8df] pt-2 text-[10px] font-medium text-[#9f2f25]">
|
||||
<TriangleAlert className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />
|
||||
|
||||
@@ -14,6 +14,9 @@ function payload(): SecurityAssetControlPlanePayload {
|
||||
status: "ready",
|
||||
source_status: "live_database_aggregate",
|
||||
summary: {
|
||||
source_count: 9,
|
||||
ready_source_count: 9,
|
||||
unavailable_source_count: 0,
|
||||
managed_asset_count: 24,
|
||||
expected_asset_type_count: 28,
|
||||
present_asset_type_count: 20,
|
||||
@@ -74,6 +77,9 @@ function payload(): SecurityAssetControlPlanePayload {
|
||||
accepted_ai_trace_count_24h: 2,
|
||||
immutable_external_audit_store_evidenced: false,
|
||||
},
|
||||
source_health: [
|
||||
{ source_id: "asset_inventory", status: "ready", reason_code: null },
|
||||
],
|
||||
work_items: [
|
||||
{
|
||||
work_item_id: "AIA-P1-001-01",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
export type SecurityControlStatus = "ready" | "degraded";
|
||||
|
||||
export type SecurityControlPlaneSummary = {
|
||||
source_count: number;
|
||||
ready_source_count: number;
|
||||
unavailable_source_count: number;
|
||||
managed_asset_count: number;
|
||||
expected_asset_type_count: number;
|
||||
present_asset_type_count: number;
|
||||
@@ -119,6 +122,11 @@ export type SecurityAssetControlPlanePayload = {
|
||||
accepted_ai_trace_count_24h: number;
|
||||
immutable_external_audit_store_evidenced: boolean;
|
||||
};
|
||||
source_health: Array<{
|
||||
source_id: string;
|
||||
status: "ready" | "unavailable";
|
||||
reason_code: string | null;
|
||||
}>;
|
||||
work_items: SecurityControlPlaneWorkItem[];
|
||||
boundaries: {
|
||||
aggregate_only: boolean;
|
||||
@@ -154,6 +162,8 @@ export function isSecurityAssetControlPlanePayload(
|
||||
}
|
||||
return (
|
||||
typeof value.summary.managed_asset_count === "number" &&
|
||||
typeof value.summary.source_count === "number" &&
|
||||
Array.isArray(value.source_health) &&
|
||||
typeof value.summary.nist_controlled_percent === "number" &&
|
||||
value.boundaries.aggregate_only === true &&
|
||||
value.boundaries.raw_asset_identity_returned === false &&
|
||||
|
||||
Reference in New Issue
Block a user