@@ -156,32 +94,27 @@ const WorkflowDetailDesign = () => {
-
- } loading={isPendingOrRunning} type="primary" onClick={handleRunClick}>
- {t("workflow.action.run.button")}
+
+
-
-
- ,
- onClick: handleRollbackClick,
- },
- ],
- }}
- trigger={["click"]}
- >
- } variant="outlined" />
-
-
-
+ ,
+ onClick: handleRollbackClick,
+ },
+ ],
+ }}
+ trigger={["click"]}
+ >
+ } variant="outlined" />
+
+
diff --git a/ui/src/stores/access/index.ts b/ui/src/stores/access/index.ts
index 207fc1fea..c51fa3bf7 100644
--- a/ui/src/stores/access/index.ts
+++ b/ui/src/stores/access/index.ts
@@ -4,18 +4,9 @@ import { create } from "zustand";
import { type AccessModel } from "@/domain/access";
import { list as listAccesses, remove as removeAccess, save as saveAccess } from "@/repository/access";
-export interface AccessesState {
- accesses: AccessModel[];
- loading: boolean;
- loadedAtOnce: boolean;
+import { type AccessesState, type AccessesStore } from "./types";
- fetchAccesses: () => Promise
;
- createAccess: (access: MaybeModelRecord) => Promise;
- updateAccess: (access: MaybeModelRecordWithId) => Promise;
- deleteAccess: (access: MaybeModelRecordWithId | MaybeModelRecordWithId[]) => Promise;
-}
-
-export const useAccessesStore = create((set) => {
+export const useAccessesStore = create((set) => {
let fetcher: Promise | null = null; // 防止多次重复请求
return {
diff --git a/ui/src/stores/access/types.ts b/ui/src/stores/access/types.ts
new file mode 100644
index 000000000..2c987beb6
--- /dev/null
+++ b/ui/src/stores/access/types.ts
@@ -0,0 +1,16 @@
+import { type AccessModel } from "@/domain/access";
+
+export interface AccessesState {
+ accesses: AccessModel[];
+ loading: boolean;
+ loadedAtOnce: boolean;
+}
+
+export interface AccessesActions {
+ fetchAccesses: () => Promise;
+ createAccess: (access: MaybeModelRecord) => Promise;
+ updateAccess: (access: MaybeModelRecordWithId) => Promise;
+ deleteAccess: (access: MaybeModelRecordWithId | MaybeModelRecordWithId[]) => Promise;
+}
+
+export interface AccessesStore extends AccessesState, AccessesActions {}
diff --git a/ui/src/stores/contact/index.ts b/ui/src/stores/contact/index.ts
index ae8e25d20..e554bfb8b 100644
--- a/ui/src/stores/contact/index.ts
+++ b/ui/src/stores/contact/index.ts
@@ -4,18 +4,9 @@ import { create } from "zustand";
import { type EmailsSettingsContent, SETTINGS_NAMES, type SettingsModel } from "@/domain/settings";
import { get as getSettings, save as saveSettings } from "@/repository/settings";
-export interface ContactEmailsState {
- emails: string[];
- loading: boolean;
- loadedAtOnce: boolean;
+import { type ContactEmailsState, type ContactEmailsStore } from "./types";
- fetchEmails: () => Promise;
- setEmails: (emails: string[]) => Promise;
- addEmail: (email: string) => Promise;
- removeEmail: (email: string) => Promise;
-}
-
-export const useContactEmailsStore = create((set, get) => {
+export const useContactEmailsStore = create((set, get) => {
let fetcher: Promise> | null = null; // 防止多次重复请求
let settings: SettingsModel; // 记录当前设置的其他字段,保存回数据库时用
diff --git a/ui/src/stores/contact/types.ts b/ui/src/stores/contact/types.ts
new file mode 100644
index 000000000..a1ee603ce
--- /dev/null
+++ b/ui/src/stores/contact/types.ts
@@ -0,0 +1,14 @@
+export interface ContactEmailsState {
+ emails: string[];
+ loading: boolean;
+ loadedAtOnce: boolean;
+}
+
+export interface ContactEmailsActions {
+ fetchEmails: () => Promise;
+ setEmails: (emails: string[]) => Promise;
+ addEmail: (email: string) => Promise;
+ removeEmail: (email: string) => Promise;
+}
+
+export interface ContactEmailsStore extends ContactEmailsState, ContactEmailsActions {}
diff --git a/ui/src/stores/workflow/index.ts b/ui/src/stores/workflow/index.ts
index 405a5bb20..962ec6a97 100644
--- a/ui/src/stores/workflow/index.ts
+++ b/ui/src/stores/workflow/index.ts
@@ -3,7 +3,6 @@ import { create } from "zustand";
import {
type WorkflowModel,
- type WorkflowNode,
type WorkflowNodeConfigForStart,
addBranch,
addNode,
@@ -14,280 +13,277 @@ import {
removeNode,
updateNode,
} from "@/domain/workflow";
-import { get as getWorkflow, save as saveWorkflow } from "@/repository/workflow";
+import { get as getWorkflow, save as saveWorkflow, subscribe as subscribeWorkflow } from "@/repository/workflow";
-export type WorkflowState = {
- workflow: WorkflowModel;
- initialized: boolean;
+import { type WorkflowState, type WorkflowStore } from "./types";
- init(id: string): void;
- setBaseInfo: (name: string, description: string) => void;
- setEnabled(enabled: boolean): void;
- publish(): void;
- rollback(): void;
- destroy(): void;
-
- addNode: (node: WorkflowNode, previousNodeId: string) => void;
- duplicateNode: (node: WorkflowNode) => void;
- updateNode: (node: WorkflowNode) => void;
- removeNode: (node: WorkflowNode) => void;
-
- addBranch: (branchId: string) => void;
- duplicateBranch: (branchId: string, index: number) => void;
- removeBranch: (branchId: string, index: number) => void;
-
- getWorkflowOuptutBeforeId: (nodeId: string, typeFilter?: string | string[]) => WorkflowNode[];
-};
-
-export const useWorkflowStore = create((set, get) => ({
- workflow: {} as WorkflowModel,
- initialized: false,
-
- init: async (id: string) => {
- const data = await getWorkflow(id);
-
- set({
- workflow: data,
- initialized: true,
- });
- },
-
- destroy: () => {
- set({
- workflow: {} as WorkflowModel,
- initialized: false,
- });
- },
-
- setBaseInfo: async (name: string, description: string) => {
+export const useWorkflowStore = create((set, get) => {
+ const ensureInitialized = () => {
if (!get().initialized) throw "Workflow not initialized yet";
+ };
- const resp = await saveWorkflow({
- id: get().workflow.id!,
- name: name || "",
- description: description || "",
- });
+ let unsubscriber: (() => void) | undefined = undefined;
- set((state: WorkflowState) => {
- return {
- workflow: produce(state.workflow, (draft) => {
- draft.name = resp.name;
- draft.description = resp.description;
- }),
- };
- });
- },
+ return {
+ workflow: {} as WorkflowModel,
+ initialized: false,
- setEnabled: async (enabled: boolean) => {
- if (!get().initialized) throw "Workflow not initialized yet";
+ init: async (id: string) => {
+ const data = await getWorkflow(id);
+ set({
+ workflow: data,
+ initialized: true,
+ });
- const resp = await saveWorkflow({
- id: get().workflow.id!,
- enabled: enabled,
- });
+ unsubscriber ??= await subscribeWorkflow(id, (cb) => {
+ if (cb.record.id !== get().workflow.id) return;
- set((state: WorkflowState) => {
- return {
- workflow: produce(state.workflow, (draft) => {
- draft.enabled = resp.enabled;
- }),
- };
- });
- },
+ set({
+ workflow: cb.record,
+ });
+ });
+ },
- publish: async () => {
- if (!get().initialized) throw "Workflow not initialized yet";
+ destroy: () => {
+ unsubscriber?.();
+ unsubscriber = undefined;
- const root = get().workflow.draft!;
- const startConfig = root.config as WorkflowNodeConfigForStart;
- const resp = await saveWorkflow({
- id: get().workflow.id!,
- trigger: startConfig.trigger,
- triggerCron: startConfig.triggerCron,
- content: root,
- hasDraft: false,
- });
+ set({
+ workflow: {} as WorkflowModel,
+ initialized: false,
+ });
+ },
- set((state: WorkflowState) => {
- return {
- workflow: produce(state.workflow, (draft) => {
- draft.trigger = resp.trigger;
- draft.triggerCron = resp.triggerCron;
- draft.content = resp.content;
- draft.draft = resp.draft;
- draft.hasDraft = resp.hasDraft;
- }),
- };
- });
- },
+ setBaseInfo: async (name, description) => {
+ ensureInitialized();
- rollback: async () => {
- if (!get().initialized) throw "Workflow not initialized yet";
+ const resp = await saveWorkflow({
+ id: get().workflow.id!,
+ name: name || "",
+ description: description || "",
+ });
- const root = get().workflow.content!;
- const startConfig = root.config as WorkflowNodeConfigForStart;
- const resp = await saveWorkflow({
- id: get().workflow.id!,
- draft: root,
- hasDraft: false,
- trigger: startConfig.trigger,
- triggerCron: startConfig.triggerCron,
- });
+ set((state: WorkflowState) => {
+ return {
+ workflow: produce(state.workflow, (draft) => {
+ draft.name = resp.name;
+ draft.description = resp.description;
+ }),
+ };
+ });
+ },
- set((state: WorkflowState) => {
- return {
- workflow: produce(state.workflow, (draft) => {
- draft.trigger = resp.trigger;
- draft.triggerCron = resp.triggerCron;
- draft.content = resp.content;
- draft.draft = resp.draft;
- draft.hasDraft = resp.hasDraft;
- }),
- };
- });
- },
+ setEnabled: async (enabled) => {
+ ensureInitialized();
- addNode: async (node: WorkflowNode, previousNodeId: string) => {
- if (!get().initialized) throw "Workflow not initialized yet";
+ const resp = await saveWorkflow({
+ id: get().workflow.id!,
+ enabled: enabled,
+ });
- const root = addNode(get().workflow.draft!, node, previousNodeId);
- const resp = await saveWorkflow({
- id: get().workflow.id!,
- draft: root,
- hasDraft: true,
- });
+ set((state: WorkflowState) => {
+ return {
+ workflow: produce(state.workflow, (draft) => {
+ draft.enabled = resp.enabled;
+ }),
+ };
+ });
+ },
- set((state: WorkflowState) => {
- return {
- workflow: produce(state.workflow, (draft) => {
- draft.draft = resp.draft;
- draft.hasDraft = resp.hasDraft;
- }),
- };
- });
- },
+ publish: async () => {
+ ensureInitialized();
- duplicateNode: async (node: WorkflowNode) => {
- if (!get().initialized) throw "Workflow not initialized yet";
+ const root = get().workflow.draft!;
+ const startConfig = root.config as WorkflowNodeConfigForStart;
+ const resp = await saveWorkflow({
+ id: get().workflow.id!,
+ trigger: startConfig.trigger,
+ triggerCron: startConfig.triggerCron,
+ content: root,
+ hasDraft: false,
+ });
- const root = duplicateNode(get().workflow.draft!, node);
- const resp = await saveWorkflow({
- id: get().workflow.id!,
- draft: root,
- hasDraft: true,
- });
+ set((state: WorkflowState) => {
+ return {
+ workflow: produce(state.workflow, (draft) => {
+ draft.trigger = resp.trigger;
+ draft.triggerCron = resp.triggerCron;
+ draft.content = resp.content;
+ draft.draft = resp.draft;
+ draft.hasDraft = resp.hasDraft;
+ }),
+ };
+ });
+ },
- set((state: WorkflowState) => {
- return {
- workflow: produce(state.workflow, (draft) => {
- draft.draft = resp.draft;
- draft.hasDraft = resp.hasDraft;
- }),
- };
- });
- },
+ rollback: async () => {
+ ensureInitialized();
- updateNode: async (node: WorkflowNode) => {
- if (!get().initialized) throw "Workflow not initialized yet";
+ const root = get().workflow.content!;
+ const startConfig = root.config as WorkflowNodeConfigForStart;
+ const resp = await saveWorkflow({
+ id: get().workflow.id!,
+ draft: root,
+ hasDraft: false,
+ trigger: startConfig.trigger,
+ triggerCron: startConfig.triggerCron,
+ });
- const root = updateNode(get().workflow.draft!, node);
- const resp = await saveWorkflow({
- id: get().workflow.id!,
- draft: root,
- hasDraft: true,
- });
+ set((state: WorkflowState) => {
+ return {
+ workflow: produce(state.workflow, (draft) => {
+ draft.trigger = resp.trigger;
+ draft.triggerCron = resp.triggerCron;
+ draft.content = resp.content;
+ draft.draft = resp.draft;
+ draft.hasDraft = resp.hasDraft;
+ }),
+ };
+ });
+ },
- set((state: WorkflowState) => {
- return {
- workflow: produce(state.workflow, (draft) => {
- draft.draft = resp.draft;
- draft.hasDraft = resp.hasDraft;
- }),
- };
- });
- },
+ addNode: async (node, previousNodeId) => {
+ ensureInitialized();
- removeNode: async (node: WorkflowNode) => {
- if (!get().initialized) throw "Workflow not initialized yet";
+ const root = addNode(get().workflow.draft!, node, previousNodeId);
+ const resp = await saveWorkflow({
+ id: get().workflow.id!,
+ draft: root,
+ hasDraft: true,
+ });
- const root = removeNode(get().workflow.draft!, node.id);
- const resp = await saveWorkflow({
- id: get().workflow.id!,
- draft: root,
- hasDraft: true,
- });
+ set((state: WorkflowState) => {
+ return {
+ workflow: produce(state.workflow, (draft) => {
+ draft.draft = resp.draft;
+ draft.hasDraft = resp.hasDraft;
+ }),
+ };
+ });
+ },
- set((state: WorkflowState) => {
- return {
- workflow: produce(state.workflow, (draft) => {
- draft.draft = resp.draft;
- draft.hasDraft = resp.hasDraft;
- }),
- };
- });
- },
+ duplicateNode: async (node) => {
+ ensureInitialized();
- addBranch: async (branchId: string) => {
- if (!get().initialized) throw "Workflow not initialized yet";
+ const root = duplicateNode(get().workflow.draft!, node);
+ const resp = await saveWorkflow({
+ id: get().workflow.id!,
+ draft: root,
+ hasDraft: true,
+ });
- const root = addBranch(get().workflow.draft!, branchId);
- const resp = await saveWorkflow({
- id: get().workflow.id!,
- draft: root,
- hasDraft: true,
- });
+ set((state: WorkflowState) => {
+ return {
+ workflow: produce(state.workflow, (draft) => {
+ draft.draft = resp.draft;
+ draft.hasDraft = resp.hasDraft;
+ }),
+ };
+ });
+ },
- set((state: WorkflowState) => {
- return {
- workflow: produce(state.workflow, (draft) => {
- draft.draft = resp.draft;
- draft.hasDraft = resp.hasDraft;
- }),
- };
- });
- },
+ updateNode: async (node) => {
+ ensureInitialized();
- duplicateBranch: async (branchId: string, index: number) => {
- if (!get().initialized) throw "Workflow not initialized yet";
+ const root = updateNode(get().workflow.draft!, node);
+ const resp = await saveWorkflow({
+ id: get().workflow.id!,
+ draft: root,
+ hasDraft: true,
+ });
- const root = duplicateBranch(get().workflow.draft!, branchId, index);
- const resp = await saveWorkflow({
- id: get().workflow.id!,
- draft: root,
- hasDraft: true,
- });
+ set((state: WorkflowState) => {
+ return {
+ workflow: produce(state.workflow, (draft) => {
+ draft.draft = resp.draft;
+ draft.hasDraft = resp.hasDraft;
+ }),
+ };
+ });
+ },
- set((state: WorkflowState) => {
- return {
- workflow: produce(state.workflow, (draft) => {
- draft.draft = resp.draft;
- draft.hasDraft = resp.hasDraft;
- }),
- };
- });
- },
+ removeNode: async (node) => {
+ ensureInitialized();
- removeBranch: async (branchId: string, index: number) => {
- if (!get().initialized) throw "Workflow not initialized yet";
+ const root = removeNode(get().workflow.draft!, node.id);
+ const resp = await saveWorkflow({
+ id: get().workflow.id!,
+ draft: root,
+ hasDraft: true,
+ });
- const root = removeBranch(get().workflow.draft!, branchId, index);
- const resp = await saveWorkflow({
- id: get().workflow.id!,
- draft: root,
- hasDraft: true,
- });
+ set((state: WorkflowState) => {
+ return {
+ workflow: produce(state.workflow, (draft) => {
+ draft.draft = resp.draft;
+ draft.hasDraft = resp.hasDraft;
+ }),
+ };
+ });
+ },
- set((state: WorkflowState) => {
- return {
- workflow: produce(state.workflow, (draft) => {
- draft.draft = resp.draft;
- draft.hasDraft = resp.hasDraft;
- }),
- };
- });
- },
+ addBranch: async (branchId) => {
+ ensureInitialized();
- getWorkflowOuptutBeforeId: (nodeId: string, typeFilter?: string | string[]) => {
- return getOutputBeforeNodeId(get().workflow.draft as WorkflowNode, nodeId, typeFilter);
- },
-}));
+ const root = addBranch(get().workflow.draft!, branchId);
+ const resp = await saveWorkflow({
+ id: get().workflow.id!,
+ draft: root,
+ hasDraft: true,
+ });
+
+ set((state: WorkflowState) => {
+ return {
+ workflow: produce(state.workflow, (draft) => {
+ draft.draft = resp.draft;
+ draft.hasDraft = resp.hasDraft;
+ }),
+ };
+ });
+ },
+
+ duplicateBranch: async (branchId, index) => {
+ ensureInitialized();
+
+ const root = duplicateBranch(get().workflow.draft!, branchId, index);
+ const resp = await saveWorkflow({
+ id: get().workflow.id!,
+ draft: root,
+ hasDraft: true,
+ });
+
+ set((state: WorkflowState) => {
+ return {
+ workflow: produce(state.workflow, (draft) => {
+ draft.draft = resp.draft;
+ draft.hasDraft = resp.hasDraft;
+ }),
+ };
+ });
+ },
+
+ removeBranch: async (branchId, index) => {
+ ensureInitialized();
+
+ const root = removeBranch(get().workflow.draft!, branchId, index);
+ const resp = await saveWorkflow({
+ id: get().workflow.id!,
+ draft: root,
+ hasDraft: true,
+ });
+
+ set((state: WorkflowState) => {
+ return {
+ workflow: produce(state.workflow, (draft) => {
+ draft.draft = resp.draft;
+ draft.hasDraft = resp.hasDraft;
+ }),
+ };
+ });
+ },
+
+ getWorkflowOuptutBeforeId: (nodeId, typeFilter) => {
+ return getOutputBeforeNodeId(get().workflow.draft!, nodeId, typeFilter);
+ },
+ };
+});
diff --git a/ui/src/stores/workflow/types.ts b/ui/src/stores/workflow/types.ts
new file mode 100644
index 000000000..da85e6f1b
--- /dev/null
+++ b/ui/src/stores/workflow/types.ts
@@ -0,0 +1,28 @@
+import { type WorkflowModel, type WorkflowNode } from "@/domain/workflow";
+
+export interface WorkflowState {
+ workflow: WorkflowModel;
+ initialized: boolean;
+}
+
+export interface WorkflowActions {
+ init(id: string): void;
+ destroy(): void;
+
+ setBaseInfo: (name: string, description: string) => void;
+ setEnabled(enabled: boolean): void;
+ publish(): void;
+ rollback(): void;
+
+ addNode: (node: WorkflowNode, previousNodeId: string) => void;
+ duplicateNode: (node: WorkflowNode) => void;
+ updateNode: (node: WorkflowNode) => void;
+ removeNode: (node: WorkflowNode) => void;
+ addBranch: (branchId: string) => void;
+ duplicateBranch: (branchId: string, index: number) => void;
+ removeBranch: (branchId: string, index: number) => void;
+
+ getWorkflowOuptutBeforeId: (nodeId: string, typeFilter?: string | string[]) => WorkflowNode[];
+}
+
+export interface WorkflowStore extends WorkflowState, WorkflowActions {}