Merge remote-tracking branch 'gitea/main' into codex/delivery-workbench-release-20260626-ffsync
This commit is contained in:
@@ -633,6 +633,159 @@ function Gate5ProjectionBadge() {
|
||||
);
|
||||
}
|
||||
|
||||
function handoffWorkItemHref(
|
||||
projectId: string,
|
||||
incidentId: string,
|
||||
chain: AwoooPStatusChain | null
|
||||
) {
|
||||
const promotion = chain?.repair_candidate_promotion;
|
||||
const href = promotion?.work_item_url
|
||||
?? promotion?.contract?.source_work_item_url
|
||||
?? null;
|
||||
if (href && href.startsWith("/")) return href;
|
||||
|
||||
const workItemId = promotion?.work_item_id
|
||||
?? promotion?.contract?.source_work_item_id
|
||||
?? chain?.automation_handoff?.work_item_id
|
||||
?? "";
|
||||
const params = new URLSearchParams({ project_id: projectId, incident_id: incidentId });
|
||||
if (workItemId) params.set("work_item_id", workItemId);
|
||||
return `/awooop/work-items?${params.toString()}`;
|
||||
}
|
||||
|
||||
function ExecutorHandoffReadinessCard({
|
||||
projectId,
|
||||
incidentId,
|
||||
chain,
|
||||
}: {
|
||||
projectId: string;
|
||||
incidentId: string;
|
||||
chain: AwoooPStatusChain | null;
|
||||
}) {
|
||||
const t = useTranslations("awooop.approvals.incidentFocus.executorHandoff");
|
||||
const promotion = chain?.repair_candidate_promotion;
|
||||
const contract = promotion?.contract;
|
||||
const closure = chain?.automation_handoff?.closure_readiness;
|
||||
const runtimeAllowed = Boolean(
|
||||
promotion?.runtime_execution_authorized
|
||||
|| contract?.runtime_execution_authorized
|
||||
|| closure?.runtime_execution_authorized
|
||||
|| chain?.automation_handoff?.runtime_execution_authorized
|
||||
);
|
||||
const ready = Number(contract?.ready_count ?? closure?.ready_count ?? 0);
|
||||
const total = Number(contract?.total_count ?? closure?.total_count ?? 0);
|
||||
const blocked = Number(contract?.blocked_count ?? closure?.blocked_count ?? 0);
|
||||
const readinessPercent = total > 0 ? Math.min(100, Math.round((ready / total) * 100)) : 0;
|
||||
const status = promotion?.status ?? contract?.status ?? closure?.status ?? chain?.automation_handoff?.status ?? "not_available";
|
||||
const nextAction = chain?.automation_handoff?.next_action
|
||||
?? closure?.next_action
|
||||
?? promotion?.summary
|
||||
?? chain?.operator_outcome?.next_action
|
||||
?? chain?.next_step
|
||||
?? "--";
|
||||
const blocker = closure?.blocked_reason
|
||||
?? promotion?.reason
|
||||
?? chain?.operator_outcome?.human_action_reason
|
||||
?? chain?.blockers?.[0]
|
||||
?? "--";
|
||||
const blockedFields = [
|
||||
...(contract?.blocked_fields ?? []),
|
||||
...(closure?.required_owner_fields ?? []),
|
||||
].filter((field, index, fields) => field && fields.indexOf(field) === index).slice(0, 8);
|
||||
const workItemHref = handoffWorkItemHref(projectId, incidentId, chain);
|
||||
const runsHref = `/awooop/runs?project_id=${encodeURIComponent(projectId)}&incident_id=${encodeURIComponent(incidentId)}`;
|
||||
|
||||
return (
|
||||
<div className="mt-3 border border-[#d9b36f] bg-[#fffaf0]">
|
||||
<div className="flex flex-wrap items-start justify-between gap-3 border-b border-[#ead9b4] bg-[#fff7e8] px-3 py-3">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<GitBranch className="h-4 w-4 text-[#8a5a08]" aria-hidden="true" />
|
||||
<h4 className="text-sm font-semibold text-[#141413]">{t("title")}</h4>
|
||||
</div>
|
||||
<p className="mt-1 text-xs leading-5 text-[#5f5b52]">{t("subtitle")}</p>
|
||||
</div>
|
||||
<span className={cn(
|
||||
"inline-flex shrink-0 items-center gap-1.5 border px-2 py-1 font-mono text-xs font-semibold",
|
||||
runtimeAllowed
|
||||
? "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]"
|
||||
: "border-[#e2a29b] bg-[#fff0ef] text-[#9f2f25]"
|
||||
)}>
|
||||
{runtimeAllowed ? t("runtime.open") : t("runtime.closed")}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-px bg-[#ead9b4] md:grid-cols-4">
|
||||
{[
|
||||
[t("metrics.readiness"), `${readinessPercent}%`],
|
||||
[t("metrics.ready"), `${ready}/${total || "--"}`],
|
||||
[t("metrics.blocked"), String(blocked)],
|
||||
[t("metrics.status"), status],
|
||||
].map(([label, value]) => (
|
||||
<div key={label} className="min-w-0 bg-white px-3 py-2">
|
||||
<p className="text-xs font-semibold text-[#77736a]">{label}</p>
|
||||
<p className="mt-1 break-words font-mono text-sm font-semibold text-[#141413]" title={String(value)}>
|
||||
{value}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="p-3">
|
||||
<div className="h-2 border border-[#d8d3c7] bg-white">
|
||||
<div
|
||||
className={cn("h-full", runtimeAllowed ? "bg-[#2f7d72]" : "bg-[#d97757]")}
|
||||
style={{ width: `${readinessPercent}%` }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-3 grid gap-2 md:grid-cols-2">
|
||||
<div className="min-w-0 border border-[#ead9b4] bg-white px-3 py-2">
|
||||
<p className="text-xs font-semibold text-[#77736a]">{t("nextAction")}</p>
|
||||
<p className="mt-1 break-words font-mono text-xs leading-5 text-[#141413]">{nextAction}</p>
|
||||
</div>
|
||||
<div className="min-w-0 border border-[#ead9b4] bg-white px-3 py-2">
|
||||
<p className="text-xs font-semibold text-[#77736a]">{t("blocker")}</p>
|
||||
<p className="mt-1 break-words font-mono text-xs leading-5 text-[#141413]">{blocker}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 border border-[#e2a29b] bg-[#fff0ef] px-3 py-2">
|
||||
<p className="text-xs font-semibold text-[#9f2f25]">{t("missingTitle")}</p>
|
||||
<div className="mt-2 flex flex-wrap gap-1.5">
|
||||
{blockedFields.length > 0 ? blockedFields.map((field) => (
|
||||
<span key={field} className="border border-[#e2a29b] bg-white px-2 py-0.5 font-mono text-[10px] font-semibold text-[#9f2f25]">
|
||||
{field}
|
||||
</span>
|
||||
)) : (
|
||||
<span className="border border-[#d8d3c7] bg-white px-2 py-0.5 text-[11px] font-semibold text-[#5f5b52]">
|
||||
{t("missingEmpty")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
<Link
|
||||
href={workItemHref as never}
|
||||
className="inline-flex items-center gap-1.5 border border-[#d9b36f] bg-white px-3 py-2 text-xs font-semibold text-[#8a5a08] hover:bg-[#fff7e8]"
|
||||
>
|
||||
{t("openWorkItem")}
|
||||
<ArrowRight className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
</Link>
|
||||
<Link
|
||||
href={runsHref as never}
|
||||
className="inline-flex items-center gap-1.5 border border-[#d8d3c7] bg-white px-3 py-2 text-xs font-semibold text-[#141413] hover:border-[#1f6feb]"
|
||||
>
|
||||
{t("openRuns")}
|
||||
<ArrowRight className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function approvalDecisionRailToneClass(tone: ApprovalDecisionRailTone) {
|
||||
if (tone === "ok") return "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]";
|
||||
if (tone === "blocked") return "border-[#e2a29b] bg-[#fff0ef] text-[#9f2f25]";
|
||||
@@ -1459,6 +1612,11 @@ function FocusedIncidentApprovalPanel({
|
||||
<p className="mt-3 text-xs leading-5 text-[#5f5b52]">
|
||||
{linkedApprovalIds.length > 0 ? t("linkedExplanation") : t("unlinkedExplanation")}
|
||||
</p>
|
||||
<ExecutorHandoffReadinessCard
|
||||
projectId={projectId}
|
||||
incidentId={incidentId}
|
||||
chain={chain}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1530,6 +1688,7 @@ export default function ApprovalsPage() {
|
||||
setError(null);
|
||||
setLegacyError(null);
|
||||
const params = new URLSearchParams();
|
||||
params.set("project_id", projectId);
|
||||
if (evidenceFilter) params.set("remediation_status", evidenceFilter);
|
||||
const qs = params.toString();
|
||||
const [platformResult, legacyResult] = await Promise.allSettled([
|
||||
@@ -1560,7 +1719,7 @@ export default function ApprovalsPage() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [evidenceFilter, t]);
|
||||
}, [evidenceFilter, projectId, t]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchApprovals();
|
||||
|
||||
@@ -326,7 +326,7 @@ type WazuhReadonlyStatusResponse = {
|
||||
}
|
||||
|
||||
type RuntimeSecurityReadbackSummaryItem = {
|
||||
key: 'controlPlane' | 'runtimeAcceptance' | 'wazuhRegistry' | 'ownerAccepted' | 'kaliRuntime' | 'runtimeGate'
|
||||
key: 'controlPlane' | 'runtimeAcceptance' | 'wazuhRegistry' | 'wazuhLive' | 'ownerAccepted' | 'kaliRuntime' | 'runtimeGate'
|
||||
value: string
|
||||
icon: typeof ShieldCheck
|
||||
tone: 'steady' | 'warn' | 'locked'
|
||||
@@ -8124,6 +8124,12 @@ function IwoooSRuntimeSecurityReadbackBoard() {
|
||||
icon: Radar,
|
||||
tone: 'locked',
|
||||
},
|
||||
{
|
||||
key: 'wazuhLive',
|
||||
value: summary ? `${summary.wazuh_live_agent_total}/${summary.wazuh_live_status}` : '...',
|
||||
icon: Route,
|
||||
tone: summary && summary.wazuh_live_metadata_available_count === 1 && summary.wazuh_live_route_degraded_count === 0 ? 'steady' : 'warn',
|
||||
},
|
||||
{
|
||||
key: 'ownerAccepted',
|
||||
value: summary ? `${summary.owner_response_accepted_count}/${summary.owner_response_received_count}` : '...',
|
||||
|
||||
@@ -100,6 +100,7 @@ export type IwoooSRuntimeSecurityReadbackTone = 'steady' | 'warn' | 'locked'
|
||||
export interface IwoooSRuntimeSecurityReadbackLane {
|
||||
lane_id:
|
||||
| 'wazuh_registry'
|
||||
| 'wazuh_live_route'
|
||||
| 'wazuh_dashboard_api'
|
||||
| 'kali_intake'
|
||||
| 'alert_readability'
|
||||
@@ -131,6 +132,15 @@ export interface IwoooSRuntimeSecurityReadbackResponse {
|
||||
wazuh_manager_registry_accepted_count: number
|
||||
wazuh_transport_observed_count: number
|
||||
wazuh_dashboard_api_degraded_observed_count: number
|
||||
wazuh_live_route_http_status: number
|
||||
wazuh_live_route_degraded_count: number
|
||||
wazuh_live_readonly_api_enabled_count: number
|
||||
wazuh_live_agent_total: number
|
||||
wazuh_live_agent_active: number
|
||||
wazuh_live_registry_empty_count: number
|
||||
wazuh_live_below_expected_count: number
|
||||
wazuh_live_metadata_available_count: number
|
||||
wazuh_live_status: string
|
||||
kali_active_scan_authorized_count: number
|
||||
kali_execute_authorized_count: number
|
||||
kali_finding_envelope_accepted_count: number
|
||||
|
||||
Reference in New Issue
Block a user