From d7ce0aaf8c27d374a28781ef936fcc3c0d265e1a Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Fri, 10 Apr 2026 22:01:02 +0200 Subject: [PATCH] feat: add agent/env counts to vendor tenant list endpoint Extend VendorTenantSummary with agentCount, environmentCount, and agentLimit fields. Fetch counts in parallel using CompletableFuture per tenant, only calling server API for ACTIVE tenants with RUNNING servers. Agent limit extracted from license limits JSONB. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../saas/vendor/VendorTenantController.java | 51 ++++++++++++++----- .../saas/vendor/VendorTenantService.java | 4 ++ 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/main/java/net/siegeln/cameleer/saas/vendor/VendorTenantController.java b/src/main/java/net/siegeln/cameleer/saas/vendor/VendorTenantController.java index 518a4fb..795c545 100644 --- a/src/main/java/net/siegeln/cameleer/saas/vendor/VendorTenantController.java +++ b/src/main/java/net/siegeln/cameleer/saas/vendor/VendorTenantController.java @@ -44,7 +44,10 @@ public class VendorTenantController { String status, String serverState, String licenseExpiry, - String provisionError + String provisionError, + int agentCount, + int environmentCount, + int agentLimit ) {} public record VendorTenantDetail( @@ -59,19 +62,39 @@ public class VendorTenantController { @GetMapping public ResponseEntity> listAll() { - List summaries = vendorTenantService.listAll().stream() - .map(tenant -> { - ServerStatus status = vendorTenantService.getServerStatus(tenant); - String licenseExpiry = vendorTenantService - .getLicenseForTenant(tenant.getId()) - .map(l -> l.getExpiresAt() != null ? l.getExpiresAt().toString() : null) - .orElse(null); - return new VendorTenantSummary( - tenant.getId(), tenant.getName(), tenant.getSlug(), - tenant.getTier().name(), tenant.getStatus().name(), - status.state().name(), licenseExpiry, tenant.getProvisionError() - ); - }) + var tenants = vendorTenantService.listAll(); + var futures = tenants.stream().map(tenant -> java.util.concurrent.CompletableFuture.supplyAsync(() -> { + ServerStatus status = vendorTenantService.getServerStatus(tenant); + String licenseExpiry = vendorTenantService + .getLicenseForTenant(tenant.getId()) + .map(l -> l.getExpiresAt() != null ? l.getExpiresAt().toString() : null) + .orElse(null); + int agentCount = 0; + int environmentCount = 0; + int agentLimit = -1; + String endpoint = tenant.getServerEndpoint(); + boolean isActive = "ACTIVE".equals(tenant.getStatus().name()); + if (isActive && endpoint != null && !endpoint.isBlank() && "RUNNING".equals(status.state().name())) { + var serverApi = vendorTenantService.getServerApiClient(); + agentCount = serverApi.getAgentCount(endpoint); + environmentCount = serverApi.getEnvironmentCount(endpoint); + } + var license = vendorTenantService.getLicenseForTenant(tenant.getId()); + if (license.isPresent() && license.get().getLimits() != null) { + var limits = license.get().getLimits(); + if (limits.containsKey("agents")) { + agentLimit = ((Number) limits.get("agents")).intValue(); + } + } + return new VendorTenantSummary( + tenant.getId(), tenant.getName(), tenant.getSlug(), + tenant.getTier().name(), tenant.getStatus().name(), + status.state().name(), licenseExpiry, tenant.getProvisionError(), + agentCount, environmentCount, agentLimit + ); + })).toList(); + List summaries = futures.stream() + .map(java.util.concurrent.CompletableFuture::join) .toList(); return ResponseEntity.ok(summaries); } diff --git a/src/main/java/net/siegeln/cameleer/saas/vendor/VendorTenantService.java b/src/main/java/net/siegeln/cameleer/saas/vendor/VendorTenantService.java index 55bde86..5f9eebb 100644 --- a/src/main/java/net/siegeln/cameleer/saas/vendor/VendorTenantService.java +++ b/src/main/java/net/siegeln/cameleer/saas/vendor/VendorTenantService.java @@ -184,6 +184,10 @@ public class VendorTenantService { } } + public ServerApiClient getServerApiClient() { + return serverApiClient; + } + public List listAll() { return tenantService.findAll().stream() .filter(t -> t.getStatus() != TenantStatus.DELETED)