fix(awooop): redesign iwooos security cockpit
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 1m4s
CD Pipeline / build-and-deploy (push) Successful in 4m44s
AWOOOI Harbor 110 Local Repair / workflow-shape (push) Successful in 0s
AWOOOI Harbor 110 Local Repair / harbor-110-local-repair (push) Successful in 18s
CD Pipeline / post-deploy-checks (push) Has been cancelled

This commit is contained in:
ogt
2026-07-09 22:15:14 +08:00
parent 10ddacc67a
commit cfeaff2ef1

View File

@@ -9310,6 +9310,68 @@ function stockplatformManagerStateKey(value: string | null | undefined) {
return "review";
}
function securityOperatorLabel(value: string | null | undefined, isZh: boolean) {
const key = String(value ?? "");
const zh: Record<string, string> = {
blocked_reboot_auto_recovery_slo_not_ready: "重開機復原 SLO 未閉合",
collector_readback_verified_source_required_before_relink_dry_run:
"候選身分待確認",
host_unreachable_after_reboot: "重開機後主機未連線",
reboot_event_required_host_unreachable: "重開機目標不可達",
waiting_readback: "等待正式讀回",
windows99_remote_execution_channel_unavailable: "Windows99 通道未連線",
windows99_vmware_autostart_readback_missing: "VMware 自啟讀回缺口",
windows99_vmware_vmx_missing: "VMX 來源缺口",
};
const en: Record<string, string> = {
blocked_reboot_auto_recovery_slo_not_ready: "Reboot recovery SLO open",
collector_readback_verified_source_required_before_relink_dry_run:
"Candidate identity pending",
host_unreachable_after_reboot: "Host unreachable after reboot",
reboot_event_required_host_unreachable: "Reboot target unreachable",
waiting_readback: "Waiting readback",
windows99_remote_execution_channel_unavailable: "Windows99 channel offline",
windows99_vmware_autostart_readback_missing: "VMware autostart readback gap",
windows99_vmware_vmx_missing: "VMX source gap",
};
if (isZh && zh[key]) return zh[key];
if (!isZh && en[key]) return en[key];
return compactBlockerLabel(key);
}
function securityNextActionLabel(value: string | null | undefined, isZh: boolean) {
const key = String(value ?? "");
if (
key.includes("console_candidate_identity") ||
key.includes("candidate_identity") ||
key.includes("relink_dry_run") ||
key.includes("vmx_source")
) {
return isZh
? "確認 Windows99 VMX 身分證據,完成 scoped relink dry-run再重跑 no-secret collector 與 SLO scorecard"
: "Confirm Windows99 VMX identity evidence, complete scoped relink dry-run, then rerun no-secret collector and SLO scorecard";
}
if (key.includes("no_secret_collector") || key.includes("scorecard")) {
return isZh
? "重跑 no-secret collector 與正式 scorecard補齊可審核讀回"
: "Rerun the no-secret collector and production scorecard for auditable readback";
}
if (!key || key === "--") return "--";
return key.replace(/_/g, " ").replace(/\s+/g, " ").trim();
}
function compactEvidenceRef(value: string | null | undefined) {
const text = String(value ?? "").trim();
if (!text) return "--";
const scheme = text.match(/^([a-z]+):\/\//)?.[1];
const lastSegment = text.split("/").filter(Boolean).pop() ?? text;
return scheme ? `${scheme}://${lastSegment}` : lastSegment;
}
function percentText(value: number | null | undefined) {
return Number.isFinite(value) ? `${Math.round(Number(value))}%` : "--";
}
function SecurityMainlineCockpit({
priority,
locale,
@@ -9325,17 +9387,25 @@ function SecurityMainlineCockpit({
eyebrow: isZh ? "IwoooS 資安主線" : "IwoooS security mainline",
title: isZh ? "IwoooS 管理者作戰盤" : "IwoooS operator cockpit",
subtitle: isZh
? "正式環境讀回:Wazuh manager registry、Windows99 locator、P0 blocker 與高風險動作邊界集中顯示。"
: "Production readback for Wazuh registry, Windows99 locator, P0 blockers, and high-risk guardrails.",
? "正式環境讀回:風險態勢、處置佇列、MCP/RAG/PlayBook 證據鏈集中顯示。"
: "Production readback for risk posture, action queue, and MCP/RAG/PlayBook evidence.",
live: isZh ? "正式讀回" : "production",
loading: isZh ? "讀取中" : "loading",
p0: isZh ? "目前 P0" : "Current P0",
posture: isZh ? "風險態勢" : "Risk posture",
visibility: isZh ? "監測覆蓋" : "Visibility",
runtime: isZh ? "Runtime 接受" : "Runtime accepted",
registry: isZh ? "Wazuh 納管" : "Wazuh registry",
locator: isZh ? "Windows99 locator" : "Windows99 locator",
guard: isZh ? "高風險邊界" : "Risk boundary",
next: isZh ? "下一步" : "Next action",
queue: isZh ? "處置佇列" : "Action queue",
evidenceFabric: isZh ? "證據鏈" : "Evidence fabric",
externalGap: isZh ? "外部整合待 tool-result 審核" : "External tool-result review pending",
blockers: isZh ? "阻擋項" : "blockers",
readiness: isZh ? "準備度" : "readiness",
needsAction: isZh ? "待處置" : "Needs action",
stable: isZh ? "穩定" : "Stable",
owner: isZh ? "owner export" : "owner export",
reviewer: isZh ? "review passed" : "review passed",
identity: isZh ? "單一身分候選" : "identity candidate",
@@ -9345,7 +9415,12 @@ function SecurityMainlineCockpit({
noPower: isZh ? "未改 VM power" : "no VM power change",
locked: isZh ? "runtime gate 鎖定" : "runtime gate locked",
deployLabel: isZh ? "部署" : "deploy",
locatorLabel: isZh ? "定位器" : "locator",
blockerLabel: isZh ? "主阻擋" : "primary blocker",
identityEvidence: isZh ? "身分證據" : "identity",
dryRun: isZh ? "Dry-run" : "Dry-run",
verify: isZh ? "驗證讀回" : "verify",
ready: isZh ? "已掛接" : "linked",
pending: isZh ? "待補齊" : "pending",
};
useEffect(() => {
@@ -9384,6 +9459,8 @@ function SecurityMainlineCockpit({
evidence?.active_blockers ??
[];
const readiness = summary?.active_p0_readiness_percent ?? 0;
const visibilityPercent = runtimeSummary?.control_plane_visibility_percent;
const runtimeAcceptancePercent = runtimeSummary?.actual_runtime_acceptance_percent;
const registryAccepted =
registrySummary?.manager_registry_accepted_count ??
runtimeSummary?.wazuh_manager_registry_accepted_count ??
@@ -9416,6 +9493,11 @@ function SecurityMainlineCockpit({
summary?.windows99_vmx_source_locator_status ??
evidence?.windows99_vmx_source_locator_status ??
"waiting_readback";
const primaryBlocker =
activeBlockers[0] ??
summary?.ai_loop_current_blocker_id ??
runtime?.status ??
"waiting_readback";
const locatorCandidates =
summary?.windows99_vmx_source_locator_candidate_source_count ??
evidence?.windows99_vmx_source_locator_candidate_source_count ??
@@ -9447,6 +9529,7 @@ function SecurityMainlineCockpit({
activeItem?.next_action ??
summary?.commander_inserted_requirement_next_action ??
"--";
const operatorNextAction = securityNextActionLabel(nextAction, isZh);
const guardClear =
runtimeGateCount === 0 &&
hostWriteCount === 0 &&
@@ -9459,16 +9542,42 @@ function SecurityMainlineCockpit({
priority?.current_head?.latest_successful_deployed_source_sha ??
priority?.current_head?.gitea_main_sha ??
null;
const shortSha = mainSha ? mainSha.slice(0, 10) : "--";
const deploySha = mainSha ? mainSha.slice(0, 10) : "--";
const ragRefs = uniqueText([
summary?.windows99_vmx_source_repair_rag_chunk_ref,
evidence?.windows99_vmx_source_repair_rag_chunk_ref,
summary?.windows99_vmware_autostart_check_mode_rag_chunk_ref,
evidence?.windows99_vmware_autostart_check_mode_rag_chunk_ref,
]);
const mcpRefs = uniqueText([
summary?.windows99_vmx_source_repair_mcp_evidence_ref,
evidence?.windows99_vmx_source_repair_mcp_evidence_ref,
summary?.windows99_vmware_autostart_check_mode_mcp_evidence_ref,
evidence?.windows99_vmware_autostart_check_mode_mcp_evidence_ref,
]);
const playbookRefs = uniqueText([
summary?.windows99_vmx_source_repair_playbook_trust_writeback_ref,
evidence?.windows99_vmx_source_repair_playbook_trust_writeback_ref,
summary?.windows99_vmware_autostart_check_mode_playbook_trust_writeback_ref,
evidence?.windows99_vmware_autostart_check_mode_playbook_trust_writeback_ref,
]);
const cards = [
{
key: "p0",
label: copy.p0,
value: summary?.active_p0_workplan_id ?? activeItem?.id ?? "P0",
detail: `${copy.readiness} ${readiness}% · ${activeBlockers.length} ${copy.blockers}`,
key: "posture",
label: copy.posture,
value: activeBlockers.length > 0 ? copy.needsAction : copy.stable,
detail: `${summary?.active_p0_workplan_id ?? activeItem?.id ?? "P0"} · ${securityOperatorLabel(primaryBlocker, isZh)}`,
icon: Radar,
tone: activeBlockers.length > 0 ? "blocked" : "live",
},
{
key: "visibility",
label: copy.visibility,
value: percentText(visibilityPercent),
detail: `${copy.runtime} ${percentText(runtimeAcceptancePercent)} · ${copy.readiness} ${readiness}%`,
icon: Gauge,
tone: Number(runtimeAcceptancePercent ?? 0) > 0 ? "in_progress" : "blocked",
},
{
key: "registry",
label: copy.registry,
@@ -9477,23 +9586,64 @@ function SecurityMainlineCockpit({
icon: Network,
tone: registryExpected > 0 && registryAccepted >= registryExpected ? "live" : "in_progress",
},
{
key: "locator",
label: copy.locator,
value: `${locatorCandidates}`,
detail: `${copy.identity} ${locatorIdentityCandidates} · ${copy.confirmation} ${locatorConfirmationRequired ? "yes" : "no"}`,
icon: SearchCheck,
tone: locatorCandidates > 0 && locatorConfirmationRequired ? "in_progress" : locatorCandidates > 0 ? "live" : "blocked",
},
{
key: "guard",
label: copy.guard,
value: guardClear ? "0" : "!",
detail: `${copy.noRaw} · ${copy.noWrite} · ${copy.noPower}`,
detail: guardClear ? `${copy.noRaw} · ${copy.noWrite}` : `${copy.noWrite} · ${copy.noPower}`,
icon: Lock,
tone: guardClear ? "live" : "blocked",
},
] as const;
const actionQueue = [
{
key: "identity",
label: copy.identityEvidence,
value: `${locatorIdentityCandidates}`,
detail: locatorConfirmationRequired ? copy.pending : copy.ready,
icon: Fingerprint,
tone: locatorConfirmationRequired ? "blocked" : "live",
},
{
key: "dryrun",
label: copy.dryRun,
value: locatorCandidates > 0 ? `${locatorCandidates}` : "--",
detail: securityOperatorLabel(locatorStatus, isZh),
icon: SearchCheck,
tone: locatorCandidates > 0 ? "in_progress" : "blocked",
},
{
key: "verify",
label: copy.verify,
value: `${activeBlockers.length}`,
detail: activeBlockers.length > 0 ? copy.pending : copy.ready,
icon: ListChecks,
tone: activeBlockers.length > 0 ? "blocked" : "live",
},
] as const;
const evidenceLanes = [
{
key: "rag",
label: "RAG",
count: ragRefs.length,
ref: compactEvidenceRef(ragRefs[0]),
icon: Database,
},
{
key: "mcp",
label: "MCP",
count: mcpRefs.length,
ref: compactEvidenceRef(mcpRefs[0]),
icon: Activity,
},
{
key: "playbook",
label: "PlayBook",
count: playbookRefs.length,
ref: compactEvidenceRef(playbookRefs[0]),
icon: ClipboardList,
},
] as const;
return (
<section
@@ -9501,7 +9651,7 @@ function SecurityMainlineCockpit({
data-testid="awooop-security-mainline-cockpit"
className="overflow-hidden border border-[#d7ddd8] bg-white"
>
<div className="grid gap-px bg-[#d7ddd8] lg:grid-cols-[1.15fr_2fr]">
<div className="grid gap-px bg-[#d7ddd8] xl:grid-cols-[0.95fr_2.25fr]">
<div className="grid gap-4 bg-[#111514] p-4 text-white">
<div className="flex flex-wrap items-center gap-2 text-xs font-semibold text-[#b8d8c5]">
<ShieldCheck className="h-4 w-4" aria-hidden="true" />
@@ -9522,13 +9672,13 @@ function SecurityMainlineCockpit({
<div className="rounded border border-white/15 bg-white/5 px-3 py-2">
<p className="text-[10px] font-semibold text-[#aeb7ad]">{copy.deployLabel}</p>
<p className="mt-1 truncate font-mono text-sm font-semibold text-[#b8d8c5]">
{shortSha}
{deploySha}
</p>
</div>
<div className="rounded border border-white/15 bg-white/5 px-3 py-2">
<p className="text-[10px] font-semibold text-[#aeb7ad]">{copy.locatorLabel}</p>
<p className="mt-1 break-words text-xs font-semibold leading-4 text-[#d7c6a9]">
{locatorStatus}
<p className="text-[10px] font-semibold text-[#aeb7ad]">{copy.blockerLabel}</p>
<p className="mt-1 text-xs font-semibold leading-4 text-[#d7c6a9]">
{securityOperatorLabel(primaryBlocker, isZh)}
</p>
</div>
</div>
@@ -9564,7 +9714,7 @@ function SecurityMainlineCockpit({
</div>
<p
className={cn(
"break-words font-mono text-2xl font-semibold leading-none",
"break-words text-2xl font-semibold leading-none",
card.tone === "live" && "text-[#17602a]",
card.tone === "in_progress" && "text-[#8a5a08]",
card.tone === "blocked" && "text-[#9f2f25]"
@@ -9580,16 +9730,84 @@ function SecurityMainlineCockpit({
})}
</div>
<div className="grid gap-2 rounded border border-[#d8d3c7] bg-[#faf9f3] p-3 md:grid-cols-[1fr_auto] md:items-center">
<div className="grid gap-2 rounded border border-[#d8d3c7] bg-[#faf9f3] p-3 xl:grid-cols-[1.1fr_1.4fr]">
<div className="min-w-0">
<p className="text-[11px] font-semibold text-[#77736a]">
{copy.next}
</p>
<p className="mt-1 break-words text-sm font-semibold leading-5 text-[#141413]">
{nextAction}
{operatorNextAction}
</p>
</div>
<div className="flex flex-wrap gap-2 md:justify-end">
<div className="grid gap-2 sm:grid-cols-3">
{actionQueue.map((item) => {
const Icon = item.icon;
return (
<div
key={item.key}
className={cn(
"rounded border bg-white px-3 py-2",
item.tone === "live" && "border-[#bddfc9]",
item.tone === "in_progress" && "border-[#e2c08f]",
item.tone === "blocked" && "border-[#e2a29b]"
)}
>
<div className="flex items-center justify-between gap-2">
<p className="truncate text-[10px] font-semibold text-[#77736a]">
{item.label}
</p>
<Icon className="h-3.5 w-3.5 text-[#5f5b52]" aria-hidden="true" />
</div>
<p className="mt-1 text-lg font-semibold leading-none text-[#141413]">
{item.value}
</p>
<p className="mt-1 truncate text-[10px] font-semibold text-[#77736a]">
{item.detail}
</p>
</div>
);
})}
</div>
</div>
<div className="grid gap-2 rounded border border-[#d5dbe4] bg-[#f7faff] p-3 xl:grid-cols-[auto_1fr_auto] xl:items-center">
<div>
<p className="text-[11px] font-semibold text-[#496276]">
{copy.evidenceFabric}
</p>
<p className="mt-1 text-[10px] font-semibold text-[#6a7280]">
{copy.externalGap}
</p>
</div>
<div className="grid gap-2 sm:grid-cols-3">
{evidenceLanes.map((lane) => {
const Icon = lane.icon;
const linked = lane.count > 0;
return (
<div
key={lane.key}
className={cn(
"rounded border bg-white px-3 py-2",
linked ? "border-[#bddfc9]" : "border-[#d8d3c7]"
)}
>
<div className="flex items-center justify-between gap-2">
<p className="text-[10px] font-semibold text-[#496276]">
{lane.label}
</p>
<Icon className="h-3.5 w-3.5 text-[#496276]" aria-hidden="true" />
</div>
<p className="mt-1 text-sm font-semibold text-[#141413]">
{linked ? `${lane.count} ${copy.ready}` : copy.pending}
</p>
<p className="mt-1 truncate font-mono text-[10px] text-[#6a7280]">
{lane.ref}
</p>
</div>
);
})}
</div>
<div className="flex flex-wrap gap-2 xl:justify-end">
{[
[copy.noRaw, !locatorRawPathOutput],
[copy.noWrite, hostWriteCount === 0 && !locatorRemoteWrite],