Improve logging recovery, resource scaling, and app deploy logging.

Auto-reconnect Elasticsearch port-forward after cluster or API restarts, poll log status in the UI, and apply storage changes through billing upgrade for all workloads. Add Redis/RabbitMQ PVC resize, Helm ES credentials for Fluent Bit, and fix deploy progress overlay behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
keyhan
2026-05-25 21:41:00 +03:30
parent bf9e827f85
commit dc9830383b
18 changed files with 1068 additions and 414 deletions
+66 -35
View File
@@ -830,7 +830,7 @@ export class BillingController {
private buildUpgradeEntityPatch(app: Application, dto: UpgradeResourcesDto): Partial<Application> {
const pt = app.productType ?? ProductType.APPLICATION;
if (pt === ProductType.MANAGED_REDIS && dto.redisResources) {
if (dto.redisResources) {
return {
optionalServiceResources: {
...app.optionalServiceResources,
@@ -844,7 +844,7 @@ export class BillingController {
};
}
if (pt === ProductType.MANAGED_RABBITMQ && dto.rabbitmqResources) {
if (dto.rabbitmqResources) {
return {
optionalServiceResources: {
...app.optionalServiceResources,
@@ -860,6 +860,10 @@ export class BillingController {
};
}
if (pt === ProductType.MANAGED_REDIS || pt === ProductType.MANAGED_RABBITMQ) {
return {};
}
return {
cpuRequest: dto.cpuRequest || app.cpuRequest,
cpuLimit: dto.cpuLimit || app.cpuLimit,
@@ -899,46 +903,39 @@ export class BillingController {
}
if (pt === ProductType.MANAGED_REDIS) {
const res = app.optionalServiceResources?.redis;
if (res) {
await this.kubernetesService.updateResources(
app,
{
cpuRequest: res.cpuRequest,
cpuLimit: res.cpuLimit,
memoryRequest: res.memoryRequest,
memoryLimit: res.memoryLimit,
},
'redis',
);
}
await this.applyOptionalServiceUpgrade(app, dto, previous, 'redis');
return;
}
if (pt === ProductType.MANAGED_RABBITMQ) {
const res = app.optionalServiceResources?.rabbitmq;
if (res) {
await this.kubernetesService.updateResources(
app,
{
cpuRequest: res.cpuRequest,
cpuLimit: res.cpuLimit,
memoryRequest: res.memoryRequest,
memoryLimit: res.memoryLimit,
},
'rabbitmq',
);
}
await this.applyOptionalServiceUpgrade(app, dto, previous, 'rabbitmq');
return;
}
await this.kubernetesService.updateResources(app, {
cpuRequest: dto.cpuRequest,
cpuLimit: dto.cpuLimit,
memoryRequest: dto.memoryRequest,
memoryLimit: dto.memoryLimit,
replicas: dto.replicas,
});
if (dto.redisResources && app.enableRedis) {
await this.applyOptionalServiceUpgrade(app, dto, previous, 'redis');
}
if (dto.rabbitmqResources && app.enableRabbitmq) {
await this.applyOptionalServiceUpgrade(app, dto, previous, 'rabbitmq');
}
const touchesAppWorkload =
dto.cpuRequest !== undefined ||
dto.cpuLimit !== undefined ||
dto.memoryRequest !== undefined ||
dto.memoryLimit !== undefined ||
dto.replicas !== undefined;
if (touchesAppWorkload) {
await this.kubernetesService.updateResources(app, {
cpuRequest: dto.cpuRequest,
cpuLimit: dto.cpuLimit,
memoryRequest: dto.memoryRequest,
memoryLimit: dto.memoryLimit,
replicas: dto.replicas,
});
}
if (dto.appStorageSize && dto.appStorageSize !== previous.appStorageSize) {
await this.kubernetesService.resizeAppStoragePvc(app, dto.appStorageSize);
@@ -957,6 +954,40 @@ export class BillingController {
}
}
private async applyOptionalServiceUpgrade(
app: Application,
dto: UpgradeResourcesDto,
previous: Application,
service: 'redis' | 'rabbitmq',
): Promise<void> {
const res = app.optionalServiceResources?.[service];
const dtoRes = service === 'redis' ? dto.redisResources : dto.rabbitmqResources;
if (res) {
await this.kubernetesService.updateResources(
app,
{
cpuRequest: res.cpuRequest,
cpuLimit: res.cpuLimit,
memoryRequest: res.memoryRequest,
memoryLimit: res.memoryLimit,
},
service,
);
}
const prevGi =
previous.optionalServiceResources?.[service]?.storageGi ?? (service === 'redis' ? 1 : 2);
const nextGi = dtoRes?.storageGi;
if (nextGi != null && nextGi > prevGi) {
const resize =
service === 'redis'
? await this.kubernetesService.resizeRedisStoragePvc(app, `${nextGi}Gi`)
: await this.kubernetesService.resizeRabbitmqStoragePvc(app, `${nextGi}Gi`);
if (!resize.success) {
throw new BadRequestException(resize.message);
}
}
}
private async getAppWithAccess(user: any, applicationId: string) {
const isAdminOrSales = user.role === UserRole.ADMIN || user.role === UserRole.SALES;