From db239d12c6962d5220b9c62d5c8109078537af06 Mon Sep 17 00:00:00 2001 From: huanglvjing <2357331198@qq.com> Date: Tue, 4 Nov 2025 17:11:40 +0800 Subject: [PATCH 1/3] 1104db_updaye --- .../src/services/backend/apis/get-database.ts | 184 +++++++++++++++++- .../dbprovider/src/types/schemas/db.ts | 91 +++++++++ 2 files changed, 266 insertions(+), 9 deletions(-) diff --git a/frontend/providers/dbprovider/src/services/backend/apis/get-database.ts b/frontend/providers/dbprovider/src/services/backend/apis/get-database.ts index 7581c6380cc3..46299ee9e8df 100644 --- a/frontend/providers/dbprovider/src/services/backend/apis/get-database.ts +++ b/frontend/providers/dbprovider/src/services/backend/apis/get-database.ts @@ -3,13 +3,26 @@ 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 { dbDetailSchema, ClusterObjectSchema } from '@/types/schemas/db'; import { CPUResourceEnum, DBDetailType, MemoryResourceEnum, - ReplicasResourceEnum + ReplicasResourceEnum, + DBType } from '@/types/db'; +import { cpuFormatToM, memoryFormatToMi, storageFormatToNum } from '@/utils/tools'; + +// connection secret names +const getSecretNames = (dbName: string, dbType: DBType): string[] => { + const secretMap: Record = { + mongodb: [`${dbName}-mongodb-account-root`, `${dbName}-mongo-conn-credential`], + redis: [`${dbName}-redis-account-default`, `${dbName}-redis-conn-credential`], + kafka: [`${dbName}-broker-account-admin`, `${dbName}-conn-credential`] + } as any; + return secretMap[dbType] || [`${dbName}-conn-credential`]; +}; + export const raw2schema = (raw: DBDetailType): z.Infer => { const dbEditSchemaFromRaw: z.Infer = { terminationPolicy: raw.terminationPolicy, @@ -55,21 +68,174 @@ export const raw2schema = (raw: DBDetailType): z.Infer => return dbEditSchemaFromRaw; }; + export async function getDatabase( k8s: Awaited>, request: { params: z.infer; } -) { - const { k8sBatch, namespace, k8sCustomObjects, k8sCore, k8sAuth } = k8s; - const { body } = (await k8sCustomObjects.getNamespacedCustomObject( +): Promise> { + const { namespace, k8sCustomObjects, k8sCore } = k8s; + const dbName = request.params.databaseName; + + const { body: cluster } = (await k8sCustomObjects.getNamespacedCustomObject( 'apps.kubeblocks.io', 'v1alpha1', namespace, 'clusters', - request.params.databaseName - )) as { - body: KbPgClusterType; + dbName + )) as { body: KbPgClusterType }; + + const dbDetail = adaptDBDetail(cluster); + const rawDbType = + cluster.metadata?.labels?.['clusterdefinition.kubeblocks.io/name'] || 'postgresql'; + const dbType = ((rawDbType as string) === 'mysql' ? 'apecloud-mysql' : rawDbType) as DBType; + + // Fetch connection - try new naming format first, fallback to old + const getConnection = async () => { + const secretNames = getSecretNames(dbName, dbType); + for (const secretName of secretNames) { + try { + const secret = await k8sCore.readNamespacedSecret(secretName, namespace); + if (!secret.body?.data) continue; + + const decode = (key: string) => + secret.body.data?.[key] + ? Buffer.from(secret.body.data[key], 'base64').toString('utf-8').trim() + : null; + + const [endpoint, host, port, username, password] = [ + decode('endpoint'), + decode('host'), + decode('port'), + decode('username'), + decode('password') + ]; + + let connectionString = null; + if (host && port && username && password) { + const protocols: Record = { + kafka: endpoint || `${host}:${port}`, + milvus: endpoint || `${host}:${port}`, + mongodb: `mongodb://${username}:${password}@${host}:${port}`, + postgresql: `postgresql://${username}:${password}@${host}:${port}`, + 'apecloud-mysql': `mysql://${username}:${password}@${host}:${port}`, + redis: `redis://${username}:${password}@${host}:${port}` + }; + connectionString = protocols[dbType] || null; + } + + return { endpoint, host, port, username, password, connectionString }; + } catch {} + } + return null; + }; + + // Fetch public connection + const getPublicConnection = async () => { + try { + const service = await k8sCore.readNamespacedService(`${dbName}-export`, namespace); + const nodePort = service.body?.spec?.ports?.[0]?.nodePort; + return nodePort ? { port: nodePort, connectionString: null } : null; + } catch { + return null; + } + }; + + // Fetch pods + const getPods = async () => { + try { + const { body } = await k8sCore.listNamespacedPod( + namespace, + undefined, + undefined, + undefined, + undefined, + `app.kubernetes.io/instance=${dbName}` + ); + + return body.items.map((pod) => { + let upTime = null; + if (pod.status?.startTime) { + const seconds = Math.floor( + (Date.now() - new Date(pod.status.startTime).getTime()) / 1000 + ); + const d = Math.floor(seconds / 86400); + const h = Math.floor((seconds % 86400) / 3600); + const m = Math.floor((seconds % 3600) / 60); + upTime = d > 0 ? `${d}d${h}h` : h > 0 ? `${h}h${m}m` : `${m}m`; + } + + return { + name: pod.metadata?.name || null, + status: pod.status?.phase || null, + upTime, + containers: + pod.status?.containerStatuses?.map((c) => ({ + name: c.name, + ready: c.ready, + state: c.state, + restartCount: c.restartCount + })) || null + }; + }); + } catch { + return []; + } + }; + + // Extract components + const components = (cluster.spec?.componentSpecs || []).map((spec) => { + const cpu = cpuFormatToM(spec.resources?.limits?.cpu || '0') / 1000; + const memory = memoryFormatToMi(spec.resources?.limits?.memory || '0') / 1024; + const storage = storageFormatToNum( + spec.volumeClaimTemplates?.[0]?.spec?.resources?.requests?.storage || '0' + ); + + return { + name: spec.name, + status: (cluster.status?.components as any)?.[spec.name]?.phase || 'unknown', + resource: { cpu, memory, storage, replicas: spec.replicas || 0 } + }; + }); + + // Calculate total resources + const totalReplicas = components.reduce((sum, c) => sum + (c.resource?.replicas || 0), 0); + const totalCpu = components.reduce( + (sum, c) => sum + (c.resource?.cpu || 0) * (c.resource?.replicas || 0), + 0 + ); + const totalMemory = components.reduce( + (sum, c) => sum + (c.resource?.memory || 0) * (c.resource?.replicas || 0), + 0 + ); + const totalStorage = components.reduce( + (sum, c) => sum + (c.resource?.storage || 0) * (c.resource?.replicas || 0), + 0 + ); + + const [privateConnection, publicConnection, pods] = await Promise.all([ + getConnection(), + getPublicConnection(), + getPods() + ]); + + return { + name: cluster.metadata?.name || dbName, + kind: cluster.kind || 'Cluster', + type: dbType, + version: cluster.metadata?.labels?.['clusterversion.kubeblocks.io/name'] || null, + operationalStatus: { createdAt: cluster.metadata?.creationTimestamp || null }, + status: cluster.status?.phase || null, + resource: { + cpu: totalReplicas ? totalCpu / totalReplicas : null, + memory: totalReplicas ? totalMemory / totalReplicas : null, + storage: totalReplicas ? totalStorage / totalReplicas : null, + replicas: totalReplicas || null + }, + components, + connection: { privateConnection, publicConnection }, + backup: cluster.spec?.backup || null, + pods }; - return raw2schema(adaptDBDetail(body)); } diff --git a/frontend/providers/dbprovider/src/types/schemas/db.ts b/frontend/providers/dbprovider/src/types/schemas/db.ts index f0e014c6e565..e1779a63e94a 100644 --- a/frontend/providers/dbprovider/src/types/schemas/db.ts +++ b/frontend/providers/dbprovider/src/types/schemas/db.ts @@ -174,3 +174,94 @@ export const updateResourceSchema = z.object({ }); export const versionListSchema = z.record(dbTypeSchema, z.array(z.string())); + +// Cluster Object Schemas for new API response format +export const ClusterResourceSchema = z.object({ + cpu: z.number().nullable().optional(), + memory: z.number().nullable().optional(), + storage: z.number().nullable().optional(), + replicas: z.number().nullable().optional() +}); + +export const ClusterComponentSchema = z.object({ + name: z.string().nullable().optional(), + status: z.string().nullable().optional(), + resource: ClusterResourceSchema.nullable().optional() +}); + +export const ClusterConnectionSchema = z.object({ + privateConnection: z + .object({ + endpoint: z.string().nullable().optional(), + host: z.string().nullable().optional(), + port: z.string().nullable().optional(), + username: z.string().nullable().optional(), + password: z.string().nullable().optional(), + connectionString: z.string().nullable().optional() + }) + .nullable() + .optional(), + publicConnection: z + .union([ + z.object({ + port: z.number().nullable().optional(), + connectionString: z.string().nullable().optional() + }), + z.array(z.any()) + ]) + .nullable() + .optional() +}); + +export const ClusterBackupSchema = z + .object({ + cronExpression: z.string().optional(), + enabled: z.boolean().optional(), + method: z.string().optional(), + pitrEnabled: z.boolean().optional(), + repoName: z.string().optional(), + retentionPeriod: z.string().optional() + }) + .optional(); + +export const PodSchema = z.object({ + name: z.string().nullable().optional(), + status: z.string().nullable().optional(), + upTime: z.string().optional().nullable(), + containers: z.any().nullable().optional() +}); + +export const ClusterObjectSchema = z.object({ + name: z.string(), + kind: z.string(), + type: z + .enum([ + 'postgresql', + 'mongodb', + 'redis', + 'apecloud-mysql', + 'kafka', + 'milvus', + 'weaviate', + 'clickhouse', + 'qdrant', + 'nebula', + 'pulsar' + ]) + .nullable() + .optional(), + version: z.string().nullable().optional(), + operationalStatus: z.any().optional().nullable(), + status: z.string().nullable().optional(), + resource: z + .union([ClusterResourceSchema, z.array(z.any())]) + .nullable() + .optional(), + components: z + .union([z.array(ClusterComponentSchema), z.object({}).passthrough()]) + .optional() + .nullable(), + connection: ClusterConnectionSchema.nullable().optional(), + backup: ClusterBackupSchema.optional().nullable(), + pods: z.array(PodSchema).optional().nullable() +}); From 1809c3835f0b2540e13a0fabb67d31eb170b6780 Mon Sep 17 00:00:00 2001 From: huanglvjing <2357331198@qq.com> Date: Tue, 4 Nov 2025 17:32:19 +0800 Subject: [PATCH 2/3] add_updata_2 --- .../dbprovider/src/services/backend/apis/get-database.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/providers/dbprovider/src/services/backend/apis/get-database.ts b/frontend/providers/dbprovider/src/services/backend/apis/get-database.ts index 46299ee9e8df..5b904a30b846 100644 --- a/frontend/providers/dbprovider/src/services/backend/apis/get-database.ts +++ b/frontend/providers/dbprovider/src/services/backend/apis/get-database.ts @@ -22,7 +22,7 @@ const getSecretNames = (dbName: string, dbType: DBType): string[] => { } as any; return secretMap[dbType] || [`${dbName}-conn-credential`]; }; - +//database transform to schema export const raw2schema = (raw: DBDetailType): z.Infer => { const dbEditSchemaFromRaw: z.Infer = { terminationPolicy: raw.terminationPolicy, @@ -69,6 +69,7 @@ export const raw2schema = (raw: DBDetailType): z.Infer => return dbEditSchemaFromRaw; }; +//get database details export async function getDatabase( k8s: Awaited>, request: { @@ -86,7 +87,7 @@ export async function getDatabase( dbName )) as { body: KbPgClusterType }; - const dbDetail = adaptDBDetail(cluster); + //get database type const rawDbType = cluster.metadata?.labels?.['clusterdefinition.kubeblocks.io/name'] || 'postgresql'; const dbType = ((rawDbType as string) === 'mysql' ? 'apecloud-mysql' : rawDbType) as DBType; From 3a1d944e244a7f4fc2f27dfec734b00bf71b00e8 Mon Sep 17 00:00:00 2001 From: huanglvjing <2357331198@qq.com> Date: Thu, 6 Nov 2025 12:01:12 +0800 Subject: [PATCH 3/3] feat: add Claude template to devbox and optimize database connection info retrieval --- .../src/services/backend/apis/get-database.ts | 276 ++++++++++-------- .../devbox/app/api/v1/devbox/route.ts | 47 +-- .../devbox/app/api/v1/devbox/schema.ts | 3 +- 3 files changed, 178 insertions(+), 148 deletions(-) diff --git a/frontend/providers/dbprovider/src/services/backend/apis/get-database.ts b/frontend/providers/dbprovider/src/services/backend/apis/get-database.ts index 5b904a30b846..8665b29440c1 100644 --- a/frontend/providers/dbprovider/src/services/backend/apis/get-database.ts +++ b/frontend/providers/dbprovider/src/services/backend/apis/get-database.ts @@ -2,7 +2,6 @@ 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, ClusterObjectSchema } from '@/types/schemas/db'; import { CPUResourceEnum, @@ -13,18 +12,135 @@ import { } from '@/types/db'; import { cpuFormatToM, memoryFormatToMi, storageFormatToNum } from '@/utils/tools'; -// connection secret names +const CONSTANTS = { + CPU_DIVISOR: 1000, + MEMORY_DIVISOR: 1024, + MILLISECONDS_PER_SECOND: 1000, + SECONDS_PER_DAY: 86400, + SECONDS_PER_HOUR: 3600, + SECONDS_PER_MINUTE: 60 +} as const; + +//three types secret +const SECRET_NAME_MAP: Partial> = { + mongodb: ['{dbName}-mongodb-account-root', '{dbName}-mongo-conn-credential'], + redis: ['{dbName}-redis-account-default', '{dbName}-redis-conn-credential'], + kafka: ['{dbName}-broker-account-admin', '{dbName}-conn-credential'] +}; + +//connection string +const CONNECTION_PROTOCOLS: Partial> = { + kafka: '{endpoint}', + milvus: '{endpoint}', + mongodb: 'mongodb://{username}:{password}@{host}:{port}', + postgresql: 'postgresql://{username}:{password}@{host}:{port}', + 'apecloud-mysql': 'mysql://{username}:{password}@{host}:{port}', + redis: 'redis://{username}:{password}@{host}:{port}' +}; + +//default secret names const getSecretNames = (dbName: string, dbType: DBType): string[] => { - const secretMap: Record = { - mongodb: [`${dbName}-mongodb-account-root`, `${dbName}-mongo-conn-credential`], - redis: [`${dbName}-redis-account-default`, `${dbName}-redis-conn-credential`], - kafka: [`${dbName}-broker-account-admin`, `${dbName}-conn-credential`] - } as any; - return secretMap[dbType] || [`${dbName}-conn-credential`]; + const baseSecrets = SECRET_NAME_MAP[dbType] || ['{dbName}-conn-credential']; + return baseSecrets.map((name) => name.replace('{dbName}', dbName)); }; -//database transform to schema + +//decode secret data +const decodeSecretData = (data: Record | undefined, key: string): string | null => { + return data?.[key] ? Buffer.from(data[key], 'base64').toString('utf-8').trim() : null; +}; + +//extract all secret data at once +const extractSecretData = (data: Record | undefined) => ({ + endpoint: decodeSecretData(data, 'endpoint'), + host: decodeSecretData(data, 'host'), + port: decodeSecretData(data, 'port'), + username: decodeSecretData(data, 'username'), + password: decodeSecretData(data, 'password') +}); + +//connection +const buildConnectionString = ( + dbType: DBType, + { endpoint, host, port, username, password }: ReturnType +): string | null => { + if (!host || !port || !username || !password) return null; + + const protocol = CONNECTION_PROTOCOLS[dbType]; + if (!protocol) return null; + + return protocol + .replace('{endpoint}', endpoint || `${host}:${port}`) + .replace('{host}', host) + .replace('{port}', port) + .replace('{username}', username) + .replace('{password}', password); +}; + +//format uptime +const formatUptime = (startTime: string | Date): string | null => { + const seconds = Math.floor( + (Date.now() - new Date(startTime).getTime()) / CONSTANTS.MILLISECONDS_PER_SECOND + ); + const d = Math.floor(seconds / CONSTANTS.SECONDS_PER_DAY); + const h = Math.floor((seconds % CONSTANTS.SECONDS_PER_DAY) / CONSTANTS.SECONDS_PER_HOUR); + const m = Math.floor((seconds % CONSTANTS.SECONDS_PER_HOUR) / CONSTANTS.SECONDS_PER_MINUTE); + return d > 0 ? `${d}d${h}h` : h > 0 ? `${h}h${m}m` : `${m}m`; +}; + +//calculate component resources +const calculateComponentResources = (spec: any) => { + const cpu = cpuFormatToM(spec.resources?.limits?.cpu || '0') / CONSTANTS.CPU_DIVISOR; + const memory = memoryFormatToMi(spec.resources?.limits?.memory || '0') / CONSTANTS.MEMORY_DIVISOR; + const storage = storageFormatToNum( + spec.volumeClaimTemplates?.[0]?.spec?.resources?.requests?.storage || '0' + ); + + return { + name: spec.name, + status: (spec as any).status?.phase || 'unknown', + resource: { + cpu, + memory, + storage, + replicas: spec.replicas || 0 + } + }; +}; + +//calculate resource totals +const calculateResourceTotals = (components: ReturnType[]) => { + return components.reduce( + (acc, component) => { + const replicas = component.resource?.replicas || 0; + acc.totalReplicas += replicas; + acc.totalCpu += (component.resource?.cpu || 0) * replicas; + acc.totalMemory += (component.resource?.memory || 0) * replicas; + acc.totalStorage += (component.resource?.storage || 0) * replicas; + return acc; + }, + { totalReplicas: 0, totalCpu: 0, totalMemory: 0, totalStorage: 0 } + ); +}; + +//convert raw data to schema export const raw2schema = (raw: DBDetailType): z.Infer => { - const dbEditSchemaFromRaw: z.Infer = { + const autoBackup = raw.autoBackup + ? { + ...raw.autoBackup, + week: raw.autoBackup.week as ( + | 'monday' + | 'tuesday' + | 'wednesday' + | 'thursday' + | 'friday' + | 'saturday' + | 'sunday' + )[], + saveType: raw.autoBackup.saveType as 'days' | 'weeks' | 'months' | 'hours' + } + : undefined; + + return { terminationPolicy: raw.terminationPolicy, name: raw.dbName, type: raw.dbType, @@ -49,33 +165,17 @@ export const raw2schema = (raw: DBDetailType): z.Infer => sourceName: raw.source.sourceName, sourceType: raw.source.sourceType }, - autoBackup: raw.autoBackup - ? { - ...raw.autoBackup, - week: raw.autoBackup.week as ( - | 'monday' - | 'tuesday' - | 'wednesday' - | 'thursday' - | 'friday' - | 'saturday' - | 'sunday' - )[], - saveType: raw.autoBackup.saveType as 'days' | 'weeks' | 'months' | 'hours' - } - : undefined + autoBackup }; - - return dbEditSchemaFromRaw; }; -//get database details +//get database export async function getDatabase( k8s: Awaited>, request: { params: z.infer; } -): Promise> { +): Promise> { const { namespace, k8sCustomObjects, k8sCore } = k8s; const dbName = request.params.databaseName; @@ -87,53 +187,32 @@ export async function getDatabase( dbName )) as { body: KbPgClusterType }; - //get database type const rawDbType = cluster.metadata?.labels?.['clusterdefinition.kubeblocks.io/name'] || 'postgresql'; const dbType = ((rawDbType as string) === 'mysql' ? 'apecloud-mysql' : rawDbType) as DBType; - // Fetch connection - try new naming format first, fallback to old - const getConnection = async () => { + const fetchConnection = async () => { const secretNames = getSecretNames(dbName, dbType); + for (const secretName of secretNames) { try { const secret = await k8sCore.readNamespacedSecret(secretName, namespace); if (!secret.body?.data) continue; - const decode = (key: string) => - secret.body.data?.[key] - ? Buffer.from(secret.body.data[key], 'base64').toString('utf-8').trim() - : null; - - const [endpoint, host, port, username, password] = [ - decode('endpoint'), - decode('host'), - decode('port'), - decode('username'), - decode('password') - ]; - - let connectionString = null; - if (host && port && username && password) { - const protocols: Record = { - kafka: endpoint || `${host}:${port}`, - milvus: endpoint || `${host}:${port}`, - mongodb: `mongodb://${username}:${password}@${host}:${port}`, - postgresql: `postgresql://${username}:${password}@${host}:${port}`, - 'apecloud-mysql': `mysql://${username}:${password}@${host}:${port}`, - redis: `redis://${username}:${password}@${host}:${port}` - }; - connectionString = protocols[dbType] || null; - } + const secretData = extractSecretData(secret.body.data); + const connectionString = buildConnectionString(dbType, secretData); - return { endpoint, host, port, username, password, connectionString }; - } catch {} + if (connectionString) { + return { ...secretData, connectionString }; + } + } catch { + continue; + } } return null; }; - // Fetch public connection - const getPublicConnection = async () => { + const fetchPublicConnection = async () => { try { const service = await k8sCore.readNamespacedService(`${dbName}-export`, namespace); const nodePort = service.body?.spec?.ports?.[0]?.nodePort; @@ -143,8 +222,7 @@ export async function getDatabase( } }; - // Fetch pods - const getPods = async () => { + const fetchPods = async () => { try { const { body } = await k8sCore.listNamespacedPod( namespace, @@ -155,70 +233,38 @@ export async function getDatabase( `app.kubernetes.io/instance=${dbName}` ); - return body.items.map((pod) => { - let upTime = null; - if (pod.status?.startTime) { - const seconds = Math.floor( - (Date.now() - new Date(pod.status.startTime).getTime()) / 1000 - ); - const d = Math.floor(seconds / 86400); - const h = Math.floor((seconds % 86400) / 3600); - const m = Math.floor((seconds % 3600) / 60); - upTime = d > 0 ? `${d}d${h}h` : h > 0 ? `${h}h${m}m` : `${m}m`; - } - - return { - name: pod.metadata?.name || null, - status: pod.status?.phase || null, - upTime, - containers: - pod.status?.containerStatuses?.map((c) => ({ - name: c.name, - ready: c.ready, - state: c.state, - restartCount: c.restartCount - })) || null - }; - }); + return body.items.map((pod) => ({ + name: pod.metadata?.name || null, + status: pod.status?.phase || null, + upTime: pod.status?.startTime ? formatUptime(pod.status.startTime) : null, + containers: + pod.status?.containerStatuses?.map((c) => ({ + name: c.name, + ready: c.ready, + state: c.state, + restartCount: c.restartCount + })) || null + })); } catch { return []; } }; - // Extract components const components = (cluster.spec?.componentSpecs || []).map((spec) => { - const cpu = cpuFormatToM(spec.resources?.limits?.cpu || '0') / 1000; - const memory = memoryFormatToMi(spec.resources?.limits?.memory || '0') / 1024; - const storage = storageFormatToNum( - spec.volumeClaimTemplates?.[0]?.spec?.resources?.requests?.storage || '0' - ); - + const resourceData = calculateComponentResources(spec); return { - name: spec.name, - status: (cluster.status?.components as any)?.[spec.name]?.phase || 'unknown', - resource: { cpu, memory, storage, replicas: spec.replicas || 0 } + ...resourceData, + status: (cluster.status?.components as any)?.[spec.name]?.phase || 'unknown' }; }); - // Calculate total resources - const totalReplicas = components.reduce((sum, c) => sum + (c.resource?.replicas || 0), 0); - const totalCpu = components.reduce( - (sum, c) => sum + (c.resource?.cpu || 0) * (c.resource?.replicas || 0), - 0 - ); - const totalMemory = components.reduce( - (sum, c) => sum + (c.resource?.memory || 0) * (c.resource?.replicas || 0), - 0 - ); - const totalStorage = components.reduce( - (sum, c) => sum + (c.resource?.storage || 0) * (c.resource?.replicas || 0), - 0 - ); + const resourceTotals = calculateResourceTotals(components); + const { totalReplicas, totalCpu, totalMemory, totalStorage } = resourceTotals; const [privateConnection, publicConnection, pods] = await Promise.all([ - getConnection(), - getPublicConnection(), - getPods() + fetchConnection(), + fetchPublicConnection(), + fetchPods() ]); return { diff --git a/frontend/providers/devbox/app/api/v1/devbox/route.ts b/frontend/providers/devbox/app/api/v1/devbox/route.ts index b6c9e57a756a..b5b696fdd63a 100644 --- a/frontend/providers/devbox/app/api/v1/devbox/route.ts +++ b/frontend/providers/devbox/app/api/v1/devbox/route.ts @@ -434,41 +434,24 @@ export async function POST(req: NextRequest) { }); } - const organization = await devboxDB.organization.findUnique({ - where: { - id: 'labring' - } - }); - - if (!organization) throw Error('organization not found'); - const regionUid = getRegionUid(); - const templateRepository = await devboxDB.templateRepository.findFirst({ - where: { - isPublic: true, - isDeleted: false, - organizationUid: organization.uid, - regionUid, - iconId: devboxForm.runtime - }, - select: { - name: true, - uid: true - } - }); - - if (!templateRepository) { - return jsonRes({ - code: 404, - message: `Runtime '${devboxForm.runtime}' not found` - }); - } - const template = await devboxDB.template.findFirst({ where: { - templateRepositoryUid: templateRepository.uid, - isDeleted: false + isDeleted: false, + templateRepository: { + isDeleted: false, + regionUid, + isPublic: true, + iconId: devboxForm.runtime, + templateRepositoryTags: { + some: { + tag: { + type: 'OFFICIAL_CONTENT' + } + } + } + } }, select: { templateRepository: { @@ -490,7 +473,7 @@ export async function POST(req: NextRequest) { if (!template) { return jsonRes({ code: 404, - message: 'Template not found for runtime' + message: `Runtime devboxForm.runtime not found` }); } diff --git a/frontend/providers/devbox/app/api/v1/devbox/schema.ts b/frontend/providers/devbox/app/api/v1/devbox/schema.ts index 28acb4dd0b1e..b5ce03ffd3c1 100644 --- a/frontend/providers/devbox/app/api/v1/devbox/schema.ts +++ b/frontend/providers/devbox/app/api/v1/devbox/schema.ts @@ -53,7 +53,8 @@ const RuntimeName = z.enum([ 'gin', 'node.js', 'echo', -'rust' +'claude-code', +'rust', ]).openapi({ description: 'Runtime environment name (lowercase)' });