fix: 调试发布run
This commit is contained in:
@@ -26,6 +26,9 @@ describe("shared operation dialogs", () => {
|
||||
expect(operationControlsSource).toContain('event.key === "Escape"');
|
||||
expect(operationControlsSource).toContain('document.body.style.overflow = "hidden"');
|
||||
expect(operationControlsSource).toContain("previousFocus?.focus()");
|
||||
expect(operationControlsSource).toContain("const onCloseRef = useRef(onClose)");
|
||||
expect(operationControlsSource).toContain('querySelector<HTMLElement>("input:not([disabled]), select:not([disabled]), textarea:not([disabled])")');
|
||||
expect(operationControlsSource).toContain("}, [open]);");
|
||||
});
|
||||
|
||||
it("keeps management forms in a dialog surface", () => {
|
||||
|
||||
@@ -8,12 +8,13 @@ interface ConfirmDialogProps {
|
||||
confirmLabel: string;
|
||||
danger?: boolean;
|
||||
busy?: boolean;
|
||||
confirmDisabled?: boolean;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export function ConfirmDialog({ open, title, description, confirmLabel, danger, busy, onConfirm, onCancel, children }: ConfirmDialogProps) {
|
||||
export function ConfirmDialog({ open, title, description, confirmLabel, danger, busy, confirmDisabled, onConfirm, onCancel, children }: ConfirmDialogProps) {
|
||||
const titleId = useId();
|
||||
const descriptionId = useId();
|
||||
const panelRef = useDialogLifecycle(open, onCancel, !busy);
|
||||
@@ -45,7 +46,7 @@ export function ConfirmDialog({ open, title, description, confirmLabel, danger,
|
||||
<button type="button" onClick={onCancel} disabled={busy}>
|
||||
取消
|
||||
</button>
|
||||
<button type="button" className={danger ? "confirm-danger" : "confirm-primary"} onClick={onConfirm} disabled={busy}>
|
||||
<button type="button" className={danger ? "confirm-danger" : "confirm-primary"} onClick={onConfirm} disabled={busy || confirmDisabled}>
|
||||
{busy ? "提交中…" : confirmLabel}
|
||||
</button>
|
||||
</div>
|
||||
@@ -97,6 +98,13 @@ export function ManagementDialog({ open, title, description, wide, onClose, chil
|
||||
|
||||
function useDialogLifecycle(open: boolean, onClose: () => void, canClose: boolean) {
|
||||
const panelRef = useRef<HTMLDivElement>(null);
|
||||
const onCloseRef = useRef(onClose);
|
||||
const canCloseRef = useRef(canClose);
|
||||
|
||||
useEffect(() => {
|
||||
onCloseRef.current = onClose;
|
||||
canCloseRef.current = canClose;
|
||||
}, [canClose, onClose]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
@@ -104,13 +112,15 @@ function useDialogLifecycle(open: boolean, onClose: () => void, canClose: boolea
|
||||
}
|
||||
const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
||||
const panel = panelRef.current;
|
||||
const focusTarget = panel?.querySelector<HTMLElement>("button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled])");
|
||||
const focusTarget =
|
||||
panel?.querySelector<HTMLElement>("input:not([disabled]), select:not([disabled]), textarea:not([disabled])") ??
|
||||
panel?.querySelector<HTMLElement>("button:not([disabled])");
|
||||
focusTarget?.focus();
|
||||
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (event.key === "Escape" && canClose) {
|
||||
if (event.key === "Escape" && canCloseRef.current) {
|
||||
event.preventDefault();
|
||||
onClose();
|
||||
onCloseRef.current();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +132,7 @@ function useDialogLifecycle(open: boolean, onClose: () => void, canClose: boolea
|
||||
document.body.style.overflow = previousBodyOverflow;
|
||||
previousFocus?.focus();
|
||||
};
|
||||
}, [canClose, onClose, open]);
|
||||
}, [open]);
|
||||
|
||||
return panelRef;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { projectRuntimeTrackedJob, runtimeBuildStages } from "./RuntimeTaskProgress";
|
||||
import { projectRuntimeTrackedJob, runtimeBuildStages, runtimeRunBuildStages } from "./RuntimeTaskProgress";
|
||||
|
||||
describe("distribution build job progress", () => {
|
||||
it("projects worker progress messages onto the real build stage", () => {
|
||||
@@ -51,4 +51,16 @@ describe("distribution build job progress", () => {
|
||||
expect(projection.status).toBe("running");
|
||||
expect(projection.message).toContain("第 2 次尝试");
|
||||
});
|
||||
|
||||
it("projects run generation onto the compact four-step run build flow", () => {
|
||||
const projection = projectRuntimeTrackedJob(runtimeRunBuildStages, {
|
||||
id: "job-run-build",
|
||||
state: "running",
|
||||
progress: { percent: 72, message: "build_compile: compiling run target" }
|
||||
});
|
||||
|
||||
expect(runtimeRunBuildStages.map((stage) => stage.label)).toEqual(["拉取 run 更新", "检测构建环境", "构建中", "构建完成"]);
|
||||
expect(projection).toMatchObject({ status: "running", currentStageKey: "build_compile" });
|
||||
expect(projection.stageStatus).toMatchObject({ git_sync: "completed", env_check: "completed", build_compile: "running", package_finalize: "pending" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,6 +45,7 @@ export interface RuntimeTaskDialogAction {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
title?: string;
|
||||
kind?: "primary" | "default" | "danger";
|
||||
}
|
||||
|
||||
@@ -56,6 +57,13 @@ export const runtimeBuildStages: RuntimeTaskStage[] = [
|
||||
{ key: "package_finalize", label: "打包成功", description: "注入配置、校验 checksum、生成 artifact。" }
|
||||
];
|
||||
|
||||
export const runtimeRunBuildStages: RuntimeTaskStage[] = [
|
||||
{ key: "git_sync", label: "拉取 run 更新", description: "同步平台批准的 run 源码或发布版本。" },
|
||||
{ key: "env_check", label: "检测构建环境", description: "检查 Go 工具链、目标平台和隔离构建目录。" },
|
||||
{ key: "build_compile", label: "构建中", description: "编译所选平台和架构的 run 二进制。" },
|
||||
{ key: "package_finalize", label: "构建完成", description: "校验 checksum、生成 artifact,并登记可下载产物。" }
|
||||
];
|
||||
|
||||
export const runtimeDownloadStages: RuntimeTaskStage[] = [
|
||||
{ key: "scope_check", label: "权限校验", description: "确认当前服务器范围和 artifact 授权。" },
|
||||
{ key: "artifact_lookup", label: "定位产物", description: "读取最新可下载 run 包引用。" },
|
||||
@@ -319,6 +327,8 @@ export function RuntimeTaskProgressDialog({ task, onClose, actions = [] }: Runti
|
||||
}
|
||||
|
||||
const activeStage = task.stages.find((stage) => stage.key === task.currentStageKey) ?? task.stages[0];
|
||||
const activeStageIndex = Math.max(0, task.stages.findIndex((stage) => stage.key === activeStage?.key));
|
||||
const activeStepLabel = task.stages.length > 0 ? `第 ${activeStageIndex + 1}/${task.stages.length} 步` : "无阶段";
|
||||
const statusLabel = runtimeTaskStatusLabel(task.status);
|
||||
const closeLabel = task.status === "running" ? "后台运行" : "关闭";
|
||||
|
||||
@@ -338,6 +348,12 @@ export function RuntimeTaskProgressDialog({ task, onClose, actions = [] }: Runti
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="runtime-task-progress-summary" aria-label="进度展示">
|
||||
<span>进度展示</span>
|
||||
<strong>{activeStage ? `${activeStepLabel} · ${activeStage.label}` : statusLabel}</strong>
|
||||
<small>{task.status === "running" ? "平台正在执行当前阶段,完成后会自动推进下一步。" : task.status === "succeeded" ? "所有阶段已完成,可下载产物或继续后续操作。" : "任务已停止,请查看失败原因和运行日志。"}</small>
|
||||
</div>
|
||||
|
||||
<div className="runtime-task-meter" aria-label={`${task.title} 进度 ${Math.round(task.percent)}%`}>
|
||||
<div className="runtime-task-meter-row">
|
||||
<span>{activeStage?.label ?? statusLabel}</span>
|
||||
@@ -401,6 +417,7 @@ export function RuntimeTaskProgressDialog({ task, onClose, actions = [] }: Runti
|
||||
type="button"
|
||||
className={cx(action.kind === "primary" && "confirm-primary", action.kind === "danger" && "confirm-danger")}
|
||||
disabled={action.disabled || task.status === "running"}
|
||||
title={action.title ?? action.label}
|
||||
onClick={action.onClick}
|
||||
>
|
||||
{action.label.includes("下载") ? <Download size={14} /> : <PackageCheck size={14} />}
|
||||
|
||||
Reference in New Issue
Block a user