From 2c61ce4e59f575e5733e0b26fdc3da07b08be4a9 Mon Sep 17 00:00:00 2001 From: jingyang <72259332+zjy365@users.noreply.github.com> Date: Wed, 18 Jun 2025 11:30:16 +0800 Subject: [PATCH] refactor: launchpad configmap logic (#5650) * feat: launchpad cm * add comfigmap key * update * support volumeName * delete log --- .../applaunchpad/src/pages/_document.tsx | 1 - .../applaunchpad/src/pages/api/checkReady.ts | 3 +- .../src/pages/api/getAppByAppName.ts | 1 - .../app/edit/components/ConfigmapModal.tsx | 7 +- .../src/pages/app/edit/components/Form.tsx | 23 +++- .../pages/app/edit/components/StoreModal.tsx | 4 +- .../providers/applaunchpad/src/types/app.d.ts | 2 + .../providers/applaunchpad/src/utils/adapt.ts | 112 +++++++++++++++--- .../applaunchpad/src/utils/deployYaml2Json.ts | 53 ++++----- .../providers/applaunchpad/src/utils/tools.ts | 28 +++-- .../providers/template/src/pages/index.tsx | 4 - 11 files changed, 160 insertions(+), 78 deletions(-) diff --git a/frontend/providers/applaunchpad/src/pages/_document.tsx b/frontend/providers/applaunchpad/src/pages/_document.tsx index 9e8e2a349..48fdd5f05 100644 --- a/frontend/providers/applaunchpad/src/pages/_document.tsx +++ b/frontend/providers/applaunchpad/src/pages/_document.tsx @@ -11,7 +11,6 @@ export default function Document() { - diff --git a/frontend/providers/applaunchpad/src/pages/api/checkReady.ts b/frontend/providers/applaunchpad/src/pages/api/checkReady.ts index cd7408ee1..6dba5ade0 100644 --- a/frontend/providers/applaunchpad/src/pages/api/checkReady.ts +++ b/frontend/providers/applaunchpad/src/pages/api/checkReady.ts @@ -27,7 +27,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ); if (!ingress.body.items || ingress.body.items.length === 0) { - throw new Error('No ingress found'); + throw new Error('Check ready error: No ingress found'); } const checkResults = await Promise.all( @@ -75,7 +75,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) data: checkResults }); } catch (error: any) { - console.error(error); return jsonRes(res, { code: 500, error: error?.message diff --git a/frontend/providers/applaunchpad/src/pages/api/getAppByAppName.ts b/frontend/providers/applaunchpad/src/pages/api/getAppByAppName.ts index 28e476248..e9fb265fc 100644 --- a/frontend/providers/applaunchpad/src/pages/api/getAppByAppName.ts +++ b/frontend/providers/applaunchpad/src/pages/api/getAppByAppName.ts @@ -40,7 +40,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< }) .filter((item) => item) .flat(); - console.log('responseData', responseData); const data = adaptAppDetail(responseData as DeployKindsType[], getServerEnv(global.AppConfig)); diff --git a/frontend/providers/applaunchpad/src/pages/app/edit/components/ConfigmapModal.tsx b/frontend/providers/applaunchpad/src/pages/app/edit/components/ConfigmapModal.tsx index 62a685562..1b507cda6 100644 --- a/frontend/providers/applaunchpad/src/pages/app/edit/components/ConfigmapModal.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/edit/components/ConfigmapModal.tsx @@ -21,12 +21,16 @@ export type ConfigMapType = { id?: string; mountPath: string; value: string; + key: string; + volumeName: string; }; const ConfigmapModal = ({ defaultValue = { mountPath: '', - value: '' + value: '', + key: '', + volumeName: '' }, listNames, successCb, @@ -68,7 +72,6 @@ const ConfigmapModal = ({ - {' '} {t('filename')} import('./CustomAccessModal')); const ConfigmapModal = dynamic(() => import('./ConfigmapModal')); @@ -1147,7 +1148,9 @@ const Form = ({ w={'100%'} height={'32px'} variant={'outline'} - onClick={() => setConfigEdit({ mountPath: '', value: '' })} + onClick={() => + setConfigEdit({ mountPath: '', value: '', key: '', volumeName: '' }) + } leftIcon={} > {t('form.add_configmap')} @@ -1341,14 +1344,22 @@ const Form = ({ .map((item) => item.mountPath.toLocaleLowerCase())} successCb={(e) => { if (!e.id) { - appendConfigMaps(e); + appendConfigMaps({ + ...e, + key: mountPathToConfigMapKey(e.mountPath), + volumeName: getValues('appName') + '-cm' + }); } else { setValue( 'configMapList', - configMaps.map((item) => ({ - mountPath: item.id === e.id ? e.mountPath : item.mountPath, - value: item.id === e.id ? e.value : item.value - })) + configMaps.map((item) => { + return { + mountPath: item.id === e.id ? e.mountPath : item.mountPath, + value: item.id === e.id ? e.value : item.value, + key: item.id === e.id ? e.key : item.key, + volumeName: item.id === e.id ? e.volumeName : item.volumeName + }; + }) ); } setConfigEdit(undefined); diff --git a/frontend/providers/applaunchpad/src/pages/app/edit/components/StoreModal.tsx b/frontend/providers/applaunchpad/src/pages/app/edit/components/StoreModal.tsx index 98ac957f8..ffc075de2 100644 --- a/frontend/providers/applaunchpad/src/pages/app/edit/components/StoreModal.tsx +++ b/frontend/providers/applaunchpad/src/pages/app/edit/components/StoreModal.tsx @@ -20,7 +20,7 @@ import { import { useForm } from 'react-hook-form'; import MyFormControl from '@/components/FormControl'; import { useTranslation } from 'next-i18next'; -import { pathToNameFormat } from '@/utils/tools'; +import { mountPathToConfigMapKey } from '@/utils/tools'; import { MyTooltip } from '@sealos/ui'; import { PVC_STORAGE_MAX } from '@/store/static'; @@ -152,7 +152,7 @@ const StoreModal = ({ return true; }, onChange(e) { - setValue('name', pathToNameFormat(e.target.value)); + setValue('name', mountPathToConfigMapKey(e.target.value)); } })} /> diff --git a/frontend/providers/applaunchpad/src/types/app.d.ts b/frontend/providers/applaunchpad/src/types/app.d.ts index dc848c519..466b24af4 100644 --- a/frontend/providers/applaunchpad/src/types/app.d.ts +++ b/frontend/providers/applaunchpad/src/types/app.d.ts @@ -108,6 +108,8 @@ export interface AppEditType { configMapList: { mountPath: string; value: string; + key: string; + volumeName: string; }[]; storeList: { name: string; diff --git a/frontend/providers/applaunchpad/src/utils/adapt.ts b/frontend/providers/applaunchpad/src/utils/adapt.ts index 3a076cf5c..42a339dce 100644 --- a/frontend/providers/applaunchpad/src/utils/adapt.ts +++ b/frontend/providers/applaunchpad/src/utils/adapt.ts @@ -8,7 +8,8 @@ import type { V1Pod, SinglePodMetrics, CoreV1EventList, - V2HorizontalPodAutoscaler + V2HorizontalPodAutoscaler, + V1VolumeMount } from '@kubernetes/client-node'; import dayjs from 'dayjs'; import type { @@ -235,7 +236,6 @@ export const adaptAppDetail = (configs: DeployKindsType[], envs: EnvResponse): A const deployKindsMap: { [YamlKindEnum.StatefulSet]?: V1StatefulSet; [YamlKindEnum.Deployment]?: V1Deployment; - // [YamlKindEnum.Service]?: V1Service; [YamlKindEnum.ConfigMap]?: V1ConfigMap; [YamlKindEnum.HorizontalPodAutoscaler]?: V2HorizontalPodAutoscaler; [YamlKindEnum.Secret]?: V1Secret; @@ -250,10 +250,86 @@ export const adaptAppDetail = (configs: DeployKindsType[], envs: EnvResponse): A const appDeploy = deployKindsMap.Deployment || deployKindsMap.StatefulSet; - if (!appDeploy) { + if (!appDeploy || !appDeploy?.metadata?.name) { throw new Error('获取APP异常'); } + const appName = appDeploy.metadata?.name; + + const getConfigMapVolumeNames = (): string[] => { + if (!deployKindsMap.ConfigMap) return []; + + const configMapName = deployKindsMap.ConfigMap.metadata?.name; + const configMapVolumes = + appDeploy?.spec?.template?.spec?.volumes?.filter( + (volume) => volume.configMap?.name === configMapName + ) || []; + + return configMapVolumes.map((volume) => volume.name).filter(Boolean) as string[]; + }; + + const configMapVolumeNames = getConfigMapVolumeNames(); + + const getConfigMapList = (): Array<{ + mountPath: string; + value: string; + key: string; + volumeName: string; + }> => { + const configMap = deployKindsMap.ConfigMap; + if (!configMap?.data || !appDeploy) { + return []; + } + const configMapName = configMap.metadata?.name; + const volumes = appDeploy.spec?.template?.spec?.volumes || []; + const configMapVolumes = volumes.filter((volume) => volume.configMap?.name === configMapName); + if (configMapVolumes.length === 0) { + return []; + } + const volumeMounts = appDeploy.spec?.template?.spec?.containers?.[0]?.volumeMounts || []; + const results: Array<{ mountPath: string; value: string; key: string; volumeName: string }> = + []; + + configMapVolumes.forEach((volume) => { + const relatedMounts = volumeMounts.filter((mount) => mount.name === volume.name); + + if (volume.configMap?.items && volume.configMap.items.length > 0) { + volume.configMap.items.forEach((item) => { + if (!item.key) return; + const matchedMount = relatedMounts.find((mount) => mount.subPath === item.path); + + if (matchedMount) { + const value = configMap.data?.[item.key] || ''; + results.push({ + mountPath: matchedMount.mountPath, + value: value, + key: item.key, + volumeName: volume.name + }); + } + }); + } else { + relatedMounts.forEach((mount) => { + if (mount.subPath) { + const configMapKey = mount.subPath; + const value = configMap?.data?.[configMapKey] || ''; + + if (value) { + results.push({ + mountPath: mount.mountPath, + value: value, + key: configMapKey, + volumeName: volume.name + }); + } + } + }); + } + }); + + return results.filter((item) => item.value); + }; + const useGpu = !!Number( appDeploy.spec?.template?.spec?.containers?.[0]?.resources?.limits?.[gpuResourceKey] ); @@ -261,14 +337,23 @@ export const adaptAppDetail = (configs: DeployKindsType[], envs: EnvResponse): A const getFilteredVolumeMounts = () => { const volumeMounts = appDeploy?.spec?.template?.spec?.containers?.[0]?.volumeMounts || []; - const configMapKeys = Object.keys(deployKindsMap.ConfigMap?.data || {}); const storeNames = deployKindsMap.StatefulSet?.spec?.volumeClaimTemplates?.map( (template) => template.metadata?.name ) || []; return volumeMounts.filter( - (mount) => !configMapKeys.includes(mount.name) && !storeNames.includes(mount.name) + (mount) => !configMapVolumeNames.includes(mount.name) && !storeNames.includes(mount.name) + ); + }; + + const getFilteredVolumes = () => { + return ( + appDeploy?.spec?.template?.spec?.volumes?.filter((volume) => { + if (!deployKindsMap.ConfigMap) return true; + const configMapName = deployKindsMap.ConfigMap.metadata?.name; + return !(volume.configMap?.name === configMapName); + }) || [] ); }; @@ -276,7 +361,7 @@ export const adaptAppDetail = (configs: DeployKindsType[], envs: EnvResponse): A labels: appDeploy?.metadata?.labels || {}, crYamlList: configs, id: appDeploy.metadata?.uid || ``, - appName: appDeploy.metadata?.name || 'app Name', + appName: appName, createTime: dayjs(appDeploy.metadata?.creationTimestamp).format('YYYY-MM-DD HH:mm'), status: appStatusMap.waiting, isPause: !!appDeploy?.metadata?.annotations?.[pauseKey], @@ -356,7 +441,7 @@ export const adaptAppDetail = (configs: DeployKindsType[], envs: EnvResponse): A customDomain: isCustomDomain ? domain : '', domain: isCustomDomain ? envs.SEALOS_DOMAIN - : item?.nodePort // 如果有 nodePort,则使用域名 + : item?.nodePort ? domain : domain.split('.').slice(1).join('.') || envs.SEALOS_DOMAIN }; @@ -383,15 +468,7 @@ export const adaptAppDetail = (configs: DeployKindsType[], envs: EnvResponse): A maxReplicas: deployKindsMap.HorizontalPodAutoscaler.spec.maxReplicas || 10 } : defaultEditVal.hpa, - configMapList: deployKindsMap.ConfigMap?.data - ? Object.entries(deployKindsMap.ConfigMap.data).map(([key, value], i) => ({ - mountPath: - appDeploy?.spec?.template.spec?.containers[0].volumeMounts?.find( - (item) => item.name === key - )?.mountPath || key, - value - })) - : [], + configMapList: getConfigMapList(), secret: atobSecretYaml(deployKindsMap?.Secret?.data?.['.dockerconfigjson']), storeList: deployKindsMap.StatefulSet?.spec?.volumeClaimTemplates ? deployKindsMap.StatefulSet?.spec?.volumeClaimTemplates.map((item) => ({ @@ -401,8 +478,7 @@ export const adaptAppDetail = (configs: DeployKindsType[], envs: EnvResponse): A })) : [], volumeMounts: getFilteredVolumeMounts(), - // keep original non-configMap type volumes - volumes: appDeploy?.spec?.template?.spec?.volumes || [], + volumes: getFilteredVolumes(), kind: appDeploy?.kind?.toLowerCase() as 'deployment' | 'statefulset', source: getAppSource(appDeploy) }; diff --git a/frontend/providers/applaunchpad/src/utils/deployYaml2Json.ts b/frontend/providers/applaunchpad/src/utils/deployYaml2Json.ts index 7c8fe5cc1..94f790b42 100644 --- a/frontend/providers/applaunchpad/src/utils/deployYaml2Json.ts +++ b/frontend/providers/applaunchpad/src/utils/deployYaml2Json.ts @@ -9,7 +9,7 @@ import { } from '@/constants/app'; import { SEALOS_USER_DOMAINS } from '@/store/static'; import type { AppEditType } from '@/types/app'; -import { pathFormat, pathToNameFormat, str2Num, strToBase64 } from '@/utils/tools'; +import { pathFormat, mountPathToConfigMapKey, str2Num, strToBase64 } from '@/utils/tools'; import dayjs from 'dayjs'; import yaml from 'js-yaml'; @@ -97,44 +97,31 @@ export const json2DeployCr = (data: AppEditType, type: 'deployment' | 'statefuls })), imagePullPolicy: 'Always' }; + const configMapVolumeMounts = data.configMapList.map((item) => ({ - name: pathToNameFormat(item.mountPath), + name: item.volumeName, mountPath: item.mountPath, - subPath: pathFormat(item.mountPath) - })); - const configMapVolumes = data.configMapList.map((item) => ({ - name: pathToNameFormat(item.mountPath), // name === [development.***.volumeMounts[*].name] - configMap: { - name: data.appName, // name === configMap.yaml.meta.name - items: [ - { - key: pathToNameFormat(item.mountPath), - path: pathFormat(item.mountPath) // path ===[development.***.volumeMounts[*].subPath] - } - ] - } + subPath: item.key })); - const deduplicateVolumes = () => { - const existingVolumes = data.volumes || []; - const formVolumeNames = new Set( - data.configMapList.map((item) => pathToNameFormat(item.mountPath)) - ); - const preservedVolumes = existingVolumes.filter( - (volume) => !(volume.configMap && formVolumeNames.has(volume.name)) - ); - return [...preservedVolumes, ...configMapVolumes]; + const generateConfigMapVolumes = () => { + if (data.configMapList.length === 0) return []; + const uniqueVolumeNames = [...new Set(data.configMapList.map((item) => item.volumeName))]; + return uniqueVolumeNames.map((volumeName) => ({ + name: volumeName, + configMap: { + name: data.appName + } + })); }; - const deduplicateVolumeMounts = () => { - const existingMounts = data.volumeMounts || []; - const formMountNames = new Set(configMapVolumeMounts.map((m) => m.name)); - const preservedMounts = existingMounts.filter((mount) => !formMountNames.has(mount.name)); - return [...preservedMounts, ...configMapVolumeMounts]; - }; + const configMapVolumes = generateConfigMapVolumes(); - const finalVolumes = deduplicateVolumes(); - const finalVolumeMounts = deduplicateVolumeMounts(); + const finalVolumes = configMapVolumes + ? [...(data.volumes || []), ...configMapVolumes] + : data.volumes || []; + + const finalVolumeMounts = [...(data.volumeMounts || []), ...configMapVolumeMounts]; // pvc settings const storageTemplates = data.storeList.map((store) => ({ @@ -452,7 +439,7 @@ export const json2ConfigMap = (data: AppEditType) => { const configFile: { [key: string]: string } = {}; data.configMapList.forEach((item) => { - configFile[pathToNameFormat(item.mountPath)] = item.value; // key === [development.***.volumes[*].configMap.items[0].key] + configFile[item.key] = item.value; }); const template = { diff --git a/frontend/providers/applaunchpad/src/utils/tools.ts b/frontend/providers/applaunchpad/src/utils/tools.ts index 5e2baa6bf..f87397a7f 100644 --- a/frontend/providers/applaunchpad/src/utils/tools.ts +++ b/frontend/providers/applaunchpad/src/utils/tools.ts @@ -69,12 +69,18 @@ export const pathFormat = (str: string) => { if (str.startsWith('/')) return `.${str}`; return `./${str}`; }; -export const pathToNameFormat = (str: string) => { + +export const mountPathToConfigMapKey = (str: string) => { const endsWithSlash = str.endsWith('/'); const withoutTrailingSlash = endsWithSlash ? str.slice(0, -1) : str; const replacedStr = withoutTrailingSlash.replace(/_/g, '-').replace(/[\/.]/g, 'vn-'); + const result = replacedStr.toLowerCase(); - return replacedStr.toLowerCase(); + if (result.length > 63) { + return result.slice(-63); + } + + return result; }; /** @@ -274,10 +280,16 @@ export const patchYamlList = ({ // find create and patch newFormJsonList.forEach((newYamlJson) => { - const oldFormJson = oldFormJsonList.find( - (item) => - item.kind === newYamlJson.kind && item?.metadata?.name === newYamlJson?.metadata?.name - ); + const oldFormJson = + newYamlJson.kind === 'ConfigMap' + ? originalYamlList.find( + (item) => + item.kind === newYamlJson.kind && item?.metadata?.name === newYamlJson?.metadata?.name + ) + : oldFormJsonList.find( + (item) => + item.kind === newYamlJson.kind && item?.metadata?.name === newYamlJson?.metadata?.name + ); if (oldFormJson) { const patchRes = jsonpatch.compare(oldFormJson, newYamlJson); @@ -299,7 +311,7 @@ export const patchYamlList = ({ if (!crOldYamlJson) return newYamlJson; - /* Fill in volumn */ + /* Fill in volume - Handle Deployment/StatefulSet */ if ( oldFormJson.kind === YamlKindEnum.Deployment || oldFormJson.kind === YamlKindEnum.StatefulSet @@ -380,8 +392,6 @@ export const patchYamlList = ({ } } - console.log('patch result:', oldFormJson.metadata?.name, oldFormJson.kind, actionsJson); - actions.push({ type: 'patch', kind: newYamlJson.kind as `${YamlKindEnum}`, diff --git a/frontend/providers/template/src/pages/index.tsx b/frontend/providers/template/src/pages/index.tsx index a4dc5955e..d5880303f 100644 --- a/frontend/providers/template/src/pages/index.tsx +++ b/frontend/providers/template/src/pages/index.tsx @@ -299,8 +299,6 @@ export async function getServerSideProps(content: any) { const forcedLanguage = process.env.FORCED_LANGUAGE; const brandName = process.env.NEXT_PUBLIC_BRAND_NAME || 'Sealos'; - console.log('content', content); - const local: string = forcedLanguage || content?.req?.cookies?.NEXT_LOCALE || @@ -325,8 +323,6 @@ export async function getServerSideProps(content: any) { } }; - console.log('local', local); - console.log('seoData', seoData); return { props: { ...(await serviceSideProps(content)),