diff --git a/frontend/providers/dbprovider/src/pages/api-docs.tsx b/frontend/providers/dbprovider/src/pages/api-docs.tsx index 8438a51c7..96610af45 100644 --- a/frontend/providers/dbprovider/src/pages/api-docs.tsx +++ b/frontend/providers/dbprovider/src/pages/api-docs.tsx @@ -7,6 +7,5 @@ export default function ApiDocs() { const config = { content: document }; - return ; } diff --git a/frontend/providers/dbprovider/src/pages/api/v1/database.ts b/frontend/providers/dbprovider/src/pages/api/v1/database.ts index 2aa0647b5..3acde0c5a 100644 --- a/frontend/providers/dbprovider/src/pages/api/v1/database.ts +++ b/frontend/providers/dbprovider/src/pages/api/v1/database.ts @@ -39,12 +39,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) }); } - await createDatabase(k8s, { + const result = await createDatabase(k8s, { body: bodyParseResult.data }); - jsonRes(res, { - data: 'success create db' + return jsonRes(res, { + message: 'success create db', + data: result }); } catch (err: any) { console.log('error create db', err); diff --git a/frontend/providers/dbprovider/src/pages/api/v1/database/[databaseName].ts b/frontend/providers/dbprovider/src/pages/api/v1/database/[databaseName].ts index 1c3805511..2ab11ee16 100644 --- a/frontend/providers/dbprovider/src/pages/api/v1/database/[databaseName].ts +++ b/frontend/providers/dbprovider/src/pages/api/v1/database/[databaseName].ts @@ -3,8 +3,11 @@ import { getK8s } from '@/services/backend/kubernetes'; import { handleK8sError, jsonRes } from '@/services/backend/response'; import { NextApiRequest, NextApiResponse } from 'next'; import { ResponseCode, ResponseMessages } from '@/types/response'; -import { updateDatabaseSchemas } from '@/types/apis'; +import { createDatabaseSchemas, getDatabaseSchemas, updateDatabaseSchemas } from '@/types/apis'; import { updateDatabase } from '@/services/backend/apis/update-database'; +import { getDatabase } from '@/services/backend/apis/get-database'; +import path from 'node:path/win32'; +import { deleteDatabase } from '@/services/backend/apis/delete-database'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { const kubeconfig = await authSession(req).catch(() => null); @@ -22,18 +25,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) message: ResponseMessages[ResponseCode.UNAUTHORIZED] }); } - + const pathParamsParseResult = getDatabaseSchemas.pathParams.safeParse(req.query); + if (!pathParamsParseResult.success) { + return jsonRes(res, { + code: ResponseCode.BAD_REQUEST, + message: ResponseMessages[ResponseCode.BAD_REQUEST], + error: pathParamsParseResult.error.issues + }); + } if (req.method === 'PATCH') { try { - const pathParamsParseResult = updateDatabaseSchemas.pathParams.safeParse(req.query); - if (!pathParamsParseResult.success) { - return jsonRes(res, { - code: 400, - message: 'Invalid request params.', - error: pathParamsParseResult.error.issues - }); - } - const bodyParseResult = updateDatabaseSchemas.body.safeParse(req.body); if (!bodyParseResult.success) { return jsonRes(res, { @@ -48,17 +49,47 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) body: bodyParseResult.data }); - return jsonRes(res, { - data: `Successfully submitted ${result.opsRequests.length} change requests` - }); + return jsonRes(res, result); } catch (err) { - console.log('error create db', err); + console.log('error update db', err); jsonRes(res, handleK8sError(err)); } + } else if (req.method === 'GET') { + try { + const pathParamsParseResult = getDatabaseSchemas.pathParams.safeParse(req.query); + if (!pathParamsParseResult.success) { + return jsonRes(res, { + code: ResponseCode.BAD_REQUEST, + message: ResponseMessages[ResponseCode.BAD_REQUEST], + error: pathParamsParseResult.error.issues + }); + } + const result = await getDatabase(k8s, { + params: pathParamsParseResult.data + }); + jsonRes(res, { + data: result + }); + } catch (err) { + console.log('error get db by name', err); + jsonRes(res, handleK8sError(err)); + } + } else if (req.method === 'DELETE') { + try { + await deleteDatabase(k8s, { + params: pathParamsParseResult.data + }); + jsonRes(res, { + data: `Successfully deleted` + }); + } catch (err) { + console.log('error delete db', err); + jsonRes(res, handleK8sError(err)); + } + } else { + return jsonRes(res, { + code: 405, + message: 'Method not allowed' + }); } - - return jsonRes(res, { - code: 405, - message: 'Method not allowed' - }); } diff --git a/frontend/providers/dbprovider/src/pages/api/v1/database/list.ts b/frontend/providers/dbprovider/src/pages/api/v1/database/list.ts new file mode 100644 index 000000000..5c6405c9e --- /dev/null +++ b/frontend/providers/dbprovider/src/pages/api/v1/database/list.ts @@ -0,0 +1,36 @@ +import { authSession } from '@/services/backend/auth'; +import { getK8s } from '@/services/backend/kubernetes'; +import { handleK8sError, jsonRes } from '@/services/backend/response'; +import { NextApiRequest, NextApiResponse } from 'next'; +import { ResponseCode, ResponseMessages } from '@/types/response'; +import { updateDatabaseSchemas } from '@/types/apis'; +import { updateDatabase } from '@/services/backend/apis/update-database'; +import { KbPgClusterType } from '@/types/cluster'; +import { adaptDBListItem } from '@/utils/adapt'; +import { getDatabaseList } from '@/services/backend/apis/list-database'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + const kubeconfig = await authSession(req).catch(() => null); + if (!kubeconfig) { + return jsonRes(res, { + code: ResponseCode.UNAUTHORIZED, + message: ResponseMessages[ResponseCode.UNAUTHORIZED] + }); + } + + const k8s = await getK8s({ kubeconfig }).catch(() => null); + if (!k8s) { + return jsonRes(res, { + code: ResponseCode.UNAUTHORIZED, + message: ResponseMessages[ResponseCode.UNAUTHORIZED] + }); + } + const data = await getDatabaseList(k8s); + + return jsonRes(res, { data }); + } catch (err) { + console.log('error list db', err); + jsonRes(res, handleK8sError(err)); + } +} diff --git a/frontend/providers/dbprovider/src/pages/api/v1/database/version/list.ts b/frontend/providers/dbprovider/src/pages/api/v1/database/version/list.ts new file mode 100644 index 000000000..e17cfab8d --- /dev/null +++ b/frontend/providers/dbprovider/src/pages/api/v1/database/version/list.ts @@ -0,0 +1,76 @@ +import { authSession } from '@/services/backend/auth'; +import { getK8s, K8sApi } from '@/services/backend/kubernetes'; +import { handleK8sError, jsonRes } from '@/services/backend/response'; +import { NextApiRequest, NextApiResponse } from 'next'; +import { ResponseCode, ResponseMessages } from '@/types/response'; +import { createDatabaseSchemas, getDatabaseSchemas, updateDatabaseSchemas } from '@/types/apis'; +import { updateDatabase } from '@/services/backend/apis/update-database'; +import { getDatabase } from '@/services/backend/apis/get-database'; +import { adaptDBDetail } from '@/utils/adapt'; +import { createDatabase } from '@/services/backend/apis/create-database'; +import { dbDetailSchema, dbTypeSchema, versionListSchema } from '@/types/schemas/db'; +import path from 'node:path/win32'; +import { deleteDatabase } from '@/services/backend/apis/delete-database'; +import { DBTypeEnum } from '@/constants/db'; +import { result } from 'lodash'; +import z from 'zod'; +import { DBVersionMap } from '@/store/static'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const kc = K8sApi(); + + const k8s = (await getK8s({ kubeconfig: kc.exportConfig() }))!; + + if (req.method === 'GET') { + try { + const DBVersion: z.Infer = { + [DBTypeEnum.postgresql]: [], + [DBTypeEnum.mongodb]: [], + [DBTypeEnum.mysql]: [], + [DBTypeEnum.redis]: [], + [DBTypeEnum.kafka]: [], + [DBTypeEnum.qdrant]: [], + [DBTypeEnum.nebula]: [], + [DBTypeEnum.weaviate]: [], + [DBTypeEnum.milvus]: [], + [DBTypeEnum.pulsar]: [], + [DBTypeEnum.clickhouse]: [] + }; + + // source price + const { k8sCustomObjects } = k8s; + + const { body } = (await k8sCustomObjects.listClusterCustomObject( + 'apps.kubeblocks.io', + 'v1alpha1', + 'clusterversions' + )) as any; + + body.items.forEach((item: any) => { + const db = item?.spec?.clusterDefinitionRef as `${DBTypeEnum}`; + if ( + DBVersionMap[db] && + item?.metadata?.name && + !DBVersionMap[db].find((db) => db.id === item.metadata.name) + ) { + DBVersionMap[db].unshift(item.metadata.name); + } + }); + + jsonRes(res, { + data: DBVersionMap + }); + jsonRes(res, { + data: result + }); + } catch (err) { + console.log('error get db by name', err); + jsonRes(res, handleK8sError(err)); + } + } else { + return jsonRes(res, { + code: 405, + message: 'Method not allowed' + }); + } +} diff --git a/frontend/providers/dbprovider/src/pages/api/v1/logs/files.ts b/frontend/providers/dbprovider/src/pages/api/v1/logs/files.ts new file mode 100644 index 000000000..2649f1692 --- /dev/null +++ b/frontend/providers/dbprovider/src/pages/api/v1/logs/files.ts @@ -0,0 +1,92 @@ +import { ServiceLogConfigs } from '@/constants/log'; +import { authSession } from '@/services/backend/auth'; +import { getK8s } from '@/services/backend/kubernetes'; +import { jsonRes } from '@/services/backend/response'; +import { ApiResp } from '@/services/kubernet'; +import { SupportReconfigureDBType } from '@/types/db'; +import { LogTypeEnum } from '@/constants/log'; +import { KubeFileSystem } from '@/utils/kubeFileSystem'; +import type { NextApiRequest, NextApiResponse } from 'next'; +import { ResponseCode, ResponseMessages } from '@/types/response'; +import { logsFileSchemas } from '@/types/apis'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + const kubeconfig = await authSession(req).catch(() => null); + if (!kubeconfig) { + return jsonRes(res, { + code: ResponseCode.UNAUTHORIZED, + message: ResponseMessages[ResponseCode.UNAUTHORIZED] + }); + } + + const k8s = await getK8s({ kubeconfig }).catch(() => null); + if (!k8s) { + return jsonRes(res, { + code: ResponseCode.UNAUTHORIZED, + message: ResponseMessages[ResponseCode.UNAUTHORIZED] + }); + } + const { namespace, k8sExec, k8sCore } = k8s; + const parseResult = logsFileSchemas.query.safeParse({ + podName: req.query.podName as string, + dbType: req.query.dbType as SupportReconfigureDBType, + logType: req.query.logType as LogTypeEnum + }); + if (!parseResult.success) { + return jsonRes(res, { + code: ResponseCode.BAD_REQUEST, + message: ResponseMessages[ResponseCode.BAD_REQUEST] + }); + } + const { podName, dbType, logType } = parseResult.data; + + const kubefs = new KubeFileSystem(k8sExec); + + const logConfig = ServiceLogConfigs[dbType][logType]; + + console.log('/api/logs/getFiles', { podName, dbType, logType, logConfig }); + + if (!logConfig) { + throw new Error('Invalid log type'); + } + + let files, directories; + let lastError: any; + for (const container of logConfig.containerNames) { + try { + const result = await kubefs.ls({ + namespace, + podName, + containerName: container, + path: logConfig.path, + showHidden: false + }); + files = result.files; + directories = result.directories; + break; + } catch (error) { + lastError = error; + console.error('/api/logs/getFiles error', error); + continue; + } + } + + if (!files) { + throw new Error(lastError?.message || 'No valid log files found in any container'); + } + + const validFiles = logConfig.filter(files); + + if (!validFiles || validFiles.length === 0) { + throw new Error('No valid log files found'); + } + + jsonRes(res, { data: validFiles }); + } catch (err: any) { + jsonRes(res, { + code: 500, + error: err + }); + } +} diff --git a/frontend/providers/dbprovider/src/pages/api/v1/logs/index.ts b/frontend/providers/dbprovider/src/pages/api/v1/logs/index.ts new file mode 100644 index 000000000..f23fcf076 --- /dev/null +++ b/frontend/providers/dbprovider/src/pages/api/v1/logs/index.ts @@ -0,0 +1,74 @@ +import { DBTypeEnum } from '@/constants/db'; +import { ServiceLogConfigs } from '@/constants/log'; +import { authSession } from '@/services/backend/auth'; +import { getK8s } from '@/services/backend/kubernetes'; +import { jsonRes } from '@/services/backend/response'; +import { ApiResp } from '@/services/kubernet'; +import { SupportReconfigureDBType } from '@/types/db'; +import { LogTypeEnum } from '@/constants/log'; +import { DatabaseLogService } from '@/utils/logParsers/LogParser'; +import type { NextApiRequest, NextApiResponse } from 'next'; +import { ResponseCode, ResponseMessages } from '@/types/response'; +import { logsDataSchemas } from '@/types/apis'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + const kubeconfig = await authSession(req).catch(() => null); + if (!kubeconfig) { + return jsonRes(res, { + code: ResponseCode.UNAUTHORIZED, + message: ResponseMessages[ResponseCode.UNAUTHORIZED] + }); + } + + const k8s = await getK8s({ kubeconfig }).catch(() => null); + if (!k8s) { + return jsonRes(res, { + code: ResponseCode.UNAUTHORIZED, + message: ResponseMessages[ResponseCode.UNAUTHORIZED] + }); + } + const { namespace, k8sExec, k8sCore } = k8s; + + const parseResult = logsDataSchemas.query.safeParse({ + podName: req.query.podName as string, + dbType: req.query.dbType as SupportReconfigureDBType, + logType: req.query.logType as LogTypeEnum, + logPath: req.query.logPath as string, + page: parseInt((req.query.page as string) || '0'), + pageSize: parseInt((req.query.pageSize as string) || '10') + }); + if (!parseResult.success) { + return jsonRes(res, { + code: ResponseCode.BAD_REQUEST, + message: ResponseMessages[ResponseCode.BAD_REQUEST] + }); + } + const { podName, dbType, logType, logPath, page = 0, pageSize = 10 } = parseResult.data; + + const logConfig = ServiceLogConfigs[dbType][logType]; + + if (!logConfig) { + throw new Error('Invalid log type'); + } + + const logService = new DatabaseLogService(k8sExec, k8sCore, namespace); + + const result = await logService.readLogs({ + podName, + containerNames: logConfig.containerNames, + logPath, + page, + pageSize, + dbType: dbType as DBTypeEnum, + logType + }); + + return jsonRes(res, { data: result }); + } catch (err: any) { + jsonRes(res, { + code: 500, + error: err + }); + } +} diff --git a/frontend/providers/dbprovider/src/pages/api/v1/monitor/data.ts b/frontend/providers/dbprovider/src/pages/api/v1/monitor/data.ts new file mode 100644 index 000000000..2d283c90b --- /dev/null +++ b/frontend/providers/dbprovider/src/pages/api/v1/monitor/data.ts @@ -0,0 +1,116 @@ +import { authSession } from '@/services/backend/auth'; +import { getK8s } from '@/services/backend/kubernetes'; +import { jsonRes } from '@/services/backend/response'; +import { handleAxiosStream } from '@/services/handleStream'; +import { ApiResp } from '@/services/kubernet'; +import { AdapterChartDataType, MonitorChartDataResult, MonitorDBResult } from '@/types/monitor'; +import { ResponseCode, ResponseMessages } from '@/types/response'; +import type { NextApiRequest, NextApiResponse } from 'next'; + +const AdapterChartData: AdapterChartDataType = { + disk: (data: MonitorDBResult) => { + const temp = data?.data?.result; + let XTimePoint: number[] = []; + const desiredResult = temp.map((dataObject, index) => { + const name = dataObject.metric?.persistentvolumeclaim; + if (index === 0) { + XTimePoint = dataObject.values.map((timePoint) => timePoint[0]); + } + const data = dataObject.values.map((item) => parseFloat(item[1])); + return { name, data }; + }); + return { + xData: XTimePoint, + yData: desiredResult + }; + }, + cpu: (data: MonitorDBResult) => { + const temp = data?.data?.result; + let XTimePoint: number[] = []; + const desiredResult = temp.map((dataObject, index) => { + const name = dataObject.metric?.pod; + if (index === 0) { + XTimePoint = dataObject.values.map((timePoint) => timePoint[0]); + } + const data = dataObject.values.map((item) => parseFloat(item[1])); + return { name, data }; + }); + return { + xData: XTimePoint, + yData: desiredResult + }; + }, + memory: (data: MonitorDBResult) => { + const temp = data?.data?.result; + let XTimePoint: number[] = []; + const desiredResult = temp.map((dataObject, index) => { + const name = dataObject.metric?.pod; + if (index === 0) { + XTimePoint = dataObject.values.map((timePoint) => timePoint[0]); + } + const data = dataObject.values.map((item) => parseFloat(item[1])); + return { name, data }; + }); + return { + xData: XTimePoint, + yData: desiredResult + }; + } +}; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + const kubeconfig = await authSession(req).catch(() => null); + if (!kubeconfig) { + return jsonRes(res, { + code: ResponseCode.UNAUTHORIZED, + message: ResponseMessages[ResponseCode.UNAUTHORIZED] + }); + } + + const k8s = await getK8s({ kubeconfig }).catch(() => null); + if (!k8s) { + return jsonRes(res, { + code: ResponseCode.UNAUTHORIZED, + message: ResponseMessages[ResponseCode.UNAUTHORIZED] + }); + } + const { namespace } = k8s; + const { dbName, dbType, queryKey, start, end, step } = req.query; + const endTime = Date.now(); // 当前时间的时间戳 + const startTime = endTime - 60 * 60 * 1000; // 前向推进1个小时的时间戳 + + const params = { + query: queryKey, + app: dbName, + type: dbType, + namespace: namespace, + start: start ? Number(start) : startTime / 1000, + end: end ? Number(end) : endTime / 1000, + step: '1m' + }; + + const result: MonitorChartDataResult = await handleAxiosStream( + { + url: '/q', + params: params + }, + kubeconfig + ).then((res) => { + // @ts-ignore + return AdapterChartData[queryKey] ? AdapterChartData[queryKey](res as MonitorDBResult) : res; + }); + + jsonRes(res, { + code: 200, + data: { + result + } + }); + } catch (error) { + jsonRes(res, { + code: 500, + error: error + }); + } +} diff --git a/frontend/providers/dbprovider/src/services/backend/apis/create-database.ts b/frontend/providers/dbprovider/src/services/backend/apis/create-database.ts index b3c877eea..1dccbe3dc 100644 --- a/frontend/providers/dbprovider/src/services/backend/apis/create-database.ts +++ b/frontend/providers/dbprovider/src/services/backend/apis/create-database.ts @@ -4,17 +4,33 @@ import { z } from 'zod'; import { BackupSupportedDBTypeList } from '@/constants/db'; import { updateBackupPolicyApi } from '@/pages/api/backup/updatePolicy'; import { KbPgClusterType } from '@/types/cluster'; -import { convertBackupFormToSpec } from '@/utils/adapt'; +import { adaptDBDetail, convertBackupFormToSpec } from '@/utils/adapt'; import { json2Account, json2CreateCluster } from '@/utils/json2Yaml'; - +import { DBEditType, EditType } from '@/types/db'; +import { raw2schema } from './get-database'; +const schema2Raw = (dbForm: z.Infer): DBEditType => { + return { + dbType: dbForm.type, + dbVersion: dbForm.version, + dbName: dbForm.name, + replicas: dbForm.resource.replicas, + cpu: parseFloat(dbForm.resource.cpu), + memory: parseFloat(dbForm.resource.memory), + storage: parseFloat(dbForm.resource.storage), + labels: {}, + terminationPolicy: dbForm.terminationPolicy, + autoBackup: dbForm.autoBackup + }; +}; export async function createDatabase( k8s: Awaited>, request: { body: z.infer; } ) { - const account = json2Account(request.body.dbForm); - const cluster = json2CreateCluster(request.body.dbForm, request.body.backupInfo, { + const rawDbForm = schema2Raw(request.body); + const account = json2Account(rawDbForm); + const cluster = json2CreateCluster(rawDbForm, undefined, { storageClassName: process.env.STORAGE_CLASSNAME }); @@ -24,34 +40,32 @@ export async function createDatabase( 'v1alpha1', k8s.namespace, 'clusters', - request.body.dbForm.dbName + rawDbForm.dbName )) as { body: KbPgClusterType; }; const dbUid = body.metadata.uid; - const updateAccountYaml = json2Account(request.body.dbForm, dbUid); + const updateAccountYaml = json2Account(rawDbForm, dbUid); await k8s.applyYamlList([updateAccountYaml], 'replace'); try { - if ( - BackupSupportedDBTypeList.includes(request.body.dbForm.dbType) && - request.body.dbForm?.autoBackup - ) { + if (BackupSupportedDBTypeList.includes(rawDbForm.dbType) && rawDbForm?.autoBackup) { const autoBackup = convertBackupFormToSpec({ - autoBackup: request.body.dbForm?.autoBackup, - dbType: request.body.dbForm.dbType + autoBackup: rawDbForm?.autoBackup, + dbType: rawDbForm.dbType }); await updateBackupPolicyApi({ - dbName: request.body.dbForm.dbName, - dbType: request.body.dbForm.dbType, + dbName: rawDbForm.dbName, + dbType: rawDbForm.dbType, autoBackup, k8sCustomObjects: k8s.k8sCustomObjects, namespace: k8s.namespace }); } + return raw2schema(adaptDBDetail(body)); } catch (err: any) { // local env will fail to update backup policy if (process.env.NODE_ENV === 'production') { diff --git a/frontend/providers/dbprovider/src/services/backend/apis/delete-database.ts b/frontend/providers/dbprovider/src/services/backend/apis/delete-database.ts new file mode 100644 index 000000000..c10292b34 --- /dev/null +++ b/frontend/providers/dbprovider/src/services/backend/apis/delete-database.ts @@ -0,0 +1,100 @@ +import { getDatabaseSchemas, updateDatabaseSchemas } from '@/types/apis'; +import { getK8s } from '../kubernetes'; +import { z } from 'zod'; +import { delMigrateByName } from '@/pages/api/migrate/delete'; +import { GetJobByName, DeleteJobByName } from '@/pages/api/migrate/delJobByName'; +import { CloudMigraionLabel } from '@/constants/db'; +import { getMigrateList } from '@/pages/api/migrate/list'; +export async function deleteDatabase( + k8s: Awaited>, + request: { + params: z.infer; + } +) { + const name = request.params.databaseName; + // get job and delete + const { k8sBatch, namespace, k8sCustomObjects, k8sCore, k8sAuth } = k8s; + const jobsToDelete = ( + await k8sBatch.listNamespacedJob( + namespace, + undefined, + undefined, + undefined, + undefined, + `${CloudMigraionLabel}=${name}` + ) + ).body.items; + + for (const job of jobsToDelete) { + if (job?.metadata?.name) { + await k8sBatch + .deleteNamespacedJob( + job.metadata.name, + namespace, + undefined, + undefined, + undefined, + undefined, + 'Background' + ) + .catch((error) => { + console.error(`Error deleting job`, error); + }); + } + } + + // get migrates and delete + /* Retrieve migrations */ + const { body } = (await k8sCustomObjects.listNamespacedCustomObject( + 'datamigration.apecloud.io', + 'v1alpha1', + namespace, + 'migrationtasks', + undefined, //pretty + undefined, //allowWatchBookmarks + undefined, //_continue + undefined, //fieldSelector + `${CloudMigraionLabel}=${name}` //labelSelector + )) as { body: { items: any[] } }; + const migrates = body?.items || []; + + /* Delete each migrationTask custom object */ + for (const migrate of migrates) { + if (migrate?.metadata?.name) { + await k8sCustomObjects + .deleteNamespacedCustomObject( + 'datamigration.apecloud.io', + 'v1alpha1', + namespace, + 'migrationtasks', + migrate.metadata.name + ) + .catch((error) => { + console.error('Failed to delete migrationTask', error); + }); + } + } + + // del service + await k8sCore.deleteNamespacedService(`${name}-export`, namespace).catch((err) => { + if (err?.body?.code !== 404) { + throw new Error(err?.message || 'Delete DB Service Export Error'); + } + }); + + // del role + await Promise.all([ + k8sAuth.deleteNamespacedRole(name, namespace), + k8sAuth.deleteNamespacedRoleBinding(name, namespace), + k8sCore.deleteNamespacedServiceAccount(name, namespace) + ]).catch((err) => console.log(err, 'delete role err')); + + // delete cluster + const result = await k8sCustomObjects.deleteNamespacedCustomObject( + 'apps.kubeblocks.io', + 'v1alpha1', + namespace, + 'clusters', + name + ); +} diff --git a/frontend/providers/dbprovider/src/services/backend/apis/get-database.ts b/frontend/providers/dbprovider/src/services/backend/apis/get-database.ts new file mode 100644 index 000000000..f2c7275a2 --- /dev/null +++ b/frontend/providers/dbprovider/src/services/backend/apis/get-database.ts @@ -0,0 +1,56 @@ +import { updateDatabaseSchemas } from '@/types/apis'; +import { getK8s } from '../kubernetes'; +import { z } from 'zod'; +import { KbPgClusterType } from '@/types/cluster'; +import { adaptDBDetail } from '@/utils/adapt'; +import { dbDetailSchema } from '@/types/schemas/db'; +import { DBDetailType } from '@/types/db'; +export const raw2schema = (raw: DBDetailType): z.Infer => { + const dbEditSchemaFromRaw: z.Infer = { + terminationPolicy: raw.terminationPolicy, + name: raw.dbName, + type: raw.dbType, + version: raw.dbVersion, + resource: { + cpu: raw.cpu.toString(), + memory: raw.memory.toString(), + storage: raw.storage.toString(), + replicas: raw.replicas + }, + id: raw.id, + status: raw.status.value, // 假设 status 是一个对象,其中的 value 是状态字符串 + createTime: raw.createTime, + totalResource: { + cpu: raw.totalCpu.toString(), + memory: raw.totalMemory.toString(), + storage: raw.totalStorage.toString() + }, + isDiskSpaceOverflow: raw.isDiskSpaceOverflow, + source: { + hasSource: raw.source.hasSource, + sourceName: raw.source.sourceName, + sourceType: raw.source.sourceType + }, + autoBackup: raw.autoBackup + }; + + return dbEditSchemaFromRaw; +}; +export async function getDatabase( + k8s: Awaited>, + request: { + params: z.infer; + } +) { + const { k8sBatch, namespace, k8sCustomObjects, k8sCore, k8sAuth } = k8s; + const { body } = (await k8sCustomObjects.getNamespacedCustomObject( + 'apps.kubeblocks.io', + 'v1alpha1', + namespace, + 'clusters', + request.params.databaseName + )) as { + body: KbPgClusterType; + }; + return raw2schema(adaptDBDetail(body)); +} diff --git a/frontend/providers/dbprovider/src/services/backend/apis/list-database.ts b/frontend/providers/dbprovider/src/services/backend/apis/list-database.ts new file mode 100644 index 000000000..94e10cc36 --- /dev/null +++ b/frontend/providers/dbprovider/src/services/backend/apis/list-database.ts @@ -0,0 +1,59 @@ +import { updateDatabaseSchemas } from '@/types/apis'; +import { getK8s } from '../kubernetes'; +import { z } from 'zod'; +import { KbPgClusterType } from '@/types/cluster'; +import { adaptDBDetail, adaptDBListItem } from '@/utils/adapt'; +import { dbDetailSchema, dblistItemSchema } from '@/types/schemas/db'; +import { DBDetailType, DBListItemType } from '@/types/db'; +const raw2schema = (raw: DBDetailType): z.Infer => { + const autoBackup = raw.autoBackup; + const dbEditSchemaFromRaw: z.Infer = { + terminationPolicy: raw.terminationPolicy, + name: raw.dbName, + type: raw.dbType, + version: raw.dbVersion, + resource: { + cpu: raw.cpu.toString(), + memory: raw.memory.toString(), + storage: raw.storage.toString(), + replicas: raw.replicas + }, + id: raw.id, + status: raw.status.value, // 假设 status 是一个对象,其中的 value 是状态字符串 + createTime: raw.createTime, + totalResource: { + cpu: raw.totalCpu.toString(), + memory: raw.totalMemory.toString(), + storage: raw.totalStorage.toString() + }, + isDiskSpaceOverflow: raw.isDiskSpaceOverflow, + source: { + hasSource: raw.source.hasSource, + sourceName: raw.source.sourceName, + sourceType: raw.source.sourceType + }, + autoBackup + }; + + return dbEditSchemaFromRaw; +}; +export async function getDatabaseList(k8s: Awaited>) { + const { k8sBatch, namespace, k8sCustomObjects, k8sCore, k8sAuth } = k8s; + const response = (await k8sCustomObjects.listNamespacedCustomObject( + 'apps.kubeblocks.io', + 'v1alpha1', + namespace, + 'clusters', + undefined, + undefined, + undefined, + undefined, + `clusterdefinition.kubeblocks.io/name` + )) as { + body: { + items: KbPgClusterType[]; + }; + }; + const data = (response?.body?.items || []).map(adaptDBDetail).map(raw2schema); + return data; +} diff --git a/frontend/providers/dbprovider/src/services/backend/apis/pause-database.ts b/frontend/providers/dbprovider/src/services/backend/apis/pause-database.ts index 9a7520279..279a1ccf3 100644 --- a/frontend/providers/dbprovider/src/services/backend/apis/pause-database.ts +++ b/frontend/providers/dbprovider/src/services/backend/apis/pause-database.ts @@ -5,6 +5,8 @@ import { getCluster } from '@/pages/api/getDBByName'; import { json2BasicOps } from '@/utils/json2Yaml'; import { PatchUtils } from '@kubernetes/client-node'; import { NextApiRequest } from 'next'; +import { raw2schema } from './get-database'; +import { adaptDBDetail } from '@/utils/adapt'; export async function pauseDatabase( k8s: Awaited>, @@ -62,5 +64,5 @@ export async function pauseDatabase( }); await k8s.applyYamlList([yaml], 'update'); - return { data: 'pause success' }; + return { data: raw2schema(adaptDBDetail(body)) }; } diff --git a/frontend/providers/dbprovider/src/services/backend/apis/start-database.ts b/frontend/providers/dbprovider/src/services/backend/apis/start-database.ts index 73dc28b60..0db5d4faf 100644 --- a/frontend/providers/dbprovider/src/services/backend/apis/start-database.ts +++ b/frontend/providers/dbprovider/src/services/backend/apis/start-database.ts @@ -5,6 +5,8 @@ import { getCluster } from '@/pages/api/getDBByName'; import { json2BasicOps } from '@/utils/json2Yaml'; import { PatchUtils } from '@kubernetes/client-node'; import { NextApiRequest } from 'next'; +import { adaptDBDetail } from '@/utils/adapt'; +import { raw2schema } from './get-database'; export async function startDatabase( k8s: Awaited>, @@ -47,5 +49,5 @@ export async function startDatabase( }); await k8s.applyYamlList([yaml], 'update'); - return { data: 'start success' }; + return { data: raw2schema(adaptDBDetail(body)) }; } diff --git a/frontend/providers/dbprovider/src/services/backend/apis/update-database.ts b/frontend/providers/dbprovider/src/services/backend/apis/update-database.ts index c614e2102..b7e87c270 100644 --- a/frontend/providers/dbprovider/src/services/backend/apis/update-database.ts +++ b/frontend/providers/dbprovider/src/services/backend/apis/update-database.ts @@ -7,6 +7,7 @@ import { json2ResourceOps } from '@/utils/json2Yaml'; import { BackupSupportedDBTypeList } from '@/constants/db'; import { updateBackupPolicyApi } from '@/pages/api/backup/updatePolicy'; import { updateTerminationPolicyApi } from '@/pages/api/createDB'; +import { raw2schema } from './get-database'; export async function updateDatabase( k8s: Awaited>, @@ -79,8 +80,16 @@ export async function updateDatabase( }); } } - + const { body: body2 } = (await k8s.k8sCustomObjects.getNamespacedCustomObject( + 'apps.kubeblocks.io', + 'v1alpha1', + k8s.namespace, + 'clusters', + request.params.databaseName + )) as { + body: KbPgClusterType; + }; return { - opsRequests + data: raw2schema(adaptDBDetail(body2)) }; } diff --git a/frontend/providers/dbprovider/src/types/apis/create-database.ts b/frontend/providers/dbprovider/src/types/apis/create-database.ts deleted file mode 100644 index bc5b76a23..000000000 --- a/frontend/providers/dbprovider/src/types/apis/create-database.ts +++ /dev/null @@ -1,12 +0,0 @@ -import * as z from 'zod'; -import { backupItemSchema, dbEditSchema } from '../schemas/db'; - -export const body = z.object({ - dbForm: dbEditSchema, - backupInfo: z.optional(backupItemSchema) -}); - -export const response = z.object({ - code: z.number(), - message: z.string() -}); diff --git a/frontend/providers/dbprovider/src/types/apis/database/create-database.ts b/frontend/providers/dbprovider/src/types/apis/database/create-database.ts new file mode 100644 index 000000000..d7ac0b766 --- /dev/null +++ b/frontend/providers/dbprovider/src/types/apis/database/create-database.ts @@ -0,0 +1,10 @@ +import * as z from 'zod'; +import { dbDetailSchema, dbEditSchema } from '../../schemas/db'; + +export const body = dbEditSchema; + +export const response = z.object({ + code: z.number(), + message: z.string(), + data: dbDetailSchema +}); diff --git a/frontend/providers/dbprovider/src/types/apis/pause-database.ts b/frontend/providers/dbprovider/src/types/apis/database/delete-database.ts similarity index 100% rename from frontend/providers/dbprovider/src/types/apis/pause-database.ts rename to frontend/providers/dbprovider/src/types/apis/database/delete-database.ts diff --git a/frontend/providers/dbprovider/src/types/apis/database/get-database.ts b/frontend/providers/dbprovider/src/types/apis/database/get-database.ts new file mode 100644 index 000000000..9dd51da07 --- /dev/null +++ b/frontend/providers/dbprovider/src/types/apis/database/get-database.ts @@ -0,0 +1,12 @@ +import * as z from 'zod'; +import { dbDetailSchema, dbEditSchema } from '../../schemas/db'; + +export const pathParams = z.object({ + databaseName: z.string() +}); + +export const response = z.object({ + code: z.number(), + message: z.string(), + data: dbDetailSchema +}); diff --git a/frontend/providers/dbprovider/src/types/apis/database/list-database.ts b/frontend/providers/dbprovider/src/types/apis/database/list-database.ts new file mode 100644 index 000000000..f34ecf43f --- /dev/null +++ b/frontend/providers/dbprovider/src/types/apis/database/list-database.ts @@ -0,0 +1,10 @@ +import * as z from 'zod'; +import { dbDetailSchema, dbEditSchema } from '../../schemas/db'; + +export const pathParams = z.object({}); + +export const response = z.object({ + code: z.number(), + message: z.string(), + data: z.array(dbDetailSchema) +}); diff --git a/frontend/providers/dbprovider/src/types/apis/start-database.ts b/frontend/providers/dbprovider/src/types/apis/database/pause-database.ts similarity index 100% rename from frontend/providers/dbprovider/src/types/apis/start-database.ts rename to frontend/providers/dbprovider/src/types/apis/database/pause-database.ts diff --git a/frontend/providers/dbprovider/src/types/apis/database/start-database.ts b/frontend/providers/dbprovider/src/types/apis/database/start-database.ts new file mode 100644 index 000000000..72a3637ca --- /dev/null +++ b/frontend/providers/dbprovider/src/types/apis/database/start-database.ts @@ -0,0 +1,10 @@ +import * as z from 'zod'; + +export const pathParams = z.object({ + databaseName: z.string() +}); + +export const response = z.object({ + code: z.number(), + message: z.string() +}); diff --git a/frontend/providers/dbprovider/src/types/apis/update-database.ts b/frontend/providers/dbprovider/src/types/apis/database/update-database.ts similarity index 63% rename from frontend/providers/dbprovider/src/types/apis/update-database.ts rename to frontend/providers/dbprovider/src/types/apis/database/update-database.ts index 84f4a607f..eee8d1679 100644 --- a/frontend/providers/dbprovider/src/types/apis/update-database.ts +++ b/frontend/providers/dbprovider/src/types/apis/database/update-database.ts @@ -1,5 +1,5 @@ import * as z from 'zod'; -import { dbEditSchema } from '../schemas/db'; +import { dbEditSchema } from '../../schemas/db'; export const pathParams = z.object({ databaseName: z.string() @@ -8,11 +8,7 @@ export const pathParams = z.object({ export const body = z.object({ dbForm: dbEditSchema .pick({ - cpu: true, - memory: true, - replicas: true, - storage: true, - terminationPolicy: true + resource: true }) .partial() }); diff --git a/frontend/providers/dbprovider/src/types/apis/database/version/list.ts b/frontend/providers/dbprovider/src/types/apis/database/version/list.ts new file mode 100644 index 000000000..a398ea71c --- /dev/null +++ b/frontend/providers/dbprovider/src/types/apis/database/version/list.ts @@ -0,0 +1,8 @@ +import { versionListSchema } from '@/types/schemas/db'; +import * as z from 'zod'; + +export const response = z.object({ + code: z.number(), + message: z.string(), + data: versionListSchema +}); diff --git a/frontend/providers/dbprovider/src/types/apis/index.ts b/frontend/providers/dbprovider/src/types/apis/index.ts index e0bb88c52..10f656891 100644 --- a/frontend/providers/dbprovider/src/types/apis/index.ts +++ b/frontend/providers/dbprovider/src/types/apis/index.ts @@ -1,16 +1,30 @@ import { createDocument } from 'zod-openapi'; -import * as createDatabaseSchemas from './create-database'; -export * as createDatabaseSchemas from './create-database'; +import * as createDatabaseSchemas from './database/create-database'; +export * as createDatabaseSchemas from './database/create-database'; -import * as updateDatabaseSchemas from './update-database'; -export * as updateDatabaseSchemas from './update-database'; +import * as updateDatabaseSchemas from './database/update-database'; +export * as updateDatabaseSchemas from './database/update-database'; -import * as startDatabaseSchemas from './start-database'; -export * as startDatabaseSchemas from './start-database'; +import * as startDatabaseSchemas from './database/start-database'; +export * as startDatabaseSchemas from './database/start-database'; -import * as pauseDatabaseSchemas from './pause-database'; -export * as pauseDatabaseSchemas from './pause-database'; +import * as pauseDatabaseSchemas from './database/pause-database'; +export * as pauseDatabaseSchemas from './database/pause-database'; + +import * as getDatabaseSchemas from './database/get-database'; +export * as getDatabaseSchemas from './database/get-database'; + +import * as deleteDatabaseSchemas from './database/delete-database'; +export * as deleteDatabaseSchemas from './database/delete-database'; + +import * as logsDataSchemas from './logs/data'; +export * as logsDataSchemas from './logs/data'; + +import * as logsFileSchemas from './logs/file'; +export * as logsFileSchemas from './logs/file'; + +import * as databaseVersionListSchemas from './database/version/list'; export const document = createDocument({ openapi: '3.1.0', @@ -37,8 +51,12 @@ export const document = createDocument({ '/database': { post: { summary: 'Create Database', - description: 'Create a database.', - security: [{ KubeconfigAuth: [] }], + description: 'Create a new database.', + security: [ + { + KubeconfigAuth: [] + } + ], requestBody: { content: { 'application/json': { @@ -48,12 +66,15 @@ export const document = createDocument({ }, responses: { '200': { - description: 'OK', + description: 'Created', content: { 'application/json': { schema: createDatabaseSchemas.response } } + }, + '400': { + description: 'Bad Request' } } } @@ -81,6 +102,55 @@ export const document = createDocument({ schema: updateDatabaseSchemas.response } } + }, + '400': { + description: 'Bad Request' + }, + '404': { + description: 'Database not found' + } + } + }, + get: { + summary: 'Get Database', + description: 'Get a database by name.', + security: [ + { + KubeconfigAuth: [] + } + ], + requestParams: { + path: getDatabaseSchemas.pathParams + }, + responses: { + '200': { + description: 'OK', + content: { + 'application/json': { + schema: getDatabaseSchemas.response + } + } + }, + '404': { + description: 'Database not found' + } + } + }, + delete: { + summary: 'Delete a Database', + description: 'Delete a database by name.', + security: [{ KubeconfigAuth: [] }], + responses: { + '200': { + description: 'OK', + content: { + 'application/json': { + schema: deleteDatabaseSchemas.response + } + } + }, + '404': { + description: 'Database not found' } } } @@ -124,6 +194,63 @@ export const document = createDocument({ } } } + }, + '/database/version/list': { + get: { + summary: 'Get Database Versions', + description: 'Get database versions.', + security: [{ KubeconfigAuth: [] }], + responses: { + '200': { + description: 'OK', + content: { + 'application/json': { + schema: databaseVersionListSchemas.response + } + } + } + } + } + }, + '/logs/data': { + get: { + summary: 'Get Logs Files', + description: 'Get logs files.', + security: [{ KubeconfigAuth: [] }], + requestParams: { + query: logsDataSchemas.query + }, + responses: { + '200': { + description: 'OK', + content: { + 'application/json': { + schema: logsDataSchemas.response + } + } + } + } + } + }, + '/logs/files': { + get: { + summary: 'Get Logs File', + description: 'Get logs file.', + security: [{ KubeconfigAuth: [] }], + requestParams: { + query: logsFileSchemas.query + }, + responses: { + '200': { + description: 'OK', + content: { + 'application/json': { + schema: logsFileSchemas.response + } + } + } + } + } } } }); diff --git a/frontend/providers/dbprovider/src/types/apis/logs/data.ts b/frontend/providers/dbprovider/src/types/apis/logs/data.ts new file mode 100644 index 000000000..ebe2c1acc --- /dev/null +++ b/frontend/providers/dbprovider/src/types/apis/logs/data.ts @@ -0,0 +1,21 @@ +import { z } from 'zod'; +import { LogTypeEnum } from '@/constants/log'; +import { DBTypeEnum } from '@/constants/db'; +import { logResultSchema } from '@/types/schemas/logs'; + +export const query = z.object({ + podName: z.string().nonempty('podName is required'), + dbType: z + .enum(DBTypeEnum) + .extract(['mysql', DBTypeEnum.mongodb, DBTypeEnum.redis, DBTypeEnum.postgresql]), + logType: z.enum(LogTypeEnum), + logPath: z.string().nonempty('logPath is required'), + page: z.number().int().min(1).optional(), + pageSize: z.number().int().min(1).optional() +}); + +export const response = z.object({ + code: z.number(), + message: z.string(), + data: logResultSchema +}); diff --git a/frontend/providers/dbprovider/src/types/apis/logs/file.ts b/frontend/providers/dbprovider/src/types/apis/logs/file.ts new file mode 100644 index 000000000..a51a234c0 --- /dev/null +++ b/frontend/providers/dbprovider/src/types/apis/logs/file.ts @@ -0,0 +1,18 @@ +import { z } from 'zod'; +import { LogTypeEnum } from '@/constants/log'; +import { DBTypeEnum } from '@/constants/db'; +import { logFileItemSchema, logResultSchema } from '@/types/schemas/logs'; + +export const query = z.object({ + podName: z.string().nonempty('podName is required'), + dbType: z + .enum(DBTypeEnum) + .extract(['mysql', DBTypeEnum.mongodb, DBTypeEnum.redis, DBTypeEnum.postgresql]), + logType: z.enum(LogTypeEnum) +}); + +export const response = z.object({ + code: z.number(), + message: z.string(), + data: z.array(logFileItemSchema) +}); diff --git a/frontend/providers/dbprovider/src/types/schemas/backup.ts b/frontend/providers/dbprovider/src/types/schemas/backup.ts index 47c544ef2..c8c56bcfd 100644 --- a/frontend/providers/dbprovider/src/types/schemas/backup.ts +++ b/frontend/providers/dbprovider/src/types/schemas/backup.ts @@ -1,3 +1,4 @@ +import { BackupTypeEnum, BackupStatusEnum } from '@/constants/backup'; import * as z from 'zod'; export const autoBackupTypeSchema = z.union([ @@ -15,3 +16,39 @@ export const autoBackupFormSchema = z.object({ saveTime: z.number(), saveType: z.string() }); + +export const backupTypeEnumSchema = z.enum([ + BackupTypeEnum.manual, + BackupTypeEnum.auto, + BackupTypeEnum.UnKnow +]); + +export const backupStatusEnumSchema = z.enum([ + BackupStatusEnum.Completed, + BackupStatusEnum.InProgress, + BackupStatusEnum.Failed, + BackupStatusEnum.UnKnow, + BackupStatusEnum.Running, + BackupStatusEnum.Deleting +]); + +// export const backupStatusMapSchema = z.object({ +// label: z.string(), +// value: backupStatusEnumSchema +// }); +export const backupBaseSchema = z.object({ + name: z.string(), + namespace: z.string(), + connectionPassword: z.string() +}); + +export const backupInfoSchema = z + .object({ + id: z.string(), + // remark: z.string(), + status: backupStatusEnumSchema, + startTime: z.date(), + // failureReason: z.string().optional(), + type: backupTypeEnumSchema + }) + .and(backupBaseSchema); diff --git a/frontend/providers/dbprovider/src/types/schemas/db.ts b/frontend/providers/dbprovider/src/types/schemas/db.ts index bae803989..815b33048 100644 --- a/frontend/providers/dbprovider/src/types/schemas/db.ts +++ b/frontend/providers/dbprovider/src/types/schemas/db.ts @@ -1,71 +1,89 @@ import { BackupStatusEnum, BackupTypeEnum } from '@/constants/backup'; import { DBTypeEnum } from '@/constants/db'; import * as z from 'zod'; -import { autoBackupFormSchema } from './backup'; +import { autoBackupFormSchema, backupInfoSchema } from './backup'; export const dbTypeSchema = z.enum([ - DBTypeEnum.postgresql, - DBTypeEnum.mongodb, - DBTypeEnum.mysql, - DBTypeEnum.redis, - DBTypeEnum.kafka, - DBTypeEnum.qdrant, - DBTypeEnum.nebula, - DBTypeEnum.weaviate, - DBTypeEnum.milvus, - DBTypeEnum.pulsar, - DBTypeEnum.clickhouse + 'postgresql', + 'mongodb', + 'apecloud-mysql', + 'redis', + 'kafka', + 'qdrant', + 'nebula', + 'weaviate', + 'milvus', + 'pulsar', + 'clickhouse' ]); - -export const kubeBlockClusterTerminationPolicySchema = z.union([ - z.literal('Delete'), - z.literal('WipeOut') -]); - -export const backupTypeEnumSchema = z.enum([ - BackupTypeEnum.manual, - BackupTypeEnum.auto, - BackupTypeEnum.UnKnow -]); - -export const backupStatusEnumSchema = z.enum([ - BackupStatusEnum.Completed, - BackupStatusEnum.InProgress, - BackupStatusEnum.Failed, - BackupStatusEnum.UnKnow, - BackupStatusEnum.Running, - BackupStatusEnum.Deleting -]); - -export const backupStatusMapSchema = z.object({ - label: z.string(), - value: backupStatusEnumSchema, - color: z.string() +export const kubeBlockClusterTerminationPolicySchema = z.enum(['Delete', 'WipeOut']); +export const baseResourceSchema = z.object({ + cpu: z.string(), + memory: z.string(), + storage: z.string() }); - -export const backupItemSchema = z.object({ - id: z.string(), - name: z.string(), - remark: z.string(), - status: backupStatusMapSchema, - startTime: z.date(), - failureReason: z.string().optional(), - type: backupTypeEnumSchema, - namespace: z.string(), - connectionPassword: z.string(), - dbName: z.string(), - dbType: z.string() -}); - +export const allResourceSchema = baseResourceSchema.and( + z.object({ + replicas: z.number().min(1).max(3) + }) +); export const dbEditSchema = z.object({ - dbType: dbTypeSchema, - dbVersion: z.string(), - dbName: z.string(), - replicas: z.number(), - cpu: z.number(), - memory: z.number(), - storage: z.number(), - labels: z.record(z.string(), z.string()), terminationPolicy: kubeBlockClusterTerminationPolicySchema, - autoBackup: z.optional(autoBackupFormSchema) + name: z.string(), + type: dbTypeSchema, + version: z.string(), + resource: allResourceSchema, + autoBackup: autoBackupFormSchema.optional() }); +// export const dbConditionItemSchema = z.object({ +// lastTransitionTime: z.string(), +// message: z.string(), +// observedGeneration: z.number(), +// reason: z.string(), +// status: z.enum(['True', 'False']), +// type: z.string() +// }); +export const dbSourceSchema = z.object({ + hasSource: z.boolean(), + sourceName: z.string(), + sourceType: z.enum(['app_store', 'sealaf']) +}); +export const dbStatusSchema = z.enum([ + 'Creating', + 'Starting', + 'Stopping', + 'Stopped', + 'Running', + 'Updating', + 'SpecUpdating', + 'Rebooting', + 'Upgrade', + 'VerticalScaling', + 'VolumeExpanding', + 'Failed', + 'UnKnow', + 'Deleting' +]); +export const dbDetailSchema = dbEditSchema.and( + z.object({ + id: z.string(), + status: dbStatusSchema, + createTime: z.string(), + totalResource: baseResourceSchema, + isDiskSpaceOverflow: z.boolean(), + source: dbSourceSchema + }) +); +export const dblistItemSchema = dbEditSchema.and( + z.object({ + id: z.string(), + status: dbStatusSchema, + createTime: z.string(), + totalResource: baseResourceSchema, + isDiskSpaceOverflow: z.boolean(), + source: dbSourceSchema + // autoBackup: autoBackupFormSchema.optional() + }) +); + +export const versionListSchema = z.record(dbTypeSchema, z.array(z.string())); diff --git a/frontend/providers/dbprovider/src/types/schemas/logs.ts b/frontend/providers/dbprovider/src/types/schemas/logs.ts new file mode 100644 index 000000000..f6a8efee0 --- /dev/null +++ b/frontend/providers/dbprovider/src/types/schemas/logs.ts @@ -0,0 +1,33 @@ +import { z } from 'zod'; + +const baseLogEntrySchema = z.object({ + timestamp: z.string(), + level: z.string(), + content: z.string() +}); + +export const logResultSchema = z.object({ + logs: z.array(baseLogEntrySchema), + metadata: z.object({ + total: z.number(), + page: z.number(), + pageSize: z.number(), + processingTime: z.string(), + hasMore: z.boolean() + }) +}); + +export const logFileItemSchema = z.object({ + name: z.string(), + path: z.string(), + dir: z.string(), + kind: z.string(), + attr: z.string(), + hardLinks: z.number(), + owner: z.string(), + group: z.string(), + size: z.number(), + updateTime: z.date(), + linkTo: z.string().optional(), + processed: z.boolean().optional() +}); diff --git a/frontend/providers/dbprovider/src/types/schemas/monitor.ts b/frontend/providers/dbprovider/src/types/schemas/monitor.ts new file mode 100644 index 000000000..c075be42b --- /dev/null +++ b/frontend/providers/dbprovider/src/types/schemas/monitor.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; + +export const MonitorChartDataResultSchema = z.object({ + xData: z.array(z.number()), + yData: z.array( + z.object({ + name: z.string().optional(), + data: z.array(z.number()) + }) + ) +}); diff --git a/frontend/providers/dbprovider/src/utils/adapt.ts b/frontend/providers/dbprovider/src/utils/adapt.ts index ad81cf913..c8196cdf9 100644 --- a/frontend/providers/dbprovider/src/utils/adapt.ts +++ b/frontend/providers/dbprovider/src/utils/adapt.ts @@ -44,6 +44,8 @@ import { has } from 'lodash'; dayjs.extend(utc); dayjs.extend(timezone); import type { BackupItemType } from '../types/db'; +import z from 'zod'; +import { dbDetailSchema, dbEditSchema, dbTypeSchema } from '@/types/schemas/db'; export const getDBSource = ( db: KbPgClusterType diff --git a/frontend/providers/dbprovider/src/utils/json2Yaml.ts b/frontend/providers/dbprovider/src/utils/json2Yaml.ts index d311ae2e1..f958684db 100644 --- a/frontend/providers/dbprovider/src/utils/json2Yaml.ts +++ b/frontend/providers/dbprovider/src/utils/json2Yaml.ts @@ -27,6 +27,8 @@ import { V1StatefulSet } from '@kubernetes/client-node'; import { customAlphabet } from 'nanoid'; import { SwitchMsData } from '@/pages/api/pod/switchPodMs'; import { distributeResources } from './database'; +import z from 'zod'; +import { backupBaseSchema } from '@/types/schemas/backup'; const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 5); @@ -40,7 +42,7 @@ const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 5); */ export const json2CreateCluster = ( rawData: Partial = {}, - backupInfo?: BackupItemType, + backupInfo?: z.Infer, options?: { storageClassName?: string; }