fix conflict
This commit is contained in:
@@ -52,7 +52,7 @@ const MultipleInput = ({
|
||||
draft.push("");
|
||||
});
|
||||
setValue(newValue);
|
||||
setTimeout(() => itemRefs.current[newValue.length - 1]?.focus(), 0);
|
||||
setTimeout(() => itemRefs.current[newValue.length - 1]?.focus(), 1);
|
||||
|
||||
onValueCreate?.(newValue.length - 1);
|
||||
};
|
||||
@@ -110,7 +110,7 @@ const MultipleInput = ({
|
||||
draft.splice(index + 1, 0, "");
|
||||
});
|
||||
setValue(newValue);
|
||||
setTimeout(() => itemRefs.current[index + 1]?.focus(), 0);
|
||||
setTimeout(() => itemRefs.current[index + 1]?.focus(), 1);
|
||||
|
||||
onValueCreate?.(index + 1);
|
||||
};
|
||||
|
||||
@@ -3,9 +3,10 @@ import { useTranslation } from "react-i18next";
|
||||
import { CopyOutlined as CopyOutlinedIcon, DownOutlined as DownOutlinedIcon, LikeOutlined as LikeOutlinedIcon } from "@ant-design/icons";
|
||||
import { Button, Dropdown, Form, Input, Space, Tooltip, message } from "antd";
|
||||
import dayjs from "dayjs";
|
||||
import { saveAs } from "file-saver";
|
||||
|
||||
import { type CertificateModel } from "@/domain/certificate";
|
||||
import { saveFiles2Zip } from "@/utils/file";
|
||||
import { archive as archiveCertificate } from "@/api/certificates";
|
||||
import { CERTIFICATE_FORMATS, type CertificateFormatType, type CertificateModel } from "@/domain/certificate";
|
||||
|
||||
export type CertificateDetailProps = {
|
||||
className?: string;
|
||||
@@ -18,20 +19,17 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => {
|
||||
|
||||
const [messageApi, MessageContextHolder] = message.useMessage();
|
||||
|
||||
const handleDownloadPEMClick = async () => {
|
||||
const zipName = `${data.id}-${data.subjectAltNames}.zip`;
|
||||
const files = [
|
||||
{
|
||||
name: `${data.subjectAltNames}.pem`,
|
||||
content: data.certificate ?? "",
|
||||
},
|
||||
{
|
||||
name: `${data.subjectAltNames}.key`,
|
||||
content: data.privateKey ?? "",
|
||||
},
|
||||
];
|
||||
|
||||
await saveFiles2Zip(zipName, files);
|
||||
const handleDownloadClick = async (format: CertificateFormatType) => {
|
||||
try {
|
||||
const res = await archiveCertificate(data.id, format);
|
||||
const bstr = atob(res.data);
|
||||
const u8arr = Uint8Array.from(bstr, (ch) => ch.charCodeAt(0));
|
||||
const blob = new Blob([u8arr], { type: "application/zip" });
|
||||
saveAs(blob, `${data.id}-${data.subjectAltNames}.zip`);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
messageApi.warning(t("common.text.operation_failed"));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -90,21 +88,17 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => {
|
||||
key: "PEM",
|
||||
label: "PEM",
|
||||
extra: <LikeOutlinedIcon />,
|
||||
onClick: () => handleDownloadPEMClick(),
|
||||
onClick: () => handleDownloadClick(CERTIFICATE_FORMATS.PEM),
|
||||
},
|
||||
{
|
||||
key: "PFX",
|
||||
label: "PFX",
|
||||
onClick: () => {
|
||||
alert("TODO: 暂时不支持下载 PFX 证书");
|
||||
},
|
||||
onClick: () => handleDownloadClick(CERTIFICATE_FORMATS.PFX),
|
||||
},
|
||||
{
|
||||
key: "JKS",
|
||||
label: "JKS",
|
||||
onClick: () => {
|
||||
alert("TODO: 暂时不支持下载 JKS 证书");
|
||||
},
|
||||
onClick: () => handleDownloadClick(CERTIFICATE_FORMATS.JKS),
|
||||
},
|
||||
],
|
||||
}}
|
||||
|
||||
@@ -42,14 +42,14 @@ const WorkflowRunDetailDrawer = ({ data, loading, trigger, ...props }: WorkflowR
|
||||
|
||||
<div className="mt-4 rounded-md bg-black p-4 text-stone-200">
|
||||
<div className="flex flex-col space-y-3">
|
||||
{data!.logs.map((item, i) => {
|
||||
{data!.logs?.map((item, i) => {
|
||||
return (
|
||||
<div key={i} className="flex flex-col space-y-2">
|
||||
<div>{item.nodeName}</div>
|
||||
<div className="flex flex-col space-y-1">
|
||||
{item.outputs.map((output, j) => {
|
||||
{item.outputs?.map((output, j) => {
|
||||
return (
|
||||
<div key={j} className="flex space-x-2 text-sm">
|
||||
<div key={j} className="flex space-x-2 text-sm" style={{ wordBreak: "break-word" }}>
|
||||
<div className="whitespace-nowrap">[{dayjs(output.time).format("YYYY-MM-DD HH:mm:ss")}]</div>
|
||||
{output.error ? <div className="text-red-500">{output.error}</div> : <div>{output.content}</div>}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,22 @@ import { forwardRef, memo, useEffect, useImperativeHandle, useMemo, useState } f
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FormOutlined as FormOutlinedIcon, PlusOutlined as PlusOutlinedIcon, QuestionCircleOutlined as QuestionCircleOutlinedIcon } from "@ant-design/icons";
|
||||
import { useControllableValue } from "ahooks";
|
||||
import { AutoComplete, type AutoCompleteProps, Button, Divider, Form, type FormInstance, Input, Select, Space, Switch, Tooltip, Typography } from "antd";
|
||||
import {
|
||||
AutoComplete,
|
||||
type AutoCompleteProps,
|
||||
Button,
|
||||
Divider,
|
||||
Flex,
|
||||
Form,
|
||||
type FormInstance,
|
||||
Input,
|
||||
InputNumber,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -42,8 +57,8 @@ const MULTIPLE_INPUT_DELIMITER = ";";
|
||||
const initFormModel = (): ApplyNodeConfigFormFieldValues => {
|
||||
return {
|
||||
keyAlgorithm: "RSA2048",
|
||||
propagationTimeout: 60,
|
||||
disableFollowCNAME: true,
|
||||
skipBeforeExpiryDays: 20,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -77,13 +92,24 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
.split(MULTIPLE_INPUT_DELIMITER)
|
||||
.every((e) => validIPv4Address(e) || validIPv6Address(e) || validDomainName(e));
|
||||
}, t("common.errmsg.host_invalid")),
|
||||
propagationTimeout: z
|
||||
dnsPropagationTimeout: z
|
||||
.union([
|
||||
z.number().int().gte(1, t("workflow_node.apply.form.propagation_timeout.placeholder")),
|
||||
z.string().refine((v) => !v || /^[1-9]\d*$/.test(v), t("workflow_node.apply.form.propagation_timeout.placeholder")),
|
||||
z.number().int().gte(1, t("workflow_node.apply.form.dns_propagation_timeout.placeholder")),
|
||||
z.string().refine((v) => !v || /^[1-9]\d*$/.test(v), t("workflow_node.apply.form.dns_propagation_timeout.placeholder")),
|
||||
])
|
||||
.nullish(),
|
||||
dnsTTL: z
|
||||
.union([
|
||||
z.number().int().gte(1, t("workflow_node.apply.form.dns_ttl.placeholder")),
|
||||
z.string().refine((v) => !v || /^[1-9]\d*$/.test(v), t("workflow_node.apply.form.dns_ttl.placeholder")),
|
||||
])
|
||||
.nullish(),
|
||||
disableFollowCNAME: z.boolean().nullish(),
|
||||
skipBeforeExpiryDays: z
|
||||
.number({ message: t("workflow_node.apply.form.skip_before_expiry_days.placeholder") })
|
||||
.int(t("workflow_node.apply.form.skip_before_expiry_days.placeholder"))
|
||||
.gte(1, t("workflow_node.apply.form.skip_before_expiry_days.placeholder"))
|
||||
.lte(60, t("workflow_node.apply.form.skip_before_expiry_days.placeholder")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const { form: formInst, formProps } = useAntdForm({
|
||||
@@ -313,18 +339,34 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="propagationTimeout"
|
||||
label={t("workflow_node.apply.form.propagation_timeout.label")}
|
||||
name="dnsPropagationTimeout"
|
||||
label={t("workflow_node.apply.form.dns_propagation_timeout.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.apply.form.propagation_timeout.tooltip") }}></span>}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.apply.form.dns_propagation_timeout.tooltip") }}></span>}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
allowClear
|
||||
min={0}
|
||||
max={3600}
|
||||
placeholder={t("workflow_node.apply.form.propagation_timeout.placeholder")}
|
||||
addonAfter={t("workflow_node.apply.form.propagation_timeout.suffix")}
|
||||
placeholder={t("workflow_node.apply.form.dns_propagation_timeout.placeholder")}
|
||||
addonAfter={t("workflow_node.apply.form.dns_propagation_timeout.unit")}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="dnsTTL"
|
||||
label={t("workflow_node.apply.form.dns_ttl.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.apply.form.dns_ttl.tooltip") }}></span>}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
allowClear
|
||||
min={0}
|
||||
max={86400}
|
||||
placeholder={t("workflow_node.apply.form.dns_ttl.placeholder")}
|
||||
addonAfter={t("workflow_node.apply.form.dns_ttl.unit")}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -337,6 +379,33 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
<Divider className="my-1">
|
||||
<Typography.Text className="text-xs font-normal" type="secondary">
|
||||
{t("workflow_node.apply.form.strategy_config.label")}
|
||||
</Typography.Text>
|
||||
</Divider>
|
||||
|
||||
<Form className={className} style={style} {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
|
||||
<Form.Item
|
||||
label={t("workflow_node.apply.form.skip_before_expiry_days.label")}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.apply.form.skip_before_expiry_days.tooltip") }}></span>}
|
||||
>
|
||||
<Flex align="center" gap={8} wrap="wrap">
|
||||
<div>{t("workflow_node.apply.form.skip_before_expiry_days.prefix")}</div>
|
||||
<Form.Item name="skipBeforeExpiryDays" noStyle rules={[formRule]}>
|
||||
<InputNumber
|
||||
className="w-36"
|
||||
min={1}
|
||||
max={60}
|
||||
placeholder={t("workflow_node.apply.form.skip_before_expiry_days.placeholder")}
|
||||
addonAfter={t("workflow_node.apply.form.skip_before_expiry_days.unit")}
|
||||
/>
|
||||
</Form.Item>
|
||||
<div>{t("workflow_node.apply.form.skip_before_expiry_days.suffix")}</div>
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Form.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { forwardRef, memo, useEffect, useImperativeHandle, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { PlusOutlined as PlusOutlinedIcon, QuestionCircleOutlined as QuestionCircleOutlinedIcon } from "@ant-design/icons";
|
||||
import { Button, Divider, Form, type FormInstance, Select, Tooltip, Typography } from "antd";
|
||||
import { Button, Divider, Flex, Form, type FormInstance, Select, Switch, Tooltip, Typography } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -65,7 +65,9 @@ export type DeployNodeConfigFormInstance = {
|
||||
};
|
||||
|
||||
const initFormModel = (): DeployNodeConfigFormFieldValues => {
|
||||
return {};
|
||||
return {
|
||||
skipOnLastSucceeded: true,
|
||||
};
|
||||
};
|
||||
|
||||
const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNodeConfigFormProps>(
|
||||
@@ -91,6 +93,7 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
.nonempty(t("workflow_node.deploy.form.provider_access.placeholder"))
|
||||
.refine(() => !!formInst.getFieldValue("provider"), t("workflow_node.deploy.form.provider.placeholder")),
|
||||
providerConfig: z.any(),
|
||||
skipOnLastSucceeded: z.boolean(),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const { form: formInst, formProps } = useAntdForm({
|
||||
@@ -340,6 +343,27 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
||||
|
||||
{nestedFormEl}
|
||||
</Show>
|
||||
|
||||
<Divider className="my-1">
|
||||
<Typography.Text className="text-xs font-normal" type="secondary">
|
||||
{t("workflow_node.deploy.form.strategy_config.label")}
|
||||
</Typography.Text>
|
||||
</Divider>
|
||||
|
||||
<Form className={className} style={style} {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
|
||||
<Form.Item label={t("workflow_node.deploy.form.skip_on_last_succeeded.label")}>
|
||||
<Flex align="center" gap={8} wrap="wrap">
|
||||
<div>{t("workflow_node.deploy.form.skip_on_last_succeeded.prefix")}</div>
|
||||
<Form.Item name="skipOnLastSucceeded" noStyle rules={[formRule]}>
|
||||
<Switch
|
||||
checkedChildren={t("workflow_node.deploy.form.skip_on_last_succeeded.enabled.on")}
|
||||
unCheckedChildren={t("workflow_node.deploy.form.skip_on_last_succeeded.enabled.off")}
|
||||
/>
|
||||
</Form.Item>
|
||||
<div>{t("workflow_node.deploy.form.skip_on_last_succeeded.suffix")}</div>
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Form.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { z } from "zod";
|
||||
import { validDomainName } from "@/utils/validators";
|
||||
|
||||
type DeployNodeConfigFormAliyunOSSConfigFieldValues = Nullish<{
|
||||
endpoint: string;
|
||||
region: string;
|
||||
bucket: string;
|
||||
domain: string;
|
||||
}>;
|
||||
@@ -33,9 +33,9 @@ const DeployNodeConfigFormAliyunOSSConfig = ({
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
endpoint: z
|
||||
.string({ message: t("workflow_node.deploy.form.aliyun_oss_endpoint.placeholder") })
|
||||
.url(t("common.errmsg.url_invalid"))
|
||||
region: z
|
||||
.string({ message: t("workflow_node.deploy.form.aliyun_oss_region.placeholder") })
|
||||
.nonempty(t("workflow_node.deploy.form.aliyun_oss_region.placeholder"))
|
||||
.trim(),
|
||||
bucket: z
|
||||
.string({ message: t("workflow_node.deploy.form.aliyun_oss_bucket.placeholder") })
|
||||
@@ -61,12 +61,12 @@ const DeployNodeConfigFormAliyunOSSConfig = ({
|
||||
onValuesChange={handleFormChange}
|
||||
>
|
||||
<Form.Item
|
||||
name="endpoint"
|
||||
label={t("workflow_node.deploy.form.aliyun_oss_endpoint.label")}
|
||||
name="region"
|
||||
label={t("workflow_node.deploy.form.aliyun_oss_region.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_oss_endpoint.tooltip") }}></span>}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_oss_region.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.aliyun_oss_endpoint.placeholder")} />
|
||||
<Input placeholder={t("workflow_node.deploy.form.aliyun_oss_region.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
|
||||
@@ -5,6 +5,7 @@ import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
import { CERTIFICATE_FORMATS } from "@/domain/certificate";
|
||||
|
||||
type DeployNodeConfigFormLocalConfigFieldValues = Nullish<{
|
||||
format: string;
|
||||
@@ -27,9 +28,9 @@ export type DeployNodeConfigFormLocalConfigProps = {
|
||||
onValuesChange?: (values: DeployNodeConfigFormLocalConfigFieldValues) => void;
|
||||
};
|
||||
|
||||
const FORMAT_PEM = "PEM" as const;
|
||||
const FORMAT_PFX = "PFX" as const;
|
||||
const FORMAT_JKS = "JKS" as const;
|
||||
const FORMAT_PEM = CERTIFICATE_FORMATS.PEM;
|
||||
const FORMAT_PFX = CERTIFICATE_FORMATS.PFX;
|
||||
const FORMAT_JKS = CERTIFICATE_FORMATS.JKS;
|
||||
|
||||
const SHELLENV_SH = "sh" as const;
|
||||
const SHELLENV_CMD = "cmd" as const;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
import { CERTIFICATE_FORMATS } from "@/domain/certificate";
|
||||
|
||||
type DeployNodeConfigFormSSHConfigFieldValues = Nullish<{
|
||||
format: string;
|
||||
@@ -26,9 +27,9 @@ export type DeployNodeConfigFormSSHConfigProps = {
|
||||
onValuesChange?: (values: DeployNodeConfigFormSSHConfigFieldValues) => void;
|
||||
};
|
||||
|
||||
const FORMAT_PEM = "PEM" as const;
|
||||
const FORMAT_PFX = "PFX" as const;
|
||||
const FORMAT_JKS = "JKS" as const;
|
||||
const FORMAT_PEM = CERTIFICATE_FORMATS.PEM;
|
||||
const FORMAT_PFX = CERTIFICATE_FORMATS.PFX;
|
||||
const FORMAT_JKS = CERTIFICATE_FORMATS.JKS;
|
||||
|
||||
const initFormModel = (): DeployNodeConfigFormSSHConfigFieldValues => {
|
||||
return {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
MoreOutlined as MoreOutlinedIcon,
|
||||
} from "@ant-design/icons";
|
||||
import { useControllableValue } from "ahooks";
|
||||
import { Button, Card, Drawer, Dropdown, Input, Modal, Popover, Space } from "antd";
|
||||
import { Button, Card, Drawer, Dropdown, Input, type InputRef, Modal, Popover, Space } from "antd";
|
||||
import { produce } from "immer";
|
||||
import { isEqual } from "radash";
|
||||
|
||||
@@ -71,9 +71,10 @@ const SharedNodeMenu = ({ trigger, node, disabled, branchId, branchIndex, afterU
|
||||
|
||||
const [modalApi, ModelContextHolder] = Modal.useModal();
|
||||
|
||||
const nameInputRef = useRef<InputRef>(null);
|
||||
const nameRef = useRef<string>();
|
||||
|
||||
const handleRenameClick = async () => {
|
||||
const handleRenameConfirm = async () => {
|
||||
const oldName = node.name;
|
||||
const newName = nameRef.current?.trim()?.substring(0, 64) || oldName;
|
||||
if (oldName === newName) {
|
||||
@@ -131,11 +132,12 @@ const SharedNodeMenu = ({ trigger, node, disabled, branchId, branchIndex, afterU
|
||||
content: (
|
||||
<div className="pb-2 pt-4">
|
||||
<Input
|
||||
ref={(ref) => setTimeout(() => ref?.focus({ cursor: "end" }), 0)}
|
||||
ref={nameInputRef}
|
||||
autoFocus
|
||||
defaultValue={node.name}
|
||||
onChange={(e) => (nameRef.current = e.target.value)}
|
||||
onPressEnter={async () => {
|
||||
await handleRenameClick();
|
||||
await handleRenameConfirm();
|
||||
dialog.destroy();
|
||||
}}
|
||||
/>
|
||||
@@ -143,8 +145,9 @@ const SharedNodeMenu = ({ trigger, node, disabled, branchId, branchIndex, afterU
|
||||
),
|
||||
icon: null,
|
||||
okText: t("common.button.save"),
|
||||
onOk: handleRenameClick,
|
||||
onOk: handleRenameConfirm,
|
||||
});
|
||||
setTimeout(() => nameInputRef.current?.focus(), 1);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user