feat(web): expose AwoooP run operator status chain
This commit is contained in:
@@ -3944,6 +3944,20 @@
|
||||
"column": "來源流程",
|
||||
"notLinked": "尚未關聯 incident",
|
||||
"detail": "providers={providers}; d/c/a={direct}/{candidate}/{applied}",
|
||||
"nextLine": "next={next}",
|
||||
"blockedLine": "blocked={blocker}",
|
||||
"mcpLine": "MCP {success}/{total}; failed={failed}; blocked={blocked}",
|
||||
"ansibleLine": "Ansible candidates={candidates}; apply={applied}; reason={reason}",
|
||||
"kmLine": "KM entries={count}",
|
||||
"operator": {
|
||||
"statuses": {
|
||||
"needsHuman": "Needs human",
|
||||
"failed": "Execution failed",
|
||||
"verified": "Verified",
|
||||
"executed": "Executed",
|
||||
"waiting": "Pending"
|
||||
}
|
||||
},
|
||||
"statuses": {
|
||||
"verified": "已驗證",
|
||||
"applied": "已套用",
|
||||
|
||||
@@ -3944,6 +3944,20 @@
|
||||
"column": "來源流程",
|
||||
"notLinked": "尚未關聯 incident",
|
||||
"detail": "providers={providers}; d/c/a={direct}/{candidate}/{applied}",
|
||||
"nextLine": "next={next}",
|
||||
"blockedLine": "blocked={blocker}",
|
||||
"mcpLine": "MCP {success}/{total}; failed={failed}; blocked={blocked}",
|
||||
"ansibleLine": "Ansible candidates={candidates}; apply={applied}; reason={reason}",
|
||||
"kmLine": "KM entries={count}",
|
||||
"operator": {
|
||||
"statuses": {
|
||||
"needsHuman": "需人工",
|
||||
"failed": "執行失敗",
|
||||
"verified": "已驗證",
|
||||
"executed": "已執行",
|
||||
"waiting": "待判讀"
|
||||
}
|
||||
},
|
||||
"statuses": {
|
||||
"verified": "已驗證",
|
||||
"applied": "已套用",
|
||||
|
||||
@@ -1693,6 +1693,7 @@ function IncidentIdsCell({ run }: { run: Run }) {
|
||||
}
|
||||
|
||||
type SourceFlowListStatus = "verified" | "applied" | "evidence" | "provider" | "waiting" | "loading";
|
||||
type OperatorFlowListStatus = "needsHuman" | "failed" | "verified" | "executed" | "waiting";
|
||||
|
||||
function sourceFlowListStatus(chain?: AwoooPStatusChain | null): SourceFlowListStatus {
|
||||
const correlation = chain?.source_refs?.correlation;
|
||||
@@ -1716,6 +1717,45 @@ function sourceFlowListClass(status: SourceFlowListStatus) {
|
||||
return "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]";
|
||||
}
|
||||
|
||||
function operatorFlowListStatus(chain?: AwoooPStatusChain | null): OperatorFlowListStatus {
|
||||
if (!chain) return "waiting";
|
||||
const outcomeState = String(chain.operator_outcome?.state ?? "").toLowerCase();
|
||||
const completionStatus = String(chain.operator_outcome?.execution_result?.completion_status ?? "").toLowerCase();
|
||||
const commandStatus = String(chain.operator_outcome?.execution_result?.command_status ?? "").toLowerCase();
|
||||
const repairState = String(chain.repair_state ?? "").toLowerCase();
|
||||
const verification = String(chain.verification ?? "").toLowerCase();
|
||||
const stageStatus = String(chain.stage_status ?? "").toLowerCase();
|
||||
|
||||
if (chain.needs_human) return "needsHuman";
|
||||
if (
|
||||
outcomeState.includes("failed") ||
|
||||
completionStatus.includes("failed") ||
|
||||
commandStatus.includes("failed") ||
|
||||
stageStatus === "error"
|
||||
) {
|
||||
return "failed";
|
||||
}
|
||||
if (repairState === "auto_repaired_verified" || verification === "verified") {
|
||||
return "verified";
|
||||
}
|
||||
if (repairState === "executed" || completionStatus === "completed" || stageStatus === "success") {
|
||||
return "executed";
|
||||
}
|
||||
return "waiting";
|
||||
}
|
||||
|
||||
function operatorFlowListClass(status: OperatorFlowListStatus) {
|
||||
if (status === "verified") return "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]";
|
||||
if (status === "executed") return "border-[#9bb6d9] bg-[#eef5ff] text-[#1f5b9b]";
|
||||
if (status === "failed" || status === "needsHuman") return "border-[#e2a29b] bg-[#fff0ef] text-[#9f2f25]";
|
||||
return "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]";
|
||||
}
|
||||
|
||||
function shortValue(value?: string | number | boolean | null) {
|
||||
if (value === null || value === undefined || value === "") return "--";
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function SourceFlowCell({
|
||||
chain,
|
||||
hasIncident,
|
||||
@@ -1727,7 +1767,34 @@ function SourceFlowCell({
|
||||
}) {
|
||||
const t = useTranslations("awooop.listEvidence.sourceFlow");
|
||||
const status = loading && hasIncident ? "loading" : sourceFlowListStatus(chain);
|
||||
const operatorStatus = operatorFlowListStatus(chain);
|
||||
const correlation = chain?.source_refs?.correlation;
|
||||
const outcome = chain?.operator_outcome;
|
||||
const executionResult = outcome?.execution_result;
|
||||
const gateway = chain?.mcp?.gateway;
|
||||
const ansible = chain?.execution?.ansible;
|
||||
const firstBlocker = chain?.blockers?.[0];
|
||||
const nextAction = outcome?.next_action ?? chain?.next_step;
|
||||
const outcomeSummary = outcome?.summary_zh
|
||||
?? executionResult?.summary_zh
|
||||
?? (chain ? `${shortValue(chain.current_stage)} / ${shortValue(chain.stage_status)}` : null);
|
||||
const blockerLine = firstBlocker
|
||||
? t("blockedLine", { blocker: firstBlocker })
|
||||
: t("nextLine", { next: shortValue(nextAction) });
|
||||
const mcpLine = t("mcpLine", {
|
||||
success: gateway?.success ?? 0,
|
||||
total: gateway?.total ?? 0,
|
||||
failed: gateway?.failed ?? 0,
|
||||
blocked: gateway?.blocked ?? 0,
|
||||
});
|
||||
const ansibleLine = t("ansibleLine", {
|
||||
candidates: ansible?.candidate_count ?? 0,
|
||||
applied: ansible?.apply_total ?? 0,
|
||||
reason: shortValue(ansible?.not_used_reason),
|
||||
});
|
||||
const kmLine = t("kmLine", {
|
||||
count: chain?.evidence?.knowledge_entries ?? 0,
|
||||
});
|
||||
const detail = !hasIncident
|
||||
? t("notLinked")
|
||||
: t("detail", {
|
||||
@@ -1737,19 +1804,46 @@ function SourceFlowCell({
|
||||
applied: correlation?.applied_link_total ?? 0,
|
||||
});
|
||||
const Icon = status === "verified" ? ShieldCheck : (status === "waiting" ? BellOff : SearchCheck);
|
||||
const OperatorIcon = operatorStatus === "needsHuman" || operatorStatus === "failed"
|
||||
? TriangleAlert
|
||||
: (operatorStatus === "verified" ? ShieldCheck : SearchCheck);
|
||||
|
||||
return (
|
||||
<div className="min-w-[190px]">
|
||||
<span className={cn(
|
||||
"inline-flex items-center gap-1.5 border px-2 py-1 text-xs font-semibold",
|
||||
sourceFlowListClass(status)
|
||||
)}>
|
||||
<Icon className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
{t(`statuses.${status}`)}
|
||||
</span>
|
||||
<div className="min-w-[280px] max-w-[340px]">
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
<span className={cn(
|
||||
"inline-flex items-center gap-1.5 border px-2 py-1 text-xs font-semibold",
|
||||
sourceFlowListClass(status)
|
||||
)}>
|
||||
<Icon className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
{t(`statuses.${status}`)}
|
||||
</span>
|
||||
<span className={cn(
|
||||
"inline-flex items-center gap-1.5 border px-2 py-1 text-xs font-semibold",
|
||||
operatorFlowListClass(operatorStatus)
|
||||
)}>
|
||||
<OperatorIcon className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
{t(`operator.statuses.${operatorStatus}`)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 truncate font-mono text-xs text-[#77736a]" title={detail}>
|
||||
{detail}
|
||||
</p>
|
||||
{chain && (
|
||||
<>
|
||||
<p className="mt-1 truncate text-xs font-semibold text-[#141413]" title={shortValue(outcomeSummary)}>
|
||||
{shortValue(outcomeSummary)}
|
||||
</p>
|
||||
<p className="mt-1 truncate font-mono text-xs text-[#9f2f25]" title={blockerLine}>
|
||||
{blockerLine}
|
||||
</p>
|
||||
<div className="mt-1 grid gap-0.5 font-mono text-[11px] leading-4 text-[#5f5b52]">
|
||||
<span className="truncate" title={mcpLine}>{mcpLine}</span>
|
||||
<span className="truncate" title={ansibleLine}>{ansibleLine}</span>
|
||||
<span className="truncate" title={kmLine}>{kmLine}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user