From cf47ac7383aa5a450f0fa9ee8be03a6ef2860671 Mon Sep 17 00:00:00 2001 From: zhujingyang <72259332+zjy365@users.noreply.github.com> Date: Mon, 16 Oct 2023 04:37:00 -0500 Subject: [PATCH] feat: template other resource (#4098) * feat: template other resource Signed-off-by: jingyang <3161362058@qq.com> * fix template develop instanceName Signed-off-by: jingyang <3161362058@qq.com> --------- Signed-off-by: jingyang <3161362058@qq.com> --- frontend/desktop/next-i18next.config.js | 2 +- frontend/desktop/src/pages/index.tsx | 3 +- frontend/desktop/src/pages/signin.tsx | 3 +- frontend/providers/template/src/api/delete.ts | 49 +++++++----- .../providers/template/src/constants/keys.ts | 1 + .../src/pages/api/app/listOtherByName.ts | 75 +++++++++++++++++-- .../src/pages/api/resource/delConfigMap.ts | 27 +++++++ .../src/pages/api/resource/delRole.ts | 27 +++++++ .../src/pages/api/resource/delRoleBinding.ts | 27 +++++++ .../pages/api/resource/delServiceAccount.ts | 27 +++++++ .../deleteIssuer.ts} | 17 +++-- .../template/src/pages/develop/index.tsx | 29 +++---- .../providers/template/src/types/resource.ts | 5 ++ 13 files changed, 243 insertions(+), 49 deletions(-) create mode 100644 frontend/providers/template/src/pages/api/resource/delConfigMap.ts create mode 100644 frontend/providers/template/src/pages/api/resource/delRole.ts create mode 100644 frontend/providers/template/src/pages/api/resource/delRoleBinding.ts create mode 100644 frontend/providers/template/src/pages/api/resource/delServiceAccount.ts rename frontend/providers/template/src/pages/api/{app/listAppCRD.ts => resource/deleteIssuer.ts} (63%) diff --git a/frontend/desktop/next-i18next.config.js b/frontend/desktop/next-i18next.config.js index 5c283f60f76..d148cb66119 100644 --- a/frontend/desktop/next-i18next.config.js +++ b/frontend/desktop/next-i18next.config.js @@ -5,7 +5,7 @@ module.exports = { i18n: { defaultLocale: 'en', - locales: ['en', 'zh', 'zh-Hans'], + locales: ['en', 'zh'], localeDetection: false } }; diff --git a/frontend/desktop/src/pages/index.tsx b/frontend/desktop/src/pages/index.tsx index e16381bed72..1c0140c97f5 100644 --- a/frontend/desktop/src/pages/index.tsx +++ b/frontend/desktop/src/pages/index.tsx @@ -105,7 +105,8 @@ export default function Home({ } export async function getServerSideProps({ req, res, locales }: any) { - const local = req?.cookies?.NEXT_LOCALE || 'en'; + const lang: string = req?.headers?.['accept-language'] || 'zh'; + const local = lang.indexOf('zh') !== -1 ? 'zh' : 'en'; const sealos_cloud_domain = process.env.SEALOS_CLOUD_DOMAIN || 'cloud.sealos.io'; return { diff --git a/frontend/desktop/src/pages/signin.tsx b/frontend/desktop/src/pages/signin.tsx index 5ffa639e5b3..5e88304d2a4 100644 --- a/frontend/desktop/src/pages/signin.tsx +++ b/frontend/desktop/src/pages/signin.tsx @@ -6,7 +6,8 @@ export default function SigninPage() { } export async function getServerSideProps({ req, res, locales }: any) { - const local = req?.cookies?.NEXT_LOCALE || 'en'; + const lang: string = req?.headers?.['accept-language'] || 'zh'; + const local = lang.indexOf('zh') !== -1 ? 'zh' : 'en'; const props = { ...(await serverSideTranslations(local, undefined, null, locales || [])) diff --git a/frontend/providers/template/src/api/delete.ts b/frontend/providers/template/src/api/delete.ts index b4418ef0533..dbdef7f6f12 100644 --- a/frontend/providers/template/src/api/delete.ts +++ b/frontend/providers/template/src/api/delete.ts @@ -22,30 +22,39 @@ export const delInstanceByName = (instanceName: string) => export const delJobByName = (instanceName: string) => DELETE('/api/resource/delJob', { instanceName }); -export const deleteResourceByKind = (instanceName: string, kind: ResourceKindType) => { - switch (kind) { - case 'CronJob': - return delCronJobByName(instanceName); - case 'App': - return deleteAppCRD(instanceName); - case 'Secret': - return deleteSecret(instanceName); - case 'AppLaunchpad': - return delApplaunchpad(instanceName); - case 'DataBase': - return delDBByName(instanceName); - case 'Instance': - return delInstanceByName(instanceName); - case 'Job': - return delJobByName(instanceName); - default: - throw new Error(`Unsupported kind: ${kind}`); - } +export const delConfigMapByName = (instanceName: string) => + DELETE('/api/resource/delConfigMap', { instanceName }); + +export const delIssuerByName = (instanceName: string) => + DELETE('/api/resource/deleteIssuer', { instanceName }); + +export const delRoleByName = (instanceName: string) => + DELETE('/api/resource/delRole', { instanceName }); + +export const delRoleBindingByName = (instanceName: string) => + DELETE('/api/resource/delRoleBinding', { instanceName }); + +export const delServiceAccountByName = (instanceName: string) => + DELETE('/api/resource/delServiceAccount', { instanceName }); + +const deleteResourceByKind: Record void> = { + CronJob: (instanceName: string) => delCronJobByName(instanceName), + App: (instanceName: string) => deleteAppCRD(instanceName), + Secret: (instanceName: string) => deleteSecret(instanceName), + AppLaunchpad: (instanceName: string) => delApplaunchpad(instanceName), + DataBase: (instanceName: string) => delDBByName(instanceName), + Instance: (instanceName: string) => delInstanceByName(instanceName), + Job: (instanceName: string) => delJobByName(instanceName), + ConfigMap: (instanceName: string) => delConfigMapByName(instanceName), + Issuer: (instanceName: string) => delIssuerByName(instanceName), + Role: (instanceName: string) => delRoleByName(instanceName), + RoleBinding: (instanceName: string) => delRoleBindingByName(instanceName), + ServiceAccount: (instanceName: string) => delServiceAccountByName(instanceName) }; export const deleteAllResources = async (resources: BaseResourceType[]) => { const deletePromises = resources.map((resource) => { - return deleteResourceByKind(resource.name, resource.kind); + return deleteResourceByKind[resource.kind](resource.name); }); const reuslt = await Promise.allSettled(deletePromises); console.log(reuslt); diff --git a/frontend/providers/template/src/constants/keys.ts b/frontend/providers/template/src/constants/keys.ts index 85106b77ee1..ad42d1f3fd5 100644 --- a/frontend/providers/template/src/constants/keys.ts +++ b/frontend/providers/template/src/constants/keys.ts @@ -12,5 +12,6 @@ export const gpuResourceKey = 'nvidia.com/gpu'; export const templateDeployKey = 'cloud.sealos.io/deploy-on-sealos'; // db export const kubeblocksTypeKey = 'clusterdefinition.kubeblocks.io/name'; +export const dbProviderKey = 'sealos-db-provider-cr'; // labels export const componentLabel = 'app.kubernetes.io/component'; diff --git a/frontend/providers/template/src/pages/api/app/listOtherByName.ts b/frontend/providers/template/src/pages/api/app/listOtherByName.ts index e8bbe974795..c3a39af3625 100644 --- a/frontend/providers/template/src/pages/api/app/listOtherByName.ts +++ b/frontend/providers/template/src/pages/api/app/listOtherByName.ts @@ -1,16 +1,15 @@ -import { templateDeployKey } from '@/constants/keys'; +import { dbProviderKey, deployManagerKey, templateDeployKey } from '@/constants/keys'; import { authSession } from '@/services/backend/auth'; import { CRDMeta, getK8s } from '@/services/backend/kubernetes'; import { jsonRes } from '@/services/backend/response'; import { ApiResp } from '@/services/kubernet'; -import { AppCrdType } from '@/types/appCRD'; import { IncomingMessage } from 'http'; import type { NextApiRequest, NextApiResponse } from 'next'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { const { instanceName } = req.query as { instanceName: string }; - const { namespace, k8sCore, k8sCustomObjects, k8sBatch } = await getK8s({ + const { namespace, k8sCore, k8sCustomObjects, k8sBatch, k8sAuth } = await getK8s({ kubeconfig: await authSession(req.headers) }); const labelSelector = `${templateDeployKey}=${instanceName}`; @@ -22,6 +21,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< plural: 'apps' }; + // secret const secretPromise = k8sCore.listNamespacedSecret( namespace, undefined, @@ -31,6 +31,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< labelSelector ); + // job const jobPromise = k8sBatch.listNamespacedJob( namespace, undefined, @@ -40,6 +41,26 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< labelSelector ); + // issuer + const certIssuerPromise = k8sCustomObjects.listNamespacedCustomObject( + 'cert-manager.io', + 'v1', + namespace, + 'issuers', + undefined, + undefined, + undefined, + undefined, + labelSelector + ) as Promise<{ + response: IncomingMessage; + body: { + items: { kind?: string }[]; + kind: 'IssuerList'; + }; + }>; + + // app cr const appCrdResourcePromise = k8sCustomObjects.listNamespacedCustomObject( appCRD.group, appCRD.version, @@ -53,17 +74,61 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< ) as Promise<{ response: IncomingMessage; body: { - items: AppCrdType[]; + items: { kind?: string }[]; kind: 'AppList'; }; }>; + // role + const rolePromise = k8sAuth.listNamespacedRole( + namespace, + undefined, + undefined, + undefined, + undefined, + `${labelSelector},!${dbProviderKey}` + ); + const roleBindingPromise = k8sAuth.listNamespacedRoleBinding( + namespace, + undefined, + undefined, + undefined, + undefined, + `${labelSelector},!${dbProviderKey}` + ); + const saPromise = k8sCore.listNamespacedServiceAccount( + namespace, + undefined, + undefined, + undefined, + undefined, + `${labelSelector},!${dbProviderKey}` + ); + const configMapPromise = k8sCore.listNamespacedConfigMap( + namespace, + undefined, + undefined, + undefined, + undefined, + `${labelSelector},!${dbProviderKey},!${deployManagerKey}` + ); // 使用 Promise.allSettled 获取所有结果 [secretResult, jobResult, customResourceResult] - const result = await Promise.allSettled([secretPromise, jobPromise, appCrdResourcePromise]); + const result = await Promise.allSettled([ + secretPromise, + jobPromise, + appCrdResourcePromise, + certIssuerPromise, + rolePromise, + roleBindingPromise, + saPromise, + configMapPromise + ]); + const data = result .map((res) => { if (res.status === 'fulfilled') { return res.value.body.items.map((item) => { + // console.log(item, '=='); return { ...item, kind: item.kind ? item.kind : res.value?.body?.kind?.replace('List', '') diff --git a/frontend/providers/template/src/pages/api/resource/delConfigMap.ts b/frontend/providers/template/src/pages/api/resource/delConfigMap.ts new file mode 100644 index 00000000000..f5e4b626b6e --- /dev/null +++ b/frontend/providers/template/src/pages/api/resource/delConfigMap.ts @@ -0,0 +1,27 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { ApiResp } from '@/services/kubernet'; +import { authSession } from '@/services/backend/auth'; +import { getK8s } from '@/services/backend/kubernetes'; +import { jsonRes } from '@/services/backend/response'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + const { instanceName } = req.query as { instanceName: string }; + if (!instanceName) { + throw new Error('deploy name is empty'); + } + + const { namespace, k8sCore } = await getK8s({ + kubeconfig: await authSession(req.headers) + }); + + const result = await k8sCore.deleteNamespacedConfigMap(instanceName, namespace); + + jsonRes(res, { data: result }); + } catch (err: any) { + jsonRes(res, { + code: 500, + error: err + }); + } +} diff --git a/frontend/providers/template/src/pages/api/resource/delRole.ts b/frontend/providers/template/src/pages/api/resource/delRole.ts new file mode 100644 index 00000000000..280c5a11d0b --- /dev/null +++ b/frontend/providers/template/src/pages/api/resource/delRole.ts @@ -0,0 +1,27 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { ApiResp } from '@/services/kubernet'; +import { authSession } from '@/services/backend/auth'; +import { getK8s } from '@/services/backend/kubernetes'; +import { jsonRes } from '@/services/backend/response'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + const { instanceName } = req.query as { instanceName: string }; + if (!instanceName) { + throw new Error('deploy name is empty'); + } + + const { namespace, k8sAuth } = await getK8s({ + kubeconfig: await authSession(req.headers) + }); + + const result = await k8sAuth.deleteNamespacedRole(instanceName, namespace); + + jsonRes(res, { data: result }); + } catch (err: any) { + jsonRes(res, { + code: 500, + error: err + }); + } +} diff --git a/frontend/providers/template/src/pages/api/resource/delRoleBinding.ts b/frontend/providers/template/src/pages/api/resource/delRoleBinding.ts new file mode 100644 index 00000000000..bf7c4e7e712 --- /dev/null +++ b/frontend/providers/template/src/pages/api/resource/delRoleBinding.ts @@ -0,0 +1,27 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { ApiResp } from '@/services/kubernet'; +import { authSession } from '@/services/backend/auth'; +import { getK8s } from '@/services/backend/kubernetes'; +import { jsonRes } from '@/services/backend/response'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + const { instanceName } = req.query as { instanceName: string }; + if (!instanceName) { + throw new Error('deploy name is empty'); + } + + const { namespace, k8sAuth } = await getK8s({ + kubeconfig: await authSession(req.headers) + }); + + const result = await k8sAuth.deleteClusterRoleBinding(instanceName, namespace); + + jsonRes(res, { data: result }); + } catch (err: any) { + jsonRes(res, { + code: 500, + error: err + }); + } +} diff --git a/frontend/providers/template/src/pages/api/resource/delServiceAccount.ts b/frontend/providers/template/src/pages/api/resource/delServiceAccount.ts new file mode 100644 index 00000000000..e3aed812dd4 --- /dev/null +++ b/frontend/providers/template/src/pages/api/resource/delServiceAccount.ts @@ -0,0 +1,27 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { ApiResp } from '@/services/kubernet'; +import { authSession } from '@/services/backend/auth'; +import { getK8s } from '@/services/backend/kubernetes'; +import { jsonRes } from '@/services/backend/response'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + const { instanceName } = req.query as { instanceName: string }; + if (!instanceName) { + throw new Error('deploy name is empty'); + } + + const { namespace, k8sCore } = await getK8s({ + kubeconfig: await authSession(req.headers) + }); + + const result = await k8sCore.deleteNamespacedServiceAccount(instanceName, namespace); + + jsonRes(res, { data: result }); + } catch (err: any) { + jsonRes(res, { + code: 500, + error: err + }); + } +} diff --git a/frontend/providers/template/src/pages/api/app/listAppCRD.ts b/frontend/providers/template/src/pages/api/resource/deleteIssuer.ts similarity index 63% rename from frontend/providers/template/src/pages/api/app/listAppCRD.ts rename to frontend/providers/template/src/pages/api/resource/deleteIssuer.ts index adfdba1e75e..969ef737777 100644 --- a/frontend/providers/template/src/pages/api/app/listAppCRD.ts +++ b/frontend/providers/template/src/pages/api/resource/deleteIssuer.ts @@ -6,27 +6,28 @@ import { jsonRes } from '@/services/backend/response'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { - const { namespace } = req.query as { namespace: string }; - const { k8sCustomObjects } = await getK8s({ + const { instanceName } = req.query as { instanceName: string }; + const { k8sCustomObjects, namespace } = await getK8s({ kubeconfig: await authSession(req.headers) }); const customResource: CRDMeta = { - group: 'app.sealos.io', + group: 'cert-manager.io', version: 'v1', namespace: namespace, - plural: 'apps' + plural: 'issuers' }; - // 获取指定命名空间中的所有自定义资源 - const customResourceList = await k8sCustomObjects.listNamespacedCustomObject( + // 删除指定名称的自定义资源 + const reuslt = await k8sCustomObjects.deleteNamespacedCustomObject( customResource.group, customResource.version, customResource.namespace, - customResource.plural + customResource.plural, + instanceName ); - jsonRes(res, { data: customResourceList, message: 'retrieved successfully' }); + jsonRes(res, { data: reuslt }); } catch (err: any) { jsonRes(res, { code: 500, diff --git a/frontend/providers/template/src/pages/develop/index.tsx b/frontend/providers/template/src/pages/develop/index.tsx index 07fae555d03..57fb8020c1d 100644 --- a/frontend/providers/template/src/pages/develop/index.tsx +++ b/frontend/providers/template/src/pages/develop/index.tsx @@ -6,10 +6,12 @@ import { useLoading } from '@/hooks/useLoading'; import { useToast } from '@/hooks/useToast'; import { YamlItemType } from '@/types'; import { TemplateSourceType, TemplateType } from '@/types/app'; +import { EnvResponse } from '@/types/index'; import { serviceSideProps } from '@/utils/i18n'; import { developGenerateYamlList, getTemplateDataSource, + handleTemplateToInstanceYaml, parseTemplateString } from '@/utils/json-yaml'; import { getTemplateDefaultValues } from '@/utils/template'; @@ -23,9 +25,8 @@ import dayjs from 'dayjs'; import JsYaml from 'js-yaml'; import { debounce, has, isObject, mapValues } from 'lodash'; import { useTranslation } from 'next-i18next'; -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useState } from 'react'; import { useForm } from 'react-hook-form'; -import { EnvResponse } from '@/types/index'; import ErrorModal from '../deploy/components/ErrorModal'; import BreadCrumbHeader from './components/BreadCrumbHeader'; import Form from './components/Form'; @@ -41,11 +42,6 @@ export default function Develop() { const [errorMessage, setErrorMessage] = useState(''); const { title, applyBtnText, applyMessage, applySuccess, applyError } = editModeMap(false); - const detailName = useMemo( - () => yamlSource?.source?.defaults?.app_name?.value || '', - [yamlSource?.source?.defaults?.app_name?.value] - ); - const { data: platformEnvs } = useQuery(['getPlatformEnvs'], getPlatformEnv) as { data: EnvResponse; }; @@ -62,6 +58,9 @@ export default function Develop() { ) as TemplateType; const yamlList = yamlData.filter((item: any) => item.kind !== 'Template'); const dataSource = getTemplateDataSource(templateYaml, platformEnvs); + const _instanceName = dataSource?.defaults?.app_name?.value || ''; + const instanceYaml = handleTemplateToInstanceYaml(templateYaml, _instanceName); + yamlList.unshift(instanceYaml); const result: TemplateSourceType = { source: { ...dataSource, @@ -73,7 +72,10 @@ export default function Develop() { return result; }; - const generateCorrectYaml = (yamlSource: TemplateSourceType, inputsForm = {}) => { + const generateCorrectYamlList = ( + yamlSource: TemplateSourceType, + inputsForm = {} + ): YamlItemType[] => { const yamlString = yamlSource?.yamlList?.map((item) => JsYaml.dump(item)).join('---\n'); const output = mapValues(yamlSource?.source.defaults, (value) => value.value); const generateStr = parseTemplateString(yamlString, /\$\{\{\s*(.*?)\s*\}\}/g, { @@ -81,7 +83,8 @@ export default function Develop() { inputs: inputsForm, defaults: output }); - return generateStr; + const _instanceName = yamlSource?.source?.defaults?.app_name?.value || ''; + return developGenerateYamlList(generateStr, _instanceName); }; const parseTemplate = (str: string) => { @@ -89,8 +92,8 @@ export default function Develop() { const result = getYamlSource(str); const defaultInputes = getTemplateDefaultValues(result); setYamlSource(result); - const correctYaml = generateCorrectYaml(result, defaultInputes); - setYamlList(developGenerateYamlList(correctYaml, detailName)); + const correctYamlList = generateCorrectYamlList(result, defaultInputes); + setYamlList(correctYamlList); } catch (error: any) { toast({ title: 'Parsing Yaml Error', @@ -115,8 +118,8 @@ export default function Develop() { const formOnchangeDebounce = debounce((data: any) => { try { if (yamlSource) { - const correctYaml = generateCorrectYaml(yamlSource, data); - setYamlList(developGenerateYamlList(correctYaml, detailName)); + const correctYamlList = generateCorrectYamlList(yamlSource, data); + setYamlList(correctYamlList); } } catch (error) { console.log(error); diff --git a/frontend/providers/template/src/types/resource.ts b/frontend/providers/template/src/types/resource.ts index 9f97c88f1af..acc3d0dd933 100644 --- a/frontend/providers/template/src/types/resource.ts +++ b/frontend/providers/template/src/types/resource.ts @@ -11,6 +11,11 @@ export type ResourceKindType = | 'App' | 'Job' | 'Secret' + | 'Issuer' + | 'Role' + | 'RoleBinding' + | 'ServiceAccount' + | 'ConfigMap' | 'Instance'; export type OtherResourceListItemType = {