import { CheckCircle2, CircleAlert, FileJson, Loader2, Play, X } from "lucide-react"; import type { ReactNode } from "react"; import { useMemo, useState } from "react"; import { createPortal } from "react-dom"; import { api, errorMessage } from "../api"; import type { CommandResult } from "../types"; import { Alert, JsonBlock } from "./ui"; type ActionTone = "neutral" | "warn" | "danger"; export function ActionButton({ label, path, payload, icon, compact = false, tone = "danger", onDone }: { label: string; path: string; payload: () => Record; icon?: ReactNode; compact?: boolean; tone?: ActionTone; onDone?: () => void; }) { const [open, setOpen] = useState(false); const [reason, setReason] = useState(""); const [result, setResult] = useState(null); const [error, setError] = useState(""); const [busy, setBusy] = useState(false); function reset() { setReason(""); setResult(null); setError(""); } async function run(confirm: boolean) { if (!reason.trim()) { setError("请填写操作原因"); return; } setBusy(true); setError(""); try { const body = { ...payload(), reason, confirm }; const commandResult = await api.action(path, body); setResult(commandResult); if (confirm) { onDone?.(); } } catch (err) { setError(errorMessage(err)); } finally { setBusy(false); } } const canConfirm = result?.dry_run && !result.error; const triggerClass = `btn ${tone === "danger" ? "danger" : tone === "warn" ? "warn" : ""} ${compact ? "compact-btn" : ""}`; const previewPayload = useMemo(() => { try { return payload(); } catch (err) { return { payload_error: errorMessage(err) }; } }, [open, payload]); return ( <> {open && createPortal(
操作流程

{label}

1填写原因
2预演检查
3确认执行