import { Account, Container, Environment, Instance, Resource, Service, Usage, } from "@kumori/aurora-interfaces"; import { convertToGigabytes, getTimestamp } from "../utils/utils"; import { Revision } from "@kumori/aurora-interfaces/interfaces/revision-interface"; interface Role { name: string; instances: Instance[]; artifactName?: string; logo?: string; category?: string; version?: string; description?: string; resource?: any[]; parameters?: { [key: string]: string }[]; registry?: string; imageTag?: string; entrypoint?: string; cmd?: string; scalling?: { cpu: { up: string; down: string }; memory: { up: string; down: string }; instances: { max: number; min: number }; histeresys: string; }; hsize?: number; } interface HandleRevisionEventParams { entityId: string; eventData: any; parentParts: { [entity: string]: string }; servicesMap: Map; revisionsMap: Map; roleMap: Map; environmentsMap: Map; accountsMap: Map; } interface HandleRevisionEventResult { revision: Revision; revisionKey: string; serviceId: string; roles: Role[]; updatedService: Service | null; updatedEnvironment: Environment | null; updatedAccount: Account | null; pendingRevisionError: { service: string; revision: Revision } | null; shouldFetchChannels: boolean; channelsFetchInfo: { serviceId: string; tenantId: string } | null; serviceDeployedEvent: Service | null; serviceDeploymentErrorEvent: Service | null; } const MAX_HISTORY = 100; const processRolesAndInstances = ( eventData: any, serviceId: string, roleMap: Map, ): { roles: Role[]; instances: Instance[]; usedCpu: number; usedMemory: number; } => { const roles: Role[] = []; const instances: Instance[] = []; let usedCpu = 0; let usedMemory = 0; const roleEntries = eventData.status.runtime ? Object.entries(eventData.status.runtime.roles) : []; for (const [roleName, roleData] of roleEntries) { const instanceEntries = Object.entries( (roleData as { instances: Record }).instances, ); const roleInstances: Instance[] = []; const existingRoles = roleMap.get(serviceId) || []; const existingRole = existingRoles.find((r) => r.name === roleName); for (const [instanceId, instanceData] of instanceEntries) { const containers: Container[] = []; const containerEntries = Object.entries(instanceData.containers || {}); for (const [containerName, containerData] of containerEntries) { const container: Container = { name: containerName, ready: (containerData as any).status.ready, reestartCount: (containerData as any).status.restarts, metrics: { cpu: (containerData as any).status.metrics.cpu, memory: (containerData as any).status.metrics.memory, }, states: (containerData as any).status.status, }; containers.push(container); } usedCpu += Number(instanceData.status.metrics.cpu); usedMemory += Number(instanceData.status.metrics.memory); const instance: Instance = { id: instanceId, name: `${instanceId}`, status: instanceData.status.status, usage: { current: { cpu: instanceData.status.metrics.cpu, memory: instanceData.status.metrics.memory, storage: 0, volatileStorage: 0, nonReplicatedStorage: 0, persistentStorage: 0, }, limit: { cpu: { max: (eventData.spec.roles[roleName]?.cpu || 0) / 1000, min: 0, }, memory: { max: (eventData.spec.roles[roleName]?.memory || 0) / 1000, min: 0, }, storage: { max: 0, min: 0 }, volatileStorage: { max: 0, min: 0 }, nonReplicatedStorage: { max: 0, min: 0 }, persistentStorage: { max: 0, min: 0 }, }, cost: 0, }, logs: [], containers, }; roleInstances.push(instance); instances.push(instance); } const role: Role = { ...(existingRole ?? {}), name: roleName, instances: roleInstances, hsize: (roleData as any).status?.scale || existingRole?.hsize || 0, }; roles.push(role); } return { roles, instances, usedCpu, usedMemory }; }; const getContainerError = ( revisionStatus: any, ): { code: string; message: string; timestamp: string } | null => { for (const roleData of Object.values(revisionStatus?.runtime?.roles ?? {})) { for (const instanceData of Object.values(roleData.instances ?? {})) { for (const container of Object.values(instanceData.containers ?? {})) { if (!container.status.ready) { return { code: container.status.status.waiting?.reason ?? "ContainerNotReady", message: container.status.status.waiting?.message ?? "One or more containers are not ready for unknown reasons.", timestamp: Date.now().toString(), }; } } } } return null; }; const handleRevisionStatus = (revisionStatus: any) => { if (!revisionStatus?.runtime) { return revisionStatus?.state; } for (const roleData of Object.values(revisionStatus.runtime.roles)) { for (const instanceData of Object.values(roleData.instances)) { for (const container of Object.values(instanceData.containers ?? {})) { if (!container.status.ready) { return { code: container.status.status.waiting?.reason ?? "ContainerNotReady", message: container.status.status.waiting?.message ?? "One or more containers are not ready for unknown reasons.", timestamp: Date.now().toString(), }; } } } } return revisionStatus?.state; }; const createRevision = ( entityId: string, eventData: any, usedCpu: number, usedMemory: number, ): Revision => { const containerError = eventData.status?.error ? null : getContainerError(eventData.status); return { id: entityId, schema: {}, usage: { current: { cpu: usedCpu, memory: usedMemory, storage: 0, volatileStorage: 0, nonReplicatedStorage: 0, persistentStorage: 0, }, limit: { cpu: { max: eventData.spec.intensives?.vcpu / 1000 || 0, min: 0 }, memory: { max: eventData.spec.intensives?.ram / 1000 || 0, min: 0 }, storage: { max: eventData.spec.intensives?.shared_disk / 1000 || 0, min: 0, }, volatileStorage: { max: eventData.spec.intensives?.volatile_disk / 1000 || 0, min: 0, }, nonReplicatedStorage: { max: eventData.spec.intensives?.nrpersistent_disk / 1000 || 0, min: 0, }, persistentStorage: { max: eventData.spec.intensives?.persistent_disk / 1000 || 0, min: 0, }, }, cost: 0, }, status: handleRevisionStatus(eventData.status), errorCode: eventData.status?.error ? eventData.status.error.code : containerError?.code === "CrashLoopBackOff" ? containerError.code : "", errorMsg: eventData.status?.error ? eventData.status.error.message : containerError?.code === "CrashLoopBackOff" ? containerError.message : "", createdAt: (eventData.status && eventData.status.runtime?.status?.createdAt) || "", }; }; const updateServiceWithRevision = ( existingService: Service, entityId: string, eventData: any, newRevision: Revision, roles: Role[], ): { updatedService: Service; deploymentErrorEvent: Service | null } => { const existingRevisionIndex = existingService.revisions.findIndex( (rev) => String(rev.id) === String(entityId), ); const existingRevision = existingRevisionIndex !== -1 ? existingService.revisions[existingRevisionIndex] : null; const hasExistingSchema = existingRevision?.schema && ("parameters" in existingRevision.schema || "resources" in existingRevision.schema); const hasNewSchema = newRevision.schema && ("parameters" in newRevision.schema || "resources" in newRevision.schema); if (hasExistingSchema && !hasNewSchema) { newRevision.schema = existingRevision!.schema; } const updatedRevisions: Revision[] = existingRevisionIndex === -1 ? [...existingService.revisions, newRevision] : existingService.revisions.map((rev) => String(rev.id) === String(entityId) ? newRevision : rev, ); const updatedService: Service = { ...existingService, revisions: updatedRevisions, role: roles.length > 0 ? roles : existingService.role, usage: newRevision.usage, startedAt: newRevision.createdAt || existingService.startedAt, }; const deploymentErrorEvent = eventData.status?.error ? updatedService : null; return { updatedService, deploymentErrorEvent }; }; const updateEnvironmentConsumption = ( environment: Environment, servicesMap: Map, revisionsMap: Map, serviceEnvironmentId: string, ): Environment => { let totalEnvCpu = 0; let totalEnvMemory = 0; servicesMap.forEach((service, svcId) => { if (service.environment === serviceEnvironmentId) { const currentRevisionKey = `/tenant/${svcId.split('/')[0]}/service/${svcId.split('/')[1]}/revision/${service.currentRevision}`; const currentRevision = revisionsMap.get(currentRevisionKey); if (currentRevision) { totalEnvCpu += currentRevision.usage.limit.cpu.max || 0; totalEnvMemory += currentRevision.usage.limit.memory.max || 0; } } }); if (!environment.usage.current.cpuConsuption) environment.usage.current.cpuConsuption = []; if (!environment.usage.current.memoryConsuption) environment.usage.current.memoryConsuption = []; environment.usage.current.cpuConsuption.push(totalEnvCpu); environment.usage.current.memoryConsuption.push(totalEnvMemory); if (environment.usage.current.cpuConsuption.length > MAX_HISTORY) environment.usage.current.cpuConsuption = environment.usage.current.cpuConsuption.slice(-MAX_HISTORY); if (environment.usage.current.memoryConsuption.length > MAX_HISTORY) environment.usage.current.memoryConsuption = environment.usage.current.memoryConsuption.slice(-MAX_HISTORY); return environment; }; const updateAccountConsumption = ( account: Account, servicesMap: Map, revisionsMap: Map, serviceAccountId: string, ): Account => { let totalAccountCpu = 0; let totalAccountMemory = 0; servicesMap.forEach((service, svcId) => { if (service.account === serviceAccountId) { const currentRevisionKey = `/tenant/${svcId.split('/')[0]}/service/${svcId.split('/')[1]}/revision/${service.currentRevision}`; const currentRevision = revisionsMap.get(currentRevisionKey); if (currentRevision) { totalAccountCpu += currentRevision.usage.limit.cpu.max || 0; totalAccountMemory += currentRevision.usage.limit.memory.max || 0; } } }); if (!account.usage.current.cpuConsuption) account.usage.current.cpuConsuption = []; if (!account.usage.current.memoryConsuption) account.usage.current.memoryConsuption = []; account.usage.current.cpuConsuption.push(totalAccountCpu); account.usage.current.memoryConsuption.push(totalAccountMemory); if (account.usage.current.cpuConsuption.length > MAX_HISTORY) account.usage.current.cpuConsuption = account.usage.current.cpuConsuption.slice(-MAX_HISTORY); if (account.usage.current.memoryConsuption.length > MAX_HISTORY) account.usage.current.memoryConsuption = account.usage.current.memoryConsuption.slice(-MAX_HISTORY); return account; }; export const handleRevisionEvent = ({ entityId, eventData, parentParts, servicesMap, revisionsMap, roleMap, environmentsMap, accountsMap, }: HandleRevisionEventParams): HandleRevisionEventResult => { const serviceId = `${parentParts.tenant}/${parentParts.service}`; const revisionKey = `${eventData.id.parent.name}/revision/${entityId}`; const { roles, instances, usedCpu, usedMemory } = processRolesAndInstances( eventData, serviceId, roleMap, ); const existingRevision = revisionsMap.get(revisionKey); const newRevision = createRevision(entityId, eventData, usedCpu, usedMemory); if ( existingRevision?.schema && Object.keys(existingRevision.schema).length > 0 ) { newRevision.schema = existingRevision.schema; } let updatedService: Service | null = null; let updatedEnvironment: Environment | null = null; let updatedAccount: Account | null = null; let pendingRevisionError: { service: string; revision: Revision } | null = null; let serviceDeployedEvent: Service | null = null; let serviceDeploymentErrorEvent: Service | null = null; let shouldFetchChannels = false; let channelsFetchInfo: { serviceId: string; tenantId: string } | null = null; const existingService = servicesMap.get(serviceId); if (existingService) { const serviceUpdateResult = updateServiceWithRevision( existingService, entityId, eventData, newRevision, roles, ); updatedService = serviceUpdateResult.updatedService; serviceDeploymentErrorEvent = serviceUpdateResult.deploymentErrorEvent; const serviceEnvironmentId = updatedService.environment; if (serviceEnvironmentId) { const environment = environmentsMap.get(serviceEnvironmentId); if (environment) { const tempServicesMap = new Map(servicesMap); tempServicesMap.set(serviceId, updatedService); const tempRevisionsMap = new Map(revisionsMap); tempRevisionsMap.set(revisionKey, newRevision); updatedEnvironment = updateEnvironmentConsumption( { ...environment }, tempServicesMap, tempRevisionsMap, serviceEnvironmentId, ); } } const serviceAccountId = updatedService.account; if (serviceAccountId) { const account = accountsMap.get(serviceAccountId); if (account) { const tempServicesMap = new Map(servicesMap); tempServicesMap.set(serviceId, updatedService); const tempRevisionsMap = new Map(revisionsMap); tempRevisionsMap.set(revisionKey, newRevision); updatedAccount = updateAccountConsumption( { ...account }, tempServicesMap, tempRevisionsMap, serviceAccountId, ); } } } else if (eventData.status && eventData.status.error) { newRevision.errorCode = eventData.status.error.code; newRevision.errorMsg = eventData.status.error.message; pendingRevisionError = { service: serviceId, revision: newRevision }; } if ( !eventData.meta.deleted && eventData.status && eventData.status.deployed ) { shouldFetchChannels = true; channelsFetchInfo = { serviceId: parentParts.service, tenantId: parentParts.tenant, }; } return { revision: newRevision, revisionKey, serviceId, roles, updatedService, updatedEnvironment, updatedAccount, pendingRevisionError, shouldFetchChannels, channelsFetchInfo, serviceDeployedEvent, serviceDeploymentErrorEvent, }; }; const collectNamedTypes = (schema: any): string[] => { if (!schema || typeof schema !== "object") return []; const names: string[] = []; if (schema.$kdsl?.const?.NamedType?.Name) names.push(schema.$kdsl.const.NamedType.Name as string); if (Array.isArray(schema.oneOf)) { for (const branch of schema.oneOf) names.push(...collectNamedTypes(branch)); } return names; }; const namedTypeToResourceKind = ( namedTypes: string[], ): { type: string; kind?: string } | null => { for (const name of namedTypes) { switch (name) { case "Secret": return { type: "secret" }; case "Domain": return { type: "domain" }; case "Port": return { type: "port" }; case "Certificate": return { type: "certificate" }; case "CA": return { type: "ca" }; case "Volatile": case "Ephemeral": return { type: "volume", kind: "volatile" }; case "NonReplicated": return { type: "volume", kind: "nonReplicated" }; case "Persisted": case "Persistent": return { type: "volume", kind: "persistent" }; case "Registered": return { type: "volume" }; } } return null; }; export const extractStructuredSchema = ( revisionData: any, ): | { parameters: { name: string; type: string; required: boolean; defaultValue?: any; }[]; resources: { name: string; type: string; kind?: string; required: boolean; }[]; } | {} => { const configSchema = revisionData?.configSchema; if (!configSchema) return {}; const deployedConfig = revisionData?.config?.config ?? {}; const parameterValues: Record = deployedConfig.parameter ?? {}; const configResourceValues: Record = deployedConfig.resource ?? {}; const requiredParams: string[] = configSchema?.properties?.config?.required ?? []; const requiredResources: string[] = configSchema?.properties?.resource?.required ?? []; const schemaConfigProps: Record = configSchema?.properties?.config?.properties ?? {}; const parameters = Object.entries(schemaConfigProps).map( ([name, propSchema]) => { const entry: { name: string; type: string; required: boolean; defaultValue?: any; } = { name, type: propSchema?.type ?? "string", required: requiredParams.includes(name), }; const currentValue = parameterValues[name]; if (currentValue !== undefined) entry.defaultValue = currentValue; else if (propSchema?.default !== undefined) entry.defaultValue = propSchema.default; return entry; }, ); const resolveResourceValue = (configValue: any): string => { if (!configValue || typeof configValue !== "object") return ""; if (typeof configValue.secret === "string") return extractResourceName(configValue.secret); if (typeof configValue.domain === "string") return extractResourceName(configValue.domain); if (typeof configValue.port === "string") return extractResourceName(configValue.port); if (typeof configValue.certificate === "string") return extractResourceName(configValue.certificate); if (typeof configValue.ca === "string") return extractResourceName(configValue.ca); if (configValue.volume) { const vol = configValue.volume; return ( convertToGigabytes(vol.size, vol.unit)?.toString() ?? String(vol.size ?? "") ); } return ""; }; const resolveResourceType = ( configValue: any, ): { type: string; kind?: string } | null => { if (!configValue || typeof configValue !== "object") return null; if ("secret" in configValue) return { type: "secret" }; if ("domain" in configValue) return { type: "domain" }; if ("port" in configValue) return { type: "port" }; if ("certificate" in configValue) return { type: "certificate" }; if ("ca" in configValue) return { type: "ca" }; if ("volume" in configValue) { const volType = configValue.volume?.type; if (volType === "persistent" || volType === "Persisted") return { type: "volume", kind: "persistent" }; if (volType === "nonReplicated" || volType === "NonReplicated") return { type: "volume", kind: "nonReplicated" }; if ( volType === "volatile" || volType === "Volatile" || volType === "ephemeral" || volType === "Ephemeral" ) return { type: "volume", kind: "volatile" }; return { type: "volume" }; } return null; }; const allResourceNames = new Set([ ...Object.keys(configResourceValues), ...Object.keys(configSchema?.properties?.resource?.properties ?? {}), ]); const resources = Array.from(allResourceNames) .map((name) => { const configValue = configResourceValues[name]; const fromConfig = resolveResourceType(configValue); const schemaProps = configSchema?.properties?.resource?.properties?.[name]; const fromSchema = schemaProps ? namedTypeToResourceKind(collectNamedTypes(schemaProps)) : null; const resolved = fromConfig ?? fromSchema; if (!resolved) return null; const entry: { name: string; type: string; kind?: string; required: boolean; value?: string; } = { name, type: resolved.type, required: requiredResources.includes(name), }; if (resolved.kind) entry.kind = resolved.kind; const value = resolveResourceValue(configValue); if (value) entry.value = value; return entry; }) .filter(Boolean) as { name: string; type: string; kind?: string; required: boolean; value?: string; }[]; return { parameters, resources }; }; export const processRevisionData = ( service: Service, revisionData: any, revisionsMap?: Map, serviceId?: string, ): Service => { const { solution } = revisionData; const topLevelConfig = revisionData?.config?.config ?? {}; const topLevelParameters = extractParametersFromConfig( topLevelConfig.parameter || {}, ); const topLevelResources = extractResources(topLevelConfig.resource || {}); const schemaResult = revisionData.revision ? extractStructuredSchema(revisionData) : {}; const schema = schemaResult && "parameters" in schemaResult ? schemaResult : {}; if ( revisionsMap && serviceId && revisionData.revision && "parameters" in schema ) { const revisionId: string = revisionData.revision; const [tenantId, serviceName] = serviceId.split('/'); const revisionKey = `/tenant/${tenantId}/service/${serviceName}/revision/${revisionId}`; const existing = revisionsMap.get(revisionKey); if (existing) { revisionsMap.set(revisionKey, { ...existing, schema }); } else { revisionsMap.set(revisionKey, { id: revisionId, schema, usage: { current: { cpu: 0, memory: 0, storage: 0, volatileStorage: 0, nonReplicatedStorage: 0, persistentStorage: 0, }, limit: { cpu: { max: revisionData.intensives?.vcpu / 1000 || 0, min: 0 }, memory: { max: revisionData.intensives?.ram / 1000 || 0, min: 0 }, storage: { max: revisionData.intensives?.shared_disk / 1000 || 0, min: 0, }, volatileStorage: { max: revisionData.intensives?.volatile_disk / 1000 || 0, min: 0, }, nonReplicatedStorage: { max: revisionData.intensives?.nrpersistent_disk / 1000 || 0, min: 0, }, persistentStorage: { max: revisionData.intensives?.persistent_disk / 1000 || 0, min: 0, }, }, cost: 0, }, status: { code: "", message: "", timestamp: "", args: [] }, errorCode: "", errorMsg: "", createdAt: "", }); } } const deploymentName = solution.top || service.name; const deployment = solution.deployments[deploymentName]; const applySchemaToService = (svc: Service): Service => { if (!revisionData.revision || !("parameters" in schema)) return svc; const revisionId: string = String(revisionData.revision); const found = svc.revisions.some((rev) => String(rev.id) === revisionId); const updatedRevisions = found ? svc.revisions.map((rev) => String(rev.id) === revisionId ? { ...rev, schema } : rev, ) : [ ...svc.revisions, { id: revisionId, schema, usage: svc.usage || { current: { cpu: 0, memory: 0, storage: 0, volatileStorage: 0, nonReplicatedStorage: 0, persistentStorage: 0, }, limit: { cpu: { max: 0, min: 0 }, memory: { max: 0, min: 0 }, storage: { max: 0, min: 0 }, volatileStorage: { max: 0, min: 0 }, nonReplicatedStorage: { max: 0, min: 0 }, persistentStorage: { max: 0, min: 0 }, }, cost: 0, }, status: { code: "", message: "", timestamp: "", args: [] }, errorCode: "", errorMsg: "", createdAt: "", }, ]; const currentRevId = String(svc.currentRevision ?? "0"); return { ...svc, revisions: updatedRevisions, currentRevision: Number(revisionId) >= Number(currentRevId) ? revisionId : svc.currentRevision, }; }; if (!deployment) { const firstDeploymentKey = Object.keys(solution.deployments)[0]; if (firstDeploymentKey) { return applySchemaToService( processDeployment( service, solution.deployments[firstDeploymentKey], revisionData, topLevelParameters, topLevelResources, ), ); } return applySchemaToService({ ...service, parameters: topLevelParameters, resources: topLevelResources, }); } return applySchemaToService( processDeployment( service, deployment, revisionData, topLevelParameters, topLevelResources, ), ); }; export const processDeployment = ( service: Service, deployment: any, revisionData: any, fallbackParameters: { [key: string]: string }[], fallbackResources: Resource[], ): Service => { const artifact = deployment.artifact; const deploymentConfig = deployment.config || {}; const rolesDefinition = artifact.description?.role || {}; const hasRoles = Object.keys(rolesDefinition).length > 0; let updatedRoles: Role[] = []; if (hasRoles) { Object.entries(rolesDefinition).forEach( ([roleName, roleData]: [string, any]) => { const existingRole = service.role.find((r) => r.name === roleName); const roleArtifactConfig = roleData?.artifact?.description?.config ?? {}; const roleParameters = extractParametersFromConfig( roleArtifactConfig.parameter ?? {}, ); const roleResources = extractResources( roleArtifactConfig.resource ?? {}, ); const parameters = roleParameters; const resources = roleResources; let hsize = 1; if (roleData.config?.scale?.hsize !== undefined) hsize = roleData.config.scale.hsize; if (deploymentConfig.scale?.detail?.[roleName]?.hsize !== undefined) hsize = deploymentConfig.scale.detail[roleName].hsize; const hasDuplex = roleData.artifact?.description?.srv?.duplex?.length > 0; const hasVolumeResource = Object.values( roleArtifactConfig.resource || {}, ).some((resourceData: any) => resourceData.volume); const artifactName: string = roleData.artifact?.ref?.name || ""; const role: Role = { name: roleName, artifactName: artifactName || undefined, instances: existingRole?.instances || [], logo: service.logo, description: roleData.artifact?.ref?.module || roleData.ref?.module || "", resource: resources, parameters, hsize, category: hasDuplex || hasVolumeResource ? "stateful" : "", }; if ( deployment.meta?.scaling && Object.keys(deployment.meta.scaling.simple || {}).length > 0 ) role.scalling = processScalingConfig(deployment.meta); updatedRoles.push(role); }, ); } else { let hsize = 1; if (deploymentConfig.scale?.hsize !== undefined) hsize = deploymentConfig.scale.hsize; if (deploymentConfig.scale?.detail?.[""]?.hsize !== undefined) hsize = deploymentConfig.scale.detail[""].hsize; updatedRoles = [ { name: service.name, instances: [], logo: service.logo, description: artifact.ref?.module || (artifact.description?.builtin ? "Builtin Service" : ""), resource: [], parameters: [], hsize, ...(deployment.meta?.scaling && Object.keys(deployment.meta.scaling.simple || {}).length > 0 && { scalling: processScalingConfig(deployment.meta), }), }, ]; } return { ...service, resources: fallbackResources, parameters: fallbackParameters, role: updatedRoles, }; }; export const extractParametersFromConfig = ( parameterConfig: any, ): { [key: string]: string }[] => { const parameters: { [key: string]: string }[] = []; if (!parameterConfig || typeof parameterConfig !== "object") return parameters; Object.entries(parameterConfig).forEach( ([paramName, paramValue]: [string, any]) => { let value: string; let description: string | undefined; if (typeof paramValue === "object" && paramValue !== null) { if (paramValue.value !== undefined) value = paramValue.value; else if (paramValue.default !== undefined) value = String(paramValue.default); else value = JSON.stringify(paramValue); if (paramValue.description) description = paramValue.description; } else { value = String(paramValue); } const parameter: { [key: string]: string } = { name: paramName, value, type: typeof paramValue, configKey: paramName, }; if (description) parameter.description = description; parameters.push(parameter); }, ); return parameters; }; export const extractParametersFromFilesystem = ( filesystem: any, currentParameters: { [key: string]: string }[], ): { [key: string]: string }[] => { const parameters: { [key: string]: string }[] = []; Object.entries(filesystem).forEach(([key, value]: [string, any]) => { if (value.data) { const existingParam = currentParameters.find( (param) => param.value === value.data.value, ); parameters.push({ name: existingParam?.name || key, path: value.path, value: value.data.value, type: "file", }); if (existingParam) currentParameters.splice(currentParameters.indexOf(existingParam), 1); } }); return parameters; }; export const extractResourcesFromFilesystem = ( filesystem: any, currentResources: Resource[], ): Resource[] => { const resources: Resource[] = []; Object.entries(filesystem).forEach(([key, value]: [string, any]) => { if (value.volume) { const existingResource = currentResources.find( (res) => res.name === value.volume, ); resources.push({ type: "volume", name: existingResource?.name || value.volume, value: existingResource?.value || value.volume, key: value.path, kind: (existingResource?.kind as | "persistent" | "volatile" | "nonReplicated") || "storage", status: existingResource?.status || "available", tenant: "", }); if (existingResource) currentResources.splice(currentResources.indexOf(existingResource), 1); } }); return resources; }; export const extractResources = (resourceConfig: any): Resource[] => { const resources: Resource[] = []; Object.entries(resourceConfig).forEach( ([resourceName, resourceData]: [string, any]) => { let resource: Resource; if (resourceData.volume) { const volumeData = resourceData.volume; const sizeInGB = convertToGigabytes(volumeData.size, volumeData.unit); resource = { type: "volume", name: resourceName, value: sizeInGB ? sizeInGB.toString() : "1", kind: volumeData.type || "storage", status: "available", tenant: "", }; } else if (resourceData.port) { resource = { type: "port", name: resourceName, value: extractResourceName(resourceData.port), status: "available", tenant: "", }; } else if (resourceData.domain) { resource = { type: "domain", name: resourceName, value: extractResourceName(resourceData.domain), status: "available", tenant: "", }; } else if (resourceData.secret) { resource = { type: "secret", name: resourceName, value: extractResourceName(resourceData.secret), status: "available", tenant: "", }; } else if (resourceData.certificate) { resource = { type: "certificate", name: resourceName, value: extractResourceName( resourceData.certificate.cert || resourceData.certificate, ), key: resourceData.key, domain: resourceData.domain, status: "available", tenant: "", }; } else if (resourceData.ca) { resource = { type: "ca", name: resourceName, value: extractResourceName(resourceData.ca), status: "available", tenant: "", }; } else { resource = { type: resourceData.type || "unknown", name: resourceName, value: extractResourceName( resourceData.value || resourceData.size || "", ), status: "available", tenant: "", }; if (resourceData.kind) resource.kind = resourceData.kind; if (resourceData.domain) resource.domain = resourceData.domain; if (resourceData.key) resource.key = resourceData.key; if (resourceData.maxItems) resource.maxItems = resourceData.maxItems; } resources.push(resource); }, ); return resources; }; const processScalingConfig = (meta: any): any => { if (!meta?.scaling?.simple || Object.keys(meta.scaling.simple).length === 0) return { cpu: { up: "80%", down: "20%" }, memory: { up: "80%", down: "20%" }, instances: { max: 1, min: 1 }, histeresys: "5", }; const firstRoleName = Object.keys(meta.scaling.simple)[0]; const roleScaling = meta.scaling.simple[firstRoleName]; if (!roleScaling) return { cpu: { up: "80%", down: "20%" }, memory: { up: "80%", down: "20%" }, instances: { max: 1, min: 1 }, histeresys: "5", }; return { cpu: { up: `${roleScaling.scale_up?.cpu || 80}%`, down: `${roleScaling.scale_down?.cpu || 20}%`, }, memory: { up: `${roleScaling.scale_up?.memory || 80}%`, down: `${roleScaling.scale_down?.memory || 20}%`, }, instances: { max: roleScaling.max_replicas || 1, min: roleScaling.min_replicas || 1, }, histeresys: `${roleScaling.hysteresis || 5}`, }; }; const extractResourceName = (resourcePath: string): string => { if (!resourcePath) return ""; const parts = resourcePath.split("/"); if (parts.length === 2) { if (parts[0] === "cluster.core") return resourcePath; return parts[1]; } return resourcePath; };