diff --git a/ui/src/domain/workflow.ts b/ui/src/domain/workflow.ts
index ac2e2b19d..c4a73f0e6 100644
--- a/ui/src/domain/workflow.ts
+++ b/ui/src/domain/workflow.ts
@@ -3,6 +3,7 @@ import { Immer, produce } from "immer";
import { nanoid } from "nanoid";
import i18n from "@/i18n";
+import { type WorkflowRunModel } from "./workflowRun";
export interface WorkflowModel extends BaseModel {
name: string;
@@ -16,6 +17,9 @@ export interface WorkflowModel extends BaseModel {
lastRunRef?: string;
lastRunStatus?: string;
lastRunTime?: string;
+ expand?: {
+ lastRunRef?: WorkflowRunModel;
+ };
}
export const WORKFLOW_TRIGGERS = Object.freeze({
diff --git a/ui/src/pages/settings/Settings.tsx b/ui/src/pages/settings/Settings.tsx
index 82fc6115b..6de4be1ed 100644
--- a/ui/src/pages/settings/Settings.tsx
+++ b/ui/src/pages/settings/Settings.tsx
@@ -16,7 +16,7 @@ const Settings = () => {
["ssl-provider", "settings.sslprovider.tab", ],
["persistence", "settings.persistence.tab", ],
] satisfies [string, string, React.ReactElement][];
- const [menuKey, setMenuValue] = useState(menus[0][0]);
+ const [menuKey, setMenuValue] = useState();
useEffect(() => {
const subpath = location.pathname.split("/")[2];
if (!subpath) {
diff --git a/ui/src/pages/workflows/WorkflowDetail.tsx b/ui/src/pages/workflows/WorkflowDetail.tsx
index aad56f363..07b2d120a 100644
--- a/ui/src/pages/workflows/WorkflowDetail.tsx
+++ b/ui/src/pages/workflows/WorkflowDetail.tsx
@@ -36,10 +36,10 @@ const WorkflowDetail = () => {
["design", "workflow.detail.design.tab", ],
["runs", "workflow.detail.runs.tab", ],
] satisfies [string, string, React.ReactElement][];
- const [tabValue, setTabValue] = useState(tabs[0][0]);
+ const [tabValue, setTabValue] = useState();
useEffect(() => {
const subpath = location.pathname.split("/")[3];
- if (!tabs.map((tab) => tab[0]).includes(subpath)) {
+ if (!subpath) {
navigate(`/workflows/${workflowId}/${tabs[0][0]}`, { replace: true });
return;
}
@@ -50,6 +50,8 @@ const WorkflowDetail = () => {
const handleTabChange = (value: string) => {
setTabValue(value);
navigate(`/workflows/${workflowId}/${value}`);
+
+ workflowState.init(workflow.id); // reload state
};
const handleEnableClick = async () => {
diff --git a/ui/src/pages/workflows/WorkflowDetailDesign.tsx b/ui/src/pages/workflows/WorkflowDetailDesign.tsx
index 1876a2ba5..5bd694e74 100644
--- a/ui/src/pages/workflows/WorkflowDetailDesign.tsx
+++ b/ui/src/pages/workflows/WorkflowDetailDesign.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useMemo, useState } from "react";
+import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { IconArrowBackUp, IconDots, IconPlayerPlay } from "@tabler/icons-react";
import { Alert, App, Button, Card, Dropdown, Space } from "antd";
@@ -10,7 +10,7 @@ import WorkflowElementsContainer from "@/components/workflow/WorkflowElementsCon
import { isAllNodesValidated } from "@/domain/workflow";
import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun";
import { useZustandShallowSelector } from "@/hooks";
-import { subscribe as subscribeWorkflow, unsubscribe as unsubscribeWorkflow } from "@/repository/workflow";
+import { subscribe as subscribeWorkflow } from "@/repository/workflow";
import { useWorkflowStore } from "@/stores/workflow";
import { getErrMsg } from "@/utils/error";
@@ -19,30 +19,32 @@ const WorkflowDetailDesign = () => {
const { message, modal, notification } = App.useApp();
- const { workflow, ...workflowState } = useWorkflowStore(useZustandShallowSelector(["workflow", "publish", "rollback"]));
+ const { workflow, ...workflowState } = useWorkflowStore(useZustandShallowSelector(["workflow", "init", "publish", "rollback"]));
const [isPendingOrRunning, setIsPendingOrRunning] = useState(false);
- const lastRunStatus = useMemo(() => workflow.lastRunStatus, [workflow]);
-
const [allowRollback, setAllowRollback] = useState(false);
const [allowPublish, setAllowPublish] = useState(false);
const [allowRun, setAllowRun] = useState(false);
useEffect(() => {
- setIsPendingOrRunning(lastRunStatus == WORKFLOW_RUN_STATUSES.PENDING || lastRunStatus == WORKFLOW_RUN_STATUSES.RUNNING);
- }, [lastRunStatus]);
+ const pending = workflow.lastRunStatus === WORKFLOW_RUN_STATUSES.PENDING || workflow.lastRunStatus === WORKFLOW_RUN_STATUSES.RUNNING;
+ setIsPendingOrRunning(pending);
+ }, [workflow]);
useEffect(() => {
if (isPendingOrRunning) {
+ let unsubscribeFn: Awaited> | undefined = undefined;
subscribeWorkflow(workflow.id, (cb) => {
if (cb.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.PENDING && cb.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.RUNNING) {
setIsPendingOrRunning(false);
- unsubscribeWorkflow(workflow.id);
+ unsubscribeFn?.();
}
+ }).then((res) => {
+ unsubscribeFn = res;
});
return () => {
- unsubscribeWorkflow(workflow.id);
+ unsubscribeFn?.();
};
}
}, [workflow.id, isPendingOrRunning]);
diff --git a/ui/src/pages/workflows/WorkflowList.tsx b/ui/src/pages/workflows/WorkflowList.tsx
index 97373a875..88641169a 100644
--- a/ui/src/pages/workflows/WorkflowList.tsx
+++ b/ui/src/pages/workflows/WorkflowList.tsx
@@ -108,8 +108,8 @@ const WorkflowList = () => {
} else {
return (
-
- {lastRunTime ? dayjs(record.lastRunTime!).format("YYYY-MM-DD HH:mm:ss") : ""}
+
+ {lastRunTime ? dayjs(lastRunTime).format("YYYY-MM-DD HH:mm:ss") : ""}
);
}
@@ -231,6 +231,7 @@ const WorkflowList = () => {
sort: sort,
page: page,
perPage: pageSize,
+ expand: true,
});
},
{
diff --git a/ui/src/repository/workflow.ts b/ui/src/repository/workflow.ts
index 9ce71f26e..60ccc557e 100644
--- a/ui/src/repository/workflow.ts
+++ b/ui/src/repository/workflow.ts
@@ -9,6 +9,7 @@ export type ListRequest = {
sort?: string;
page?: number;
perPage?: number;
+ expand?: boolean;
};
export const list = async (request: ListRequest) => {
@@ -28,6 +29,7 @@ export const list = async (request: ListRequest) => {
const perPage = request.perPage || 10;
return await pb.collection(COLLECTION_NAME_WORKFLOW).getList(page, perPage, {
+ expand: request.expand ? "lastRunRef" : undefined,
filter: filters.join(" && "),
sort: sort,
requestKey: null,
@@ -36,6 +38,7 @@ export const list = async (request: ListRequest) => {
export const get = async (id: string) => {
return await getPocketBase().collection(COLLECTION_NAME_WORKFLOW).getOne(id, {
+ expand: "lastRunRef",
requestKey: null,
});
};
diff --git a/ui/src/repository/workflowRun.ts b/ui/src/repository/workflowRun.ts
index 2fdc011f2..4f2082f16 100644
--- a/ui/src/repository/workflowRun.ts
+++ b/ui/src/repository/workflowRun.ts
@@ -22,15 +22,27 @@ export const list = async (request: ListRequest) => {
const page = request.page || 1;
const perPage = request.perPage || 10;
return await pb.collection(COLLECTION_NAME_WORKFLOW_RUN).getList(page, perPage, {
+ expand: request.expand ? "workflowRef" : undefined,
filter: filters.join(" && "),
sort: "-created",
requestKey: null,
- expand: request.expand ? "workflowRef" : undefined,
});
};
-export const remove = async (record: MaybeModelRecordWithId) => {
- return await getPocketBase().collection(COLLECTION_NAME_WORKFLOW_RUN).delete(record.id);
+export const remove = async (record: MaybeModelRecordWithId | MaybeModelRecordWithId[]) => {
+ const pb = getPocketBase();
+
+ if (Array.isArray(record)) {
+ const batch = pb.createBatch();
+ for (const item of record) {
+ batch.collection(COLLECTION_NAME_WORKFLOW_RUN).delete(item.id);
+ }
+ const res = await batch.send();
+ return res.every((e) => e.status === 200);
+ } else {
+ await pb.collection(COLLECTION_NAME_WORKFLOW_RUN).delete(record.id!);
+ return true;
+ }
};
export const subscribe = async (id: string, cb: (e: RecordSubscription) => void) => {