diff --git a/apix-admin/src/main/java/org/botstandards/apix/admin/resource/DashboardResource.java b/apix-admin/src/main/java/org/botstandards/apix/admin/resource/DashboardResource.java index defe785..ac12145 100644 --- a/apix-admin/src/main/java/org/botstandards/apix/admin/resource/DashboardResource.java +++ b/apix-admin/src/main/java/org/botstandards/apix/admin/resource/DashboardResource.java @@ -6,6 +6,7 @@ import io.quarkus.qute.TemplateInstance; import jakarta.inject.Inject; import jakarta.ws.rs.*; import jakarta.ws.rs.core.*; +import java.util.Map; import org.botstandards.apix.common.AdminStatsResponse; import org.botstandards.apix.common.SandboxDashboardResponse; import org.botstandards.apix.admin.client.AdminRegistryClient; @@ -47,6 +48,24 @@ public class DashboardResource { } } + @GET + @Path("/sandbox/{uuid}/poll") + @Produces(MediaType.APPLICATION_JSON) + public Response sandboxPoll(@PathParam("uuid") String uuid) { + try { + SandboxDashboardResponse d = registry.sandboxDetail(adminKey, uuid); + long total = d.usage() == null ? 0L : + d.usage().values().stream().mapToLong(Long::longValue).sum(); + return Response.ok(Map.of( + "totalRequests", total, + "ratePerMinute", d.ratePerMinute() + )).build(); + } catch (Exception e) { + LOG.errorf(e, "Poll failed for sandbox %s", uuid); + return Response.serverError().build(); + } + } + @GET @Path("/sandbox/{uuid}") public TemplateInstance sandboxDashboard(@PathParam("uuid") String uuid) { diff --git a/apix-admin/src/main/resources/META-INF/resources/admin.js b/apix-admin/src/main/resources/META-INF/resources/admin.js index faed53e..9999279 100644 --- a/apix-admin/src/main/resources/META-INF/resources/admin.js +++ b/apix-admin/src/main/resources/META-INF/resources/admin.js @@ -62,10 +62,12 @@ points = __D.registrarPoints.map(p => ({ lat: p.lat, lon: p.lon, label: p.name, tier: p.tier, type: 'registrar' })); - } else if (__D.registrarLat != null) { - // sandbox drill-down — registrar star - points.push({ lat: __D.registrarLat, lon: __D.registrarLon, label: __D.name, tier: __D.tier, type: 'registrar' }); - // agent visits + } else if (__D.sandboxId) { + // sandbox drill-down + if (__D.registrarLat != null) { + points.push({ lat: __D.registrarLat, lon: __D.registrarLon, label: __D.name, tier: __D.tier, type: 'registrar' }); + } + // agent blinks — always, even when sandbox has no declared location (__D.recentVisits || []).forEach(v => { points.push({ lat: v.lat, lon: v.lon, type: 'agent' }); }); diff --git a/apix-admin/src/main/resources/templates/DashboardResource/sandbox.html b/apix-admin/src/main/resources/templates/DashboardResource/sandbox.html index 145d903..97aed2d 100644 --- a/apix-admin/src/main/resources/templates/DashboardResource/sandbox.html +++ b/apix-admin/src/main/resources/templates/DashboardResource/sandbox.html @@ -63,6 +63,15 @@ button.promote:hover { background: #2ea043; } 0% { opacity:0; r:2; } 25% { opacity:0.9; r:5; } 65% { opacity:0.4; r:4; } 100% { opacity:0; r:2; } } .agent-blink { fill: #3d8bfd; opacity:0; animation: blink-pulse 2.8s ease-in-out infinite; } + +#rate-wrap { background: #060d18; padding: 0 1.5rem 1rem; border-bottom: 1px solid #21262d; } +.rate-header { display: flex; align-items: baseline; gap: 0.75rem; padding: 0.6rem 0 0.4rem; } +.rate-title { font-size: 0.65rem; color: #484f58; text-transform: uppercase; letter-spacing: 0.06em; } +.rate-live { font-size: 0.75rem; color: #388bfd; } +.rate-limit-label { font-size: 0.65rem; color: #f8514966; margin-left: auto; } +#rate-chart { display: block; width: 100%; overflow: visible; } +.rate-area { fill: #1f6feb33; stroke: #388bfd; stroke-width: 1.5; } +.rate-limit-line { stroke: #f85149; stroke-width: 0.8; stroke-dasharray: 4 3; opacity: 0.5; } @@ -93,7 +102,7 @@ button.promote:hover { background: #2ea043; }
Rate limit
{dashboard.ratePerMinute}
-
req / min
+
now: req/min
Expires
@@ -109,6 +118,15 @@ button.promote:hover { background: #2ea043; } {/if}
+
+
+ Live rate + — req/min + limit: {dashboard.ratePerMinute}/min ── +
+ +
+
@@ -132,5 +150,71 @@ button.promote:hover { background: #2ea043; } +