From 5f6c39b2230e2ff1847d1ff7ac715624b5d5f616 Mon Sep 17 00:00:00 2001 From: zhujingyang <72259332+zjy365@users.noreply.github.com> Date: Tue, 29 Aug 2023 14:00:07 +0800 Subject: [PATCH] feat:template develop dryrun deploy (#3778) Signed-off-by: jingyang <3161362058@qq.com> --- .../template/public/locales/en/common.json | 3 +- .../template/public/locales/zh/common.json | 3 +- frontend/providers/template/src/api/app.ts | 4 +- .../template/src/pages/api/applyApp.ts | 8 +- .../template/src/pages/deploy/index.tsx | 11 +- .../develop/components/BreadCrumbHeader.tsx | 24 ++-- .../src/pages/develop/components/Form.tsx | 10 +- .../template/src/pages/develop/index.tsx | 111 ++++++++++++------ .../src/services/backend/kubernetes.ts | 9 +- 9 files changed, 112 insertions(+), 71 deletions(-) diff --git a/frontend/providers/template/public/locales/en/common.json b/frontend/providers/template/public/locales/en/common.json index 0f7d8c2e24c..640608cacbe 100644 --- a/frontend/providers/template/public/locales/en/common.json +++ b/frontend/providers/template/public/locales/en/common.json @@ -154,6 +154,7 @@ "Preview": "Preview", "Configure Form": "Configure Form", "YAML File": "YAML File", - "Template Development": "Template Development" + "Template Development": "Template Development", + "Dryrun Deploy": "Dryrun Deploy" } } \ No newline at end of file diff --git a/frontend/providers/template/public/locales/zh/common.json b/frontend/providers/template/public/locales/zh/common.json index 1abef47c963..088de9f0b57 100644 --- a/frontend/providers/template/public/locales/zh/common.json +++ b/frontend/providers/template/public/locales/zh/common.json @@ -160,6 +160,7 @@ "Preview": "预览", "Configure Form": "配置表单", "YAML File": "YAML 文件", - "Template Development": "模板开发" + "Template Development": "模板开发", + "Dryrun Deploy": "试运行部署" } } \ No newline at end of file diff --git a/frontend/providers/template/src/api/app.ts b/frontend/providers/template/src/api/app.ts index cd9f594e9e6..35f4b015fa6 100644 --- a/frontend/providers/template/src/api/app.ts +++ b/frontend/providers/template/src/api/app.ts @@ -1,4 +1,6 @@ import { POST } from '@/services/request'; -export const postDeployApp = (yamlList: string[]) => POST('/api/applyApp', { yamlList }); +export const postDeployApp = (yamlList: string[], type: 'create' | 'replace' | 'dryrun') => + POST('/api/applyApp', { yamlList, type }); + export const getTemplate = (templateName: string) => POST('/api/getTemplate', { templateName }); diff --git a/frontend/providers/template/src/pages/api/applyApp.ts b/frontend/providers/template/src/pages/api/applyApp.ts index fb770634545..de4308f65f7 100644 --- a/frontend/providers/template/src/pages/api/applyApp.ts +++ b/frontend/providers/template/src/pages/api/applyApp.ts @@ -5,7 +5,11 @@ import { ApiResp } from '@/services/kubernet'; import type { NextApiRequest, NextApiResponse } from 'next'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const { yamlList }: { yamlList: string[] } = req.body; + const { yamlList, type = 'create' } = req.body as { + yamlList: string[]; + type: 'create' | 'replace' | 'dryrun'; + }; + if (!yamlList || yamlList.length < 2) { jsonRes(res, { code: 500, @@ -18,7 +22,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< kubeconfig: await authSession(req.headers) }); - const applyRes = await applyYamlList(yamlList, 'create'); + const applyRes = await applyYamlList(yamlList, type); jsonRes(res, { data: applyRes.map((item) => item.kind) }); } catch (err: any) { diff --git a/frontend/providers/template/src/pages/deploy/index.tsx b/frontend/providers/template/src/pages/deploy/index.tsx index f85d78b1501..38515aa756e 100644 --- a/frontend/providers/template/src/pages/deploy/index.tsx +++ b/frontend/providers/template/src/pages/deploy/index.tsx @@ -143,7 +143,7 @@ const EditApp = ({ appName, tabType }: { appName?: string; tabType: string }) => return JSYAML.dump(_item); }); - const result = await postDeployApp(yamls); + const result = await postDeployApp(yamls, 'create'); toast({ title: t(applySuccess), @@ -246,8 +246,7 @@ const EditApp = ({ appName, tabType }: { appName?: string; tabType: string }) => justifyContent={'start'} alignItems={'center'} backgroundColor={'rgba(255, 255, 255)'} - backdropBlur={'100px'} - > + backdropBlur={'100px'}> router.push('/')}> @@ -256,8 +255,7 @@ const EditApp = ({ appName, tabType }: { appName?: string; tabType: string }) => fontWeight={500} fontSize={16} textDecoration={'none'} - color={'#7B838B'} - > + color={'#7B838B'}> {t('Template List')} @@ -276,8 +274,7 @@ const EditApp = ({ appName, tabType }: { appName?: string; tabType: string }) => flexDirection={'column'} width={'100%'} flexGrow={1} - backgroundColor={'rgba(255, 255, 255, 0.90)'} - > + backgroundColor={'rgba(255, 255, 255, 0.90)'}>
{ +const BreadCrumbHeader = ({ applyCb }: { applyCb: () => void }) => { const router = useRouter(); const { t } = useTranslation(); @@ -23,8 +14,7 @@ const BreadCrumbHeader = () => { justifyContent={'start'} alignItems={'center'} backgroundColor={'rgba(255, 255, 255)'} - backdropBlur={'100px'} - > + backdropBlur={'100px'}> router.push('/')}> @@ -33,8 +23,7 @@ const BreadCrumbHeader = () => { fontWeight={500} fontSize={16} textDecoration={'none'} - color={'#7B838B'} - > + color={'#7B838B'}> {t('Template List')} @@ -46,6 +35,9 @@ const BreadCrumbHeader = () => { + ); }; diff --git a/frontend/providers/template/src/pages/develop/components/Form.tsx b/frontend/providers/template/src/pages/develop/components/Form.tsx index efd6aff07d2..f65c6cd38fe 100644 --- a/frontend/providers/template/src/pages/develop/components/Form.tsx +++ b/frontend/providers/template/src/pages/develop/components/Form.tsx @@ -37,8 +37,7 @@ const Form = ({ w="200px" className="template-dynamic-label" color={'#333'} - userSelect={'none'} - > + userSelect={'none'}> {item?.label} {item?.required && ( @@ -51,7 +50,6 @@ const Form = ({ maxW={'500px'} ml={'20px'} defaultValue={item?.default} - autoFocus={true} placeholder={item?.description} {...register(item?.key, { required: item?.required @@ -68,16 +66,14 @@ const Form = ({ alignItems="center" h={'100%'} w={'100%'} - flexDirection="column" - > + flexDirection="column"> + alignItems={'center'}> diff --git a/frontend/providers/template/src/pages/develop/index.tsx b/frontend/providers/template/src/pages/develop/index.tsx index 18a40b2cc69..931f367af87 100644 --- a/frontend/providers/template/src/pages/develop/index.tsx +++ b/frontend/providers/template/src/pages/develop/index.tsx @@ -1,42 +1,42 @@ -import { Flex, Box, Button, Text, Textarea } from '@chakra-ui/react'; -import { useTranslation } from 'next-i18next'; import MyIcon from '@/components/Icon'; -import { useRouter } from 'next/router'; -import { ChangeEvent, useMemo, useState } from 'react'; -import { serviceSideProps } from '@/utils/i18n'; -import Form from './components/Form'; -import { useQuery } from '@tanstack/react-query'; -import { GET } from '@/services/request'; -import JsYaml from 'js-yaml'; +import { editModeMap } from '@/constants/editApp'; +import { useLoading } from '@/hooks/useLoading'; import { useToast } from '@/hooks/useToast'; -import { debounce, mapValues } from 'lodash'; -import { YamlSourceType, TemplateType, YamlType } from '@/types/app'; +import { GET } from '@/services/request'; +import { YamlItemType } from '@/types'; +import { TemplateType, YamlSourceType } from '@/types/app'; +import { serviceSideProps } from '@/utils/i18n'; import { developGenerateYamlList, getTemplateDataSource, parseTemplateString } from '@/utils/json-yaml'; -import YamlList from './components/YamlList'; -import { useForm } from 'react-hook-form'; import { getTemplateDefaultValues } from '@/utils/template'; -import { YamlItemType } from '@/types'; -import Header from './components/Header'; -import BreadCrumbHeader from './components/BreadCrumbHeader'; -import CodeMirror from '@uiw/react-codemirror'; +import { Box, Flex, Text } from '@chakra-ui/react'; import { StreamLanguage } from '@codemirror/language'; import { yaml } from '@codemirror/legacy-modes/mode/yaml'; -import { useGlobalStore } from '@/store/global'; +import { useQuery } from '@tanstack/react-query'; +import CodeMirror from '@uiw/react-codemirror'; +import JsYaml from 'js-yaml'; +import { debounce, has, isObject, mapValues } from 'lodash'; +import { useTranslation } from 'next-i18next'; +import { useMemo, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import ErrorModal from '../deploy/components/ErrorModal'; +import BreadCrumbHeader from './components/BreadCrumbHeader'; +import Form from './components/Form'; +import YamlList from './components/YamlList'; +import { postDeployApp } from '@/api/app'; -const Develop = () => { +export default function Develop() { const { t } = useTranslation(); - const router = useRouter(); const [yamlValue, setYamlValue] = useState(''); const { toast } = useToast(); const [yamlSource, setYamlSource] = useState(); const [yamlList, setYamlList] = useState([]); - const { screenWidth } = useGlobalStore(); - const isLargeScreen = useMemo(() => screenWidth > 1024, [screenWidth]); - const [showSlider, setShowSlider] = useState(false); + const { Loading, setIsLoading } = useLoading(); + const [errorMessage, setErrorMessage] = useState(''); + const { title, applyBtnText, applyMessage, applySuccess, applyError } = editModeMap(false); const detailName = useMemo( () => yamlSource?.source?.defaults?.app_name?.value || '', @@ -116,16 +116,61 @@ const Develop = () => { } }, 1000); + const submitSuccess = async () => { + setIsLoading(true); + try { + const result: string[] = await postDeployApp( + yamlList.map((item) => item.value), + 'dryrun' + ); + toast({ + title: t(applySuccess), + status: 'success', + description: result?.toString() + }); + } catch (error) { + console.log(error, 'sadasdas'); + + setErrorMessage(JSON.stringify(error)); + } + setIsLoading(false); + }; + + const submitError = () => { + formHook.getValues(); + function deepSearch(obj: any): string { + if (has(obj, 'message')) { + return obj.message; + } + for (let key in obj) { + if (isObject(obj[key])) { + let message = deepSearch(obj[key]); + if (message) { + return message; + } + } + } + return t('Submit Error'); + } + + toast({ + title: deepSearch(formHook.formState.errors), + status: 'error', + position: 'top', + duration: 3000, + isClosable: true + }); + }; + return ( - + formHook.handleSubmit(submitSuccess, submitError)()} /> + flex={1}> {/* left */} { alignItems={'center'} backgroundColor={'#F8FAFB'} px="36px" - borderRadius={'8px 8px 0px 0px '} - > + borderRadius={'8px 8px 0px 0px '}> {t('develop.Development')} @@ -164,8 +208,7 @@ const Develop = () => { alignItems={'center'} backgroundColor={'#F8FAFB'} pl="42px" - borderRadius={'8px 8px 0px 0px '} - > + borderRadius={'8px 8px 0px 0px '}> {t('develop.Preview')} @@ -187,9 +230,13 @@ const Develop = () => { + + {!!errorMessage && ( + setErrorMessage('')} /> + )} ); -}; +} export async function getServerSideProps(content: any) { return { @@ -198,5 +245,3 @@ export async function getServerSideProps(content: any) { } }; } - -export default Develop; diff --git a/frontend/providers/template/src/services/backend/kubernetes.ts b/frontend/providers/template/src/services/backend/kubernetes.ts index 90a3aa14132..2a1dbe53965 100644 --- a/frontend/providers/template/src/services/backend/kubernetes.ts +++ b/frontend/providers/template/src/services/backend/kubernetes.ts @@ -53,7 +53,8 @@ export type CRDMeta = { export async function CreateYaml( kc: k8s.KubeConfig, - specs: k8s.KubernetesObject[] + specs: k8s.KubernetesObject[], + dryRun?: 'All' ): Promise { const client = k8s.KubernetesObjectApi.makeApiClient(kc); const validSpecs = specs.filter((s) => s && s.kind && s.metadata); @@ -68,7 +69,7 @@ export async function CreateYaml( JSON.stringify(spec); console.log('create yaml: ', spec.kind); - const response = await client.create(spec); + const response = await client.create(spec, undefined, dryRun ? dryRun : undefined); created.push(response.body); } } catch (error: any) { @@ -151,7 +152,7 @@ export async function getK8s({ kubeconfig }: { kubeconfig: string }) { const namespace = GetUserDefaultNameSpace(kube_user.name); - const applyYamlList = async (yamlList: string[], type: 'create' | 'replace') => { + const applyYamlList = async (yamlList: string[], type: 'create' | 'replace' | 'dryrun') => { // insert namespace const formatYaml: k8s.KubernetesObject[] = yamlList .map((item) => yaml.loadAll(item)) @@ -167,6 +168,8 @@ export async function getK8s({ kubeconfig }: { kubeconfig: string }) { return CreateYaml(kc, formatYaml); } else if (type === 'replace') { return replaceYaml(kc, formatYaml); + } else if (type === 'dryrun') { + return CreateYaml(kc, formatYaml, 'All'); } return CreateYaml(kc, formatYaml); };