mirror of
https://github.com/woodchen-ink/certimate.git
synced 2025-07-18 17:31:55 +08:00
feat(ui): release & run workflow
This commit is contained in:
parent
5c1854948c
commit
6075cc5c95
@ -199,7 +199,7 @@ func createDeployer(target string, accessConfig string, deployConfig map[string]
|
||||
ShellEnv: providerLocal.ShellEnvType(maps.GetValueAsString(deployConfig, "shellEnv")),
|
||||
PreCommand: maps.GetValueAsString(deployConfig, "preCommand"),
|
||||
PostCommand: maps.GetValueAsString(deployConfig, "postCommand"),
|
||||
OutputFormat: providerLocal.OutputFormatType(maps.GetValueOrDefaultAsString(deployConfig, "format", "PEM")),
|
||||
OutputFormat: providerLocal.OutputFormatType(maps.GetValueOrDefaultAsString(deployConfig, "format", string(providerLocal.OUTPUT_FORMAT_PEM))),
|
||||
OutputCertPath: maps.GetValueAsString(deployConfig, "certPath"),
|
||||
OutputKeyPath: maps.GetValueAsString(deployConfig, "keyPath"),
|
||||
PfxPassword: maps.GetValueAsString(deployConfig, "pfxPassword"),
|
||||
@ -259,7 +259,7 @@ func createDeployer(target string, accessConfig string, deployConfig map[string]
|
||||
SshKeyPassphrase: access.KeyPassphrase,
|
||||
PreCommand: maps.GetValueAsString(deployConfig, "preCommand"),
|
||||
PostCommand: maps.GetValueAsString(deployConfig, "postCommand"),
|
||||
OutputFormat: providerSSH.OutputFormatType(maps.GetValueOrDefaultAsString(deployConfig, "format", "PEM")),
|
||||
OutputFormat: providerSSH.OutputFormatType(maps.GetValueOrDefaultAsString(deployConfig, "format", string(providerSSH.OUTPUT_FORMAT_PEM))),
|
||||
OutputCertPath: maps.GetValueAsString(deployConfig, "certPath"),
|
||||
OutputKeyPath: maps.GetValueAsString(deployConfig, "keyPath"),
|
||||
PfxPassword: maps.GetValueAsString(deployConfig, "pfxPassword"),
|
||||
|
@ -3,8 +3,8 @@ import { useTranslation } from "react-i18next";
|
||||
import { useRequest } from "ahooks";
|
||||
import { Button, Form, Input, message, notification, Skeleton } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { z } from "zod";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
import { useAntdForm } from "@/hooks";
|
||||
|
@ -353,17 +353,14 @@ const FormFieldDomainsModalForm = ({
|
||||
setModel({ domains: data?.split(MULTIPLE_INPUT_DELIMITER) });
|
||||
}, [data]);
|
||||
|
||||
const handleFinish = useCallback(
|
||||
(values: z.infer<typeof formSchema>) => {
|
||||
onFinish?.(
|
||||
values.domains
|
||||
.map((e) => e.trim())
|
||||
.filter((e) => !!e)
|
||||
.join(MULTIPLE_INPUT_DELIMITER)
|
||||
);
|
||||
},
|
||||
[onFinish]
|
||||
);
|
||||
const handleFormFinish = (values: z.infer<typeof formSchema>) => {
|
||||
onFinish?.(
|
||||
values.domains
|
||||
.map((e) => e.trim())
|
||||
.filter((e) => !!e)
|
||||
.join(MULTIPLE_INPUT_DELIMITER)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalForm
|
||||
@ -375,7 +372,7 @@ const FormFieldDomainsModalForm = ({
|
||||
trigger={trigger}
|
||||
validateTrigger="onSubmit"
|
||||
width={480}
|
||||
onFinish={handleFinish}
|
||||
onFinish={handleFormFinish}
|
||||
>
|
||||
<Form.Item name="domains" rules={[formRule]}>
|
||||
<MultipleInput placeholder={t("workflow_node.apply.form.domains.multiple_input_modal.placeholder")} />
|
||||
@ -400,17 +397,14 @@ const FormFieldNameserversModalForm = ({ data, trigger, onFinish }: { data: stri
|
||||
setModel({ nameservers: data?.split(MULTIPLE_INPUT_DELIMITER) });
|
||||
}, [data]);
|
||||
|
||||
const handleFinish = useCallback(
|
||||
(values: z.infer<typeof formSchema>) => {
|
||||
onFinish?.(
|
||||
values.nameservers
|
||||
.map((e) => e.trim())
|
||||
.filter((e) => !!e)
|
||||
.join(MULTIPLE_INPUT_DELIMITER)
|
||||
);
|
||||
},
|
||||
[onFinish]
|
||||
);
|
||||
const handleFormFinish = (values: z.infer<typeof formSchema>) => {
|
||||
onFinish?.(
|
||||
values.nameservers
|
||||
.map((e) => e.trim())
|
||||
.filter((e) => !!e)
|
||||
.join(MULTIPLE_INPUT_DELIMITER)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalForm
|
||||
@ -422,7 +416,7 @@ const FormFieldNameserversModalForm = ({ data, trigger, onFinish }: { data: stri
|
||||
trigger={trigger}
|
||||
validateTrigger="onSubmit"
|
||||
width={480}
|
||||
onFinish={handleFinish}
|
||||
onFinish={handleFormFinish}
|
||||
>
|
||||
<Form.Item name="nameservers" rules={[formRule]}>
|
||||
<MultipleInput placeholder={t("workflow_node.apply.form.nameservers.multiple_input_modal.placeholder")} />
|
||||
|
@ -6,9 +6,9 @@ import { z } from "zod";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
|
||||
const FORMAT_PEM = "pem" as const;
|
||||
const FORMAT_PFX = "pfx" as const;
|
||||
const FORMAT_JKS = "jks" as const;
|
||||
const FORMAT_PEM = "PEM" as const;
|
||||
const FORMAT_PFX = "PFX" as const;
|
||||
const FORMAT_JKS = "JKS" as const;
|
||||
|
||||
const SHELLENV_SH = "sh" as const;
|
||||
const SHELLENV_CMD = "cmd" as const;
|
||||
|
@ -6,9 +6,9 @@ import { z } from "zod";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
|
||||
const FORMAT_PEM = "pem" as const;
|
||||
const FORMAT_PFX = "pfx" as const;
|
||||
const FORMAT_JKS = "jks" as const;
|
||||
const FORMAT_PEM = "PEM" as const;
|
||||
const FORMAT_PFX = "PFX" as const;
|
||||
const FORMAT_JKS = "JKS" as const;
|
||||
|
||||
const DeployNodeFormSSHFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
@ -32,28 +32,11 @@ const WorkflowRunDetailDrawer = ({ data, loading, trigger, ...props }: WorkflowR
|
||||
<Drawer closable destroyOnClose open={open} loading={loading} placement="right" title={`runlog-${data?.id}`} width={640} onClose={() => setOpen(false)}>
|
||||
<Show when={!!data}>
|
||||
<Show when={data!.succeed}>
|
||||
<Alert
|
||||
showIcon
|
||||
type="success"
|
||||
message={
|
||||
<>
|
||||
<Typography.Text type="success">{t("workflow_run.props.status.succeeded")}</Typography.Text>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Alert showIcon type="success" message={<Typography.Text type="success">{t("workflow_run.props.status.succeeded")}</Typography.Text>} />
|
||||
</Show>
|
||||
|
||||
<Show when={!!data!.error}>
|
||||
<Alert
|
||||
showIcon
|
||||
type="error"
|
||||
message={
|
||||
<>
|
||||
<Typography.Text type="danger">{t("workflow_run.props.status.failed")}</Typography.Text>
|
||||
</>
|
||||
}
|
||||
description={data!.error}
|
||||
/>
|
||||
<Alert showIcon type="error" message={<Typography.Text type="danger">{t("workflow_run.props.status.failed")}</Typography.Text>} />
|
||||
</Show>
|
||||
|
||||
<div className="mt-4 p-4 bg-black text-stone-200 rounded-md">
|
||||
|
@ -54,12 +54,12 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
|
||||
</Space>
|
||||
);
|
||||
} else {
|
||||
<Tooltip title={record.error}>
|
||||
return (
|
||||
<Space>
|
||||
<CloseCircleOutlinedIcon style={{ color: themeToken.colorError }} />
|
||||
<Typography.Text type="danger">{t("workflow_run.props.status.failed")}</Typography.Text>
|
||||
</Space>
|
||||
</Tooltip>;
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -362,20 +362,20 @@ export const getWorkflowOutputBeforeId = (node: WorkflowNode | WorkflowBranchNod
|
||||
return output;
|
||||
};
|
||||
|
||||
export const allNodesValidated = (node: WorkflowNode | WorkflowBranchNode): boolean => {
|
||||
let current = node;
|
||||
export const isAllNodesValidated = (node: WorkflowNode | WorkflowBranchNode): boolean => {
|
||||
let current = node as typeof node | undefined;
|
||||
while (current) {
|
||||
if (!isWorkflowBranchNode(current) && !current.validated) {
|
||||
return false;
|
||||
}
|
||||
if (isWorkflowBranchNode(current)) {
|
||||
for (const branch of current.branches) {
|
||||
if (!allNodesValidated(branch)) {
|
||||
if (!isAllNodesValidated(branch)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
current = current.next as WorkflowNode;
|
||||
current = current.next;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
@ -7,11 +7,14 @@
|
||||
"workflow.action.edit": "Edit workflow",
|
||||
"workflow.action.delete": "Delete workflow",
|
||||
"workflow.action.delete.confirm": "Are you sure to delete this workflow?",
|
||||
"workflow.action.discard": "Discard",
|
||||
"workflow.action.discard": "Discard changes",
|
||||
"workflow.action.discard.confirm": "Are you sure to discard your changes?",
|
||||
"workflow.action.release": "Release",
|
||||
"workflow.action.release.confirm": "Are you sure to release your changes?",
|
||||
"workflow.action.execute": "Run",
|
||||
"workflow.action.release.failed.uncompleted": "Please complete the orchestration first",
|
||||
"workflow.action.run": "Run",
|
||||
"workflow.action.run.confirm": "There are unreleased changes, are you sure to run this workflow based on the latest released version?",
|
||||
"workflow.action.enable.failed.uncompleted": "Please complete the orchestration and publish the changes first",
|
||||
|
||||
"workflow.props.name": "Name",
|
||||
"workflow.props.description": "Description",
|
||||
@ -35,13 +38,6 @@
|
||||
"workflow.detail.baseinfo.form.description.placeholder": "Please enter description",
|
||||
|
||||
"workflow.common.certificate.label": "Certificate",
|
||||
"workflow.detail.action.save": "Save updates",
|
||||
"workflow.detail.action.save.failed": "Save failed",
|
||||
"workflow.detail.action.save.failed.uncompleted": "Please complete the orchestration and publish the changes first",
|
||||
"workflow.detail.action.run": "Run",
|
||||
"workflow.detail.action.run.failed": "Run failed",
|
||||
"workflow.detail.action.run.success": "Run success",
|
||||
"workflow.detail.action.running": "Running",
|
||||
"workflow.node.setting.label": "Setting Node",
|
||||
"workflow.node.delete.label": "Delete Node",
|
||||
"workflow.node.addBranch.label": "Add Branch",
|
||||
|
@ -11,8 +11,10 @@
|
||||
"workflow.action.discard.confirm": "确定要撤销更改并回退到最近一次发布的版本吗?",
|
||||
"workflow.action.release": "发布更改",
|
||||
"workflow.action.release.confirm": "确定要发布更改吗?",
|
||||
"workflow.action.execute": "执行",
|
||||
"workflow.action.execute.confirm": "确定要立即执行此工作流吗?",
|
||||
"workflow.action.release.failed.uncompleted": "请先完成流程编排",
|
||||
"workflow.action.run": "执行",
|
||||
"workflow.action.run.confirm": "存在未发布的更改,确定要按最近一次发布的版本来执行此工作流吗?",
|
||||
"workflow.action.enable.failed.uncompleted": "请先完成流程编排并发布更改",
|
||||
|
||||
"workflow.props.name": "名称",
|
||||
"workflow.props.description": "描述",
|
||||
@ -36,13 +38,6 @@
|
||||
"workflow.detail.baseinfo.form.description.placeholder": "请输入工作流描述",
|
||||
|
||||
"workflow.common.certificate.label": "证书",
|
||||
"workflow.detail.action.save": "保存变更",
|
||||
"workflow.detail.action.save.failed": "保存失败",
|
||||
"workflow.detail.action.save.failed.uncompleted": "请先完成流程编排并发布更改",
|
||||
"workflow.detail.action.run": "立即执行",
|
||||
"workflow.detail.action.run.failed": "执行失败",
|
||||
"workflow.detail.action.run.success": "执行成功",
|
||||
"workflow.detail.action.running": "正在执行",
|
||||
"workflow.node.setting.label": "设置节点",
|
||||
"workflow.node.delete.label": "删除节点",
|
||||
"workflow.node.addBranch.label": "添加分支",
|
||||
|
@ -28,7 +28,7 @@ const Login = () => {
|
||||
onSubmit: async (values) => {
|
||||
try {
|
||||
await getPocketBase().admins.authWithPassword(values.username, values.password);
|
||||
navigage("/");
|
||||
await navigage("/");
|
||||
} catch (err) {
|
||||
notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
|
||||
}
|
||||
|
@ -1,14 +1,18 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, Card, Dropdown, Form, Input, message, Modal, notification, Tabs, Typography } from "antd";
|
||||
import { useDeepCompareEffect } from "ahooks";
|
||||
import { Button, Card, Dropdown, Form, Input, message, Modal, notification, Space, Tabs, Typography } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { PageHeader } from "@ant-design/pro-components";
|
||||
import {
|
||||
CaretRightOutlined as CaretRightOutlinedIcon,
|
||||
DeleteOutlined as DeleteOutlinedIcon,
|
||||
EllipsisOutlined as EllipsisOutlinedIcon,
|
||||
UndoOutlined as UndoOutlinedIcon,
|
||||
} from "@ant-design/icons";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { isEqual } from "radash";
|
||||
import { z } from "zod";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
@ -18,9 +22,10 @@ import NodeRender from "@/components/workflow/NodeRender";
|
||||
import WorkflowRuns from "@/components/workflow/run/WorkflowRuns";
|
||||
import WorkflowProvider from "@/components/workflow/WorkflowProvider";
|
||||
import { useAntdForm, useZustandShallowSelector } from "@/hooks";
|
||||
import { allNodesValidated, type WorkflowModel, type WorkflowNode } from "@/domain/workflow";
|
||||
import { isAllNodesValidated, type WorkflowModel, type WorkflowNode } from "@/domain/workflow";
|
||||
import { useWorkflowStore } from "@/stores/workflow";
|
||||
import { remove as removeWorkflow } from "@/repository/workflow";
|
||||
import { run as runWorkflow } from "@/api/workflow";
|
||||
import { getErrMsg } from "@/utils/error";
|
||||
|
||||
const WorkflowDetail = () => {
|
||||
@ -33,16 +38,17 @@ const WorkflowDetail = () => {
|
||||
const [notificationApi, NotificationContextHolder] = notification.useNotification();
|
||||
|
||||
const { id: workflowId } = useParams();
|
||||
const { workflow, init, setBaseInfo, switchEnable } = useWorkflowStore(useZustandShallowSelector(["workflow", "init", "setBaseInfo", "switchEnable"]));
|
||||
const { workflow, init, save, setBaseInfo, switchEnable } = useWorkflowStore(
|
||||
useZustandShallowSelector(["workflow", "init", "save", "setBaseInfo", "switchEnable"])
|
||||
);
|
||||
useEffect(() => {
|
||||
// TODO: loading
|
||||
init(workflowId);
|
||||
}, [workflowId, init]);
|
||||
|
||||
const [tabValue, setTabValue] = useState<"orchestration" | "runs">("orchestration");
|
||||
|
||||
// const [running, setRunning] = useState(false);
|
||||
|
||||
const elements = useMemo(() => {
|
||||
const workflowNodes = useMemo(() => {
|
||||
let current = workflow.draft as WorkflowNode;
|
||||
|
||||
const elements: JSX.Element[] = [];
|
||||
@ -58,6 +64,16 @@ const WorkflowDetail = () => {
|
||||
return elements;
|
||||
}, [workflow]);
|
||||
|
||||
const [workflowRunning, setWorkflowRunning] = useState(false);
|
||||
|
||||
const [allowDiscard, setAllowDiscard] = useState(false);
|
||||
const [allowRelease, setAllowRelease] = useState(false);
|
||||
useDeepCompareEffect(() => {
|
||||
const hasChanges = workflow.hasDraft! || !isEqual(workflow.draft, workflow.content);
|
||||
setAllowDiscard(hasChanges && !workflowRunning);
|
||||
setAllowRelease(hasChanges && !workflowRunning);
|
||||
}, [workflow, workflowRunning]);
|
||||
|
||||
const handleBaseInfoFormFinish = async (values: Pick<WorkflowModel, "name" | "description">) => {
|
||||
try {
|
||||
await setBaseInfo(values.name!, values.description!);
|
||||
@ -69,10 +85,11 @@ const WorkflowDetail = () => {
|
||||
};
|
||||
|
||||
const handleEnableChange = () => {
|
||||
if (!workflow.enabled && !allNodesValidated(workflow.content!)) {
|
||||
messageApi.warning(t("workflow.detail.action.save.failed.uncompleted"));
|
||||
if (!workflow.enabled && !isAllNodesValidated(workflow.content!)) {
|
||||
messageApi.warning(t("workflow.action.enable.failed.uncompleted"));
|
||||
return;
|
||||
}
|
||||
|
||||
switchEnable();
|
||||
};
|
||||
|
||||
@ -84,7 +101,7 @@ const WorkflowDetail = () => {
|
||||
try {
|
||||
const resp: boolean = await removeWorkflow(workflow);
|
||||
if (resp) {
|
||||
navigate("/workflows");
|
||||
navigate("/workflows", { replace: true });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@ -94,29 +111,59 @@ const WorkflowDetail = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: 发布更改 撤销更改 立即执行
|
||||
// const handleWorkflowSaveClick = () => {
|
||||
// if (!allNodesValidated(workflow.draft as WorkflowNode)) {
|
||||
// messageApi.warning(t("workflow.detail.action.save.failed.uncompleted"));
|
||||
// return;
|
||||
// }
|
||||
// save();
|
||||
// };
|
||||
const handleDiscardClick = () => {
|
||||
alert("TODO");
|
||||
};
|
||||
|
||||
// const handleRunClick = async () => {
|
||||
// if (running) return;
|
||||
const handleReleaseClick = () => {
|
||||
if (!isAllNodesValidated(workflow.draft!)) {
|
||||
messageApi.warning(t("workflow.action.release.failed.uncompleted"));
|
||||
return;
|
||||
}
|
||||
|
||||
// setRunning(true);
|
||||
// try {
|
||||
// await runWorkflow(workflow.id as string);
|
||||
// messageApi.success(t("workflow.detail.action.run.success"));
|
||||
// } catch (err) {
|
||||
// console.error(err);
|
||||
// messageApi.warning(t("workflow.detail.action.run.failed"));
|
||||
// } finally {
|
||||
// setRunning(false);
|
||||
// }
|
||||
// };
|
||||
save();
|
||||
|
||||
messageApi.success(t("common.text.operation_succeeded"));
|
||||
};
|
||||
|
||||
const handleRunClick = () => {
|
||||
if (!workflow.enabled) {
|
||||
alert("TODO: 暂时只支持执行已启用的工作流");
|
||||
return;
|
||||
}
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
if (workflow.hasDraft) {
|
||||
modalApi.confirm({
|
||||
title: t("workflow.action.run"),
|
||||
content: t("workflow.action.run.confirm"),
|
||||
onOk: () => resolve(void 0),
|
||||
onCancel: () => reject(),
|
||||
});
|
||||
} else {
|
||||
resolve(void 0);
|
||||
}
|
||||
|
||||
// TODO: 异步执行
|
||||
promise.then(async () => {
|
||||
setWorkflowRunning(true);
|
||||
|
||||
try {
|
||||
await runWorkflow(workflowId!);
|
||||
|
||||
messageApi.warning(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 {
|
||||
setWorkflowRunning(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -175,17 +222,37 @@ const WorkflowDetail = () => {
|
||||
<Show when={tabValue === "orchestration"}>
|
||||
<div className="relative">
|
||||
<div className="flex flex-col items-center py-12 pr-48">
|
||||
<WorkflowProvider>{elements}</WorkflowProvider>
|
||||
<WorkflowProvider>{workflowNodes}</WorkflowProvider>
|
||||
</div>
|
||||
<div className="absolute top-0 right-0 z-[1]">
|
||||
<Button.Group>
|
||||
<Button onClick={() => alert("TODO")}>{t("workflow.action.discard")}</Button>
|
||||
<Button onClick={() => alert("TODO")}>{t("workflow.action.release")}</Button>
|
||||
<Button type="primary" onClick={() => alert("TODO")}>
|
||||
<CaretRightOutlinedIcon />
|
||||
{t("workflow.action.execute")}
|
||||
<Space>
|
||||
<Button icon={<CaretRightOutlinedIcon />} loading={workflowRunning} type="primary" onClick={handleRunClick}>
|
||||
{t("workflow.action.run")}
|
||||
</Button>
|
||||
</Button.Group>
|
||||
|
||||
<Button.Group>
|
||||
<Button color="primary" disabled={!allowRelease} variant="outlined" onClick={handleReleaseClick}>
|
||||
{t("workflow.action.release")}
|
||||
</Button>
|
||||
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: "discard",
|
||||
disabled: !allowDiscard,
|
||||
label: t("workflow.action.discard"),
|
||||
icon: <UndoOutlinedIcon />,
|
||||
onClick: handleDiscardClick,
|
||||
},
|
||||
],
|
||||
}}
|
||||
trigger={["click"]}
|
||||
>
|
||||
<Button color="primary" icon={<EllipsisOutlinedIcon />} variant="outlined" />
|
||||
</Dropdown>
|
||||
</Button.Group>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
@ -237,7 +304,7 @@ const WorkflowBaseInfoModalForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const handleFinish = async () => {
|
||||
const handleFormFinish = async () => {
|
||||
return formApi.submit();
|
||||
};
|
||||
|
||||
@ -252,7 +319,7 @@ const WorkflowBaseInfoModalForm = ({
|
||||
trigger={trigger}
|
||||
width={480}
|
||||
{...formProps}
|
||||
onFinish={handleFinish}
|
||||
onFinish={handleFormFinish}
|
||||
>
|
||||
<Form.Item name="name" label={t("workflow.detail.baseinfo.form.name.label")} rules={[formRule]}>
|
||||
<Input placeholder={t("workflow.detail.baseinfo.form.name.placeholder")} />
|
||||
@ -264,4 +331,5 @@ const WorkflowBaseInfoModalForm = ({
|
||||
</ModalForm>
|
||||
);
|
||||
};
|
||||
|
||||
export default WorkflowDetail;
|
||||
|
@ -25,7 +25,7 @@ import { DeleteOutlined as DeleteOutlinedIcon, EditOutlined as EditOutlinedIcon,
|
||||
import dayjs from "dayjs";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
import { allNodesValidated, type WorkflowModel } from "@/domain/workflow";
|
||||
import { isAllNodesValidated, type WorkflowModel } from "@/domain/workflow";
|
||||
import { list as listWorkflow, remove as removeWorkflow, save as saveWorkflow } from "@/repository/workflow";
|
||||
import { getErrMsg } from "@/utils/error";
|
||||
|
||||
@ -241,8 +241,8 @@ const WorkflowList = () => {
|
||||
|
||||
const handleEnabledChange = async (workflow: WorkflowModel) => {
|
||||
try {
|
||||
if (!workflow.enabled && !allNodesValidated(workflow.content!)) {
|
||||
messageApi.warning(t("workflow.detail.action.save.failed.uncompleted"));
|
||||
if (!workflow.enabled && !isAllNodesValidated(workflow.content!)) {
|
||||
messageApi.warning(t("workflow.action.enable.failed.uncompleted"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,19 @@ export const useAccessStore = create<AccessState>((set) => {
|
||||
loading: false,
|
||||
loadedAtOnce: false,
|
||||
|
||||
fetchAccesses: async () => {
|
||||
fetcher ??= listAccess();
|
||||
|
||||
try {
|
||||
set({ loading: true });
|
||||
const accesses = await fetcher;
|
||||
set({ accesses: accesses ?? [], loadedAtOnce: true });
|
||||
} finally {
|
||||
fetcher = null;
|
||||
set({ loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
createAccess: async (access) => {
|
||||
const record = await saveAccess(access);
|
||||
set(
|
||||
@ -58,18 +71,5 @@ export const useAccessStore = create<AccessState>((set) => {
|
||||
|
||||
return access as AccessModel;
|
||||
},
|
||||
|
||||
fetchAccesses: async () => {
|
||||
fetcher ??= listAccess();
|
||||
|
||||
try {
|
||||
set({ loading: true });
|
||||
const accesses = await fetcher;
|
||||
set({ accesses: accesses ?? [], loadedAtOnce: true });
|
||||
} finally {
|
||||
fetcher = null;
|
||||
set({ loading: false });
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -24,6 +24,19 @@ export const useContactStore = create<ContactState>((set, get) => {
|
||||
loading: false,
|
||||
loadedAtOnce: false,
|
||||
|
||||
fetchEmails: async () => {
|
||||
fetcher ??= getSettings<EmailsSettingsContent>(SETTINGS_NAMES.EMAILS);
|
||||
|
||||
try {
|
||||
set({ loading: true });
|
||||
settings = await fetcher;
|
||||
set({ emails: settings.content.emails?.sort() ?? [], loadedAtOnce: true });
|
||||
} finally {
|
||||
fetcher = null;
|
||||
set({ loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
setEmails: async (emails) => {
|
||||
settings ??= await getSettings<EmailsSettingsContent>(SETTINGS_NAMES.EMAILS);
|
||||
settings = await saveSettings<EmailsSettingsContent>({
|
||||
@ -58,18 +71,5 @@ export const useContactStore = create<ContactState>((set, get) => {
|
||||
});
|
||||
get().setEmails(emails);
|
||||
},
|
||||
|
||||
fetchEmails: async () => {
|
||||
fetcher ??= getSettings<EmailsSettingsContent>(SETTINGS_NAMES.EMAILS);
|
||||
|
||||
try {
|
||||
set({ loading: true });
|
||||
settings = await fetcher;
|
||||
set({ emails: settings.content.emails?.sort() ?? [], loadedAtOnce: true });
|
||||
} finally {
|
||||
fetcher = null;
|
||||
set({ loading: false });
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -23,6 +23,19 @@ export const useNotifyChannelStore = create<NotifyChannelState>((set, get) => {
|
||||
loading: false,
|
||||
loadedAtOnce: false,
|
||||
|
||||
fetchChannels: async () => {
|
||||
fetcher ??= getSettings<NotifyChannelsSettingsContent>(SETTINGS_NAMES.NOTIFY_CHANNELS);
|
||||
|
||||
try {
|
||||
set({ loading: true });
|
||||
settings = await fetcher;
|
||||
set({ channels: settings.content ?? {}, loadedAtOnce: true });
|
||||
} finally {
|
||||
fetcher = null;
|
||||
set({ loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
setChannel: async (channel, config) => {
|
||||
settings ??= await getSettings<NotifyChannelsSettingsContent>(SETTINGS_NAMES.NOTIFY_CHANNELS);
|
||||
return get().setChannels(
|
||||
@ -47,18 +60,5 @@ export const useNotifyChannelStore = create<NotifyChannelState>((set, get) => {
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
fetchChannels: async () => {
|
||||
fetcher ??= getSettings<NotifyChannelsSettingsContent>(SETTINGS_NAMES.NOTIFY_CHANNELS);
|
||||
|
||||
try {
|
||||
set({ loading: true });
|
||||
settings = await fetcher;
|
||||
set({ channels: settings.content ?? {}, loadedAtOnce: true });
|
||||
} finally {
|
||||
fetcher = null;
|
||||
set({ loading: false });
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -5,9 +5,9 @@
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": [
|
||||
"ES2020",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
"DOM.Iterable",
|
||||
"ESNext",
|
||||
],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
Loading…
x
Reference in New Issue
Block a user