mirror of https://github.com/usual2970/certimate
				
				
				
			feat(ui): new WorkflowDetail using antd
							parent
							
								
									8b1ae309fb
								
							
						
					
					
						commit
						4e5373de73
					
				| 
						 | 
				
			
			@ -1,6 +1,12 @@
 | 
			
		|||
import React from "react";
 | 
			
		||||
 | 
			
		||||
const Show = ({ when, children, fallback }: { when: boolean; children: React.ReactNode; fallback?: React.ReactNode }) => {
 | 
			
		||||
export type ShowProps = {
 | 
			
		||||
  when: boolean;
 | 
			
		||||
  children: React.ReactNode;
 | 
			
		||||
  fallback?: React.ReactNode;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const Show = ({ when, children, fallback }: ShowProps) => {
 | 
			
		||||
  return when ? children : fallback;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,14 +27,14 @@ import AccessEditFormVolcEngineConfig from "./AccessEditFormVolcEngineConfig";
 | 
			
		|||
import AccessEditFormWebhookConfig from "./AccessEditFormWebhookConfig";
 | 
			
		||||
 | 
			
		||||
type AccessEditFormModelType = Partial<MaybeModelRecord<AccessModel>>;
 | 
			
		||||
type AccessEditFormModes = "add" | "edit";
 | 
			
		||||
type AccessEditFormPresets = "add" | "edit";
 | 
			
		||||
 | 
			
		||||
export type AccessEditFormProps = {
 | 
			
		||||
  className?: string;
 | 
			
		||||
  style?: React.CSSProperties;
 | 
			
		||||
  disabled?: boolean;
 | 
			
		||||
  mode: AccessEditFormModes;
 | 
			
		||||
  model?: AccessEditFormModelType;
 | 
			
		||||
  preset: AccessEditFormPresets;
 | 
			
		||||
  onModelChange?: (model: AccessEditFormModelType) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -44,7 +44,7 @@ export type AccessEditFormInstance = {
 | 
			
		|||
  validateFields: () => Promise<AccessEditFormModelType>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const AccessEditForm = forwardRef<AccessEditFormInstance, AccessEditFormProps>(({ className, style, disabled, mode, model, onModelChange }, ref) => {
 | 
			
		||||
const AccessEditForm = forwardRef<AccessEditFormInstance, AccessEditFormProps>(({ className, style, disabled, model, preset, onModelChange }, ref) => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
| 
						 | 
				
			
			@ -160,7 +160,7 @@ const AccessEditForm = forwardRef<AccessEditFormInstance, AccessEditFormProps>((
 | 
			
		|||
            rules={[formRule]}
 | 
			
		||||
            tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.type.tooltip") }}></span>}
 | 
			
		||||
          >
 | 
			
		||||
            <AccessTypeSelect disabled={mode !== "add"} placeholder={t("access.form.type.placeholder")} showSearch={!disabled} />
 | 
			
		||||
            <AccessTypeSelect disabled={preset !== "add"} placeholder={t("access.form.type.placeholder")} showSearch={!disabled} />
 | 
			
		||||
          </Form.Item>
 | 
			
		||||
        </Form>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,13 +11,13 @@ import AccessEditForm, { type AccessEditFormInstance, type AccessEditFormProps }
 | 
			
		|||
export type AccessEditModalProps = {
 | 
			
		||||
  data?: AccessEditFormProps["model"];
 | 
			
		||||
  loading?: boolean;
 | 
			
		||||
  mode: AccessEditFormProps["mode"];
 | 
			
		||||
  open?: boolean;
 | 
			
		||||
  preset: AccessEditFormProps["preset"];
 | 
			
		||||
  trigger?: React.ReactElement;
 | 
			
		||||
  onOpenChange?: (open: boolean) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const AccessEditModal = ({ data, loading, mode, trigger, ...props }: AccessEditModalProps) => {
 | 
			
		||||
const AccessEditModal = ({ data, loading, trigger, preset, ...props }: AccessEditModalProps) => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const [notificationApi, NotificationContextHolder] = notification.useNotification();
 | 
			
		||||
| 
						 | 
				
			
			@ -57,13 +57,13 @@ const AccessEditModal = ({ data, loading, mode, trigger, ...props }: AccessEditM
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      if (mode === "add") {
 | 
			
		||||
      if (preset === "add") {
 | 
			
		||||
        if (data?.id) {
 | 
			
		||||
          throw "Invalid props: `data`";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await createAccess(formRef.current!.getFieldsValue() as AccessModel);
 | 
			
		||||
      } else if (mode === "edit") {
 | 
			
		||||
      } else if (preset === "edit") {
 | 
			
		||||
        if (!data?.id) {
 | 
			
		||||
          throw "Invalid props: `data`";
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -100,14 +100,15 @@ const AccessEditModal = ({ data, loading, mode, trigger, ...props }: AccessEditM
 | 
			
		|||
        confirmLoading={formPending}
 | 
			
		||||
        destroyOnClose
 | 
			
		||||
        loading={loading}
 | 
			
		||||
        okText={mode === "edit" ? t("common.button.save") : t("common.button.submit")}
 | 
			
		||||
        okText={preset === "edit" ? t("common.button.save") : t("common.button.submit")}
 | 
			
		||||
        open={open}
 | 
			
		||||
        title={t(`access.action.${mode}`)}
 | 
			
		||||
        title={t(`access.action.${preset}`)}
 | 
			
		||||
        width={480}
 | 
			
		||||
        onOk={handleClickOk}
 | 
			
		||||
        onCancel={handleClickCancel}
 | 
			
		||||
      >
 | 
			
		||||
        <div className="pt-4 pb-2">
 | 
			
		||||
          <AccessEditForm ref={formRef} mode={mode === "add" ? "add" : "edit"} model={data} />
 | 
			
		||||
          <AccessEditForm ref={formRef} preset={preset === "add" ? "add" : "edit"} model={data} />
 | 
			
		||||
        </div>
 | 
			
		||||
      </Modal>
 | 
			
		||||
    </>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
 | 
			
		|||
import { useDeepCompareMemo } from "@ant-design/pro-components";
 | 
			
		||||
import { Button, Collapse, message, notification, Skeleton, Space, Switch, type CollapseProps } from "antd";
 | 
			
		||||
 | 
			
		||||
import Show from "@/components/Show";
 | 
			
		||||
import NotifyChannelEditForm, { type NotifyChannelEditFormInstance } from "./NotifyChannelEditForm";
 | 
			
		||||
import NotifyTestButton from "./NotifyTestButton";
 | 
			
		||||
import { notifyChannelsMap } from "@/domain/settings";
 | 
			
		||||
| 
						 | 
				
			
			@ -105,11 +106,9 @@ const NotifyChannels = ({ className, classNames, style, styles }: NotifyChannels
 | 
			
		|||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={className} style={style}>
 | 
			
		||||
      {!loadedAtOnce ? (
 | 
			
		||||
        <Skeleton active />
 | 
			
		||||
      ) : (
 | 
			
		||||
      <Show when={loadedAtOnce} fallback={<Skeleton active />}>
 | 
			
		||||
        <Collapse className={classNames?.collapse} style={styles?.collapse} accordion={true} bordered={false} items={channelCollapseItems} />
 | 
			
		||||
      )}
 | 
			
		||||
      </Show>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@ import { createSchemaFieldRule } from "antd-zod";
 | 
			
		|||
import { z } from "zod";
 | 
			
		||||
import { ClientResponseError } from "pocketbase";
 | 
			
		||||
 | 
			
		||||
import Show from "@/components/Show";
 | 
			
		||||
import { defaultNotifyTemplate, SETTINGS_NAMES, type NotifyTemplatesSettingsContent } from "@/domain/settings";
 | 
			
		||||
import { get as getSettings, save as saveSettings } from "@/repository/settings";
 | 
			
		||||
import { getErrMsg } from "@/utils/error";
 | 
			
		||||
| 
						 | 
				
			
			@ -88,9 +89,7 @@ const NotifyTemplateForm = ({ className, style }: NotifyTemplateFormProps) => {
 | 
			
		|||
      {MessageContextHolder}
 | 
			
		||||
      {NotificationContextHolder}
 | 
			
		||||
 | 
			
		||||
      {loading ? (
 | 
			
		||||
        <Skeleton active />
 | 
			
		||||
      ) : (
 | 
			
		||||
      <Show when={!loading} fallback={<Skeleton active />}>
 | 
			
		||||
        <Form form={form} disabled={formPending} initialValues={initialValues} layout="vertical" onFinish={handleFormFinish}>
 | 
			
		||||
          <Form.Item
 | 
			
		||||
            name="subject"
 | 
			
		||||
| 
						 | 
				
			
			@ -120,7 +119,7 @@ const NotifyTemplateForm = ({ className, style }: NotifyTemplateFormProps) => {
 | 
			
		|||
            </Button>
 | 
			
		||||
          </Form.Item>
 | 
			
		||||
        </Form>
 | 
			
		||||
      )}
 | 
			
		||||
      </Show>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -161,7 +161,7 @@ const ApplyForm = ({ data }: ApplyFormProps) => {
 | 
			
		|||
                <FormLabel className="flex justify-between w-full">
 | 
			
		||||
                  <div>{t("domain.application.form.access.label")}</div>
 | 
			
		||||
                  <AccessEditModal
 | 
			
		||||
                    mode="add"
 | 
			
		||||
                    preset="add"
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="flex items-center font-normal cursor-pointer text-primary hover:underline">
 | 
			
		||||
                        <PlusIcon size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -103,7 +103,7 @@ const DeployToAliyunALB = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
                  <AccessEditModal
 | 
			
		||||
                    data={{ configType: "aliyun" }}
 | 
			
		||||
                    mode="add"
 | 
			
		||||
                    preset="add"
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -84,7 +84,7 @@ const DeployToAliyunCDN = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
                  <AccessEditModal
 | 
			
		||||
                    data={{ configType: "aliyun" }}
 | 
			
		||||
                    mode="add"
 | 
			
		||||
                    preset="add"
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -103,7 +103,7 @@ const DeployToAliyunCLB = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
                  <AccessEditModal
 | 
			
		||||
                    data={{ configType: "aliyun" }}
 | 
			
		||||
                    mode="add"
 | 
			
		||||
                    preset="add"
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -93,7 +93,7 @@ const DeployToAliyunOSS = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
                  <AccessEditModal
 | 
			
		||||
                    data={{ configType: "aliyun" }}
 | 
			
		||||
                    mode="add"
 | 
			
		||||
                    preset="add"
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -83,7 +83,7 @@ const DeployToBaiduCloudCDN = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
                  <AccessEditModal
 | 
			
		||||
                    data={{ configType: "baiducloud" }}
 | 
			
		||||
                    mode="add"
 | 
			
		||||
                    preset="add"
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -83,7 +83,7 @@ const DeployToByteplusCDN = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
                  <AccessEditModal
 | 
			
		||||
                    data={{ configType: "byteplus" }}
 | 
			
		||||
                    mode="add"
 | 
			
		||||
                    preset="add"
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -85,7 +85,7 @@ const DeployToDogeCloudCDN = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
                  <AccessEditModal
 | 
			
		||||
                    data={{ configType: "dogecloud" }}
 | 
			
		||||
                    mode="add"
 | 
			
		||||
                    preset="add"
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -90,7 +90,7 @@ const DeployToHuaweiCloudCDN = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
                  <AccessEditModal
 | 
			
		||||
                    data={{ configType: "huaweicloud" }}
 | 
			
		||||
                    mode="add"
 | 
			
		||||
                    preset="add"
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -112,7 +112,7 @@ const DeployToHuaweiCloudELB = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
                  <AccessEditModal
 | 
			
		||||
                    data={{ configType: "huaweicloud" }}
 | 
			
		||||
                    mode="add"
 | 
			
		||||
                    preset="add"
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -99,7 +99,7 @@ const DeployToKubernetesSecret = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
                  <AccessEditModal
 | 
			
		||||
                    data={{ configType: "k8s" }}
 | 
			
		||||
                    mode="add"
 | 
			
		||||
                    preset="add"
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,11 +32,11 @@ const formSchema = z
 | 
			
		|||
    certPath: z
 | 
			
		||||
      .string()
 | 
			
		||||
      .min(1, t("domain.deployment.form.file_cert_path.placeholder"))
 | 
			
		||||
      .max(255, t("common.errmsg.string_max", { max: 255 })),
 | 
			
		||||
      .max(256, t("common.errmsg.string_max", { max: 256 })),
 | 
			
		||||
    keyPath: z
 | 
			
		||||
      .string()
 | 
			
		||||
      .min(0, t("domain.deployment.form.file_key_path.placeholder"))
 | 
			
		||||
      .max(255, t("common.errmsg.string_max", { max: 255 }))
 | 
			
		||||
      .max(256, t("common.errmsg.string_max", { max: 256 }))
 | 
			
		||||
      .nullish(),
 | 
			
		||||
    pfxPassword: z.string().nullish(),
 | 
			
		||||
    jksAlias: z.string().nullish(),
 | 
			
		||||
| 
						 | 
				
			
			@ -209,7 +209,7 @@ Remove-Item -Path "$pfxPath" -Force
 | 
			
		|||
 | 
			
		||||
                <AccessEditModal
 | 
			
		||||
                  data={{ configType: "local" }}
 | 
			
		||||
                  mode="add"
 | 
			
		||||
                  preset="add"
 | 
			
		||||
                  trigger={
 | 
			
		||||
                    <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                      <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -85,7 +85,7 @@ const DeployToQiniuCDN = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
                  <AccessEditModal
 | 
			
		||||
                    data={{ configType: "qiniu" }}
 | 
			
		||||
                    mode="add"
 | 
			
		||||
                    preset="add"
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,11 +31,11 @@ const formSchema = z
 | 
			
		|||
    certPath: z
 | 
			
		||||
      .string()
 | 
			
		||||
      .min(1, t("domain.deployment.form.file_cert_path.placeholder"))
 | 
			
		||||
      .max(255, t("common.errmsg.string_max", { max: 255 })),
 | 
			
		||||
      .max(256, t("common.errmsg.string_max", { max: 256 })),
 | 
			
		||||
    keyPath: z
 | 
			
		||||
      .string()
 | 
			
		||||
      .min(0, t("domain.deployment.form.file_key_path.placeholder"))
 | 
			
		||||
      .max(255, t("common.errmsg.string_max", { max: 255 }))
 | 
			
		||||
      .max(256, t("common.errmsg.string_max", { max: 256 }))
 | 
			
		||||
      .nullish(),
 | 
			
		||||
    pfxPassword: z.string().nullish(),
 | 
			
		||||
    jksAlias: z.string().nullish(),
 | 
			
		||||
| 
						 | 
				
			
			@ -127,7 +127,7 @@ const DeployToSSH = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
                <AccessEditModal
 | 
			
		||||
                  data={{ configType: "ssh" }}
 | 
			
		||||
                  mode="add"
 | 
			
		||||
                  preset="add"
 | 
			
		||||
                  trigger={
 | 
			
		||||
                    <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                      <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -83,7 +83,7 @@ const DeployToTencentCDN = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
                  <AccessEditModal
 | 
			
		||||
                    data={{ configType: "tencentcloud" }}
 | 
			
		||||
                    mode="add"
 | 
			
		||||
                    preset="add"
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -123,7 +123,7 @@ const DeployToTencentCLB = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
                  <AccessEditModal
 | 
			
		||||
                    data={{ configType: "tencentcloud" }}
 | 
			
		||||
                    mode="add"
 | 
			
		||||
                    preset="add"
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -89,7 +89,7 @@ const DeployToTencentCOS = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
                  <AccessEditModal
 | 
			
		||||
                    data={{ configType: "tencentcloud" }}
 | 
			
		||||
                    mode="add"
 | 
			
		||||
                    preset="add"
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -86,7 +86,7 @@ const DeployToTencentTEO = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
                  <AccessEditModal
 | 
			
		||||
                    data={{ configType: "tencentcloud" }}
 | 
			
		||||
                    mode="add"
 | 
			
		||||
                    preset="add"
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -83,7 +83,7 @@ const DeployToVolcengineCDN = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
                  <AccessEditModal
 | 
			
		||||
                    data={{ configType: "volcengine" }}
 | 
			
		||||
                    mode="add"
 | 
			
		||||
                    preset="add"
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -83,7 +83,7 @@ const DeployToVolcengineLive = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
                  <AccessEditModal
 | 
			
		||||
                    data={{ configType: "volcengine" }}
 | 
			
		||||
                    mode="add"
 | 
			
		||||
                    preset="add"
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -86,7 +86,7 @@ const DeployToWebhook = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
                  <AccessEditModal
 | 
			
		||||
                    data={{ configType: "webhook" }}
 | 
			
		||||
                    mode="add"
 | 
			
		||||
                    preset="add"
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,125 +0,0 @@
 | 
			
		|||
import { z } from "zod";
 | 
			
		||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "../ui/dialog";
 | 
			
		||||
import { useWorkflowStore } from "@/stores/workflow";
 | 
			
		||||
import { useZustandShallowSelector } from "@/hooks";
 | 
			
		||||
import { useForm } from "react-hook-form";
 | 
			
		||||
import { zodResolver } from "@hookform/resolvers/zod";
 | 
			
		||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form";
 | 
			
		||||
import { Input } from "../ui/input";
 | 
			
		||||
import { Button } from "../ui/button";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { memo, useEffect, useMemo, useState } from "react";
 | 
			
		||||
import { Textarea } from "../ui/textarea";
 | 
			
		||||
 | 
			
		||||
type WorkflowNameEditDialogProps = {
 | 
			
		||||
  trigger: React.ReactNode;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const formSchema = z.object({
 | 
			
		||||
  name: z.string(),
 | 
			
		||||
  description: z.string(),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const WorkflowNameBaseInfoDialog = ({ trigger }: WorkflowNameEditDialogProps) => {
 | 
			
		||||
  const { setBaseInfo, workflow } = useWorkflowStore(useZustandShallowSelector(["setBaseInfo", "workflow"]));
 | 
			
		||||
 | 
			
		||||
  const form = useForm<z.infer<typeof formSchema>>({
 | 
			
		||||
    resolver: zodResolver(formSchema),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const memoWorkflow = useMemo(() => workflow, [workflow]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    form.reset({ name: workflow.name, description: workflow.description });
 | 
			
		||||
  }, [memoWorkflow]);
 | 
			
		||||
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const [open, setOpen] = useState(false);
 | 
			
		||||
 | 
			
		||||
  const onSubmit = async (config: z.infer<typeof formSchema>) => {
 | 
			
		||||
    await setBaseInfo(config.name, config.description);
 | 
			
		||||
    setOpen(false);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <Dialog
 | 
			
		||||
        open={open}
 | 
			
		||||
        onOpenChange={(val) => {
 | 
			
		||||
          setOpen(val);
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <DialogTrigger>{trigger}</DialogTrigger>
 | 
			
		||||
        <DialogContent>
 | 
			
		||||
          <DialogHeader>
 | 
			
		||||
            <DialogTitle className="dark:text-stone-200">{t("workflow.baseinfo.title")}</DialogTitle>
 | 
			
		||||
          </DialogHeader>
 | 
			
		||||
          <div>
 | 
			
		||||
            <Form {...form}>
 | 
			
		||||
              <form
 | 
			
		||||
                onSubmit={(e) => {
 | 
			
		||||
                  e.stopPropagation();
 | 
			
		||||
                  form.handleSubmit(onSubmit)(e);
 | 
			
		||||
                }}
 | 
			
		||||
                className="space-y-8 dark:text-stone-200"
 | 
			
		||||
              >
 | 
			
		||||
                <FormField
 | 
			
		||||
                  control={form.control}
 | 
			
		||||
                  name="name"
 | 
			
		||||
                  render={({ field }) => (
 | 
			
		||||
                    <FormItem>
 | 
			
		||||
                      <FormLabel>{t("workflow.props.name")}</FormLabel>
 | 
			
		||||
                      <FormControl>
 | 
			
		||||
                        <Input
 | 
			
		||||
                          placeholder={t("workflow.props.name.placeholder")}
 | 
			
		||||
                          {...field}
 | 
			
		||||
                          value={field.value}
 | 
			
		||||
                          defaultValue={workflow.name}
 | 
			
		||||
                          onChange={(e) => {
 | 
			
		||||
                            form.setValue("name", e.target.value);
 | 
			
		||||
                          }}
 | 
			
		||||
                        />
 | 
			
		||||
                      </FormControl>
 | 
			
		||||
 | 
			
		||||
                      <FormMessage />
 | 
			
		||||
                    </FormItem>
 | 
			
		||||
                  )}
 | 
			
		||||
                />
 | 
			
		||||
 | 
			
		||||
                <FormField
 | 
			
		||||
                  control={form.control}
 | 
			
		||||
                  name="description"
 | 
			
		||||
                  render={({ field }) => (
 | 
			
		||||
                    <FormItem>
 | 
			
		||||
                      <FormLabel>{t("workflow.props.description")}</FormLabel>
 | 
			
		||||
                      <FormControl>
 | 
			
		||||
                        <Textarea
 | 
			
		||||
                          placeholder={t("workflow.props.description.placeholder")}
 | 
			
		||||
                          {...field}
 | 
			
		||||
                          value={field.value}
 | 
			
		||||
                          defaultValue={workflow.description}
 | 
			
		||||
                          onChange={(e) => {
 | 
			
		||||
                            form.setValue("description", e.target.value);
 | 
			
		||||
                          }}
 | 
			
		||||
                        />
 | 
			
		||||
                      </FormControl>
 | 
			
		||||
 | 
			
		||||
                      <FormMessage />
 | 
			
		||||
                    </FormItem>
 | 
			
		||||
                  )}
 | 
			
		||||
                />
 | 
			
		||||
 | 
			
		||||
                <div className="flex justify-end">
 | 
			
		||||
                  <Button type="submit">{t("common.button.save")}</Button>
 | 
			
		||||
                </div>
 | 
			
		||||
              </form>
 | 
			
		||||
            </Form>
 | 
			
		||||
          </div>
 | 
			
		||||
        </DialogContent>
 | 
			
		||||
      </Dialog>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default memo(WorkflowNameBaseInfoDialog);
 | 
			
		||||
| 
						 | 
				
			
			@ -3,6 +3,9 @@
 | 
			
		|||
  "common.button.cancel": "Cancel",
 | 
			
		||||
  "common.button.copy": "Copy",
 | 
			
		||||
  "common.button.delete": "Delete",
 | 
			
		||||
  "common.button.disable": "Disable",
 | 
			
		||||
  "common.button.edit": "Edit",
 | 
			
		||||
  "common.button.enable": "Enable",
 | 
			
		||||
  "common.button.ok": "Ok",
 | 
			
		||||
  "common.button.reset": "Reset",
 | 
			
		||||
  "common.button.save": "Save",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,31 +9,31 @@
 | 
			
		|||
  "workflow.action.delete.confirm": "Are you sure to delete this workflow?",
 | 
			
		||||
 | 
			
		||||
  "workflow.props.name": "Name",
 | 
			
		||||
  "workflow.props.name.placeholder": "Please enter name",
 | 
			
		||||
  "workflow.props.name.default": "Unnamed",
 | 
			
		||||
  "workflow.props.description": "Description",
 | 
			
		||||
  "workflow.props.description.placeholder": "Please enter description",
 | 
			
		||||
  "workflow.props.trigger": "Trigger",
 | 
			
		||||
  "workflow.props.trigger.auto": "Auto",
 | 
			
		||||
  "workflow.props.trigger.manual": "Manual",
 | 
			
		||||
  "workflow.props.latest_execution_status": "Latest Execution Status",
 | 
			
		||||
  "workflow.props.state": "State",
 | 
			
		||||
  "workflow.props.state.filter.enabled": "Enabled",
 | 
			
		||||
  "workflow.props.state.filter.disabled": "Disabled",
 | 
			
		||||
  "workflow.props.created_at": "Created At",
 | 
			
		||||
  "workflow.props.updated_at": "Updated At",
 | 
			
		||||
 | 
			
		||||
  "workflow.detail.title": "Workflow",
 | 
			
		||||
  "workflow.detail.history": "History",
 | 
			
		||||
  "workflow.baseinfo.modal.title": "Workflow Base Information",
 | 
			
		||||
  "workflow.baseinfo.form.name.label": "Name",
 | 
			
		||||
  "workflow.baseinfo.form.name.placeholder": "Please enter name",
 | 
			
		||||
  "workflow.baseinfo.form.description.label": "Description",
 | 
			
		||||
  "workflow.baseinfo.form.description.placeholder": "Please enter description",
 | 
			
		||||
 | 
			
		||||
  "workflow.detail.action.save": "Save updates",
 | 
			
		||||
  "workflow.detail.action.save.failed": "Save failed",
 | 
			
		||||
  "workflow.detail.action.save.failed.uncompleted": "Save failed, please complete all node settings",
 | 
			
		||||
  "workflow.detail.action.save.failed.uncompleted": "Please complete all node settings",
 | 
			
		||||
  "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.baseinfo.title": "Basic Information",
 | 
			
		||||
 | 
			
		||||
  "workflow.history.page.title": "Logs",
 | 
			
		||||
  "workflow.history.props.state": "State",
 | 
			
		||||
  "workflow.history.props.state.success": "Success",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,9 @@
 | 
			
		|||
  "common.button.cancel": "取消",
 | 
			
		||||
  "common.button.copy": "复制",
 | 
			
		||||
  "common.button.delete": "刪除",
 | 
			
		||||
  "common.button.disable": "禁用",
 | 
			
		||||
  "common.button.edit": "编辑",
 | 
			
		||||
  "common.button.enable": "启用",
 | 
			
		||||
  "common.button.ok": "确定",
 | 
			
		||||
  "common.button.reset": "重置",
 | 
			
		||||
  "common.button.save": "保存",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,24 +9,26 @@
 | 
			
		|||
  "workflow.action.delete.confirm": "确定要删除此工作流吗?",
 | 
			
		||||
 | 
			
		||||
  "workflow.props.name": "名称",
 | 
			
		||||
  "workflow.props.name.placeholder": "请输入名称",
 | 
			
		||||
  "workflow.props.name.default": "未命名工作流",
 | 
			
		||||
  "workflow.props.description": "描述",
 | 
			
		||||
  "workflow.props.description.placeholder": "请输入描述",
 | 
			
		||||
  "workflow.props.trigger": "触发方式",
 | 
			
		||||
  "workflow.props.trigger.auto": "自动",
 | 
			
		||||
  "workflow.props.trigger.manual": "手动",
 | 
			
		||||
  "workflow.props.latest_execution_status": "最近执行状态",
 | 
			
		||||
  "workflow.props.state": "启用状态",
 | 
			
		||||
  "workflow.props.state.filter.enabled": "启用",
 | 
			
		||||
  "workflow.props.state.filter.disabled": "未启用",
 | 
			
		||||
  "workflow.props.created_at": "创建时间",
 | 
			
		||||
  "workflow.props.updated_at": "更新时间",
 | 
			
		||||
 | 
			
		||||
  "workflow.detail.title": "流程",
 | 
			
		||||
  "workflow.detail.history": "历史",
 | 
			
		||||
  "workflow.baseinfo.modal.title": "编辑基本信息",
 | 
			
		||||
  "workflow.baseinfo.form.name.label": "名称",
 | 
			
		||||
  "workflow.baseinfo.form.name.placeholder": "请输入工作流名称",
 | 
			
		||||
  "workflow.baseinfo.form.description.label": "描述",
 | 
			
		||||
  "workflow.baseinfo.form.description.placeholder": "请输入工作流描述",
 | 
			
		||||
 | 
			
		||||
  "workflow.detail.action.save": "保存变更",
 | 
			
		||||
  "workflow.detail.action.save.failed": "保存失败",
 | 
			
		||||
  "workflow.detail.action.save.failed.uncompleted": "保存失败,请完成所有节点设置",
 | 
			
		||||
  "workflow.detail.action.save.failed.uncompleted": "请完成所有节点设置",
 | 
			
		||||
  "workflow.detail.action.run": "立即执行",
 | 
			
		||||
  "workflow.detail.action.run.failed": "执行失败",
 | 
			
		||||
  "workflow.detail.action.run.success": "执行成功",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
import { Navigate, Outlet } from "react-router-dom";
 | 
			
		||||
 | 
			
		||||
import Version from "@/components/Version";
 | 
			
		||||
import Version from "@/components/ui/Version";
 | 
			
		||||
import { getPocketBase } from "@/repository/pocketbase";
 | 
			
		||||
 | 
			
		||||
const AuthLayout = () => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,7 +15,7 @@ import {
 | 
			
		|||
  Workflow as WorkflowIcon,
 | 
			
		||||
} from "lucide-react";
 | 
			
		||||
 | 
			
		||||
import Version from "@/components/Version";
 | 
			
		||||
import Version from "@/components/ui/Version";
 | 
			
		||||
import { useBrowserTheme } from "@/hooks";
 | 
			
		||||
import { getPocketBase } from "@/repository/pocketbase";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -73,7 +73,7 @@ const AccessList = () => {
 | 
			
		|||
          <Space size={0}>
 | 
			
		||||
            <AccessEditModal
 | 
			
		||||
              data={record}
 | 
			
		||||
              mode="edit"
 | 
			
		||||
              preset="edit"
 | 
			
		||||
              trigger={
 | 
			
		||||
                <Tooltip title={t("access.action.edit")}>
 | 
			
		||||
                  <Button type="link" icon={<PencilIcon size={16} />} />
 | 
			
		||||
| 
						 | 
				
			
			@ -83,7 +83,7 @@ const AccessList = () => {
 | 
			
		|||
 | 
			
		||||
            <AccessEditModal
 | 
			
		||||
              data={{ ...record, id: undefined, name: `${record.name}-copy` }}
 | 
			
		||||
              mode="add"
 | 
			
		||||
              preset="add"
 | 
			
		||||
              trigger={
 | 
			
		||||
                <Tooltip title={t("access.action.copy")}>
 | 
			
		||||
                  <Button type="link" icon={<CopyIcon size={16} />} />
 | 
			
		||||
| 
						 | 
				
			
			@ -168,7 +168,7 @@ const AccessList = () => {
 | 
			
		|||
        extra={[
 | 
			
		||||
          <AccessEditModal
 | 
			
		||||
            key="create"
 | 
			
		||||
            mode="add"
 | 
			
		||||
            preset="add"
 | 
			
		||||
            trigger={
 | 
			
		||||
              <Button type="primary" icon={<PlusIcon size={16} />}>
 | 
			
		||||
                {t("access.action.add")}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,46 +1,40 @@
 | 
			
		|||
import { useEffect, useMemo, useState } from "react";
 | 
			
		||||
import { useNavigate, useParams } from "react-router-dom";
 | 
			
		||||
import { cloneElement, memo, useEffect, useMemo, useState } from "react";
 | 
			
		||||
import { useParams } from "react-router-dom";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { Button, message, notification, Switch } from "antd";
 | 
			
		||||
import { ArrowLeft as ArrowLeftIcon } from "lucide-react";
 | 
			
		||||
import { useDeepCompareEffect } from "ahooks";
 | 
			
		||||
import { Button, Card, Form, Input, message, Modal, notification, Tabs, Typography, type FormInstance } from "antd";
 | 
			
		||||
import { createSchemaFieldRule } from "antd-zod";
 | 
			
		||||
import { PageHeader } from "@ant-design/pro-components";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
 | 
			
		||||
import Show from "@/components/Show";
 | 
			
		||||
import End from "@/components/workflow/End";
 | 
			
		||||
import NodeRender from "@/components/workflow/NodeRender";
 | 
			
		||||
import WorkflowBaseInfoEditDialog from "@/components/workflow/WorkflowBaseInfoEditDialog";
 | 
			
		||||
import WorkflowLog from "@/components/workflow/WorkflowLog";
 | 
			
		||||
import WorkflowProvider from "@/components/workflow/WorkflowProvider";
 | 
			
		||||
import { cn } from "@/components/ui/utils";
 | 
			
		||||
import { useZustandShallowSelector } from "@/hooks";
 | 
			
		||||
import { allNodesValidated, WorkflowNode } from "@/domain/workflow";
 | 
			
		||||
import { allNodesValidated, type WorkflowModel, type WorkflowNode } from "@/domain/workflow";
 | 
			
		||||
import { useWorkflowStore } from "@/stores/workflow";
 | 
			
		||||
import { run as runWorkflow } from "@/api/workflow";
 | 
			
		||||
import { getErrMsg } from "@/utils/error";
 | 
			
		||||
 | 
			
		||||
const WorkflowDetail = () => {
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const { id } = useParams();
 | 
			
		||||
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const [messageApi, MessageContextHolder] = message.useMessage();
 | 
			
		||||
  const [_, NotificationContextHolder] = notification.useNotification();
 | 
			
		||||
 | 
			
		||||
  // 3. 使用正确的选择器和 shallow 比较
 | 
			
		||||
  const { workflow, init, switchEnable, save } = useWorkflowStore(useZustandShallowSelector(["workflow", "init", "switchEnable", "save"]));
 | 
			
		||||
 | 
			
		||||
  // 从 url 中获取 workflowId
 | 
			
		||||
  const [locId, setLocId] = useState<string>("");
 | 
			
		||||
 | 
			
		||||
  const [tab, setTab] = useState("workflow");
 | 
			
		||||
 | 
			
		||||
  const [running, setRunning] = useState(false);
 | 
			
		||||
  const [notificationApi, NotificationContextHolder] = notification.useNotification();
 | 
			
		||||
 | 
			
		||||
  const { id: workflowId } = useParams();
 | 
			
		||||
  const { workflow, init, setBaseInfo, switchEnable, save } = useWorkflowStore(
 | 
			
		||||
    useZustandShallowSelector(["workflow", "init", "setBaseInfo", "switchEnable", "save"])
 | 
			
		||||
  );
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    init(id ?? "");
 | 
			
		||||
    if (id) {
 | 
			
		||||
      setLocId(id);
 | 
			
		||||
    }
 | 
			
		||||
  }, [id]);
 | 
			
		||||
    init(workflowId);
 | 
			
		||||
  }, [workflowId]);
 | 
			
		||||
 | 
			
		||||
  const [tabValue, setTabValue] = useState<"orchestrate" | "runlog">("orchestrate");
 | 
			
		||||
 | 
			
		||||
  // const [running, setRunning] = useState(false);
 | 
			
		||||
 | 
			
		||||
  const elements = useMemo(() => {
 | 
			
		||||
    let current = workflow.draft as WorkflowNode;
 | 
			
		||||
| 
						 | 
				
			
			@ -58,130 +52,213 @@ const WorkflowDetail = () => {
 | 
			
		|||
    return elements;
 | 
			
		||||
  }, [workflow]);
 | 
			
		||||
 | 
			
		||||
  const handleBackClick = () => {
 | 
			
		||||
    // 返回上一步
 | 
			
		||||
    navigate(-1);
 | 
			
		||||
  const handleBaseInfoFormFinish = async (fields: Pick<WorkflowModel, "name" | "description">) => {
 | 
			
		||||
    try {
 | 
			
		||||
      await setBaseInfo(fields.name!, fields.description!);
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      console.error(err);
 | 
			
		||||
      notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleEnableChange = () => {
 | 
			
		||||
    if (!workflow.enabled && !allNodesValidated(workflow.draft as WorkflowNode)) {
 | 
			
		||||
    if (!workflow.enabled && !allNodesValidated(workflow.content!)) {
 | 
			
		||||
      messageApi.warning(t("workflow.detail.action.save.failed.uncompleted"));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    switchEnable();
 | 
			
		||||
    if (!locId) {
 | 
			
		||||
      navigate(`/workflows/${workflow.id}`);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleWorkflowSaveClick = () => {
 | 
			
		||||
    if (!allNodesValidated(workflow.draft as WorkflowNode)) {
 | 
			
		||||
      messageApi.warning(t("workflow.detail.action.save.failed.uncompleted"));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    save();
 | 
			
		||||
    if (!locId) {
 | 
			
		||||
      navigate(`/workflows/${workflow.id}`);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
  // const handleWorkflowSaveClick = () => {
 | 
			
		||||
  //   if (!allNodesValidated(workflow.draft as WorkflowNode)) {
 | 
			
		||||
  //     messageApi.warning(t("workflow.detail.action.save.failed.uncompleted"));
 | 
			
		||||
  //     return;
 | 
			
		||||
  //   }
 | 
			
		||||
  //   save();
 | 
			
		||||
  // };
 | 
			
		||||
 | 
			
		||||
  const getTabCls = (tabName: string) => {
 | 
			
		||||
    if (tab === tabName) {
 | 
			
		||||
      return "text-primary border-primary";
 | 
			
		||||
    }
 | 
			
		||||
    return "border-transparent hover:text-primary hover:border-b-primary";
 | 
			
		||||
  };
 | 
			
		||||
  // const handleRunClick = async () => {
 | 
			
		||||
  //   if (running) return;
 | 
			
		||||
 | 
			
		||||
  const handleRunClick = async () => {
 | 
			
		||||
    if (running) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    setRunning(true);
 | 
			
		||||
    try {
 | 
			
		||||
      await runWorkflow(workflow.id as string);
 | 
			
		||||
      messageApi.success(t("workflow.detail.action.run.success"));
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      messageApi.warning(t("workflow.detail.action.run.failed"));
 | 
			
		||||
    }
 | 
			
		||||
    setRunning(false);
 | 
			
		||||
  };
 | 
			
		||||
  //   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);
 | 
			
		||||
  //   }
 | 
			
		||||
  // };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      {MessageContextHolder}
 | 
			
		||||
      {NotificationContextHolder}
 | 
			
		||||
 | 
			
		||||
      <WorkflowProvider>
 | 
			
		||||
        <div className="h-16 sticky  top-0 left-0 z-[1]` shadow-md bg-muted/40 flex justify-between items-center">
 | 
			
		||||
          <div className="px-5 text-stone-700 dark:text-stone-200 flex items-center space-x-2">
 | 
			
		||||
            <ArrowLeftIcon className="cursor-pointer" onClick={handleBackClick} />
 | 
			
		||||
            <WorkflowBaseInfoEditDialog
 | 
			
		||||
              trigger={
 | 
			
		||||
                <div className="flex flex-col space-y-1 cursor-pointer items-start">
 | 
			
		||||
                  <div className="truncate  max-w-[200px]">{workflow.name ? workflow.name : t("workflow.props.name.default")}</div>
 | 
			
		||||
                  <div className="text-sm text-muted-foreground truncate  max-w-[200px]">
 | 
			
		||||
                    {workflow.description ? workflow.description : t("workflow.props.description.placeholder")}
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              }
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div className="flex justify-between space-x-5 text-muted-foreground text-lg h-full">
 | 
			
		||||
            <div
 | 
			
		||||
              className={cn("h-full flex items-center cursor-pointer border-b-2", getTabCls("workflow"))}
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                setTab("workflow");
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              <div>{t("workflow.detail.title")}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div
 | 
			
		||||
              className={cn("h-full flex items-center cursor-pointer border-b-2", getTabCls("history"))}
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                setTab("history");
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              <div>{t("workflow.detail.history")}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div className="px-5 flex items-center space-x-3">
 | 
			
		||||
            <Show when={!!workflow.enabled}>
 | 
			
		||||
              <Show
 | 
			
		||||
                when={!!workflow.hasDraft}
 | 
			
		||||
                fallback={
 | 
			
		||||
                  <Button type="text" onClick={handleRunClick}>
 | 
			
		||||
                    {running ? t("workflow.detail.action.running") : t("workflow.detail.action.run")}
 | 
			
		||||
      <Card styles={{ body: { padding: "0.5rem", paddingBottom: 0 } }}>
 | 
			
		||||
        <PageHeader
 | 
			
		||||
          style={{ paddingBottom: 0 }}
 | 
			
		||||
          title={workflow.name}
 | 
			
		||||
          extra={[
 | 
			
		||||
            <Button.Group key="actions">
 | 
			
		||||
              <WorkflowBaseInfoModalForm
 | 
			
		||||
                model={workflow}
 | 
			
		||||
                trigger={
 | 
			
		||||
                  <Button ghost type="primary">
 | 
			
		||||
                    {t("common.button.edit")}
 | 
			
		||||
                  </Button>
 | 
			
		||||
                }
 | 
			
		||||
              >
 | 
			
		||||
                <Button type="primary" onClick={handleWorkflowSaveClick}>
 | 
			
		||||
                  {t("workflow.detail.action.save")}
 | 
			
		||||
                </Button>
 | 
			
		||||
              </Show>
 | 
			
		||||
                onFinish={handleBaseInfoFormFinish}
 | 
			
		||||
              />
 | 
			
		||||
 | 
			
		||||
              <Button ghost type="primary" onClick={handleEnableChange}>
 | 
			
		||||
                {workflow.enabled ? t("common.button.disable") : t("common.button.enable")}
 | 
			
		||||
              </Button>
 | 
			
		||||
            </Button.Group>,
 | 
			
		||||
          ]}
 | 
			
		||||
        >
 | 
			
		||||
          <Typography.Paragraph type="secondary">{workflow.description}</Typography.Paragraph>
 | 
			
		||||
          <Tabs
 | 
			
		||||
            activeKey={tabValue}
 | 
			
		||||
            defaultActiveKey="orchestrate"
 | 
			
		||||
            items={[
 | 
			
		||||
              { key: "orchestrate", label: "流程编排" },
 | 
			
		||||
              { key: "runlog", label: "执行历史" },
 | 
			
		||||
            ]}
 | 
			
		||||
            renderTabBar={(props, DefaultTabBar) => <DefaultTabBar {...props} style={{ margin: 0 }} />}
 | 
			
		||||
            tabBarStyle={{ border: "none" }}
 | 
			
		||||
            onChange={(key) => setTabValue(key as typeof tabValue)}
 | 
			
		||||
          />
 | 
			
		||||
        </PageHeader>
 | 
			
		||||
      </Card>
 | 
			
		||||
 | 
			
		||||
      <div className="p-4">
 | 
			
		||||
        <Card>
 | 
			
		||||
          <WorkflowProvider>
 | 
			
		||||
            <Show when={tabValue === "orchestrate"}>
 | 
			
		||||
              <div className="flex flex-col items-center">{elements}</div>
 | 
			
		||||
            </Show>
 | 
			
		||||
 | 
			
		||||
            <Switch checked={workflow.enabled ?? false} onChange={handleEnableChange} />
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <Show when={tab === "workflow"}>
 | 
			
		||||
          <div className="p-4">
 | 
			
		||||
            <div className="flex flex-col items-center p-4 bg-background">{elements}</div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </Show>
 | 
			
		||||
 | 
			
		||||
        <Show when={!!locId && tab === "history"}>
 | 
			
		||||
          <div className="p-4">
 | 
			
		||||
            <div className="flex flex-col items-center p-4 bg-background">
 | 
			
		||||
            <Show when={tabValue === "runlog"}>
 | 
			
		||||
              <WorkflowLog />
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </Show>
 | 
			
		||||
      </WorkflowProvider>
 | 
			
		||||
            </Show>
 | 
			
		||||
          </WorkflowProvider>
 | 
			
		||||
        </Card>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const WorkflowBaseInfoModalForm = memo(
 | 
			
		||||
  ({
 | 
			
		||||
    model,
 | 
			
		||||
    trigger,
 | 
			
		||||
    onFinish,
 | 
			
		||||
  }: {
 | 
			
		||||
    model: Pick<WorkflowModel, "name" | "description">;
 | 
			
		||||
    trigger?: React.ReactElement;
 | 
			
		||||
    onFinish?: (fields: Pick<WorkflowModel, "name" | "description">) => Promise<void | boolean>;
 | 
			
		||||
  }) => {
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
    const [open, setOpen] = useState(false);
 | 
			
		||||
 | 
			
		||||
    const triggerEl = useMemo(() => {
 | 
			
		||||
      if (!trigger) {
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return cloneElement(trigger, {
 | 
			
		||||
        ...trigger.props,
 | 
			
		||||
        onClick: () => {
 | 
			
		||||
          setOpen(true);
 | 
			
		||||
          trigger.props?.onClick?.();
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    }, [trigger, setOpen]);
 | 
			
		||||
 | 
			
		||||
    const formSchema = z.object({
 | 
			
		||||
      name: z
 | 
			
		||||
        .string({ message: t("workflow.baseinfo.form.name.placeholder") })
 | 
			
		||||
        .trim()
 | 
			
		||||
        .min(1, t("workflow.baseinfo.form.name.placeholder"))
 | 
			
		||||
        .max(64, t("common.errmsg.string_max", { max: 64 })),
 | 
			
		||||
      description: z
 | 
			
		||||
        .string({ message: t("workflow.baseinfo.form.description.placeholder") })
 | 
			
		||||
        .trim()
 | 
			
		||||
        .min(0, t("workflow.baseinfo.form.description.placeholder"))
 | 
			
		||||
        .max(256, t("common.errmsg.string_max", { max: 256 }))
 | 
			
		||||
        .nullish(),
 | 
			
		||||
    });
 | 
			
		||||
    const formRule = createSchemaFieldRule(formSchema);
 | 
			
		||||
    const [form] = Form.useForm<FormInstance<z.infer<typeof formSchema>>>();
 | 
			
		||||
    const [formPending, setFormPending] = useState(false);
 | 
			
		||||
 | 
			
		||||
    const [initialValues, setInitialValues] = useState<Partial<z.infer<typeof formSchema>>>(model as Partial<z.infer<typeof formSchema>>);
 | 
			
		||||
    useDeepCompareEffect(() => {
 | 
			
		||||
      setInitialValues(model as Partial<z.infer<typeof formSchema>>);
 | 
			
		||||
    }, [model]);
 | 
			
		||||
 | 
			
		||||
    const handleClickOk = async () => {
 | 
			
		||||
      setFormPending(true);
 | 
			
		||||
      try {
 | 
			
		||||
        await form.validateFields();
 | 
			
		||||
      } catch (err) {
 | 
			
		||||
        setFormPending(false);
 | 
			
		||||
        return Promise.reject();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        const ret = await onFinish?.(form.getFieldsValue(true));
 | 
			
		||||
        if (ret != null && !ret) return;
 | 
			
		||||
 | 
			
		||||
        setOpen(false);
 | 
			
		||||
      } finally {
 | 
			
		||||
        setFormPending(false);
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const handleClickCancel = () => {
 | 
			
		||||
      if (formPending) return Promise.reject();
 | 
			
		||||
 | 
			
		||||
      setOpen(false);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <>
 | 
			
		||||
        {triggerEl}
 | 
			
		||||
 | 
			
		||||
        <Modal
 | 
			
		||||
          afterClose={() => setOpen(false)}
 | 
			
		||||
          cancelButtonProps={{ disabled: formPending }}
 | 
			
		||||
          closable
 | 
			
		||||
          confirmLoading={formPending}
 | 
			
		||||
          destroyOnClose
 | 
			
		||||
          okText={t("common.button.save")}
 | 
			
		||||
          open={open}
 | 
			
		||||
          title={t(`workflow.baseinfo.modal.title`)}
 | 
			
		||||
          width={480}
 | 
			
		||||
          onOk={handleClickOk}
 | 
			
		||||
          onCancel={handleClickCancel}
 | 
			
		||||
        >
 | 
			
		||||
          <div className="pt-4 pb-2">
 | 
			
		||||
            <Form form={form} initialValues={initialValues} layout="vertical">
 | 
			
		||||
              <Form.Item name="name" label={t("workflow.baseinfo.form.name.label")} rules={[formRule]}>
 | 
			
		||||
                <Input placeholder={t("workflow.baseinfo.form.name.placeholder")} />
 | 
			
		||||
              </Form.Item>
 | 
			
		||||
 | 
			
		||||
              <Form.Item name="description" label={t("workflow.baseinfo.form.description.label")} rules={[formRule]}>
 | 
			
		||||
                <Input placeholder={t("workflow.baseinfo.form.description.placeholder")} />
 | 
			
		||||
              </Form.Item>
 | 
			
		||||
            </Form>
 | 
			
		||||
          </div>
 | 
			
		||||
        </Modal>
 | 
			
		||||
      </>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export default WorkflowDetail;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@ import {
 | 
			
		|||
  Divider,
 | 
			
		||||
  Empty,
 | 
			
		||||
  Menu,
 | 
			
		||||
  message,
 | 
			
		||||
  Modal,
 | 
			
		||||
  notification,
 | 
			
		||||
  Radio,
 | 
			
		||||
| 
						 | 
				
			
			@ -24,7 +25,7 @@ import { Filter as FilterIcon, Pencil as PencilIcon, Plus as PlusIcon, Trash2 as
 | 
			
		|||
import dayjs from "dayjs";
 | 
			
		||||
import { ClientResponseError } from "pocketbase";
 | 
			
		||||
 | 
			
		||||
import { WorkflowModel } from "@/domain/workflow";
 | 
			
		||||
import { allNodesValidated, type WorkflowModel } from "@/domain/workflow";
 | 
			
		||||
import { list as listWorkflow, remove as removeWorkflow, save as saveWorkflow } from "@/repository/workflow";
 | 
			
		||||
import { getErrMsg } from "@/utils/error";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -36,6 +37,7 @@ const WorkflowList = () => {
 | 
			
		|||
 | 
			
		||||
  const { token: themeToken } = theme.useToken();
 | 
			
		||||
 | 
			
		||||
  const [messageApi, MessageContextHolder] = message.useMessage();
 | 
			
		||||
  const [modalApi, ModelContextHolder] = Modal.useModal();
 | 
			
		||||
  const [notificationApi, NotificationContextHolder] = notification.useNotification();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -146,7 +148,7 @@ const WorkflowList = () => {
 | 
			
		|||
    },
 | 
			
		||||
    {
 | 
			
		||||
      key: "lastExecutedAt",
 | 
			
		||||
      title: "最近执行状态",
 | 
			
		||||
      title: t("workflow.props.latest_execution_status"),
 | 
			
		||||
      render: () => {
 | 
			
		||||
        // TODO: 最近执行状态
 | 
			
		||||
        return <>TODO</>;
 | 
			
		||||
| 
						 | 
				
			
			@ -237,6 +239,11 @@ const WorkflowList = () => {
 | 
			
		|||
 | 
			
		||||
  const handleEnabledChange = async (workflow: WorkflowModel) => {
 | 
			
		||||
    try {
 | 
			
		||||
      if (!workflow.enabled && !allNodesValidated(workflow.content!)) {
 | 
			
		||||
        messageApi.warning(t("workflow.detail.action.save.failed.uncompleted"));
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const resp = await saveWorkflow({
 | 
			
		||||
        id: workflow.id,
 | 
			
		||||
        enabled: !tableData.find((item) => item.id === workflow.id)?.enabled,
 | 
			
		||||
| 
						 | 
				
			
			@ -276,11 +283,12 @@ const WorkflowList = () => {
 | 
			
		|||
  };
 | 
			
		||||
 | 
			
		||||
  const handleCreateClick = () => {
 | 
			
		||||
    navigate("/workflows/");
 | 
			
		||||
    alert("TODO");
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="p-4">
 | 
			
		||||
      {MessageContextHolder}
 | 
			
		||||
      {ModelContextHolder}
 | 
			
		||||
      {NotificationContextHolder}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,8 +59,8 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
 | 
			
		|||
  setBaseInfo: async (name: string, description: string) => {
 | 
			
		||||
    const data: Record<string, string | boolean | WorkflowNode> = {
 | 
			
		||||
      id: (get().workflow.id as string) ?? "",
 | 
			
		||||
      name: name,
 | 
			
		||||
      description: description,
 | 
			
		||||
      name: name || "",
 | 
			
		||||
      description: description || "",
 | 
			
		||||
    };
 | 
			
		||||
    if (!data.id) {
 | 
			
		||||
      data.draft = get().workflow.draft as WorkflowNode;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue