From 4f46d33a1f06bc60d2cb519d6050197bea3e3d04 Mon Sep 17 00:00:00 2001 From: jingyang <3161362058@qq.com> Date: Tue, 2 Jan 2024 21:04:09 +0800 Subject: [PATCH] feat: Name modification and deployment count display Signed-off-by: jingyang <3161362058@qq.com> --- frontend/.gitignore | 5 +- frontend/providers/template/.env.template | 4 +- .../deploy/manifests/deploy.yaml.tmpl | 49 +++++- .../template/public/locales/en/common.json | 10 +- .../template/public/locales/zh/common.json | 10 +- .../src/components/layout/appmenu.tsx | 8 +- .../providers/template/src/constants/keys.ts | 2 + .../src/pages/api/getTemplateSource.ts | 9 +- .../template/src/pages/api/listTemplate.ts | 1 - .../template/src/pages/api/updateRepo.ts | 38 ++++- .../src/pages/app/components/list.tsx | 86 +++++----- .../src/pages/deploy/components/Header.tsx | 58 ++++--- .../template/src/pages/deploy/index.tsx | 21 +-- .../develop/components/BreadCrumbHeader.tsx | 20 +-- .../providers/template/src/pages/index.tsx | 61 ++++--- .../src/pages/instance/components/header.tsx | 152 +++++++++++++----- .../src/services/backend/kubernetes.ts | 7 + frontend/providers/template/src/types/app.ts | 5 + .../providers/template/src/utils/adapt.ts | 12 +- .../providers/template/src/utils/json-yaml.ts | 2 +- .../providers/template/src/utils/tools.ts | 12 ++ 21 files changed, 403 insertions(+), 169 deletions(-) diff --git a/frontend/.gitignore b/frontend/.gitignore index 40b878db5b1..c92f5419545 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1 +1,4 @@ -node_modules/ \ No newline at end of file +node_modules/ +.next +.env*.local +next-env.d.ts \ No newline at end of file diff --git a/frontend/providers/template/.env.template b/frontend/providers/template/.env.template index b44b2cf47fd..eb627d53e32 100644 --- a/frontend/providers/template/.env.template +++ b/frontend/providers/template/.env.template @@ -1,4 +1,6 @@ NEXT_PUBLIC_MOCK_USER= SEALOS_CLOUD_DOMAIN= SEALOS_CERT_SECRET_NAME= -TEMPLATE_REPO_URL="https://github.com/labring-actions/templates" \ No newline at end of file +TEMPLATE_REPO_URL="https://github.com/labring-actions/templates" +# The CDN_URL environment variable is used to specify a CDN address; when set, it replaces raw.githubusercontent.com in the resource loading URL. If not set, the default address is used. +CDN_URL= \ No newline at end of file diff --git a/frontend/providers/template/deploy/manifests/deploy.yaml.tmpl b/frontend/providers/template/deploy/manifests/deploy.yaml.tmpl index 66a3bf191fb..1f0dafcd9bd 100644 --- a/frontend/providers/template/deploy/manifests/deploy.yaml.tmpl +++ b/frontend/providers/template/deploy/manifests/deploy.yaml.tmpl @@ -83,4 +83,51 @@ spec: protocol: TCP targetPort: 3000 selector: - app: template-frontend \ No newline at end of file + app: template-frontend +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + namespace: template-frontend + name: template-static +spec: + schedule: "0 0 * * *" + jobTemplate: + spec: + template: + spec: + serviceAccountName: default + containers: + - name: template-static + image: bitnami/kubectl:latest + command: + - /bin/sh + - -c + args: + - > + echo "$(kubectl get instances -A -o=jsonpath='{range .items[*]}{.spec.title}{"\n"}{end}' | tr a-z A-Z | sort | uniq -c | sort -nr | sed 's/ *//g')" > install-count + kubectl create configmap -n template-frontend template-static --from-file=install-count -o yaml --dry-run=client | kubectl apply -f - + imagePullPolicy: IfNotPresent + restartPolicy: OnFailure +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: template-frontend-static-role +rules: +- apiGroups: [""] + resources: ["configmaps"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: template-frontend-static-role-binding +subjects: +- kind: ServiceAccount + name: default + namespace: template-frontend +roleRef: + kind: ClusterRole + name: template-frontend-static-role + apiGroup: rbac.authorization.k8s.io \ No newline at end of file diff --git a/frontend/providers/template/public/locales/en/common.json b/frontend/providers/template/public/locales/en/common.json index 4f4f27a8017..2f446293f94 100644 --- a/frontend/providers/template/public/locales/en/common.json +++ b/frontend/providers/template/public/locales/en/common.json @@ -148,7 +148,6 @@ "Heading to sealos soon": "Heading to sealos soon", "develop": { "publish": "publish", - "YAML Detection Tool": "YAML Detection Tool", "Development": "Development", "Please enter YAML code": "Please enter YAML code", "Preview": "Preview", @@ -156,7 +155,8 @@ "YAML File": "YAML File", "Template Development": "Template Development", "Dryrun Deploy": "Dryrun Deploy", - "Formal Deploy": "Formal Deploy" + "Formal Deploy": "Formal Deploy", + "Debugging Template": "Debugging Template" }, "SideBar": { "Applications": "Applications", @@ -183,5 +183,9 @@ "Markdown Part": "Markdown Part", "Button Effect": "Button Effect", "Type": "Type", - "Deployment successful, please go to My Application to view": "Deployment successful, please go to My Application to view" + "Deployment successful, please go to My Application to view": "Deployment successful, please go to My Application to view", + "Edit": "Edit", + "Edit App Name": "Edit App Name", + "Installation Time": "Installation Time", + "users installed the app": "{{count}} users have installed the app" } \ 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 e8ee6a2f11b..952ca177013 100644 --- a/frontend/providers/template/public/locales/zh/common.json +++ b/frontend/providers/template/public/locales/zh/common.json @@ -154,7 +154,6 @@ "Heading to sealos soon": "即将前往sealos", "develop": { "publish": "发布", - "YAML Detection Tool": "YAML 检测工具", "Development": "开发", "Please enter YAML code": "请输入yaml代码", "Preview": "预览", @@ -162,7 +161,8 @@ "YAML File": "YAML 文件", "Template Development": "模板开发", "Dryrun Deploy": "试运行部署", - "Formal Deploy": "正式部署" + "Formal Deploy": "正式部署", + "Debugging Template": "在线调试模板" }, "SideBar": { "Applications": "所有应用", @@ -189,5 +189,9 @@ "Markdown Part": "Markdown 片段", "Button Effect": "按钮效果", "Deployment successful, please go to My Application to view": "部署成功,请前往我的应用查看", - "Type": "类型" + "Type": "类型", + "Edit": "编辑", + "Edit App Name": "编辑应用名称", + "Installation Time": "安装时间", + "users installed the app": "已有 {{count}} 名用户安装应用" } \ No newline at end of file diff --git a/frontend/providers/template/src/components/layout/appmenu.tsx b/frontend/providers/template/src/components/layout/appmenu.tsx index 87ac968955c..1d7ceb19d1b 100644 --- a/frontend/providers/template/src/components/layout/appmenu.tsx +++ b/frontend/providers/template/src/components/layout/appmenu.tsx @@ -26,8 +26,7 @@ export default function AppMenu({ isMobile }: { isMobile: boolean }) { width="16" height="17" viewBox="0 0 16 17" - fill="none" - > + fill="none"> router.push('/develop')} - > + onClick={() => router.push('/develop')}> {!isMobile && ( - {t('develop.YAML Detection Tool')} + {t('develop.Debugging Template')} )} diff --git a/frontend/providers/template/src/constants/keys.ts b/frontend/providers/template/src/constants/keys.ts index ad42d1f3fd5..31845b39e75 100644 --- a/frontend/providers/template/src/constants/keys.ts +++ b/frontend/providers/template/src/constants/keys.ts @@ -10,6 +10,8 @@ export const gpuNodeSelectorKey = 'nvidia.com/gpu.product'; export const gpuResourceKey = 'nvidia.com/gpu'; // template export const templateDeployKey = 'cloud.sealos.io/deploy-on-sealos'; +export const templateDisplayNameKey = 'cloud.sealos.io/deploy-on-sealos-displayName'; + // db export const kubeblocksTypeKey = 'clusterdefinition.kubeblocks.io/name'; export const dbProviderKey = 'sealos-db-provider-cr'; diff --git a/frontend/providers/template/src/pages/api/getTemplateSource.ts b/frontend/providers/template/src/pages/api/getTemplateSource.ts index 29be8f0535e..d2df0e50ec8 100644 --- a/frontend/providers/template/src/pages/api/getTemplateSource.ts +++ b/frontend/providers/template/src/pages/api/getTemplateSource.ts @@ -63,6 +63,8 @@ export async function GetTemplateByName({ templateName: string; }) { const cdnUrl = process.env.CDN_URL; + const targetFolder = process.env.TEMPLATE_REPO_FOLDER || 'template'; + const TemplateEnvs = { SEALOS_CLOUD_DOMAIN: process.env.SEALOS_CLOUD_DOMAIN || 'cloud.sealos.io', SEALOS_CERT_SECRET_NAME: process.env.SEALOS_CERT_SECRET_NAME || 'wildcard-cert', @@ -72,13 +74,15 @@ export async function GetTemplateByName({ }; const originalPath = process.cwd(); - const targetPath = path.resolve(originalPath, 'FastDeployTemplates', 'template'); + const targetPath = path.resolve(originalPath, 'FastDeployTemplates', targetFolder); // 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 yamlString = _tempalte?.spec?.filePath + ? fs.readFileSync(_tempalte?.spec?.filePath, 'utf-8') + : fs.readFileSync(`${targetPath}/${_tempalteName}`, 'utf-8'); const yamlData = yaml.loadAll(yamlString); const templateYaml: TemplateType = yamlData.find( @@ -90,6 +94,7 @@ export async function GetTemplateByName({ message: 'Lack of kind template' }; } + templateYaml.spec.deployCount = _tempalte?.spec?.deployCount; if (cdnUrl) { templateYaml.spec.readme = replaceRawWithCDN(templateYaml.spec.readme, cdnUrl); templateYaml.spec.icon = replaceRawWithCDN(templateYaml.spec.icon, cdnUrl); diff --git a/frontend/providers/template/src/pages/api/listTemplate.ts b/frontend/providers/template/src/pages/api/listTemplate.ts index 16152932270..d02dcdc8749 100644 --- a/frontend/providers/template/src/pages/api/listTemplate.ts +++ b/frontend/providers/template/src/pages/api/listTemplate.ts @@ -20,7 +20,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< const originalPath = process.cwd(); const jsonPath = path.resolve(originalPath, 'fast_deploy_template.json'); const cdnUrl = process.env.CDN_URL; - try { if (fs.existsSync(jsonPath)) { const jsonData = fs.readFileSync(jsonPath, 'utf8'); diff --git a/frontend/providers/template/src/pages/api/updateRepo.ts b/frontend/providers/template/src/pages/api/updateRepo.ts index bb0cd1e9452..787ad6be44d 100644 --- a/frontend/providers/template/src/pages/api/updateRepo.ts +++ b/frontend/providers/template/src/pages/api/updateRepo.ts @@ -1,3 +1,4 @@ +import { K8sApiDefault } from '@/services/backend/kubernetes'; import { jsonRes } from '@/services/backend/response'; import { ApiResp } from '@/services/kubernet'; import { TemplateType } from '@/types/app'; @@ -7,9 +8,10 @@ import JSYAML from 'js-yaml'; import type { NextApiRequest, NextApiResponse } from 'next'; import path from 'path'; import util from 'util'; +import * as k8s from '@kubernetes/client-node'; const execAsync = util.promisify(exec); -const readFileList = (targetPath: string, fileList: unknown[] = [], handlePath: string) => { +const readFileList = (targetPath: string, fileList: unknown[] = []) => { // fix ci const sanitizePath = (inputPath: string) => { if (typeof inputPath !== 'string') { @@ -26,16 +28,39 @@ const readFileList = (targetPath: string, fileList: unknown[] = [], handlePath: const isYamlFile = path.extname(item) === '.yaml' || path.extname(item) === '.yml'; if (stats.isFile() && isYamlFile && item !== 'template.yaml') { fileList.push(filePath); - } else if (stats.isDirectory() && item === handlePath) { - readFileList(filePath, fileList, handlePath); + } else if (stats.isDirectory()) { + readFileList(filePath, fileList); } }); }; +export async function GetTemplateStatic() { + try { + const defaultKC = K8sApiDefault(); + const result = await defaultKC + .makeApiClient(k8s.CoreV1Api) + .readNamespacedConfigMap('template-static', 'template-frontend'); + + const inputString = result?.body?.data?.['install-count'] || ''; + const installCountArray = inputString.split(/\n/).filter(Boolean); + + const temp: { [key: string]: number } = {}; + installCountArray.forEach((item) => { + const [count, name] = item.trim().split(/\s/); + temp[name] = parseInt(count, 10); + }); + return temp; + } catch (error) { + console.log(error, 'error: kubectl get configmap/template-static '); + return {}; + } +} + export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { const repoHttpUrl = process.env.TEMPLATE_REPO_URL || 'https://github.com/labring-actions/templates'; + const targetFolder = process.env.TEMPLATE_REPO_FOLDER || 'template'; const originalPath = process.cwd(); const targetPath = path.resolve(originalPath, 'FastDeployTemplates'); const jsonPath = path.resolve(originalPath, 'fast_deploy_template.json'); @@ -60,8 +85,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< } let fileList: unknown[] = []; - readFileList(targetPath, fileList, 'template'); + const _targetPath = path.join(targetPath, targetFolder); + readFileList(_targetPath, fileList); + const templateStaticMap: { [key: string]: number } = await GetTemplateStatic(); let jsonObjArr: unknown[] = []; fileList.forEach((item: any) => { try { @@ -70,6 +97,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< const content = fs.readFileSync(item, 'utf-8'); const yamlTemplate = JSYAML.loadAll(content)[0] as TemplateType; if (!!yamlTemplate) { + const appTitle = yamlTemplate.spec.title.toUpperCase(); + yamlTemplate.spec['deployCount'] = templateStaticMap[appTitle]; + yamlTemplate.spec['filePath'] = item; yamlTemplate.spec['fileName'] = fileName; jsonObjArr.push(yamlTemplate); } diff --git a/frontend/providers/template/src/pages/app/components/list.tsx b/frontend/providers/template/src/pages/app/components/list.tsx index 82ae2db281a..631b73778ce 100644 --- a/frontend/providers/template/src/pages/app/components/list.tsx +++ b/frontend/providers/template/src/pages/app/components/list.tsx @@ -37,8 +37,7 @@ export default function InstanceList() { w={'48px'} h={'48px'} justifyContent="center" - alignItems={'center'} - > + alignItems={'center'}> @@ -52,14 +51,13 @@ export default function InstanceList() { + overflow={'auto'}> {data && data?.map((item) => { return ( @@ -73,39 +71,50 @@ export default function InstanceList() { }} key={item.id} flexDirection={'column'} - minH={'214px'} - // h="214px" p={'24px'} borderRadius={'8px'} backgroundColor={'#fff'} boxShadow={'0px 2px 4px 0px rgba(187, 196, 206, 0.25)'} - border={'1px solid #EAEBF0'} - > - - - - - {item?.id} - - - {t('Creation Time')}: {item?.createTime} - - + border={'1px solid #EAEBF0'}> + + + + + + + {item?.displayName ? item.displayName : item?.id} + + + {item?.id} + + + + + + + + + {t('Installation Time')}: {item?.createTime} + + + {/* + borderRadius={'4px'}> - {/* {t('Unload')} - */} - + + */} ); })} diff --git a/frontend/providers/template/src/pages/deploy/components/Header.tsx b/frontend/providers/template/src/pages/deploy/components/Header.tsx index ff7a529be19..af99d0f3346 100644 --- a/frontend/providers/template/src/pages/deploy/components/Header.tsx +++ b/frontend/providers/template/src/pages/deploy/components/Header.tsx @@ -1,8 +1,10 @@ import { CopyLinkIcon, HomePageIcon, HtmlIcon, MdIcon, ShareIcon } from '@/components/icons'; import { TemplateType } from '@/types/app'; import type { YamlItemType } from '@/types/index'; -import { downLoadBold, useCopyData } from '@/utils/tools'; +import { downLoadBold, formatStarNumber, useCopyData } from '@/utils/tools'; import { + Avatar, + AvatarGroup, Box, Button, Divider, @@ -18,6 +20,7 @@ import { Tooltip } from '@chakra-ui/react'; import dayjs from 'dayjs'; +import { nanoid } from 'nanoid'; import { useTranslation } from 'next-i18next'; import { MouseEvent, useCallback, useMemo } from 'react'; @@ -38,6 +41,8 @@ const Header = ({ templateDetail: TemplateType; cloudDomain: string; }) => { + console.log(templateDetail, 'templateDetail'); + const { t } = useTranslation(); const { copyData } = useCopyData(); const handleExportYaml = useCallback(async () => { @@ -74,6 +79,28 @@ const Header = ({ const HtmlPart = `Deploy on Sealos`; + const DeployCountComponent = useMemo(() => { + return ( + <> + {templateDetail?.spec?.deployCount && templateDetail?.spec?.deployCount > 6 && ( + + + + + + + + +{formatStarNumber(templateDetail.spec.deployCount)} + + + )} + + ); + }, [t, templateDetail?.spec?.deployCount]); + return ( + border={' 1px solid rgba(255, 255, 255, 0.50)'}> - + {templateDetail?.spec?.title} + {DeployCountComponent} goGithub(e, templateDetail?.spec?.gitRepo)} - > + onClick={(e) => goGithub(e, templateDetail?.spec?.gitRepo)}> {t('Home Page')} @@ -115,14 +140,12 @@ const Header = ({ + }}> {t('Share')} @@ -137,8 +160,7 @@ const Header = ({ w="60px" flexDirection={'column'} justifyContent={'center'} - alignItems={'center'} - > + alignItems={'center'}> @@ -154,8 +176,7 @@ const Header = ({ copyData(HtmlPart)} - > + onClick={() => copyData(HtmlPart)}> @@ -167,8 +188,7 @@ const Header = ({ flexDirection={'column'} alignItems={'center'} ml="auto" - onClick={() => copyData(MdPart)} - > + onClick={() => copyData(MdPart)}> @@ -195,8 +215,7 @@ const Header = ({ fontSize={'12px'} color={'5A646E'} fontWeight={400} - onClick={() => copyData(templateDetail?.spec?.description)} - > + onClick={() => copyData(templateDetail?.spec?.description)}> {templateDetail?.spec?.description} @@ -211,8 +230,7 @@ const Header = ({ bg={'myWhite.600'} borderColor={'myGray.200'} variant={'base'} - onClick={handleExportYaml} - > + onClick={handleExportYaml}> {t('Export')} Yaml )} diff --git a/frontend/providers/template/src/pages/index.tsx b/frontend/providers/template/src/pages/index.tsx index 999368d5126..f6f956c96ba 100644 --- a/frontend/providers/template/src/pages/index.tsx +++ b/frontend/providers/template/src/pages/index.tsx @@ -4,11 +4,14 @@ import { useCachedStore } from '@/store/cached'; import { useSearchStore } from '@/store/search'; import { TemplateType } from '@/types/app'; import { serviceSideProps } from '@/utils/i18n'; -import { Box, Flex, Grid, Icon, Image, Text } from '@chakra-ui/react'; +import { Avatar, AvatarGroup, Box, Flex, Grid, Icon, Image, Text, Tooltip } from '@chakra-ui/react'; import { useQuery } from '@tanstack/react-query'; import { useTranslation } from 'next-i18next'; import { useRouter } from 'next/router'; import { MouseEvent, useEffect, useMemo } from 'react'; +import { customAlphabet } from 'nanoid'; +import { formatStarNumber } from '@/utils/tools'; +const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'); export default function AppList() { const { t } = useTranslation(); @@ -73,15 +76,13 @@ export default function AppList() { borderRadius={'12px'} background={'linear-gradient(180deg, #FFF 0%, rgba(255, 255, 255, 0.70) 100%)'} py={'36px'} - px="42px" - > + px="42px"> + minW={'765px'}> {filterData && filterData?.map((item: TemplateType) => { return ( @@ -101,19 +102,35 @@ export default function AppList() { borderRadius={'8px'} backgroundColor={'#fff'} boxShadow={'0px 2px 4px 0px rgba(187, 196, 206, 0.25)'} - border={'1px solid #EAEBF0'} - > - - - + border={'1px solid #EAEBF0'}> + + + + + {item.spec?.deployCount && item.spec?.deployCount > 6 && ( + + + + + + + + +{formatStarNumber(item.spec.deployCount)} + + + )} + {item?.spec?.title} @@ -131,8 +148,7 @@ export default function AppList() { mt={'8px'} fontSize={'12px'} color={'5A646E'} - fontWeight={400} - > + fontWeight={400}> {item?.spec?.description} @@ -148,8 +164,7 @@ export default function AppList() { fill="#5A646E" _hover={{ fill: '#0884DD' - }} - > + }}> diff --git a/frontend/providers/template/src/pages/instance/components/header.tsx b/frontend/providers/template/src/pages/instance/components/header.tsx index a6d0cadfb8a..e2e45f6d091 100644 --- a/frontend/providers/template/src/pages/instance/components/header.tsx +++ b/frontend/providers/template/src/pages/instance/components/header.tsx @@ -1,31 +1,82 @@ +import { postDeployApp } from '@/api/app'; import { getInstanceByName } from '@/api/instance'; -import { Box, Flex, Image, Text, Icon, useDisclosure } from '@chakra-ui/react'; +import { templateDisplayNameKey } from '@/constants/keys'; +import { useToast } from '@/hooks/useToast'; +import { useResourceStore } from '@/store/resource'; +import { TemplateInstanceType } from '@/types/app'; +import { + Box, + Button, + Flex, + Icon, + Image, + Input, + Modal, + ModalCloseButton, + ModalContent, + ModalHeader, + ModalOverlay, + Text, + useDisclosure +} from '@chakra-ui/react'; import { useQuery } from '@tanstack/react-query'; +import JSYAML from 'js-yaml'; +import { useTranslation } from 'next-i18next'; import { useRouter } from 'next/router'; +import { useRef, useState } from 'react'; import DelModal from './delDodal'; -import { useTranslation } from 'next-i18next'; -import { useResourceStore } from '@/store/resource'; export default function Header({ instanceName }: { instanceName: string }) { const router = useRouter(); const { t } = useTranslation(); + const { toast } = useToast(); const { appendResource } = useResourceStore(); + const { isOpen, onOpen, onClose } = useDisclosure(); + const { + isOpen: isOpenDelModal, + onOpen: onOpenDelModal, + onClose: onCloseDelModal + } = useDisclosure(); + const [displayName, setDisplayName] = useState(''); + const yamlCR = useRef(); - const { data } = useQuery( + const { data, refetch } = useQuery( ['getInstanceByName', instanceName], () => getInstanceByName(instanceName), { onSuccess(data) { + yamlCR.current = data.yamlCR; + setDisplayName(data?.displayName || instanceName); appendResource([{ id: data.id, name: data.id, kind: 'Instance' }]); } } ); - const { - isOpen: isOpenDelModal, - onOpen: onOpenDelModal, - onClose: onCloseDelModal - } = useDisclosure(); + const handleDisplayName = async () => { + try { + if (yamlCR.current) { + yamlCR.current.metadata.labels = yamlCR.current.metadata.labels ?? {}; + if (displayName) { + yamlCR.current.metadata.labels[templateDisplayNameKey] = displayName; + } + const yaml = JSYAML.dump(yamlCR.current); + await postDeployApp([yaml], 'replace'); + refetch(); + toast({ + status: 'success', + title: 'success' + }); + } + } catch (error) { + if (typeof error === 'string') { + toast({ + status: 'error', + title: error + }); + } + } + onClose(); + }; return ( + borderBottom={'1px solid rgba(0, 0, 0, 0.07)'}> router.push('/app')} - > + onClick={() => router.push('/app')}> + border={' 1px solid rgba(255, 255, 255, 0.50)'}> - - {data?.id} - + + + + + {data?.displayName ? data?.displayName : data?.id} + + + + + + + + + {t('Edit')} + + + + + {data?.id} + + + + onClick={onOpenDelModal}> @@ -80,22 +160,22 @@ export default function Header({ instanceName }: { instanceName: string }) { {t('Unload')} - {/* - - - - - 启动 - - */} + + + + {t('Edit App Name')} + + + + {t('App Name')} + + setDisplayName(e.target.value)} /> + + + + {isOpenDelModal && ( ; }; spec: { gitRepo: string; @@ -139,4 +142,6 @@ export type InstanceListItemType = { templateType: string; title: string; url: string; + yamlCR: TemplateInstanceType; + displayName?: string; }; diff --git a/frontend/providers/template/src/utils/adapt.ts b/frontend/providers/template/src/utils/adapt.ts index 022e1a627a5..bd81d752628 100644 --- a/frontend/providers/template/src/utils/adapt.ts +++ b/frontend/providers/template/src/utils/adapt.ts @@ -1,4 +1,10 @@ -import { componentLabel, maxReplicasKey, minReplicasKey, pauseKey } from '@/constants/keys'; +import { + componentLabel, + maxReplicasKey, + minReplicasKey, + pauseKey, + templateDisplayNameKey +} from '@/constants/keys'; import { StatusEnum, StatusMap } from '@/constants/status'; import { InstanceListItemType, TemplateInstanceType } from '@/types/app'; import { AppCrdType } from '@/types/appCRD'; @@ -35,7 +41,9 @@ export function adaptInstanceListItem(item: TemplateInstanceType): InstanceListI readme: item.spec.readme, templateType: item.spec.templateType, title: item.spec.title, - url: item.spec.url + url: item.spec.url, + yamlCR: item, + displayName: item.metadata?.labels?.[templateDisplayNameKey] }; } diff --git a/frontend/providers/template/src/utils/json-yaml.ts b/frontend/providers/template/src/utils/json-yaml.ts index ecaec884bbc..2fe33446089 100644 --- a/frontend/providers/template/src/utils/json-yaml.ts +++ b/frontend/providers/template/src/utils/json-yaml.ts @@ -127,7 +127,7 @@ export const getTemplateDataSource = ( default: item.default, required: item.required, key: key, - label: key.replace('_', ' ') + label: key }; }); return inputsArr; diff --git a/frontend/providers/template/src/utils/tools.ts b/frontend/providers/template/src/utils/tools.ts index 2a8ec0b8d99..d748342ef8b 100644 --- a/frontend/providers/template/src/utils/tools.ts +++ b/frontend/providers/template/src/utils/tools.ts @@ -243,3 +243,15 @@ export function deepSearch(obj: any): string { } return 'Error'; } + +export const formatStarNumber = (number: number) => { + if (number < 1000) { + return number.toString(); + } else if (number < 10000) { + const thousands = Math.floor(number / 1000); + const remainder = number % 1000; + return `${thousands}.${remainder.toString()[0]}k`; + } else { + return (number / 1000).toFixed(1) + 'k'; + } +};