mirror of
https://github.com/labring/sealos.git
synced 2026-09-01 04:58:47 +08:00
feat:launchpad jsonpatch & template add filename (#4436)
* feat:launchpad jsonpatch & template add filename Signed-off-by: jingyang <3161362058@qq.com> * add db services.nodeports price Signed-off-by: jingyang <3161362058@qq.com> --------- Signed-off-by: jingyang <3161362058@qq.com>
This commit is contained in:
@@ -116,5 +116,6 @@
|
||||
"Click on any shadow to skip": "Click on any shadow to skip",
|
||||
"Start your Sealos journey": "Start your Sealos journey",
|
||||
"gift amount": "Reward {{amount}} balance.",
|
||||
"Recharge Amount": "Recharge Amount"
|
||||
"Recharge Amount": "Recharge Amount",
|
||||
"Doc": "Doc"
|
||||
}
|
||||
|
||||
@@ -109,5 +109,6 @@
|
||||
"start immediately": "立即开始",
|
||||
"Click on any shadow to skip": "点击任意阴影跳过",
|
||||
"Start your Sealos journey": "开始您的 Sealos 之旅",
|
||||
"gift amount": "赠送 {{amount}} 余额."
|
||||
}
|
||||
"gift amount": "赠送 {{amount}} 余额.",
|
||||
"Doc": "文档"
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ const NsMenu = () => {
|
||||
w="250px"
|
||||
background="linear-gradient(270deg, #F1F1F1 0%, #EEE 43.75%, #ECECEC 100%)"
|
||||
>
|
||||
<PopoverBody px="0" pb="0" pt="4px">
|
||||
<PopoverBody px="0" pb="0" pt="4px" maxH={'320px'} overflowY={'auto'}>
|
||||
<NsList
|
||||
displayPoint={true}
|
||||
selected_ns_uid={ns_uid}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable jsx-a11y/alt-text */
|
||||
import useAppStore from '@/stores/app';
|
||||
import { Box, Flex, Image } from '@chakra-ui/react';
|
||||
import { Box, Flex, Image, Text } from '@chakra-ui/react';
|
||||
import clsx from 'clsx';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import Draggable, { DraggableEventHandler } from 'react-draggable';
|
||||
@@ -107,7 +107,7 @@ export default function AppWindow(props: {
|
||||
setPosition({ x: 0, y: 0 });
|
||||
}}
|
||||
>
|
||||
<Flex ml="16px" alignItems={'center'}>
|
||||
<Flex ml="16px" alignItems={'center'} fontSize={'12px'} fontWeight={400}>
|
||||
<Image
|
||||
src={wnapp?.icon}
|
||||
fallbackSrc="/images/sealos.svg"
|
||||
@@ -120,6 +120,18 @@ export default function AppWindow(props: {
|
||||
? wnapp.i18n?.[i18n?.language]?.name
|
||||
: t(wnapp?.name)}
|
||||
</Box>
|
||||
{wnapp.menuData?.helpDocs && (
|
||||
<Text
|
||||
color={'#24282C'}
|
||||
ml="16px"
|
||||
onClick={() => {
|
||||
typeof wnapp.menuData?.helpDocs === 'string' &&
|
||||
window.open(wnapp.menuData?.helpDocs);
|
||||
}}
|
||||
>
|
||||
{t('Doc')}
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex ml={'auto'}>
|
||||
<Box
|
||||
|
||||
@@ -11,6 +11,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
await (async () =>
|
||||
new Promise((resolve, reject) => {
|
||||
dns.resolveCname(customDomain, (err, address) => {
|
||||
console.log(err, address);
|
||||
if (err) return reject(err);
|
||||
|
||||
if (address[0] !== publicDomain)
|
||||
|
||||
@@ -974,16 +974,18 @@ const Form = ({
|
||||
return (
|
||||
<tr key={env.id}>
|
||||
<th>{env.key}</th>
|
||||
<MyTooltip label={valText}>
|
||||
<th
|
||||
className={styles.textEllipsis}
|
||||
style={{
|
||||
userSelect: 'auto'
|
||||
}}
|
||||
>
|
||||
{valText}
|
||||
</th>
|
||||
</MyTooltip>
|
||||
<th>
|
||||
<MyTooltip label={valText}>
|
||||
<Box
|
||||
className={styles.textEllipsis}
|
||||
style={{
|
||||
userSelect: 'auto'
|
||||
}}
|
||||
>
|
||||
{valText}
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
</th>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
json2Service
|
||||
} from '@/utils/deployYaml2Json';
|
||||
import { serviceSideProps } from '@/utils/i18n';
|
||||
import { getErrText, patchYamlList, patchYamlListV1 } from '@/utils/tools';
|
||||
import { getErrText, patchYamlList } from '@/utils/tools';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@@ -158,10 +158,9 @@ const EditApp = ({ appName, tabType }: { appName?: string; tabType: string }) =>
|
||||
if (appName) {
|
||||
const patch = patchYamlList({
|
||||
formOldYamlList: formOldYamls.current.map((item) => item.value),
|
||||
crYamlList: crOldYamls.current,
|
||||
newYamlList: yamls
|
||||
newYamlList: yamls,
|
||||
crYamlList: crOldYamls.current
|
||||
});
|
||||
|
||||
await putApp({
|
||||
patch,
|
||||
appName,
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import yaml from 'js-yaml';
|
||||
import type { AppEditType, DeployKindsType } from '@/types/app';
|
||||
import { strToBase64, str2Num, pathFormat, pathToNameFormat } from '@/utils/tools';
|
||||
import { SEALOS_DOMAIN, INGRESS_SECRET } from '@/store/static';
|
||||
import {
|
||||
maxReplicasKey,
|
||||
minReplicasKey,
|
||||
appDeployKey,
|
||||
publicDomainKey,
|
||||
deployPVCResizeKey,
|
||||
gpuNodeSelectorKey,
|
||||
gpuResourceKey,
|
||||
deployPVCResizeKey
|
||||
maxReplicasKey,
|
||||
minReplicasKey,
|
||||
publicDomainKey
|
||||
} from '@/constants/app';
|
||||
import { INGRESS_SECRET, SEALOS_DOMAIN } from '@/store/static';
|
||||
import type { AppEditType } from '@/types/app';
|
||||
import { pathFormat, pathToNameFormat, str2Num, strToBase64 } from '@/utils/tools';
|
||||
import dayjs from 'dayjs';
|
||||
import jsonpatch, { Operation } from 'fast-json-patch';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
export const json2DeployCr = (data: AppEditType, type: 'deployment' | 'statefulset') => {
|
||||
const totalStorage = data.storeList.reduce((acc, item) => acc + item.value, 0);
|
||||
|
||||
@@ -233,13 +233,13 @@ export const patchYamlList = ({
|
||||
newYamlList: string[];
|
||||
crYamlList: DeployKindsType[];
|
||||
}) => {
|
||||
console.log(formOldYamlList, newYamlList, crYamlList, '=======');
|
||||
|
||||
const oldFormJsonList = formOldYamlList
|
||||
.map((item) => yaml.loadAll(item))
|
||||
.flat() as DeployKindsType[];
|
||||
const newFormJsonList = newYamlList.map((item) => yaml.loadAll(item)).flat() as DeployKindsType[];
|
||||
|
||||
console.log(oldFormJsonList, newFormJsonList, crYamlList, '===patchYamlList===');
|
||||
|
||||
const actions: AppPatchPropsType = [];
|
||||
|
||||
// find delete
|
||||
@@ -298,7 +298,21 @@ export const patchYamlList = ({
|
||||
}
|
||||
|
||||
/* generate new json */
|
||||
const patchResYamlJson = jsonpatch.applyPatch(crOldYamlJson, patchRes, true).newDocument;
|
||||
const _patchRes: jsonpatch.Operation[] = patchRes.map((item) => {
|
||||
let jsonPatchError = jsonpatch.validate([item], crOldYamlJson);
|
||||
if (
|
||||
jsonPatchError?.operation &&
|
||||
jsonPatchError?.name === 'OPERATION_PATH_UNRESOLVABLE'
|
||||
) {
|
||||
return {
|
||||
...jsonPatchError.operation,
|
||||
op: 'add'
|
||||
};
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
});
|
||||
const patchResYamlJson = jsonpatch.applyPatch(crOldYamlJson, _patchRes, true).newDocument;
|
||||
|
||||
// delete invalid field
|
||||
// @ts-ignore
|
||||
@@ -314,8 +328,7 @@ export const patchYamlList = ({
|
||||
|
||||
return patchResYamlJson;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
console.log('ACTIONS JSON ERROR\n', error);
|
||||
return newYamlJson;
|
||||
}
|
||||
})();
|
||||
@@ -344,6 +357,14 @@ export const patchYamlList = ({
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
'patch result===',
|
||||
oldFormJson.metadata?.name,
|
||||
oldFormJson.kind,
|
||||
patchRes,
|
||||
actionsJson
|
||||
);
|
||||
|
||||
actions.push({
|
||||
type: 'patch',
|
||||
kind: newYamlJson.kind as `${YamlKindEnum}`,
|
||||
@@ -357,69 +378,6 @@ export const patchYamlList = ({
|
||||
});
|
||||
}
|
||||
});
|
||||
console.log(actions, 'actions');
|
||||
return actions;
|
||||
};
|
||||
|
||||
export const patchYamlListV1 = ({
|
||||
newYamlList,
|
||||
oldYamlList
|
||||
}: {
|
||||
newYamlList: string[];
|
||||
oldYamlList: DeployKindsType[];
|
||||
}) => {
|
||||
const newFormJsonList = newYamlList.map((item) => yaml.loadAll(item)).flat() as DeployKindsType[];
|
||||
console.log('new:', newFormJsonList, '\n old', oldYamlList);
|
||||
|
||||
const actions: AppPatchPropsType = [];
|
||||
|
||||
// find delete
|
||||
oldYamlList.forEach((oldYamlJson) => {
|
||||
const item = newFormJsonList.find(
|
||||
(item) => item.kind === oldYamlJson.kind && item.metadata?.name === oldYamlJson.metadata?.name
|
||||
);
|
||||
if (!item && oldYamlJson.metadata?.name) {
|
||||
actions.push({
|
||||
type: 'delete',
|
||||
kind: oldYamlJson.kind as `${YamlKindEnum}`,
|
||||
name: oldYamlJson.metadata?.name
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// find create and patch
|
||||
newFormJsonList.forEach((newYamlJson) => {
|
||||
const oldFormJson = oldYamlList.find(
|
||||
(item) =>
|
||||
item.kind === newYamlJson.kind && item?.metadata?.name === newYamlJson?.metadata?.name
|
||||
);
|
||||
|
||||
if (oldFormJson) {
|
||||
// adapt service ports
|
||||
if (newYamlJson.kind === YamlKindEnum.Service) {
|
||||
// @ts-ignore
|
||||
const ports = newYamlJson?.spec.ports || [];
|
||||
|
||||
// @ts-ignore
|
||||
if (ports.length > 1 && !ports[0]?.name) {
|
||||
// @ts-ignore
|
||||
newYamlJson.spec.ports[0].name = 'adaptport';
|
||||
}
|
||||
}
|
||||
|
||||
actions.push({
|
||||
type: 'patch',
|
||||
kind: newYamlJson.kind as `${YamlKindEnum}`,
|
||||
value: newYamlJson as any
|
||||
});
|
||||
} else {
|
||||
actions.push({
|
||||
type: 'create',
|
||||
kind: newYamlJson.kind as `${YamlKindEnum}`,
|
||||
value: yaml.dump(newYamlJson)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return actions;
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ export type Response = {
|
||||
cpu: number;
|
||||
memory: number;
|
||||
storage: number;
|
||||
nodeports: number;
|
||||
};
|
||||
type ResourceType =
|
||||
| 'cpu'
|
||||
@@ -18,7 +19,8 @@ type ResourceType =
|
||||
| 'mongodb'
|
||||
| 'minio'
|
||||
| 'infra-memory'
|
||||
| 'infra-disk';
|
||||
| 'infra-disk'
|
||||
| 'services.nodeports';
|
||||
type PriceCrdType = {
|
||||
apiVersion: 'account.sealos.io/v1';
|
||||
kind: 'PriceQuery';
|
||||
@@ -72,7 +74,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
const data = {
|
||||
cpu: countSourcePrice(priceResponse, 'cpu'),
|
||||
memory: countSourcePrice(priceResponse, 'memory'),
|
||||
storage: countSourcePrice(priceResponse, 'storage')
|
||||
storage: countSourcePrice(priceResponse, 'storage'),
|
||||
nodeports: countSourcePrice(priceResponse, 'services.nodeports')
|
||||
};
|
||||
|
||||
jsonRes(res, {
|
||||
|
||||
@@ -9,6 +9,7 @@ import MyIcon from '@/components/Icon';
|
||||
import { DBStatusEnum, DBTypeEnum, DBTypeSecretMap, defaultDBDetail } from '@/constants/db';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import useEnvStore from '@/store/env';
|
||||
import { SOURCE_PRICE } from '@/store/static';
|
||||
import type { DBDetailType } from '@/types/db';
|
||||
import { json2NetworkService } from '@/utils/json2Yaml';
|
||||
import { printMemory, useCopyData } from '@/utils/tools';
|
||||
@@ -395,21 +396,9 @@ const AppBaseInfo = ({ db = defaultDBDetail }: { db: DBDetailType }) => {
|
||||
{t('Billing Standards')}
|
||||
</Text>
|
||||
<Center mt="16px" color={'#24282C'} fontSize={'24px'} fontWeight={600}>
|
||||
0.5
|
||||
{SOURCE_PRICE.nodeports}
|
||||
<MyIcon ml="8px" name="currency" w="32px" h="32px"></MyIcon>/ {t('Hour')}
|
||||
</Center>
|
||||
{/* <Center
|
||||
p="8px"
|
||||
mt="20px"
|
||||
bg="#EBF7FD"
|
||||
color={'#1D8CDC'}
|
||||
cursor={'pointer'}
|
||||
w="100%"
|
||||
onClick={() => window.open('https://forum.laf.run/d/1092')}
|
||||
>
|
||||
<MyIcon name="infoCircle" w="16px" fill={'#48A7EC'}></MyIcon>
|
||||
免费方案
|
||||
</Center> */}
|
||||
<Button mt="32px" variant={'primary'} onClick={openNetWorkService}>
|
||||
{t('Turn On')}
|
||||
</Button>
|
||||
|
||||
@@ -6,7 +6,8 @@ import type { Response as resourcePriceResponse } from '@/pages/api/platform/res
|
||||
export let SOURCE_PRICE: resourcePriceResponse = {
|
||||
cpu: 0.067,
|
||||
memory: 0.033792,
|
||||
storage: 0.002048
|
||||
storage: 0.002048,
|
||||
nodeports: 0.5
|
||||
};
|
||||
|
||||
export let INSTALL_ACCOUNT = false;
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { authSession } from '@/services/backend/auth';
|
||||
import { getK8s } from '@/services/backend/kubernetes';
|
||||
import { jsonRes } from '@/services/backend/response';
|
||||
import { ApiResp } from '@/services/kubernet';
|
||||
import { TemplateType } from '@/types/app';
|
||||
import fs from 'fs';
|
||||
import yaml from 'js-yaml';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import path from 'path';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<ApiResp>) {
|
||||
try {
|
||||
const { templateName } = req.query as { templateName: string };
|
||||
let user_namespace = '';
|
||||
|
||||
try {
|
||||
const { namespace } = await getK8s({
|
||||
kubeconfig: await authSession(req.headers)
|
||||
});
|
||||
user_namespace = namespace;
|
||||
} catch (error) {
|
||||
console.log(error, 'errpr-');
|
||||
}
|
||||
|
||||
const originalPath = process.cwd();
|
||||
const targetPath = path.resolve(originalPath, 'FastDeployTemplates', 'template');
|
||||
const yamlString = fs.readFileSync(`${targetPath}/${templateName}.yaml`, 'utf-8');
|
||||
const yamlData = yaml.loadAll(yamlString);
|
||||
const templateYaml: TemplateType = yamlData.find(
|
||||
(item: any) => item.kind === 'Template'
|
||||
) as TemplateType;
|
||||
|
||||
jsonRes(res, {
|
||||
code: 200,
|
||||
data: templateYaml
|
||||
});
|
||||
} catch (err: any) {
|
||||
console.log(err);
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import { replaceRawWithCDN } from './listTemplate';
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<ApiResp>) {
|
||||
try {
|
||||
const { templateName } = req.query as { templateName: string };
|
||||
|
||||
let user_namespace = '';
|
||||
|
||||
try {
|
||||
@@ -72,8 +73,12 @@ export async function GetTemplateByName({
|
||||
|
||||
const originalPath = process.cwd();
|
||||
const targetPath = path.resolve(originalPath, 'FastDeployTemplates', 'template');
|
||||
|
||||
const yamlString = fs.readFileSync(`${targetPath}/${templateName}.yaml`, 'utf-8');
|
||||
// Query by file name in template details
|
||||
const jsonPath = path.resolve(originalPath, 'fast_deploy_template.json');
|
||||
const jsonData: TemplateType[] = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
||||
const _tempalte = jsonData.find((item) => item.metadata.name === templateName);
|
||||
const _tempalteName = _tempalte ? _tempalte.spec.fileName : `${templateName}.yaml`;
|
||||
const yamlString = fs.readFileSync(`${targetPath}/${_tempalteName}`, 'utf-8');
|
||||
|
||||
const yamlData = yaml.loadAll(yamlString);
|
||||
const templateYaml: TemplateType = yamlData.find(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { jsonRes } from '@/services/backend/response';
|
||||
import { ApiResp } from '@/services/kubernet';
|
||||
import { TemplateType } from '@/types/app';
|
||||
import { exec } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import JSYAML from 'js-yaml';
|
||||
@@ -65,9 +66,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
fileList.forEach((item: any) => {
|
||||
try {
|
||||
if (!item) return;
|
||||
const fileName = path.basename(item);
|
||||
const content = fs.readFileSync(item, 'utf-8');
|
||||
const yamlTemplate: any = JSYAML.loadAll(content)[0];
|
||||
const yamlTemplate = JSYAML.loadAll(content)[0] as TemplateType;
|
||||
if (!!yamlTemplate) {
|
||||
yamlTemplate.spec['fileName'] = fileName;
|
||||
jsonObjArr.push(yamlTemplate);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -62,8 +62,7 @@ const Form = ({
|
||||
w="200px"
|
||||
className="template-dynamic-label"
|
||||
color={'#333'}
|
||||
userSelect={'none'}
|
||||
>
|
||||
userSelect={'none'}>
|
||||
{item?.label}
|
||||
{item?.required && (
|
||||
<Text ml="2px" color={'#E53E3E'}>
|
||||
@@ -79,7 +78,7 @@ const Form = ({
|
||||
autoFocus={true}
|
||||
placeholder={item?.description}
|
||||
{...register(item?.key, {
|
||||
required: item?.required
|
||||
required: item?.required ? `${item.label} is required` : ''
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
@@ -93,16 +92,14 @@ const Form = ({
|
||||
alignItems="center"
|
||||
h={'160px'}
|
||||
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'}>
|
||||
|
||||
@@ -162,8 +162,8 @@ export default function EditApp({ appName }: { appName?: string }) {
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const submitError = () => {
|
||||
formHook.getValues();
|
||||
const submitError = async () => {
|
||||
await formHook.trigger();
|
||||
toast({
|
||||
title: deepSearch(formHook.formState.errors),
|
||||
status: 'error',
|
||||
|
||||
@@ -56,7 +56,7 @@ export default function Develop() {
|
||||
const onYamlChange = debounce((state: EditorState) => {
|
||||
const value = state.doc.toString();
|
||||
parseTemplate(value);
|
||||
}, 1000);
|
||||
}, 500);
|
||||
|
||||
const getYamlSource = (str: string): TemplateSourceType => {
|
||||
const yamlData = JsYaml.loadAll(str);
|
||||
|
||||
@@ -7,6 +7,8 @@ export type TemplateType = {
|
||||
spec: {
|
||||
gitRepo: string; // new
|
||||
templateType: 'inline'; // new
|
||||
fileName: string; // new
|
||||
|
||||
template_type?: string;
|
||||
author: string;
|
||||
title: string;
|
||||
|
||||
Reference in New Issue
Block a user