chore: rename cameleer3 to cameleer
Rename Java packages from net.siegeln.cameleer3 to net.siegeln.cameleer, update all references in workflows, Docker configs, docs, and bootstrap. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
210
docs/superpowers/plans/2026-04-10-fleet-health-plan.md
Normal file
210
docs/superpowers/plans/2026-04-10-fleet-health-plan.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# Fleet Health at a Glance Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Add agent count, environment count, and agent limit columns to the vendor tenant list so the vendor can see fleet utilization at a glance.
|
||||
|
||||
**Architecture:** Extend the existing `VendorTenantSummary` record with three int fields. The list endpoint fetches counts from each active tenant's server via existing M2M API methods (`getAgentCount`, `getEnvironmentCount`), parallelized with `CompletableFuture`. Frontend adds two columns (Agents, Envs) to the DataTable.
|
||||
|
||||
**Tech Stack:** Java 21, Spring Boot, CompletableFuture, React, TypeScript, @cameleer/design-system DataTable
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Extend backend — VendorTenantSummary + parallel fetch
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/main/java/net/siegeln/cameleer/saas/vendor/VendorTenantController.java`
|
||||
|
||||
- [ ] **Step 1: Extend the VendorTenantSummary record**
|
||||
|
||||
In `VendorTenantController.java`, replace the record at lines 39-48:
|
||||
|
||||
```java
|
||||
public record VendorTenantSummary(
|
||||
UUID id,
|
||||
String name,
|
||||
String slug,
|
||||
String tier,
|
||||
String status,
|
||||
String serverState,
|
||||
String licenseExpiry,
|
||||
String provisionError,
|
||||
int agentCount,
|
||||
int environmentCount,
|
||||
int agentLimit
|
||||
) {}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update the listAll() endpoint to fetch counts in parallel**
|
||||
|
||||
Replace the `listAll()` method at lines 60-77:
|
||||
|
||||
```java
|
||||
@GetMapping
|
||||
public ResponseEntity<List<VendorTenantSummary>> listAll() {
|
||||
var tenants = vendorTenantService.listAll();
|
||||
|
||||
// Parallel health fetch for active tenants
|
||||
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<VendorTenantSummary> summaries = futures.stream()
|
||||
.map(java.util.concurrent.CompletableFuture::join)
|
||||
.toList();
|
||||
|
||||
return ResponseEntity.ok(summaries);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Expose ServerApiClient from VendorTenantService**
|
||||
|
||||
Add a getter in `src/main/java/net/siegeln/cameleer/saas/vendor/VendorTenantService.java`:
|
||||
|
||||
```java
|
||||
public ServerApiClient getServerApiClient() {
|
||||
return serverApiClient;
|
||||
}
|
||||
```
|
||||
|
||||
(The `serverApiClient` field already exists in VendorTenantService — check around line 30.)
|
||||
|
||||
- [ ] **Step 4: Verify compilation**
|
||||
|
||||
Run: `./mvnw compile -pl . -q`
|
||||
Expected: BUILD SUCCESS
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add src/main/java/net/siegeln/cameleer/saas/vendor/VendorTenantController.java \
|
||||
src/main/java/net/siegeln/cameleer/saas/vendor/VendorTenantService.java
|
||||
git commit -m "feat: add agent/env counts to vendor tenant list endpoint"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Update frontend types and columns
|
||||
|
||||
**Files:**
|
||||
- Modify: `ui/src/types/api.ts`
|
||||
- Modify: `ui/src/pages/vendor/VendorTenantsPage.tsx`
|
||||
|
||||
- [ ] **Step 1: Add fields to VendorTenantSummary TypeScript type**
|
||||
|
||||
In `ui/src/types/api.ts`, update the `VendorTenantSummary` interface:
|
||||
|
||||
```typescript
|
||||
export interface VendorTenantSummary {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
tier: string;
|
||||
status: string;
|
||||
serverState: string;
|
||||
licenseExpiry: string | null;
|
||||
provisionError: string | null;
|
||||
agentCount: number;
|
||||
environmentCount: number;
|
||||
agentLimit: number;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add Agents and Envs columns to VendorTenantsPage**
|
||||
|
||||
In `ui/src/pages/vendor/VendorTenantsPage.tsx`, add a helper function after `statusColor`:
|
||||
|
||||
```typescript
|
||||
function formatUsage(used: number, limit: number): string {
|
||||
return limit < 0 ? `${used} / ∞` : `${used} / ${limit}`;
|
||||
}
|
||||
```
|
||||
|
||||
Then add two column entries in the `columns` array, after the `serverState` column (after line 54) and before the `licenseExpiry` column:
|
||||
|
||||
```typescript
|
||||
{
|
||||
key: 'agentCount',
|
||||
header: 'Agents',
|
||||
render: (_v, row) => (
|
||||
<span style={{ fontFamily: 'monospace', fontSize: '0.875rem' }}>
|
||||
{formatUsage(row.agentCount, row.agentLimit)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'environmentCount',
|
||||
header: 'Envs',
|
||||
render: (_v, row) => (
|
||||
<span style={{ fontFamily: 'monospace', fontSize: '0.875rem' }}>
|
||||
{row.environmentCount}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Build the UI**
|
||||
|
||||
Run: `cd ui && npm run build`
|
||||
Expected: Build succeeds with no errors.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add ui/src/types/api.ts ui/src/pages/vendor/VendorTenantsPage.tsx
|
||||
git commit -m "feat: show agent/env counts in vendor tenant list"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Verify end-to-end
|
||||
|
||||
- [ ] **Step 1: Run backend tests**
|
||||
|
||||
Run: `./mvnw test -pl . -q`
|
||||
Expected: All tests pass. (Existing tests use mocks, the new parallel fetch doesn't break them since it only affects the controller's list mapping.)
|
||||
|
||||
- [ ] **Step 2: Verify in browser**
|
||||
|
||||
Navigate to the vendor tenant list. Confirm:
|
||||
- "Agents" column shows "0 / ∞" (or actual count if agents are connected)
|
||||
- "Envs" column shows "1" (or actual count)
|
||||
- PROVISIONING/SUSPENDED tenants show "0" for both
|
||||
- 30s auto-refresh still works
|
||||
|
||||
- [ ] **Step 3: Final commit and push**
|
||||
|
||||
```bash
|
||||
git push
|
||||
```
|
||||
Reference in New Issue
Block a user