mirror of
https://github.com/labring/sealos.git
synced 2026-09-24 15:46:19 +08:00
refactor: launchpad configmap logic (#5650)
* feat: launchpad cm * add comfigmap key * update * support volumeName * delete log
This commit is contained in:
@@ -11,7 +11,6 @@ export default function Document() {
|
||||
<meta name="apple-mobile-web-app-title" content="Sealos" />
|
||||
<meta name="description" content="sealos cloud" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<body>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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 = ({
|
||||
<ModalBody>
|
||||
<MyFormControl showError errorText={errors.mountPath?.message}>
|
||||
<Box mb={'8px'} fontSize={'14px'} fontWeight={500} color={'grayModern.900'}>
|
||||
{' '}
|
||||
{t('filename')}
|
||||
</Box>
|
||||
<Input
|
||||
|
||||
@@ -43,6 +43,7 @@ import PriceBox from './PriceBox';
|
||||
import QuotaBox from './QuotaBox';
|
||||
import type { StoreType } from './StoreModal';
|
||||
import styles from './index.module.scss';
|
||||
import { mountPathToConfigMapKey } from '@/utils/tools';
|
||||
|
||||
const CustomAccessModal = dynamic(() => 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={<MyIcon name="plus" w={'16px'} fill="#485264" />}
|
||||
>
|
||||
{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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
})}
|
||||
/>
|
||||
|
||||
@@ -108,6 +108,8 @@ export interface AppEditType {
|
||||
configMapList: {
|
||||
mountPath: string;
|
||||
value: string;
|
||||
key: string;
|
||||
volumeName: string;
|
||||
}[];
|
||||
storeList: {
|
||||
name: string;
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -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)),
|
||||
|
||||
Reference in New Issue
Block a user