diff --git a/ui/package-lock.json b/ui/package-lock.json index ef7758b6..39a27371 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -27,6 +27,7 @@ "@radix-ui/react-tabs": "^1.1.0", "@radix-ui/react-toast": "^1.2.1", "@radix-ui/react-tooltip": "^1.1.2", + "@tanstack/react-table": "^8.20.5", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.0", @@ -2731,6 +2732,37 @@ "win32" ] }, + "node_modules/@tanstack/react-table": { + "version": "8.20.5", + "resolved": "https://registry.npmmirror.com/@tanstack/react-table/-/react-table-8.20.5.tgz", + "integrity": "sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==", + "dependencies": { + "@tanstack/table-core": "8.20.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.20.5", + "resolved": "https://registry.npmmirror.com/@tanstack/table-core/-/table-core-8.20.5.tgz", + "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", diff --git a/ui/package.json b/ui/package.json index 78bdcda4..9ef49558 100644 --- a/ui/package.json +++ b/ui/package.json @@ -29,6 +29,7 @@ "@radix-ui/react-tabs": "^1.1.0", "@radix-ui/react-toast": "^1.2.1", "@radix-ui/react-tooltip": "^1.1.2", + "@tanstack/react-table": "^8.20.5", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.0", diff --git a/ui/src/components/certimate/AccessEditDialog.tsx b/ui/src/components/certimate/AccessEditDialog.tsx index 8f226343..8c4ac6b7 100644 --- a/ui/src/components/certimate/AccessEditDialog.tsx +++ b/ui/src/components/certimate/AccessEditDialog.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { cn } from "@/lib/utils"; @@ -29,15 +29,22 @@ type AccessEditProps = { className?: string; trigger: React.ReactNode; data?: Access; + outConfigType?: string; }; -const AccessEditDialog = ({ trigger, op, data, className }: AccessEditProps) => { +const AccessEditDialog = ({ trigger, op, data, className, outConfigType }: AccessEditProps) => { const { t } = useTranslation(); const [open, setOpen] = useState(false); const [configType, setConfigType] = useState(data?.configType || ""); + useEffect(() => { + if (outConfigType) { + setConfigType(outConfigType); + } + }, [outConfigType]); + let childComponent = <> ; switch (configType) { case "aliyun": diff --git a/ui/src/components/ui/table.tsx b/ui/src/components/ui/table.tsx index 56d92d98..7f3502f8 100644 --- a/ui/src/components/ui/table.tsx +++ b/ui/src/components/ui/table.tsx @@ -1,47 +1,117 @@ -import * as React from "react"; +import * as React from "react" -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils" -const Table = React.forwardRef>(({ className, ...props }, ref) => ( +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => (
- +
-)); -Table.displayName = "Table"; +)) +Table.displayName = "Table" -const TableHeader = React.forwardRef>(({ className, ...props }, ref) => ( +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( -)); -TableHeader.displayName = "TableHeader"; +)) +TableHeader.displayName = "TableHeader" -const TableBody = React.forwardRef>(({ className, ...props }, ref) => ( - -)); -TableBody.displayName = "TableBody"; +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableBody.displayName = "TableBody" -const TableFooter = React.forwardRef>(({ className, ...props }, ref) => ( - tr]:last:border-b-0", className)} {...props} /> -)); -TableFooter.displayName = "TableFooter"; +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className + )} + {...props} + /> +)) +TableFooter.displayName = "TableFooter" -const TableRow = React.forwardRef>(({ className, ...props }, ref) => ( - -)); -TableRow.displayName = "TableRow"; +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableRow.displayName = "TableRow" -const TableHead = React.forwardRef>(({ className, ...props }, ref) => ( -
-)); -TableHead.displayName = "TableHead"; +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableHead.displayName = "TableHead" -const TableCell = React.forwardRef>(({ className, ...props }, ref) => ( - -)); -TableCell.displayName = "TableCell"; +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableCell.displayName = "TableCell" -const TableCaption = React.forwardRef>(({ className, ...props }, ref) => ( -
-)); -TableCaption.displayName = "TableCaption"; +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableCaption.displayName = "TableCaption" -export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }; +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} diff --git a/ui/src/components/workflow/AccessSelect.tsx b/ui/src/components/workflow/AccessSelect.tsx new file mode 100644 index 00000000..73834468 --- /dev/null +++ b/ui/src/components/workflow/AccessSelect.tsx @@ -0,0 +1,59 @@ +import React, { useEffect } from "react"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "../ui/select"; +import { accessProvidersMap } from "@/domain/access"; +import { useTranslation } from "react-i18next"; +import { useConfigContext } from "@/providers/config"; +import { deployTargetsMap } from "@/domain/domain"; + +type AccessSelectProps = { + providerType: string; + value: string; + onValueChange: (val: string) => void; +}; +const AccessSelect = ({ value, onValueChange, providerType }: AccessSelectProps) => { + const [localValue, setLocalValue] = React.useState(""); + const { t } = useTranslation(); + const { + config: { accesses }, + } = useConfigContext(); + + useEffect(() => { + setLocalValue(value); + }, [value]); + + const targetAccesses = accesses.filter((item) => { + console.log(item, providerType); + return item.configType === deployTargetsMap.get(providerType)?.provider; + }); + + return ( + <> + + + ); +}; + +export default AccessSelect; diff --git a/ui/src/components/workflow/DeployToAliyunALB.tsx b/ui/src/components/workflow/DeployToAliyunALB.tsx index e58a5673..afb16c43 100644 --- a/ui/src/components/workflow/DeployToAliyunALB.tsx +++ b/ui/src/components/workflow/DeployToAliyunALB.tsx @@ -14,6 +14,10 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form"; import { Button } from "../ui/button"; +import AccessSelect from "./AccessSelect"; +import AccessEditDialog from "../certimate/AccessEditDialog"; +import { Plus } from "lucide-react"; + const selectState = (state: WorkflowState) => ({ updateNode: state.updateNode, getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId, @@ -35,6 +39,7 @@ const DeployToAliyunALB = ({ data }: DeployFormProps) => { const formSchema = z .object({ providerType: z.string(), + access: z.string().min(1, t("domain.deployment.form.access.placeholder")), certificate: z.string().min(1), region: z.string().min(1, t("domain.deployment.form.aliyun_alb_region.placeholder")), resourceType: z.union([z.literal("loadbalancer"), z.literal("listener")], { @@ -54,18 +59,20 @@ const DeployToAliyunALB = ({ data }: DeployFormProps) => { let config: WorkflowNodeConfig = { certificate: "", - providerType: "aliyun-alb", + providerType: "", region: "", resourceType: "", loadbalancerId: "", listenerId: "", + access: "", }; if (data) config = data.config ?? config; const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - providerType: config.providerType as string, + providerType: "aliyun-alb", + access: config.access as string, certificate: config.certificate as string, region: config.region as string, resourceType: config.resourceType as "loadbalancer" | "listener", @@ -91,6 +98,41 @@ const DeployToAliyunALB = ({ data }: DeployFormProps) => { }} className="space-y-8" > + ( + + +
{t("domain.deployment.form.access.label")}
+ + + + {t("common.add")} + + } + op="add" + outConfigType="aliyun" + /> +
+ + { + form.setValue("access", value); + }} + providerType="aliyun-alb" + /> + + + +
+ )} + /> + { {beforeOutput.map((item) => ( - <> - - {item.name} - {item.output?.map((output) => ( - -
- {item.name}-{output.label} -
-
- ))} -
- + + {item.name} + {item.output?.map((output) => ( + +
+ {item.name}-{output.label} +
+
+ ))} +
))}
diff --git a/ui/src/components/workflow/DeployToAliyunCDN.tsx b/ui/src/components/workflow/DeployToAliyunCDN.tsx index 5ec5d303..6161255e 100644 --- a/ui/src/components/workflow/DeployToAliyunCDN.tsx +++ b/ui/src/components/workflow/DeployToAliyunCDN.tsx @@ -15,6 +15,9 @@ import { Button } from "../ui/button"; import { useEffect, useState } from "react"; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select"; import { SelectLabel } from "@radix-ui/react-select"; +import AccessSelect from "./AccessSelect"; +import AccessEditDialog from "../certimate/AccessEditDialog"; +import { Plus } from "lucide-react"; const selectState = (state: WorkflowState) => ({ updateNode: state.updateNode, @@ -35,6 +38,7 @@ const DeployToAliyunCDN = ({ data }: DeployFormProps) => { const formSchema = z.object({ providerType: z.string(), + access: z.string().min(1, t("domain.deployment.form.access.placeholder")), certificate: z.string().min(1), domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, { message: t("common.errmsg.domain_invalid"), @@ -43,8 +47,8 @@ const DeployToAliyunCDN = ({ data }: DeployFormProps) => { let config: WorkflowNodeConfig = { certificate: "", - providerType: "aliyun-cdn", - + providerType: "", + access: "", domain: "", }; if (data) config = data.config ?? config; @@ -52,7 +56,8 @@ const DeployToAliyunCDN = ({ data }: DeployFormProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - providerType: config.providerType as string, + providerType: "aliyun-cdn", + access: config.access as string, certificate: config.certificate as string, domain: config.domain as string, }, @@ -73,6 +78,41 @@ const DeployToAliyunCDN = ({ data }: DeployFormProps) => { }} className="space-y-8" > + ( + + +
{t("domain.deployment.form.access.label")}
+ + + + {t("common.add")} + + } + op="add" + outConfigType="aliyun" + /> +
+ + { + form.setValue("access", value); + }} + providerType="aliyun-cdn" + /> + + + +
+ )} + /> + ({ updateNode: state.updateNode, getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId, @@ -35,6 +39,7 @@ const DeployToAliyunCLB = ({ data }: DeployFormProps) => { const formSchema = z .object({ providerType: z.string(), + access: z.string().min(1, t("domain.deployment.form.access.placeholder")), certificate: z.string().min(1), region: z.string().min(1, t("domain.deployment.form.aliyun_clb_region.placeholder")), resourceType: z.union([z.literal("certificate"), z.literal("loadbalancer"), z.literal("listener")], { @@ -54,7 +59,8 @@ const DeployToAliyunCLB = ({ data }: DeployFormProps) => { let config: WorkflowNodeConfig = { certificate: "", - providerType: "aliyun-clb", + providerType: "", + access: "", region: "", resourceType: "", loadbalancerId: "", @@ -65,7 +71,8 @@ const DeployToAliyunCLB = ({ data }: DeployFormProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - providerType: config.providerType as string, + providerType: "aliyun-clb", + access: config.access as string, certificate: config.certificate as string, region: config.region as string, resourceType: config.resourceType as "loadbalancer" | "listener", @@ -91,6 +98,40 @@ const DeployToAliyunCLB = ({ data }: DeployFormProps) => { }} className="space-y-8" > + ( + + +
{t("domain.deployment.form.access.label")}
+ + + + {t("common.add")} + + } + op="add" + outConfigType="aliyun" + /> +
+ + { + form.setValue("access", value); + }} + providerType="aliyun-clb" + /> + + + +
+ )} + /> { useEffect(() => { const rs = getWorkflowOuptutBeforeId(data.id, "certificate"); - console.log(rs); setBeforeOutput(rs); }, [data]); const formSchema = z .object({ providerType: z.string(), + access: z.string().min(1, t("domain.deployment.form.access.placeholder")), certificate: z.string().min(1), region: z.string().min(1, t("domain.deployment.form.aliyun_nlb_region.placeholder")), resourceType: z.union([z.literal("loadbalancer"), z.literal("listener")], { @@ -54,7 +54,8 @@ const DeployToAliyunNLB = ({ data }: DeployFormProps) => { let config: WorkflowNodeConfig = { certificate: "", - providerType: "aliyun-nlb", + providerType: "", + access: "", region: "", resourceType: "", loadbalancerId: "", @@ -65,7 +66,7 @@ const DeployToAliyunNLB = ({ data }: DeployFormProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - providerType: config.providerType as string, + providerType: "aliyun-nlb", certificate: config.certificate as string, region: config.region as string, resourceType: config.resourceType as "loadbalancer" | "listener", diff --git a/ui/src/components/workflow/DeployToAliyunOss.tsx b/ui/src/components/workflow/DeployToAliyunOss.tsx index f9d1d274..98058237 100644 --- a/ui/src/components/workflow/DeployToAliyunOss.tsx +++ b/ui/src/components/workflow/DeployToAliyunOss.tsx @@ -16,6 +16,10 @@ import { useEffect, useState } from "react"; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select"; import { SelectLabel } from "@radix-ui/react-select"; +import AccessSelect from "./AccessSelect"; +import AccessEditDialog from "../certimate/AccessEditDialog"; +import { Plus } from "lucide-react"; + const selectState = (state: WorkflowState) => ({ updateNode: state.updateNode, getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId, @@ -35,6 +39,7 @@ const DeployToAliyunOSS = ({ data }: DeployFormProps) => { const formSchema = z.object({ providerType: z.string(), + access: z.string().min(1, t("domain.deployment.form.access.placeholder")), certificate: z.string().min(1), endpoint: z.string().min(1, { message: t("domain.deployment.form.aliyun_oss_endpoint.placeholder"), @@ -49,7 +54,8 @@ const DeployToAliyunOSS = ({ data }: DeployFormProps) => { let config: WorkflowNodeConfig = { certificate: "", - providerType: "aliyun-oss", + providerType: "", + access: "", endpoint: "", bucket: "", domain: "", @@ -59,7 +65,8 @@ const DeployToAliyunOSS = ({ data }: DeployFormProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - providerType: config.providerType as string, + providerType: "aliyun-oss", + access: config.access as string, certificate: config.certificate as string, endpoint: config.endpoint as string, bucket: config.bucket as string, @@ -82,6 +89,40 @@ const DeployToAliyunOSS = ({ data }: DeployFormProps) => { }} className="space-y-8" > + ( + + +
{t("domain.deployment.form.access.label")}
+ + + + {t("common.add")} + + } + op="add" + outConfigType="aliyun" + /> +
+ + { + form.setValue("access", value); + }} + providerType="aliyun-oss" + /> + + + +
+ )} + /> ({ updateNode: state.updateNode, @@ -35,6 +38,7 @@ const DeployToBaiduCloudCDN = ({ data }: DeployFormProps) => { const formSchema = z.object({ providerType: z.string(), + access: z.string().min(1, t("domain.deployment.form.access.placeholder")), certificate: z.string().min(1), domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, { message: t("common.errmsg.domain_invalid"), @@ -44,6 +48,7 @@ const DeployToBaiduCloudCDN = ({ data }: DeployFormProps) => { let config: WorkflowNodeConfig = { certificate: "", providerType: "baiducloud-cdn", + access: "", domain: "", }; @@ -52,7 +57,8 @@ const DeployToBaiduCloudCDN = ({ data }: DeployFormProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - providerType: config.providerType as string, + providerType: "baiducloud-cdn", + access: config.access as string, certificate: config.certificate as string, domain: config.domain as string, }, @@ -73,6 +79,41 @@ const DeployToBaiduCloudCDN = ({ data }: DeployFormProps) => { }} className="space-y-8" > + ( + + +
{t("domain.deployment.form.access.label")}
+ + + + {t("common.add")} + + } + op="add" + outConfigType="baiducloud" + /> +
+ + { + form.setValue("access", value); + }} + providerType="baiducloud-cdn" + /> + + + +
+ )} + /> + ({ updateNode: state.updateNode, @@ -35,6 +38,7 @@ const DeployToDogeCloudCDN = ({ data }: DeployFormProps) => { const formSchema = z.object({ providerType: z.string(), + access: z.string().min(1, t("domain.deployment.form.access.placeholder")), certificate: z.string().min(1), domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, { message: t("common.errmsg.domain_invalid"), @@ -43,7 +47,8 @@ const DeployToDogeCloudCDN = ({ data }: DeployFormProps) => { let config: WorkflowNodeConfig = { certificate: "", - providerType: "dogecloud-cdn", + providerType: "", + access: "", domain: "", }; @@ -52,7 +57,8 @@ const DeployToDogeCloudCDN = ({ data }: DeployFormProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - providerType: config.providerType as string, + providerType: "dogecloud-cdn", + access: config.access as string, certificate: config.certificate as string, domain: config.domain as string, }, @@ -73,6 +79,40 @@ const DeployToDogeCloudCDN = ({ data }: DeployFormProps) => { }} className="space-y-8" > + ( + + +
{t("domain.deployment.form.access.label")}
+ + + + {t("common.add")} + + } + op="add" + outConfigType="dogecloud" + /> +
+ + { + form.setValue("access", value); + }} + providerType="dogecloud-cdn" + /> + + + +
+ )} + /> ({ updateNode: state.updateNode, getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId, @@ -35,6 +39,7 @@ const DeployToHuaweiCloudCDN = ({ data }: DeployFormProps) => { const formSchema = z.object({ providerType: z.string(), + access: z.string().min(1, t("domain.deployment.form.access.placeholder")), certificate: z.string().min(1), region: z.string().min(1, { message: t("domain.deployment.form.huaweicloud_cdn_region.placeholder"), @@ -46,7 +51,8 @@ const DeployToHuaweiCloudCDN = ({ data }: DeployFormProps) => { let config: WorkflowNodeConfig = { certificate: "", - providerType: "huaweicloud-cdn", + providerType: "", + access: "", region: "", domain: "", }; @@ -55,7 +61,8 @@ const DeployToHuaweiCloudCDN = ({ data }: DeployFormProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - providerType: config.providerType as string, + providerType: "huaweicloud-cdn", + access: config.access as string, certificate: config.certificate as string, region: config.region as string, domain: config.domain as string, @@ -77,6 +84,41 @@ const DeployToHuaweiCloudCDN = ({ data }: DeployFormProps) => { }} className="space-y-8" > + ( + + +
{t("domain.deployment.form.access.label")}
+ + + + {t("common.add")} + + } + op="add" + outConfigType="huaweicloud" + /> +
+ + { + form.setValue("access", value); + }} + providerType="huaweicloud-cdn" + /> + + + +
+ )} + /> + ({ updateNode: state.updateNode, @@ -41,6 +44,7 @@ const DeployToHuaweiCloudELB = ({ data }: DeployFormProps) => { const formSchema = z .object({ providerType: z.string(), + access: z.string().min(1, t("domain.deployment.form.access.placeholder")), certificate: z.string().min(1), region: z.string().min(1, t("domain.deployment.form.huaweicloud_elb_region.placeholder")), resourceType: z.union([z.literal("certificate"), z.literal("loadbalancer"), z.literal("listener")], { @@ -65,7 +69,8 @@ const DeployToHuaweiCloudELB = ({ data }: DeployFormProps) => { let config: WorkflowNodeConfig = { certificate: "", - providerType: "huaweicloud-elb", + providerType: "", + access: "", resouceType: "", certificateId: "", loadbalancerId: "", @@ -76,7 +81,8 @@ const DeployToHuaweiCloudELB = ({ data }: DeployFormProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - providerType: config.providerType as string, + providerType: "huaweicloud-elb", + access: config.access as string, certificate: config.certificate as string, region: config.region as string, resourceType: config.resourceType as "certificate" | "loadbalancer" | "listener", @@ -100,6 +106,41 @@ const DeployToHuaweiCloudELB = ({ data }: DeployFormProps) => { }} className="space-y-8" > + ( + + +
{t("domain.deployment.form.access.label")}
+ + + + {t("common.add")} + + } + op="add" + outConfigType="huaweicloud" + /> +
+ + { + form.setValue("access", value); + }} + providerType="huaweicloud-elb" + /> + + + +
+ )} + /> + ({ updateNode: state.updateNode, @@ -35,6 +38,7 @@ const DeployToKubernetesSecret = ({ data }: DeployFormProps) => { const formSchema = z.object({ providerType: z.string(), + access: z.string().min(1, t("domain.deployment.form.access.placeholder")), certificate: z.string().min(1), namespace: z.string().min(1, { message: t("domain.deployment.form.k8s_namespace.placeholder"), @@ -52,7 +56,8 @@ const DeployToKubernetesSecret = ({ data }: DeployFormProps) => { let config: WorkflowNodeConfig = { certificate: "", - providerType: "k8s-secret", + providerType: "", + access: "", namespace: "", secretName: "", secretDataKeyForCrt: "", @@ -63,7 +68,8 @@ const DeployToKubernetesSecret = ({ data }: DeployFormProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - providerType: config.providerType as string, + providerType: "k8s-secret", + access: config.access as string, certificate: config.certificate as string, namespace: config.namespace as string, secretName: config.secretName as string, @@ -87,6 +93,41 @@ const DeployToKubernetesSecret = ({ data }: DeployFormProps) => { }} className="space-y-8" > + ( + + +
{t("domain.deployment.form.access.label")}
+ + + + {t("common.add")} + + } + op="add" + outConfigType="k8s" + /> +
+ + { + form.setValue("access", value); + }} + providerType="k8s-secret" + /> + + + +
+ )} + /> + ({ updateNode: state.updateNode, @@ -26,6 +29,7 @@ const t = i18n.t; const formSchema = z .object({ providerType: z.string(), + access: z.string().min(1, t("domain.deployment.form.access.placeholder")), certificate: z.string().min(1), format: z.union([z.literal("pem"), z.literal("pfx"), z.literal("jks")], { message: t("domain.deployment.form.file_format.placeholder"), @@ -86,6 +90,7 @@ const DeployToLocal = ({ data }: DeployFormProps) => { resolver: zodResolver(formSchema), defaultValues: { providerType: "local", + access: data.config?.access as string, certificate: data.config?.certificate as string, format: (data.config?.format as "pem" | "pfx" | "jks") || "pem", certPath: (data.config?.certPath as string) || "/etc/ssl/certs/cert.crt", @@ -198,6 +203,41 @@ Remove-Item -Path "$pfxPath" -Force return (
+ ( + + +
{t("domain.deployment.form.access.label")}
+ + + + {t("common.add")} + + } + op="add" + outConfigType="local" + /> +
+ + { + form.setValue("access", value); + }} + providerType="local" + /> + + + +
+ )} + /> + ({ updateNode: state.updateNode, @@ -35,6 +38,7 @@ const DeployToQiniuCDN = ({ data }: DeployFormProps) => { const formSchema = z.object({ providerType: z.string(), + access: z.string().min(1, t("domain.deployment.form.access.placeholder")), certificate: z.string().min(1), domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, { message: t("common.errmsg.domain_invalid"), @@ -43,7 +47,8 @@ const DeployToQiniuCDN = ({ data }: DeployFormProps) => { let config: WorkflowNodeConfig = { certificate: "", - providerType: "qiniu-cdn", + providerType: "", + access: "", domain: "", }; @@ -52,7 +57,8 @@ const DeployToQiniuCDN = ({ data }: DeployFormProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - providerType: config.providerType as string, + providerType: "qiniu-cdn", + access: config.access as string, certificate: config.certificate as string, domain: config.domain as string, }, @@ -73,6 +79,41 @@ const DeployToQiniuCDN = ({ data }: DeployFormProps) => { }} className="space-y-8" > + ( + + +
{t("domain.deployment.form.access.label")}
+ + + + {t("common.add")} + + } + op="add" + outConfigType="qiniu" + /> +
+ + { + form.setValue("access", value); + }} + providerType="qiniu-cdn" + /> + + + +
+ )} + /> + ({ updateNode: state.updateNode, @@ -25,6 +28,7 @@ const t = i18n.t; const formSchema = z .object({ providerType: z.string(), + access: z.string().min(1, t("domain.deployment.form.access.placeholder")), certificate: z.string().min(1), format: z.union([z.literal("pem"), z.literal("pfx"), z.literal("jks")], { message: t("domain.deployment.form.file_format.placeholder"), @@ -82,6 +86,7 @@ const DeployToSSH = ({ data }: DeployFormProps) => { resolver: zodResolver(formSchema), defaultValues: { providerType: "ssh", + access: data.config?.access as string, certificate: data.config?.certificate as string, format: (data.config?.format as "pem" | "pfx" | "jks") || "pem", certPath: (data.config?.certPath as string) || "/etc/ssl/certs/cert.crt", @@ -116,6 +121,41 @@ const DeployToSSH = ({ data }: DeployFormProps) => { return ( + ( + + +
{t("domain.deployment.form.access.label")}
+ + + + {t("common.add")} + + } + op="add" + outConfigType="ssh" + /> +
+ + { + form.setValue("access", value); + }} + providerType="ssh" + /> + + + +
+ )} + /> + ({ updateNode: state.updateNode, @@ -35,6 +38,7 @@ const DeployToTencentCDN = ({ data }: DeployFormProps) => { const formSchema = z.object({ providerType: z.string(), + access: z.string().min(1, t("domain.deployment.form.access.placeholder")), certificate: z.string().min(1), domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, { message: t("common.errmsg.domain_invalid"), @@ -43,7 +47,7 @@ const DeployToTencentCDN = ({ data }: DeployFormProps) => { let config: WorkflowNodeConfig = { certificate: "", - providerType: "tencent-cdn", + providerType: "", domain: "", }; @@ -52,7 +56,8 @@ const DeployToTencentCDN = ({ data }: DeployFormProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - providerType: config.providerType as string, + providerType: "tencent-cdn", + access: config.access as string, certificate: config.certificate as string, domain: config.domain as string, }, @@ -73,6 +78,41 @@ const DeployToTencentCDN = ({ data }: DeployFormProps) => { }} className="space-y-8" > + ( + + +
{t("domain.deployment.form.access.label")}
+ + + + {t("common.add")} + + } + op="add" + outConfigType="tencent" + /> +
+ + { + form.setValue("access", value); + }} + providerType="tencent-cdn" + /> + + + +
+ )} + /> + { const formSchema = z .object({ providerType: z.string(), + access: z.string().min(1, t("domain.deployment.form.access.placeholder")), certificate: z.string().min(1), region: z.string().min(1, t("domain.deployment.form.tencent_clb_region.placeholder")), resourceType: z.union([z.literal("ssl-deploy"), z.literal("loadbalancer"), z.literal("listener"), z.literal("ruledomain")], { @@ -76,7 +80,8 @@ const DeployToTencentCLB = ({ data }: DeployFormProps) => { let config: WorkflowNodeConfig = { certificate: "", - providerType: "tencent-clb", + providerType: "", + access: "", resouceType: "", domain: "", loadbalancerId: "", @@ -87,7 +92,8 @@ const DeployToTencentCLB = ({ data }: DeployFormProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - providerType: config.providerType as string, + providerType: "tencent-clb", + access: config.access as string, certificate: config.certificate as string, region: config.region as string, resourceType: config.resourceType as TencentResourceType, @@ -112,6 +118,41 @@ const DeployToTencentCLB = ({ data }: DeployFormProps) => { }} className="space-y-8" > + ( + + +
{t("domain.deployment.form.access.label")}
+ + + + {t("common.add")} + + } + op="add" + outConfigType="tencent" + /> +
+ + { + form.setValue("access", value); + }} + providerType="tencent-clb" + /> + + + +
+ )} + /> + ({ updateNode: state.updateNode, @@ -35,6 +38,7 @@ const DeployToTencentCOS = ({ data }: DeployFormProps) => { const formSchema = z.object({ providerType: z.string(), + access: z.string().min(1, t("domain.deployment.form.access.placeholder")), certificate: z.string().min(1), region: z.string().min(1, t("domain.deployment.form.tencent_cos_region.placeholder")), bucket: z.string().min(1, t("domain.deployment.form.tencent_cos_bucket.placeholder")), @@ -45,7 +49,8 @@ const DeployToTencentCOS = ({ data }: DeployFormProps) => { let config: WorkflowNodeConfig = { certificate: "", - providerType: "tencent-cos", + providerType: "", + access: "", region: "", bucket: "", domain: "", @@ -55,7 +60,8 @@ const DeployToTencentCOS = ({ data }: DeployFormProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - providerType: config.providerType as string, + providerType: "tencent-cos", + access: config.access as string, certificate: config.certificate as string, region: config.region as string, bucket: config.bucket as string, @@ -78,6 +84,41 @@ const DeployToTencentCOS = ({ data }: DeployFormProps) => { }} className="space-y-8" > + ( + + +
{t("domain.deployment.form.access.label")}
+ + + + {t("common.add")} + + } + op="add" + outConfigType="tencent" + /> +
+ + { + form.setValue("access", value); + }} + providerType="tencent-cos" + /> + + + +
+ )} + /> + ({ updateNode: state.updateNode, @@ -35,6 +38,7 @@ const DeployToTencentTEO = ({ data }: DeployFormProps) => { const formSchema = z.object({ providerType: z.string(), + access: z.string().min(1, t("domain.deployment.form.access.placeholder")), certificate: z.string().min(1), zoneId: z.string().min(1, t("domain.deployment.form.tencent_teo_zone_id.placeholder")), domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, { @@ -44,7 +48,8 @@ const DeployToTencentTEO = ({ data }: DeployFormProps) => { let config: WorkflowNodeConfig = { certificate: "", - providerType: "tencent-teo", + providerType: "", + access: "", zoneId: "", domain: "", }; @@ -53,7 +58,8 @@ const DeployToTencentTEO = ({ data }: DeployFormProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - providerType: config.providerType as string, + providerType: "tencent-teo", + access: config.access as string, certificate: config.certificate as string, zoneId: config.zoneId as string, domain: config.domain as string, @@ -75,6 +81,41 @@ const DeployToTencentTEO = ({ data }: DeployFormProps) => { }} className="space-y-8" > + ( + + +
{t("domain.deployment.form.access.label")}
+ + + + {t("common.add")} + + } + op="add" + outConfigType="tencent" + /> +
+ + { + form.setValue("access", value); + }} + providerType="tencent-teo" + /> + + + +
+ )} + /> + ({ updateNode: state.updateNode, @@ -39,13 +42,15 @@ const DeployToWebhook = ({ data }: DeployFormProps) => { const formSchema = z.object({ providerType: z.string(), + access: z.string().min(1, t("domain.deployment.form.access.placeholder")), certificate: z.string().min(1), variables: z.array(KVTypeSchema).optional(), }); let config: WorkflowNodeConfig = { certificate: "", - providerType: "webhook", + providerType: "", + access: "", variables: [], }; @@ -54,7 +59,8 @@ const DeployToWebhook = ({ data }: DeployFormProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - providerType: config.providerType as string, + providerType: "webhook", + access: config.access as string, certificate: config.certificate as string, variables: config.variables as { key: string; value: string }[], }, @@ -76,6 +82,40 @@ const DeployToWebhook = ({ data }: DeployFormProps) => { }} className="space-y-8" > + ( + + +
{t("domain.deployment.form.access.label")}
+ + + + {t("common.add")} + + } + op="add" + outConfigType="webhook" + /> +
+ + { + form.setValue("access", value); + }} + providerType="webhook" + /> + + + +
+ )} + /> { const channelLabel = channelLabelMap.get(data.config?.channel as string); return (
-
{t(channelLabel?.label ?? "")}
+
{t(channelLabel?.label ?? "")}
{(data.config?.title as string) ?? ""}
); diff --git a/ui/src/components/workflow/WorkflowBaseInfoEditDialog.tsx b/ui/src/components/workflow/WorkflowBaseInfoEditDialog.tsx new file mode 100644 index 00000000..d3881c47 --- /dev/null +++ b/ui/src/components/workflow/WorkflowBaseInfoEditDialog.tsx @@ -0,0 +1,110 @@ +import { z } from "zod"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "../ui/dialog"; +import { useWorkflowStore, WorkflowState } from "@/providers/workflow"; +import { useShallow } from "zustand/shallow"; +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 { useState } from "react"; + +type WorkflowNameEditDialogProps = { + trigger: React.ReactNode; +}; + +const formSchema = z.object({ + name: z.string(), + description: z.string(), +}); + +const selectState = (state: WorkflowState) => ({ + setBaseInfo: state.setBaseInfo, + workflow: state.workflow, +}); +const WorkflowNameBaseInfoDialog = ({ trigger }: WorkflowNameEditDialogProps) => { + const { setBaseInfo, workflow } = useWorkflowStore(useShallow(selectState)); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: workflow.name, + description: workflow.description, + }, + }); + + const { t } = useTranslation(); + + const [open, setOpen] = useState(false); + + const onSubmit = async (config: z.infer) => { + await setBaseInfo(config.name, config.description); + setOpen(false); + }; + + return ( + <> + { + setOpen(val); + }} + > + {trigger} + + + 基础信息 + +
+ + { + e.stopPropagation(); + form.handleSubmit(onSubmit)(e); + }} + className="space-y-8" + > + ( + + 名称 + + + + + + + )} + /> + + ( + + 说明 + + + + + + + )} + /> + +
+ +
+ + +
+
+
+ + ); +}; + +export default WorkflowNameBaseInfoDialog; diff --git a/ui/src/domain/workflow.ts b/ui/src/domain/workflow.ts index 0e7729c7..109cb476 100644 --- a/ui/src/domain/workflow.ts +++ b/ui/src/domain/workflow.ts @@ -332,6 +332,24 @@ export const getWorkflowOutputBeforeId = (node: WorkflowNode | WorkflowBranchNod return output; }; +export const allNodesValidated = (node: WorkflowNode | WorkflowBranchNode): boolean => { + let current = node; + while (current) { + if (!isWorkflowBranchNode(current) && !current.validated) { + return false; + } + if (isWorkflowBranchNode(current)) { + for (const branch of current.branches) { + if (!allNodesValidated(branch)) { + return false; + } + } + } + current = current.next as WorkflowNode; + } + return true; +}; + export type WorkflowBranchNode = { id: string; name: string; diff --git a/ui/src/pages/LoginLayout.tsx b/ui/src/pages/LoginLayout.tsx index 9c738610..8a9aa7a4 100644 --- a/ui/src/pages/LoginLayout.tsx +++ b/ui/src/pages/LoginLayout.tsx @@ -18,4 +18,3 @@ const LoginLayout = () => { }; export default LoginLayout; - diff --git a/ui/src/pages/workflow/WorkflowDetail.tsx b/ui/src/pages/workflow/WorkflowDetail.tsx index bee32a2d..f257e239 100644 --- a/ui/src/pages/workflow/WorkflowDetail.tsx +++ b/ui/src/pages/workflow/WorkflowDetail.tsx @@ -2,11 +2,14 @@ import Show from "@/components/Show"; import { Button } from "@/components/ui/button"; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; import { Switch } from "@/components/ui/switch"; +import { Toaster } from "@/components/ui/toaster"; +import { useToast } from "@/components/ui/use-toast"; import End from "@/components/workflow/End"; import NodeRender from "@/components/workflow/NodeRender"; +import WorkflowBaseInfoEditDialog from "@/components/workflow/WorkflowBaseInfoEditDialog"; import WorkflowProvider from "@/components/workflow/WorkflowProvider"; -import { WorkflowNode } from "@/domain/workflow"; +import { allNodesValidated, WorkflowNode } from "@/domain/workflow"; import { useWorkflowStore, WorkflowState } from "@/providers/workflow"; import { ArrowLeft } from "lucide-react"; import { useEffect, useMemo } from "react"; @@ -36,6 +39,8 @@ const WorkflowDetail = () => { const navigate = useNavigate(); + const { toast } = useToast(); + const elements = useMemo(() => { let current = workflow.draft as WorkflowNode; @@ -57,24 +62,45 @@ const WorkflowDetail = () => { }; const handleEnableChange = () => { + if (!workflow.enabled && !allNodesValidated(workflow.draft as WorkflowNode)) { + toast({ + title: "无法启用", + description: "有尚未设置完成的节点", + variant: "destructive", + }); + return; + } switchEnable(); }; const handleWorkflowSaveClick = () => { + if (!allNodesValidated(workflow.draft as WorkflowNode)) { + toast({ + title: "保存失败", + description: "有尚未设置完成的节点", + variant: "destructive", + }); + return; + } save(); }; return ( <> +
-
-
工作流
-
工作流详情
-
+ +
{workflow.name ?? "未命名工作流"}
+
{workflow.description ?? "添加流程说明"}
+
+ } + />
diff --git a/ui/src/providers/workflow/index.ts b/ui/src/providers/workflow/index.ts index 0cb688f3..cb5695da 100644 --- a/ui/src/providers/workflow/index.ts +++ b/ui/src/providers/workflow/index.ts @@ -26,6 +26,7 @@ export type WorkflowState = { switchEnable(): void; save(): void; init(id?: string): void; + setBaseInfo: (name: string, description: string) => void; }; export const useWorkflowStore = create((set, get) => ({ @@ -52,6 +53,23 @@ export const useWorkflowStore = create((set, get) => ({ initialized: true, }); }, + setBaseInfo: async (name: string, description: string) => { + const resp = await save({ + id: (get().workflow.id as string) ?? "", + name: name, + description: description, + }); + set((state: WorkflowState) => { + return { + workflow: { + ...state.workflow, + name, + description, + id: resp.id, + }, + }; + }); + }, switchEnable: async () => { const resp = await save({ id: (get().workflow.id as string) ?? "",