Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat:template develop dryrun deploy #3778

Merged
merged 1 commit into from Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion frontend/providers/template/public/locales/en/common.json
Expand Up @@ -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"
}
}
3 changes: 2 additions & 1 deletion frontend/providers/template/public/locales/zh/common.json
Expand Up @@ -160,6 +160,7 @@
"Preview": "预览",
"Configure Form": "配置表单",
"YAML File": "YAML 文件",
"Template Development": "模板开发"
"Template Development": "模板开发",
"Dryrun Deploy": "试运行部署"
}
}
4 changes: 3 additions & 1 deletion 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 });
8 changes: 6 additions & 2 deletions frontend/providers/template/src/pages/api/applyApp.ts
Expand Up @@ -5,7 +5,11 @@ import { ApiResp } from '@/services/kubernet';
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse<ApiResp>) {
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,
Expand All @@ -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) {
Expand Down
11 changes: 4 additions & 7 deletions frontend/providers/template/src/pages/deploy/index.tsx
Expand Up @@ -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),
Expand Down Expand Up @@ -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'}>
<Box cursor={'pointer'} onClick={() => router.push('/')}>
<MyIcon ml={'46px'} name="arrowLeft" color={'#24282C'} w={'16px'} h={'16px'}></MyIcon>
</Box>
Expand All @@ -256,8 +255,7 @@ const EditApp = ({ appName, tabType }: { appName?: string; tabType: string }) =>
fontWeight={500}
fontSize={16}
textDecoration={'none'}
color={'#7B838B'}
>
color={'#7B838B'}>
<BreadcrumbItem textDecoration={'none'}>
<BreadcrumbLink _hover={{ color: '#219BF4', textDecoration: 'none' }} href="/">
{t('Template List')}
Expand All @@ -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)'}>
<Header
templateDetail={templateDetail}
appName={''}
Expand Down
@@ -1,18 +1,9 @@
import MyIcon from '@/components/Icon';
import {
Flex,
Button,
Text,
Box,
BreadcrumbItem,
BreadcrumbLink,
Breadcrumb
} from '@chakra-ui/react';
import { t } from 'i18next';
import { useRouter } from 'next/router';
import { Box, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Button, Flex } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { useRouter } from 'next/router';

const BreadCrumbHeader = () => {
const BreadCrumbHeader = ({ applyCb }: { applyCb: () => void }) => {
const router = useRouter();
const { t } = useTranslation();

Expand All @@ -23,8 +14,7 @@ const BreadCrumbHeader = () => {
justifyContent={'start'}
alignItems={'center'}
backgroundColor={'rgba(255, 255, 255)'}
backdropBlur={'100px'}
>
backdropBlur={'100px'}>
<Box cursor={'pointer'} onClick={() => router.push('/')}>
<MyIcon name="arrowLeft" color={'#24282C'} w={'16px'} h={'16px'}></MyIcon>
</Box>
Expand All @@ -33,8 +23,7 @@ const BreadCrumbHeader = () => {
fontWeight={500}
fontSize={16}
textDecoration={'none'}
color={'#7B838B'}
>
color={'#7B838B'}>
<BreadcrumbItem textDecoration={'none'}>
<BreadcrumbLink _hover={{ color: '#219BF4', textDecoration: 'none' }} href="/">
{t('Template List')}
Expand All @@ -46,6 +35,9 @@ const BreadCrumbHeader = () => {
</BreadcrumbLink>
</BreadcrumbItem>
</Breadcrumb>
<Button ml="auto" px={4} minW={'120px'} h={'34px'} variant={'primary'} onClick={applyCb}>
{t('develop.Dryrun Deploy')}
</Button>
</Flex>
);
};
Expand Down
Expand Up @@ -37,8 +37,7 @@ const Form = ({
w="200px"
className="template-dynamic-label"
color={'#333'}
userSelect={'none'}
>
userSelect={'none'}>
{item?.label}
{item?.required && (
<Text ml="2px" color={'#E53E3E'}>
Expand All @@ -51,7 +50,6 @@ const Form = ({
maxW={'500px'}
ml={'20px'}
defaultValue={item?.default}
autoFocus={true}
placeholder={item?.description}
{...register(item?.key, {
required: item?.required
Expand All @@ -68,16 +66,14 @@ const Form = ({
alignItems="center"
h={'100%'}
w={'100%'}
flexDirection="column"
>
flexDirection="column">
<Flex
border={'1px dashed #9CA2A8'}
borderRadius="50%"
w={'48px'}
h={'48px'}
justifyContent="center"
alignItems={'center'}
>
alignItems={'center'}>
<MyIcon color={'#7B838B'} name="empty"></MyIcon>
</Flex>
<Text mt={'12px'} fontSize={14} color={'#5A646E'}>
Expand Down
111 changes: 78 additions & 33 deletions 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<YamlSourceType>();
const [yamlList, setYamlList] = useState<YamlItemType[]>([]);
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 || '',
Expand Down Expand Up @@ -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 (
<Flex flexDirection={'column'} p={'0px 34px 20px 34px '} h="100vh" maxW={'1440px'} mx="auto">
<BreadCrumbHeader />
<BreadCrumbHeader applyCb={() => formHook.handleSubmit(submitSuccess, submitError)()} />
<Flex
border={'1px solid #DEE0E2'}
borderRadius={'8px'}
overflowY={'hidden'}
overflowX={'scroll'}
flex={1}
>
flex={1}>
{/* left */}
<Flex flexDirection={'column'} flex={'0 1 500px'} borderRight={'1px solid #EFF0F1'}>
<Flex
Expand All @@ -135,8 +180,7 @@ const Develop = () => {
alignItems={'center'}
backgroundColor={'#F8FAFB'}
px="36px"
borderRadius={'8px 8px 0px 0px '}
>
borderRadius={'8px 8px 0px 0px '}>
<MyIcon name="dev" color={'#24282C'} w={'24px'} h={'24px'}></MyIcon>
<Text fontWeight={'500'} fontSize={'16px'} color={'#24282C'} ml="8px">
{t('develop.Development')}
Expand Down Expand Up @@ -164,8 +208,7 @@ const Develop = () => {
alignItems={'center'}
backgroundColor={'#F8FAFB'}
pl="42px"
borderRadius={'8px 8px 0px 0px '}
>
borderRadius={'8px 8px 0px 0px '}>
<MyIcon name="eyeShow" color={'#24282C'} w={'24px'} h={'24px'}></MyIcon>
<Text fontWeight={'500'} fontSize={'16px'} color={'#24282C'} ml="8px">
{t('develop.Preview')}
Expand All @@ -187,9 +230,13 @@ const Develop = () => {
</Flex>
</Flex>
</Flex>
<Loading />
{!!errorMessage && (
<ErrorModal title={applyError} content={errorMessage} onClose={() => setErrorMessage('')} />
)}
</Flex>
);
};
}

export async function getServerSideProps(content: any) {
return {
Expand All @@ -198,5 +245,3 @@ export async function getServerSideProps(content: any) {
}
};
}

export default Develop;
Expand Up @@ -53,7 +53,8 @@ export type CRDMeta = {

export async function CreateYaml(
kc: k8s.KubeConfig,
specs: k8s.KubernetesObject[]
specs: k8s.KubernetesObject[],
dryRun?: 'All'
): Promise<k8s.KubernetesObject[]> {
const client = k8s.KubernetesObjectApi.makeApiClient(kc);
const validSpecs = specs.filter((s) => s && s.kind && s.metadata);
Expand All @@ -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) {
Expand Down Expand Up @@ -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))
Expand All @@ -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);
};
Expand Down