feat: dbprovider api (#5820)

This commit is contained in:
xudaotutou
2025-08-21 14:25:06 +08:00
committed by GitHub
parent 011f2cf2f6
commit 441cf31d69
33 changed files with 1103 additions and 133 deletions
@@ -7,6 +7,5 @@ export default function ApiDocs() {
const config = {
content: document
};
return <ApiReferenceReact configuration={config} />;
}
@@ -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);
@@ -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'
});
}
@@ -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));
}
}
@@ -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<typeof versionListSchema> = {
[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'
});
}
}
@@ -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<ApiResp>) {
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
});
}
}
@@ -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<ApiResp>) {
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
});
}
}
@@ -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<ApiResp>) {
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
});
}
}
@@ -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<typeof createDatabaseSchemas.body>): 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<ReturnType<typeof getK8s>>,
request: {
body: z.infer<typeof createDatabaseSchemas.body>;
}
) {
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') {
@@ -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<ReturnType<typeof getK8s>>,
request: {
params: z.infer<typeof getDatabaseSchemas.pathParams>;
}
) {
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
);
}
@@ -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<typeof dbDetailSchema> => {
const dbEditSchemaFromRaw: z.Infer<typeof dbDetailSchema> = {
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<ReturnType<typeof getK8s>>,
request: {
params: z.infer<typeof updateDatabaseSchemas.pathParams>;
}
) {
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));
}
@@ -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<typeof dblistItemSchema> => {
const autoBackup = raw.autoBackup;
const dbEditSchemaFromRaw: z.Infer<typeof dblistItemSchema> = {
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<ReturnType<typeof getK8s>>) {
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;
}
@@ -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<ReturnType<typeof getK8s>>,
@@ -62,5 +64,5 @@ export async function pauseDatabase(
});
await k8s.applyYamlList([yaml], 'update');
return { data: 'pause success' };
return { data: raw2schema(adaptDBDetail(body)) };
}
@@ -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<ReturnType<typeof getK8s>>,
@@ -47,5 +49,5 @@ export async function startDatabase(
});
await k8s.applyYamlList([yaml], 'update');
return { data: 'start success' };
return { data: raw2schema(adaptDBDetail(body)) };
}
@@ -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<ReturnType<typeof getK8s>>,
@@ -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))
};
}
@@ -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()
});
@@ -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
});
@@ -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
});
@@ -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)
});
@@ -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()
});
@@ -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()
});
@@ -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
});
@@ -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
}
}
}
}
}
}
}
});
@@ -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
});
@@ -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)
});
@@ -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);
@@ -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()));
@@ -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()
});
@@ -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())
})
)
});
@@ -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
@@ -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<DBEditType> = {},
backupInfo?: BackupItemType,
backupInfo?: z.Infer<typeof backupBaseSchema>,
options?: {
storageClassName?: string;
}