From cfeaff2ef1d0ee5484bf08a7d9308ca3cc61967f Mon Sep 17 00:00:00 2001 From: ogt Date: Thu, 9 Jul 2026 22:15:14 +0800 Subject: [PATCH] fix(awooop): redesign iwooos security cockpit --- .../app/[locale]/awooop/work-items/page.tsx | 270 ++++++++++++++++-- 1 file changed, 244 insertions(+), 26 deletions(-) diff --git a/apps/web/src/app/[locale]/awooop/work-items/page.tsx b/apps/web/src/app/[locale]/awooop/work-items/page.tsx index 37f4606cf..72120e046 100644 --- a/apps/web/src/app/[locale]/awooop/work-items/page.tsx +++ b/apps/web/src/app/[locale]/awooop/work-items/page.tsx @@ -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 = { + 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 = { + 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 (
-
+
@@ -9564,7 +9714,7 @@ function SecurityMainlineCockpit({

-

+

{copy.next}

- {nextAction} + {operatorNextAction}

-
+
+ {actionQueue.map((item) => { + const Icon = item.icon; + return ( +
+
+

+ {item.label} +

+
+

+ {item.value} +

+

+ {item.detail} +

+
+ ); + })} +
+
+ +
+
+

+ {copy.evidenceFabric} +

+

+ {copy.externalGap} +

+
+
+ {evidenceLanes.map((lane) => { + const Icon = lane.icon; + const linked = lane.count > 0; + return ( +
+
+

+ {lane.label} +

+
+

+ {linked ? `${lane.count} ${copy.ready}` : copy.pending} +

+

+ {lane.ref} +

+
+ ); + })} +
+
{[ [copy.noRaw, !locatorRawPathOutput], [copy.noWrite, hostWriteCount === 0 && !locatorRemoteWrite],