import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate, useParams } from "react-router-dom"; import { ApartmentOutlined as ApartmentOutlinedIcon, CaretRightOutlined as CaretRightOutlinedIcon, DeleteOutlined as DeleteOutlinedIcon, DownOutlined as DownOutlinedIcon, EllipsisOutlined as EllipsisOutlinedIcon, HistoryOutlined as HistoryOutlinedIcon, UndoOutlined as UndoOutlinedIcon, } from "@ant-design/icons"; import { PageHeader } from "@ant-design/pro-components"; import { Alert, Button, Card, Dropdown, Form, Input, Modal, Space, Tabs, Typography, message, notification } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { ClientResponseError } from "pocketbase"; import { isEqual } from "radash"; import { z } from "zod"; import { run as runWorkflow } from "@/api/workflow"; import ModalForm from "@/components/ModalForm"; import Show from "@/components/Show"; import WorkflowElements from "@/components/workflow/WorkflowElements"; import WorkflowRuns from "@/components/workflow/WorkflowRuns"; import { isAllNodesValidated } from "@/domain/workflow"; import { useAntdForm, useZustandShallowSelector } from "@/hooks"; import { remove as removeWorkflow } from "@/repository/workflow"; import { useWorkflowStore } from "@/stores/workflow"; import { getErrMsg } from "@/utils/error"; const WorkflowDetail = () => { const navigate = useNavigate(); const { t } = useTranslation(); const [messageApi, MessageContextHolder] = message.useMessage(); const [modalApi, ModalContextHolder] = Modal.useModal(); const [notificationApi, NotificationContextHolder] = notification.useNotification(); const { id: workflowId } = useParams(); const { workflow, initialized, ...workflowState } = useWorkflowStore( useZustandShallowSelector(["workflow", "initialized", "init", "destroy", "setEnabled", "release", "discard"]) ); useEffect(() => { // TODO: loading & error workflowState.init(workflowId!); return () => { workflowState.destroy(); }; }, [workflowId]); const [tabValue, setTabValue] = useState<"orchestration" | "runs">("orchestration"); const [isRunning, setIsRunning] = useState(false); const [allowDiscard, setAllowDiscard] = useState(false); const [allowRelease, setAllowRelease] = useState(false); const [allowRun, setAllowRun] = useState(false); useEffect(() => { const hasReleased = !!workflow.content; const hasChanges = workflow.hasDraft! || !isEqual(workflow.draft, workflow.content); setAllowDiscard(!isRunning && hasReleased && hasChanges); setAllowRelease(!isRunning && hasChanges); setAllowRun(hasReleased); }, [workflow.content, workflow.draft, workflow.hasDraft, isRunning]); const handleEnableChange = async () => { if (!workflow.enabled && (!workflow.content || !isAllNodesValidated(workflow.content))) { messageApi.warning(t("workflow.action.enable.failed.uncompleted")); return; } try { await workflowState.setEnabled(!workflow.enabled); } catch (err) { console.error(err); notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); } }; const handleDeleteClick = () => { modalApi.confirm({ title: t("workflow.action.delete"), content: t("workflow.action.delete.confirm"), onOk: async () => { try { const resp: boolean = await removeWorkflow(workflow); if (resp) { navigate("/workflows", { replace: true }); } } catch (err) { console.error(err); notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); } }, }); }; const handleDiscardClick = () => { modalApi.confirm({ title: t("workflow.detail.orchestration.action.discard"), content: t("workflow.detail.orchestration.action.discard.confirm"), onOk: async () => { try { await workflowState.discard(); messageApi.success(t("common.text.operation_succeeded")); } catch (err) { console.error(err); notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); } }, }); }; const handleReleaseClick = () => { if (!isAllNodesValidated(workflow.draft!)) { messageApi.warning(t("workflow.detail.orchestration.action.release.failed.uncompleted")); return; } modalApi.confirm({ title: t("workflow.detail.orchestration.action.release"), content: t("workflow.detail.orchestration.action.release.confirm"), onOk: async () => { try { await workflowState.release(); messageApi.success(t("common.text.operation_succeeded")); } catch (err) { console.error(err); notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); } }, }); }; const handleRunClick = () => { const { promise, resolve, reject } = Promise.withResolvers(); if (workflow.hasDraft) { modalApi.confirm({ title: t("workflow.detail.orchestration.action.run"), content: t("workflow.detail.orchestration.action.run.confirm"), onOk: () => resolve(void 0), onCancel: () => reject(), }); } else { resolve(void 0); } // TODO: 异步执行 promise.then(async () => { setIsRunning(true); try { await runWorkflow(workflowId!); messageApi.success(t("common.text.operation_succeeded")); } catch (err) { if (err instanceof ClientResponseError && err.isAbort) { return; } console.error(err); messageApi.warning(t("common.text.operation_failed")); } finally { setIsRunning(false); } }); }; return (
{MessageContextHolder} {ModalContextHolder} {NotificationContextHolder} {t("common.button.edit")}} />, , , onClick: () => { handleDeleteClick(); }, }, ], }} trigger={["click"]} > , ] : [] } > {workflow.description} }, { key: "runs", label: t("workflow.detail.runs.tab"), icon: }, ]} renderTabBar={(props, DefaultTabBar) => } tabBarStyle={{ border: "none" }} onChange={(key) => setTabValue(key as typeof tabValue)} />
{t("workflow.detail.orchestration.draft.alert")}
} type="warning" />
, onClick: handleDiscardClick, }, ], }} trigger={["click"]} >
); }; const WorkflowBaseInfoModal = ({ trigger }: { trigger?: React.ReactNode }) => { const { t } = useTranslation(); const [notificationApi, NotificationContextHolder] = notification.useNotification(); const { workflow, ...workflowState } = useWorkflowStore(useZustandShallowSelector(["workflow", "setBaseInfo"])); const formSchema = z.object({ name: z .string({ message: t("workflow.detail.baseinfo.form.name.placeholder") }) .min(1, t("workflow.detail.baseinfo.form.name.placeholder")) .max(64, t("common.errmsg.string_max", { max: 64 })) .trim(), description: z .string({ message: t("workflow.detail.baseinfo.form.description.placeholder") }) .max(256, t("common.errmsg.string_max", { max: 256 })) .trim() .nullish(), }); const formRule = createSchemaFieldRule(formSchema); const { form: formInst, formPending, formProps, submit: submitForm, } = useAntdForm>({ initialValues: { name: workflow.name, description: workflow.description }, onSubmit: async (values) => { try { await workflowState.setBaseInfo(values.name!, values.description!); } catch (err) { console.error(err); notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); return false; } }, }); const handleFormFinish = async () => { return submitForm(); }; return ( <> {NotificationContextHolder} ); }; export default WorkflowDetail;