refactor: useSchedulesStore.ts
This commit is contained in:
@@ -0,0 +1,659 @@
|
||||
// List slice: schedule list, artifact list, current schedule, loading/busy flags,
|
||||
// and most mutation/loader actions. Filter inputs (scheduleKeyword/artifactKeyword)
|
||||
// live in canvasSlice since they bind to the left-panel UI. Cross-slice actions
|
||||
// live here when their primary write is to LIST fields; they reach into
|
||||
// CANVAS / DIALOG / RUNS via `get()`.
|
||||
|
||||
import { ApiRequestError, type Schedule, type ScheduleArtifact, type ScheduleEdge, type ScheduleNode } from "../../../services/api";
|
||||
import type { StateCreator } from "zustand";
|
||||
|
||||
import {
|
||||
applyPositionDrafts,
|
||||
applyServerUpdatedSchedule,
|
||||
artifactNodeKey,
|
||||
notify,
|
||||
parseObject,
|
||||
requireApi,
|
||||
setApiOnline,
|
||||
scheduleToForm,
|
||||
withMutation,
|
||||
} from "./helpers";
|
||||
import {
|
||||
PYTHON_VERSION_OPTIONS,
|
||||
type NodeForm,
|
||||
type ScheduleForm,
|
||||
} from "./types";
|
||||
import type { SchedulesStore } from "./useSchedulesStore";
|
||||
|
||||
// ---- State ----
|
||||
|
||||
export type ListSliceState = {
|
||||
schedules: Schedule[];
|
||||
artifacts: ScheduleArtifact[];
|
||||
schedule: Schedule | null;
|
||||
loading: boolean;
|
||||
busy: string | null;
|
||||
};
|
||||
|
||||
// ---- Actions ----
|
||||
|
||||
export type ListSliceActions = {
|
||||
// pure setters
|
||||
setSchedule: (
|
||||
value: Schedule | null | ((current: Schedule | null) => Schedule | null),
|
||||
) => void;
|
||||
setSchedules: (
|
||||
value: Schedule[] | ((current: Schedule[]) => Schedule[]),
|
||||
) => void;
|
||||
setArtifacts: (
|
||||
value: ScheduleArtifact[]
|
||||
| ((current: ScheduleArtifact[]) => ScheduleArtifact[]),
|
||||
) => void;
|
||||
setLoading: (loading: boolean) => void;
|
||||
setBusy: (busy: string | null) => void;
|
||||
|
||||
// data loaders
|
||||
loadInitial: () => Promise<void>;
|
||||
refreshLists: (preferredScheduleId?: string | null) => Promise<void>;
|
||||
chooseSchedule: (scheduleId: string) => Promise<void>;
|
||||
|
||||
// mutations
|
||||
addSchedule: (name: string) => Promise<void>;
|
||||
removeSchedule: (target?: Schedule) => Promise<void>;
|
||||
renameSchedule: (target: Schedule) => Promise<void>;
|
||||
removeArtifact: (artifact: ScheduleArtifact) => Promise<void>;
|
||||
saveSchedule: () => Promise<void>;
|
||||
runNow: () => Promise<void>;
|
||||
addArtifactAt: (
|
||||
artifact: ScheduleArtifact,
|
||||
positionX: number,
|
||||
positionY: number,
|
||||
) => Promise<void>;
|
||||
saveNode: (selectedNode: ScheduleNode | null) => Promise<void>;
|
||||
removeNode: (target?: ScheduleNode) => Promise<void>;
|
||||
removeEdge: (target?: ScheduleEdge) => Promise<void>;
|
||||
checkDag: () => Promise<void>;
|
||||
connectTo: (targetNodeId: string) => Promise<void>;
|
||||
};
|
||||
|
||||
// `SchedulesStore` is the combined state+actions of all slices, so `get()`
|
||||
// returns a fully typed snapshot — no `any` propagation.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const createListSlice: StateCreator<SchedulesStore, [], [], ListSliceState & ListSliceActions> = (set, get) => {
|
||||
// ---- initial state ----
|
||||
const state: ListSliceState = {
|
||||
schedules: [],
|
||||
artifacts: [],
|
||||
schedule: null,
|
||||
loading: true,
|
||||
busy: null,
|
||||
};
|
||||
|
||||
return {
|
||||
...state,
|
||||
|
||||
// ---- pure setters ----
|
||||
|
||||
setSchedule: (value) =>
|
||||
set((s: any) => ({
|
||||
schedule:
|
||||
typeof value === "function"
|
||||
? (value as (current: Schedule | null) => Schedule | null)(s.schedule)
|
||||
: value,
|
||||
})),
|
||||
setSchedules: (value) =>
|
||||
set((s: any) => ({
|
||||
schedules:
|
||||
typeof value === "function"
|
||||
? (value as (current: Schedule[]) => Schedule[])(s.schedules)
|
||||
: value,
|
||||
})),
|
||||
setArtifacts: (value) =>
|
||||
set((s: any) => ({
|
||||
artifacts:
|
||||
typeof value === "function"
|
||||
? (value as (current: ScheduleArtifact[]) => ScheduleArtifact[])(
|
||||
s.artifacts,
|
||||
)
|
||||
: value,
|
||||
})),
|
||||
setLoading: (loading) => set({ loading }),
|
||||
setBusy: (busy) => set({ busy }),
|
||||
|
||||
// ---- data loaders ----
|
||||
|
||||
loadInitial: async () => {
|
||||
const api = requireApi();
|
||||
let cancelled = false;
|
||||
set({ loading: true });
|
||||
try {
|
||||
const [scheduleItems, artifactItems] = await Promise.all([
|
||||
api.listSchedules(),
|
||||
api.listScheduleArtifacts(),
|
||||
]);
|
||||
if (cancelled) return;
|
||||
set({ schedules: scheduleItems, artifacts: artifactItems });
|
||||
if (scheduleItems[0]) {
|
||||
const detail = await api.getSchedule(scheduleItems[0].schedule_id);
|
||||
if (!cancelled) {
|
||||
set({ schedule: applyPositionDrafts(detail) });
|
||||
}
|
||||
}
|
||||
setApiOnline(true);
|
||||
} catch (error) {
|
||||
if (cancelled) return;
|
||||
setApiOnline(false);
|
||||
notify({
|
||||
tone: "error",
|
||||
message: error instanceof Error ? error.message : "调度数据加载失败",
|
||||
});
|
||||
} finally {
|
||||
if (!cancelled) set({ loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
refreshLists: async (preferredScheduleId) => {
|
||||
const api = requireApi();
|
||||
const [scheduleItems, artifactItems] = await Promise.all([
|
||||
api.listSchedules(),
|
||||
api.listScheduleArtifacts(),
|
||||
]);
|
||||
set({ schedules: scheduleItems, artifacts: artifactItems });
|
||||
const targetId =
|
||||
preferredScheduleId
|
||||
?? get().schedule?.schedule_id
|
||||
?? scheduleItems[0]?.schedule_id
|
||||
?? null;
|
||||
if (!targetId) {
|
||||
set({ schedule: null });
|
||||
return;
|
||||
}
|
||||
const detail = await api.getSchedule(targetId);
|
||||
set({ schedule: applyPositionDrafts(detail) });
|
||||
},
|
||||
|
||||
chooseSchedule: async (scheduleId) => {
|
||||
const api = requireApi();
|
||||
const current = get().schedule;
|
||||
if (scheduleId === current?.schedule_id || get().busy) return;
|
||||
set({ busy: "load-schedule" });
|
||||
get().clearAllPositionDrafts();
|
||||
set({
|
||||
selectedNodeId: null,
|
||||
selectedEdgeId: null,
|
||||
linkSourceId: null,
|
||||
cronResult: null,
|
||||
runs: [],
|
||||
runsLoading: false,
|
||||
});
|
||||
try {
|
||||
set({ schedule: await api.getSchedule(scheduleId) });
|
||||
setApiOnline(true);
|
||||
} catch (error) {
|
||||
const { handleError } = await import("./helpers");
|
||||
await handleError(get, error, "调度详情加载失败");
|
||||
} finally {
|
||||
set({ busy: null });
|
||||
}
|
||||
},
|
||||
|
||||
// ---- mutations ----
|
||||
|
||||
addSchedule: async (name) => {
|
||||
const api = requireApi();
|
||||
const scheduleName = name.trim();
|
||||
if (get().busy || !scheduleName) return;
|
||||
set({ busy: "create-schedule" });
|
||||
try {
|
||||
const created = await api.createSchedule({
|
||||
schedule_name: scheduleName,
|
||||
description: "在画布中拖入稳定版本并配置执行顺序",
|
||||
trigger_type: "manual",
|
||||
timezone: "Asia/Shanghai",
|
||||
enabled: false,
|
||||
});
|
||||
set((s: any) => ({
|
||||
schedules: [created, ...s.schedules],
|
||||
schedule: created,
|
||||
createDialogOpen: false,
|
||||
newScheduleName: "",
|
||||
}));
|
||||
get().clearAllPositionDrafts();
|
||||
set({ selectedNodeId: null, selectedEdgeId: null });
|
||||
notify({ tone: "success", message: "调度方案已创建" });
|
||||
} catch (error) {
|
||||
const { handleError } = await import("./helpers");
|
||||
await handleError(get, error, "创建调度失败");
|
||||
} finally {
|
||||
set({ busy: null });
|
||||
}
|
||||
},
|
||||
|
||||
removeSchedule: async (target) => {
|
||||
const api = requireApi();
|
||||
const target_ = target ?? get().schedule;
|
||||
if (!target_ || get().busy) return;
|
||||
set({ contextMenu: null });
|
||||
if (!window.confirm(`确定删除调度"${target_.schedule_name}"吗?`)) return;
|
||||
set({ busy: "delete-schedule" });
|
||||
try {
|
||||
await api.deleteSchedule(target_.schedule_id, target_.workflow_version);
|
||||
const remaining = get().schedules.filter(
|
||||
(item) => item.schedule_id !== target_.schedule_id,
|
||||
);
|
||||
set({ schedules: remaining });
|
||||
if (get().schedule?.schedule_id === target_.schedule_id) {
|
||||
get().clearAllPositionDrafts();
|
||||
set({
|
||||
schedule: null,
|
||||
selectedNodeId: null,
|
||||
selectedEdgeId: null,
|
||||
});
|
||||
if (remaining[0]) {
|
||||
const detail = await api.getSchedule(remaining[0].schedule_id);
|
||||
set({ schedule: detail });
|
||||
}
|
||||
}
|
||||
notify({ tone: "success", message: "调度方案已删除" });
|
||||
} catch (error) {
|
||||
const { handleError } = await import("./helpers");
|
||||
await handleError(get, error, "删除调度失败");
|
||||
} finally {
|
||||
set({ busy: null });
|
||||
}
|
||||
},
|
||||
|
||||
renameSchedule: async (target) => {
|
||||
const api = requireApi();
|
||||
if (get().busy) return;
|
||||
set({ contextMenu: null });
|
||||
const scheduleName = window
|
||||
.prompt("请输入新的调度方案名称", target.schedule_name)
|
||||
?.trim();
|
||||
if (!scheduleName || scheduleName === target.schedule_name) return;
|
||||
set({ busy: "rename-schedule" });
|
||||
try {
|
||||
const updated = await api.updateSchedule(target.schedule_id, {
|
||||
workflow_version: target.workflow_version,
|
||||
schedule_name: scheduleName,
|
||||
});
|
||||
set((s: any) => ({
|
||||
schedules: s.schedules.map((item: Schedule) =>
|
||||
item.schedule_id === updated.schedule_id
|
||||
? { ...updated, nodes: [], edges: [] }
|
||||
: item
|
||||
),
|
||||
schedule:
|
||||
s.schedule?.schedule_id === updated.schedule_id
|
||||
? updated
|
||||
: s.schedule,
|
||||
}));
|
||||
notify({ tone: "success", message: "调度方案已改名" });
|
||||
} catch (error) {
|
||||
const { handleError } = await import("./helpers");
|
||||
await handleError(get, error, "调度方案改名失败");
|
||||
} finally {
|
||||
set({ busy: null });
|
||||
}
|
||||
},
|
||||
|
||||
removeArtifact: async (artifact) => {
|
||||
const api = requireApi();
|
||||
if (get().busy) return;
|
||||
set({ contextMenu: null });
|
||||
if (
|
||||
!window.confirm(
|
||||
`确定将"${artifact.script_name} ${artifact.version_label}"移出调度列表吗?\n`
|
||||
+ "稳定版本本身和历史运行记录不会被删除。",
|
||||
)
|
||||
) return;
|
||||
set({ busy: "delete-artifact" });
|
||||
try {
|
||||
await api.hideScheduleArtifact(artifact.versions_id);
|
||||
set((s: any) => ({
|
||||
artifacts: s.artifacts.filter(
|
||||
(item: ScheduleArtifact) => item.versions_id !== artifact.versions_id,
|
||||
),
|
||||
}));
|
||||
notify({
|
||||
tone: "success",
|
||||
message: "已移出调度列表,稳定版本和历史记录保持不变",
|
||||
});
|
||||
} catch (error) {
|
||||
const { handleError } = await import("./helpers");
|
||||
await handleError(get, error, "移出调度列表失败");
|
||||
} finally {
|
||||
set({ busy: null });
|
||||
}
|
||||
},
|
||||
|
||||
saveSchedule: async () => {
|
||||
const api = requireApi();
|
||||
const state_ = get();
|
||||
const { schedule, scheduleForm } = state_;
|
||||
if (!schedule || state_.busy) return;
|
||||
const scheduleFormTyped = scheduleForm as ScheduleForm;
|
||||
const maxConcurrency = Number(scheduleFormTyped.maxConcurrency);
|
||||
if (!scheduleFormTyped.scheduleName.trim()) {
|
||||
notify({ tone: "error", message: "调度名称不能为空" });
|
||||
return;
|
||||
}
|
||||
if (!Number.isInteger(maxConcurrency) || maxConcurrency < 1) {
|
||||
notify({ tone: "error", message: "最大并发数必须是正整数" });
|
||||
return;
|
||||
}
|
||||
set({ busy: "save-schedule" });
|
||||
let updated: Schedule = schedule;
|
||||
try {
|
||||
const { positionDrafts } = await import("./helpers");
|
||||
for (const [nodeId, position] of positionDrafts.entries()) {
|
||||
updated = await api.updateScheduleNode(updated.schedule_id, nodeId, {
|
||||
workflow_version: updated.workflow_version,
|
||||
position_x: position.position_x,
|
||||
position_y: position.position_y,
|
||||
});
|
||||
}
|
||||
updated = await api.updateSchedule(updated.schedule_id, {
|
||||
workflow_version: updated.workflow_version,
|
||||
schedule_name: scheduleFormTyped.scheduleName.trim(),
|
||||
description: scheduleFormTyped.description.trim() || null,
|
||||
trigger_type: scheduleFormTyped.triggerType,
|
||||
cron_expression:
|
||||
scheduleFormTyped.triggerType === "cron"
|
||||
? scheduleFormTyped.cronExpression.trim()
|
||||
: null,
|
||||
timezone: scheduleFormTyped.timezone.trim(),
|
||||
enabled: scheduleFormTyped.enabled,
|
||||
max_concurrency: maxConcurrency,
|
||||
failure_policy: scheduleFormTyped.failurePolicy,
|
||||
});
|
||||
get().clearAllPositionDrafts();
|
||||
set((s: any) => ({
|
||||
schedule: updated,
|
||||
schedules: s.schedules.map((item: Schedule) =>
|
||||
item.schedule_id === updated.schedule_id
|
||||
? { ...updated, nodes: [], edges: [] }
|
||||
: item
|
||||
),
|
||||
}));
|
||||
|
||||
notify({ tone: "success", message: "调度配置已保存" });
|
||||
} catch (error) {
|
||||
const localDraft = applyPositionDrafts(updated);
|
||||
set({ schedule: localDraft });
|
||||
const { handleError } = await import("./helpers");
|
||||
await handleError(get, error, "调度配置保存失败");
|
||||
} finally {
|
||||
set({ busy: null });
|
||||
}
|
||||
},
|
||||
|
||||
runNow: async () => {
|
||||
const api = requireApi();
|
||||
const state_ = get();
|
||||
const { schedule, positionDraftCount } = state_;
|
||||
if (!schedule || state_.busy) return;
|
||||
if (positionDraftCount > 0) {
|
||||
notify({
|
||||
tone: "info",
|
||||
message: "还有节点位置未保存,请先点击'保存配置'再运行",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!schedule.dag_validation.valid || schedule.nodes.length === 0) {
|
||||
notify({
|
||||
tone: "error",
|
||||
message: "当前调度必须包含有效的非空 DAG 才能运行",
|
||||
});
|
||||
return;
|
||||
}
|
||||
set({ busy: "run-now" });
|
||||
try {
|
||||
const created = await api.runScheduleNow(schedule.schedule_id);
|
||||
set((s: any) => ({
|
||||
runs: [
|
||||
created,
|
||||
...s.runs.filter((item: { run_id: string }) => item.run_id !== created.run_id),
|
||||
].slice(0, 20),
|
||||
schedule:
|
||||
s.schedule
|
||||
? { ...s.schedule, last_run_at: created.queued_at }
|
||||
: s.schedule,
|
||||
schedules: s.schedules.map((item: Schedule) =>
|
||||
item.schedule_id === schedule.schedule_id
|
||||
? { ...item, last_run_at: created.queued_at }
|
||||
: item
|
||||
),
|
||||
}));
|
||||
notify({
|
||||
tone: "success",
|
||||
message: `运行 ${created.run_id.slice(-8)} 已进入队列`,
|
||||
});
|
||||
} catch (error) {
|
||||
const { handleError } = await import("./helpers");
|
||||
await handleError(get, error, "立即运行失败");
|
||||
} finally {
|
||||
set({ busy: null });
|
||||
}
|
||||
},
|
||||
|
||||
addArtifactAt: async (artifact, positionX, positionY) => {
|
||||
const state_ = get();
|
||||
const { schedule } = state_;
|
||||
if (!schedule) {
|
||||
notify({ tone: "info", message: "请先新建或选择一个调度方案" });
|
||||
return;
|
||||
}
|
||||
const nodeKey = artifactNodeKey(artifact, schedule);
|
||||
const updated = await withMutation(
|
||||
set,
|
||||
get,
|
||||
"add-node",
|
||||
() => requireApi().createScheduleNode(schedule.schedule_id, {
|
||||
workflow_version: schedule.workflow_version,
|
||||
node_key: nodeKey,
|
||||
node_name: artifact.script_name,
|
||||
versions_id: artifact.versions_id,
|
||||
timeout_seconds: 600,
|
||||
retry_count: 0,
|
||||
retry_interval_sec: 5,
|
||||
position_x: Math.max(20, Math.round(positionX)),
|
||||
position_y: Math.max(20, Math.round(positionY)),
|
||||
arguments_json: {},
|
||||
env_refs_json: {},
|
||||
python_version: "3.12",
|
||||
}),
|
||||
`${artifact.script_name} 已加入画布`,
|
||||
);
|
||||
if (updated) {
|
||||
const created = updated.nodes.find((item) => item.node_key === nodeKey);
|
||||
set({ selectedNodeId: created?.node_id ?? null });
|
||||
}
|
||||
},
|
||||
|
||||
saveNode: async (selectedNode) => {
|
||||
const api = requireApi();
|
||||
const state_ = get();
|
||||
const { schedule, nodeForm } = state_;
|
||||
if (!schedule || !selectedNode) return;
|
||||
try {
|
||||
const nodeFormTyped = nodeForm as NodeForm;
|
||||
const timeoutSeconds = Number(nodeFormTyped.timeoutSeconds);
|
||||
const retryCount = Number(nodeFormTyped.retryCount);
|
||||
const retryIntervalSec = Number(nodeFormTyped.retryIntervalSec);
|
||||
if (!nodeFormTyped.nodeName.trim()) throw new Error("节点名称不能为空");
|
||||
if (!Number.isInteger(timeoutSeconds) || timeoutSeconds < 1) {
|
||||
throw new Error("超时时间必须是正整数");
|
||||
}
|
||||
if (!Number.isInteger(retryCount) || retryCount < 0) {
|
||||
throw new Error("重试次数必须是非负整数");
|
||||
}
|
||||
if (!Number.isInteger(retryIntervalSec) || retryIntervalSec < 0) {
|
||||
throw new Error("重试间隔必须是非负整数");
|
||||
}
|
||||
if (!PYTHON_VERSION_OPTIONS.includes(nodeFormTyped.pythonVersion)) {
|
||||
throw new Error("Python 版本必须是 3.8 / 3.10 / 3.12");
|
||||
}
|
||||
const argumentsJson = parseObject(nodeFormTyped.argumentsJson, "运行参数");
|
||||
const rawEnv = parseObject(nodeFormTyped.envRefsJson, "环境引用");
|
||||
const envRefsJson = Object.fromEntries(
|
||||
Object.entries(rawEnv).map(([key, value]) => {
|
||||
if (typeof value !== "string") {
|
||||
throw new Error("环境引用的值必须是字符串");
|
||||
}
|
||||
return [key, value];
|
||||
}),
|
||||
);
|
||||
await withMutation(
|
||||
set,
|
||||
get,
|
||||
"save-node",
|
||||
() =>
|
||||
api.updateScheduleNode(
|
||||
schedule.schedule_id,
|
||||
selectedNode.node_id,
|
||||
{
|
||||
workflow_version: schedule.workflow_version,
|
||||
node_name: nodeFormTyped.nodeName.trim(),
|
||||
timeout_seconds: timeoutSeconds,
|
||||
retry_count: retryCount,
|
||||
retry_interval_sec: retryIntervalSec,
|
||||
arguments_json: argumentsJson,
|
||||
env_refs_json: envRefsJson,
|
||||
python_version: nodeFormTyped.pythonVersion,
|
||||
},
|
||||
),
|
||||
"节点配置已保存",
|
||||
);
|
||||
} catch (error) {
|
||||
const { handleError } = await import("./helpers");
|
||||
await handleError(get, error, "节点配置保存失败");
|
||||
}
|
||||
},
|
||||
|
||||
removeNode: async (target) => {
|
||||
const api = requireApi();
|
||||
const state_ = get();
|
||||
const { schedule, selectedNodeId } = state_;
|
||||
const node = target
|
||||
?? schedule?.nodes.find((item) => item.node_id === selectedNodeId)
|
||||
?? null;
|
||||
if (!schedule || !node || state_.busy) return;
|
||||
set({ contextMenu: null });
|
||||
if (!window.confirm(`确定删除节点"${node.node_name}"吗?`)) return;
|
||||
set({ busy: "delete-node" });
|
||||
try {
|
||||
let updated: Schedule;
|
||||
try {
|
||||
// 先走普通删除:没有历史记录时不额外打扰用户。
|
||||
updated = await api.deleteScheduleNode(
|
||||
schedule.schedule_id,
|
||||
node.node_id,
|
||||
schedule.workflow_version,
|
||||
);
|
||||
} catch (error) {
|
||||
const requiresHistoryConfirmation =
|
||||
error instanceof ApiRequestError
|
||||
&& error.status === 409
|
||||
&& error.code === "node_execution_history_exists";
|
||||
if (!requiresHistoryConfirmation) throw error;
|
||||
if (!window.confirm("该节点有运行日志,是否一并删除?")) return;
|
||||
updated = await api.deleteScheduleNode(
|
||||
schedule.schedule_id,
|
||||
node.node_id,
|
||||
schedule.workflow_version,
|
||||
{ delete_execution_history: true },
|
||||
);
|
||||
}
|
||||
applyServerUpdatedSchedule(set, updated);
|
||||
if (selectedNodeId === node.node_id) set({ selectedNodeId: null });
|
||||
notify({ tone: "success", message: "节点及其运行日志已删除" });
|
||||
} catch (error) {
|
||||
const { handleError } = await import("./helpers");
|
||||
await handleError(get, error, "删除节点失败");
|
||||
} finally {
|
||||
set({ busy: null });
|
||||
}
|
||||
},
|
||||
|
||||
removeEdge: async (target) => {
|
||||
const api = requireApi();
|
||||
const state_ = get();
|
||||
const { schedule, selectedEdgeId } = state_;
|
||||
const edge = target
|
||||
?? schedule?.edges.find((item) => item.edge_id === selectedEdgeId)
|
||||
?? null;
|
||||
if (!schedule || !edge || state_.busy) return;
|
||||
set({ contextMenu: null });
|
||||
const updated = await withMutation(
|
||||
set,
|
||||
get,
|
||||
"delete-edge",
|
||||
() =>
|
||||
api.deleteScheduleEdge(
|
||||
schedule.schedule_id,
|
||||
edge.edge_id,
|
||||
schedule.workflow_version,
|
||||
),
|
||||
"连线已删除",
|
||||
);
|
||||
if (updated && selectedEdgeId === edge.edge_id) {
|
||||
set({ selectedEdgeId: null });
|
||||
}
|
||||
},
|
||||
|
||||
checkDag: async () => {
|
||||
const api = requireApi();
|
||||
const { schedule } = get();
|
||||
if (!schedule || get().busy) return;
|
||||
set({ busy: "validate" });
|
||||
try {
|
||||
const result = await api.validateSchedule(schedule.schedule_id);
|
||||
set((s: any) => ({
|
||||
schedule: s.schedule
|
||||
? { ...s.schedule, dag_validation: result }
|
||||
: s.schedule,
|
||||
}));
|
||||
notify({
|
||||
tone: result.valid ? "success" : "error",
|
||||
message: result.valid
|
||||
? "当前 DAG 通过校验"
|
||||
: "当前 DAG 存在校验问题",
|
||||
});
|
||||
} catch (error) {
|
||||
const { handleError } = await import("./helpers");
|
||||
await handleError(get, error, "DAG 校验失败");
|
||||
} finally {
|
||||
set({ busy: null });
|
||||
}
|
||||
},
|
||||
|
||||
connectTo: async (targetNodeId) => {
|
||||
const state_ = get();
|
||||
const { schedule, linkSourceId } = state_;
|
||||
if (!schedule || !linkSourceId || state_.busy) return;
|
||||
if (linkSourceId === targetNodeId) {
|
||||
set({ linkSourceId: null });
|
||||
notify({ tone: "info", message: "已取消连线" });
|
||||
return;
|
||||
}
|
||||
const sourceId = linkSourceId;
|
||||
set({ linkSourceId: null });
|
||||
await withMutation(
|
||||
set,
|
||||
get,
|
||||
"create-edge",
|
||||
() =>
|
||||
requireApi().createScheduleEdge(schedule.schedule_id, {
|
||||
workflow_version: schedule.workflow_version,
|
||||
source_node_id: sourceId,
|
||||
target_node_id: targetNodeId,
|
||||
}),
|
||||
"节点连线已创建",
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// (avoid unused-import warning for scheduleToForm — referenced for parity with original store)
|
||||
void scheduleToForm;
|
||||
Reference in New Issue
Block a user