refactor: rename agent group→application across entire codebase
Complete the group→application terminology rename in the agent registry subsystem: - AgentInfo: field group → application, all wither methods updated - AgentRegistryService: findByGroup → findByApplication - AgentInstanceResponse: field group → application (API response) - AgentRegistrationRequest: field group → application (API request) - JwtServiceImpl: parameter names group → application (JWT claim string "group" preserved for token backward compatibility) - All controllers, lifecycle monitor, command controller updated - Integration tests: JSON request bodies "group" → "application" - Frontend: schema.d.ts, openapi.json, agent queries, AgentHealth RBAC group references (groups table, GroupAdminController, etc.) are NOT affected — they are a separate domain concept. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -51,7 +51,7 @@ public class AgentLifecycleMonitor {
|
|||||||
if (before != null && before != agent.state()) {
|
if (before != null && before != agent.state()) {
|
||||||
String eventType = mapTransitionEvent(before, agent.state());
|
String eventType = mapTransitionEvent(before, agent.state());
|
||||||
if (eventType != null) {
|
if (eventType != null) {
|
||||||
agentEventService.recordEvent(agent.id(), agent.group(), eventType,
|
agentEventService.recordEvent(agent.id(), agent.application(), eventType,
|
||||||
agent.name() + " " + before + " -> " + agent.state());
|
agent.name() + " " + before + " -> " + agent.state());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ public class AgentCommandController {
|
|||||||
|
|
||||||
List<AgentInfo> agents = registryService.findAll().stream()
|
List<AgentInfo> agents = registryService.findAll().stream()
|
||||||
.filter(a -> a.state() == AgentState.LIVE)
|
.filter(a -> a.state() == AgentState.LIVE)
|
||||||
.filter(a -> group.equals(a.group()))
|
.filter(a -> group.equals(a.application()))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
List<String> commandIds = new ArrayList<>();
|
List<String> commandIds = new ArrayList<>();
|
||||||
|
|||||||
@@ -102,21 +102,21 @@ public class AgentRegistrationController {
|
|||||||
return ResponseEntity.badRequest().build();
|
return ResponseEntity.badRequest().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
String group = request.group() != null ? request.group() : "default";
|
String application = request.application() != null ? request.application() : "default";
|
||||||
List<String> routeIds = request.routeIds() != null ? request.routeIds() : List.of();
|
List<String> routeIds = request.routeIds() != null ? request.routeIds() : List.of();
|
||||||
var capabilities = request.capabilities() != null ? request.capabilities() : Collections.<String, Object>emptyMap();
|
var capabilities = request.capabilities() != null ? request.capabilities() : Collections.<String, Object>emptyMap();
|
||||||
|
|
||||||
AgentInfo agent = registryService.register(
|
AgentInfo agent = registryService.register(
|
||||||
request.agentId(), request.name(), group, request.version(), routeIds, capabilities);
|
request.agentId(), request.name(), application, request.version(), routeIds, capabilities);
|
||||||
log.info("Agent registered: {} (name={}, group={})", request.agentId(), request.name(), group);
|
log.info("Agent registered: {} (name={}, application={})", request.agentId(), request.name(), application);
|
||||||
|
|
||||||
agentEventService.recordEvent(request.agentId(), group, "REGISTERED",
|
agentEventService.recordEvent(request.agentId(), application, "REGISTERED",
|
||||||
"Agent registered: " + request.name());
|
"Agent registered: " + request.name());
|
||||||
|
|
||||||
// Issue JWT tokens with AGENT role
|
// Issue JWT tokens with AGENT role
|
||||||
List<String> roles = List.of("AGENT");
|
List<String> roles = List.of("AGENT");
|
||||||
String accessToken = jwtService.createAccessToken(request.agentId(), group, roles);
|
String accessToken = jwtService.createAccessToken(request.agentId(), application, roles);
|
||||||
String refreshToken = jwtService.createRefreshToken(request.agentId(), group, roles);
|
String refreshToken = jwtService.createRefreshToken(request.agentId(), application, roles);
|
||||||
|
|
||||||
return ResponseEntity.ok(new AgentRegistrationResponse(
|
return ResponseEntity.ok(new AgentRegistrationResponse(
|
||||||
agent.id(),
|
agent.id(),
|
||||||
@@ -166,8 +166,8 @@ public class AgentRegistrationController {
|
|||||||
// Preserve roles from refresh token
|
// Preserve roles from refresh token
|
||||||
List<String> roles = result.roles().isEmpty()
|
List<String> roles = result.roles().isEmpty()
|
||||||
? List.of("AGENT") : result.roles();
|
? List.of("AGENT") : result.roles();
|
||||||
String newAccessToken = jwtService.createAccessToken(agentId, agent.group(), roles);
|
String newAccessToken = jwtService.createAccessToken(agentId, agent.application(), roles);
|
||||||
String newRefreshToken = jwtService.createRefreshToken(agentId, agent.group(), roles);
|
String newRefreshToken = jwtService.createRefreshToken(agentId, agent.application(), roles);
|
||||||
|
|
||||||
return ResponseEntity.ok(new AgentRefreshResponse(newAccessToken, newRefreshToken));
|
return ResponseEntity.ok(new AgentRefreshResponse(newAccessToken, newRefreshToken));
|
||||||
}
|
}
|
||||||
@@ -187,13 +187,13 @@ public class AgentRegistrationController {
|
|||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@Operation(summary = "List all agents",
|
@Operation(summary = "List all agents",
|
||||||
description = "Returns all registered agents with runtime metrics, optionally filtered by status and/or group")
|
description = "Returns all registered agents with runtime metrics, optionally filtered by status and/or application")
|
||||||
@ApiResponse(responseCode = "200", description = "Agent list returned")
|
@ApiResponse(responseCode = "200", description = "Agent list returned")
|
||||||
@ApiResponse(responseCode = "400", description = "Invalid status filter",
|
@ApiResponse(responseCode = "400", description = "Invalid status filter",
|
||||||
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
|
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
|
||||||
public ResponseEntity<List<AgentInstanceResponse>> listAgents(
|
public ResponseEntity<List<AgentInstanceResponse>> listAgents(
|
||||||
@RequestParam(required = false) String status,
|
@RequestParam(required = false) String status,
|
||||||
@RequestParam(required = false) String group) {
|
@RequestParam(required = false) String application) {
|
||||||
List<AgentInfo> agents;
|
List<AgentInfo> agents;
|
||||||
|
|
||||||
if (status != null) {
|
if (status != null) {
|
||||||
@@ -207,10 +207,10 @@ public class AgentRegistrationController {
|
|||||||
agents = registryService.findAll();
|
agents = registryService.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply group filter if specified
|
// Apply application filter if specified
|
||||||
if (group != null && !group.isBlank()) {
|
if (application != null && !application.isBlank()) {
|
||||||
agents = agents.stream()
|
agents = agents.stream()
|
||||||
.filter(a -> group.equals(a.group()))
|
.filter(a -> application.equals(a.application()))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,11 +221,11 @@ public class AgentRegistrationController {
|
|||||||
List<AgentInstanceResponse> response = finalAgents.stream()
|
List<AgentInstanceResponse> response = finalAgents.stream()
|
||||||
.map(a -> {
|
.map(a -> {
|
||||||
AgentInstanceResponse dto = AgentInstanceResponse.from(a);
|
AgentInstanceResponse dto = AgentInstanceResponse.from(a);
|
||||||
double[] m = agentMetrics.get(a.group());
|
double[] m = agentMetrics.get(a.application());
|
||||||
if (m != null) {
|
if (m != null) {
|
||||||
long groupAgentCount = finalAgents.stream()
|
long appAgentCount = finalAgents.stream()
|
||||||
.filter(ag -> ag.group().equals(a.group())).count();
|
.filter(ag -> ag.application().equals(a.application())).count();
|
||||||
double agentTps = groupAgentCount > 0 ? m[0] / groupAgentCount : 0;
|
double agentTps = appAgentCount > 0 ? m[0] / appAgentCount : 0;
|
||||||
double errorRate = m[1];
|
double errorRate = m[1];
|
||||||
int activeRoutes = (int) m[2];
|
int activeRoutes = (int) m[2];
|
||||||
return dto.withMetrics(agentTps, errorRate, activeRoutes);
|
return dto.withMetrics(agentTps, errorRate, activeRoutes);
|
||||||
|
|||||||
@@ -90,14 +90,14 @@ public class DiagramRenderController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@Operation(summary = "Find diagram by application group and route ID",
|
@Operation(summary = "Find diagram by application and route ID",
|
||||||
description = "Resolves group to agent IDs and finds the latest diagram for the route")
|
description = "Resolves application to agent IDs and finds the latest diagram for the route")
|
||||||
@ApiResponse(responseCode = "200", description = "Diagram layout returned")
|
@ApiResponse(responseCode = "200", description = "Diagram layout returned")
|
||||||
@ApiResponse(responseCode = "404", description = "No diagram found for the given group and route")
|
@ApiResponse(responseCode = "404", description = "No diagram found for the given application and route")
|
||||||
public ResponseEntity<DiagramLayout> findByGroupAndRoute(
|
public ResponseEntity<DiagramLayout> findByApplicationAndRoute(
|
||||||
@RequestParam String group,
|
@RequestParam String application,
|
||||||
@RequestParam String routeId) {
|
@RequestParam String routeId) {
|
||||||
List<String> agentIds = registryService.findByGroup(group).stream()
|
List<String> agentIds = registryService.findByApplication(application).stream()
|
||||||
.map(AgentInfo::id)
|
.map(AgentInfo::id)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public class ExecutionController {
|
|||||||
|
|
||||||
private String resolveApplicationName(String agentId) {
|
private String resolveApplicationName(String agentId) {
|
||||||
AgentInfo agent = registryService.findById(agentId);
|
AgentInfo agent = registryService.findById(agentId);
|
||||||
return agent != null ? agent.group() : "";
|
return agent != null ? agent.application() : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<RouteExecution> parsePayload(String body) throws JsonProcessingException {
|
private List<RouteExecution> parsePayload(String body) throws JsonProcessingException {
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ public class RouteCatalogController {
|
|||||||
public ResponseEntity<List<AppCatalogEntry>> getCatalog() {
|
public ResponseEntity<List<AppCatalogEntry>> getCatalog() {
|
||||||
List<AgentInfo> allAgents = registryService.findAll();
|
List<AgentInfo> allAgents = registryService.findAll();
|
||||||
|
|
||||||
// Group agents by application (group name)
|
// Group agents by application name
|
||||||
Map<String, List<AgentInfo>> agentsByApp = allAgents.stream()
|
Map<String, List<AgentInfo>> agentsByApp = allAgents.stream()
|
||||||
.collect(Collectors.groupingBy(AgentInfo::group, LinkedHashMap::new, Collectors.toList()));
|
.collect(Collectors.groupingBy(AgentInfo::application, LinkedHashMap::new, Collectors.toList()));
|
||||||
|
|
||||||
// Collect all distinct routes per app
|
// Collect all distinct routes per app
|
||||||
Map<String, Set<String>> routesByApp = new LinkedHashMap<>();
|
Map<String, Set<String>> routesByApp = new LinkedHashMap<>();
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ public class SearchController {
|
|||||||
if (application == null || application.isBlank()) {
|
if (application == null || application.isBlank()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return registryService.findByGroup(application).stream()
|
return registryService.findByApplication(application).stream()
|
||||||
.map(AgentInfo::id)
|
.map(AgentInfo::id)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import java.util.Map;
|
|||||||
public record AgentInstanceResponse(
|
public record AgentInstanceResponse(
|
||||||
@NotNull String id,
|
@NotNull String id,
|
||||||
@NotNull String name,
|
@NotNull String name,
|
||||||
@NotNull String group,
|
@NotNull String application,
|
||||||
@NotNull String status,
|
@NotNull String status,
|
||||||
@NotNull List<String> routeIds,
|
@NotNull List<String> routeIds,
|
||||||
@NotNull Instant registeredAt,
|
@NotNull Instant registeredAt,
|
||||||
@@ -29,7 +29,7 @@ public record AgentInstanceResponse(
|
|||||||
public static AgentInstanceResponse from(AgentInfo info) {
|
public static AgentInstanceResponse from(AgentInfo info) {
|
||||||
long uptime = Duration.between(info.registeredAt(), Instant.now()).toSeconds();
|
long uptime = Duration.between(info.registeredAt(), Instant.now()).toSeconds();
|
||||||
return new AgentInstanceResponse(
|
return new AgentInstanceResponse(
|
||||||
info.id(), info.name(), info.group(),
|
info.id(), info.name(), info.application(),
|
||||||
info.state().name(), info.routeIds(),
|
info.state().name(), info.routeIds(),
|
||||||
info.registeredAt(), info.lastHeartbeat(),
|
info.registeredAt(), info.lastHeartbeat(),
|
||||||
info.version(), info.capabilities(),
|
info.version(), info.capabilities(),
|
||||||
@@ -41,7 +41,7 @@ public record AgentInstanceResponse(
|
|||||||
|
|
||||||
public AgentInstanceResponse withMetrics(double tps, double errorRate, int activeRoutes) {
|
public AgentInstanceResponse withMetrics(double tps, double errorRate, int activeRoutes) {
|
||||||
return new AgentInstanceResponse(
|
return new AgentInstanceResponse(
|
||||||
id, name, group, status, routeIds, registeredAt, lastHeartbeat,
|
id, name, application, status, routeIds, registeredAt, lastHeartbeat,
|
||||||
version, capabilities,
|
version, capabilities,
|
||||||
tps, errorRate, activeRoutes, totalRoutes, uptimeSeconds
|
tps, errorRate, activeRoutes, totalRoutes, uptimeSeconds
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import java.util.Map;
|
|||||||
public record AgentRegistrationRequest(
|
public record AgentRegistrationRequest(
|
||||||
@NotNull String agentId,
|
@NotNull String agentId,
|
||||||
@NotNull String name,
|
@NotNull String name,
|
||||||
@Schema(defaultValue = "default") String group,
|
@Schema(defaultValue = "default") String application,
|
||||||
String version,
|
String version,
|
||||||
List<String> routeIds,
|
List<String> routeIds,
|
||||||
Map<String, Object> capabilities
|
Map<String, Object> capabilities
|
||||||
|
|||||||
@@ -60,13 +60,13 @@ public class JwtServiceImpl implements JwtService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String createAccessToken(String subject, String group, List<String> roles) {
|
public String createAccessToken(String subject, String application, List<String> roles) {
|
||||||
return createToken(subject, group, roles, "access", properties.getAccessTokenExpiryMs());
|
return createToken(subject, application, roles, "access", properties.getAccessTokenExpiryMs());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String createRefreshToken(String subject, String group, List<String> roles) {
|
public String createRefreshToken(String subject, String application, List<String> roles) {
|
||||||
return createToken(subject, group, roles, "refresh", properties.getRefreshTokenExpiryMs());
|
return createToken(subject, application, roles, "refresh", properties.getRefreshTokenExpiryMs());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -84,12 +84,12 @@ public class JwtServiceImpl implements JwtService {
|
|||||||
return validateAccessToken(token).subject();
|
return validateAccessToken(token).subject();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createToken(String subject, String group, List<String> roles,
|
private String createToken(String subject, String application, List<String> roles,
|
||||||
String type, long expiryMs) {
|
String type, long expiryMs) {
|
||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
JWTClaimsSet claims = new JWTClaimsSet.Builder()
|
JWTClaimsSet claims = new JWTClaimsSet.Builder()
|
||||||
.subject(subject)
|
.subject(subject)
|
||||||
.claim("group", group)
|
.claim("group", application)
|
||||||
.claim("type", type)
|
.claim("type", type)
|
||||||
.claim("roles", roles)
|
.claim("roles", roles)
|
||||||
.issueTime(Date.from(now))
|
.issueTime(Date.from(now))
|
||||||
@@ -132,7 +132,7 @@ public class JwtServiceImpl implements JwtService {
|
|||||||
throw new InvalidTokenException("Token has no subject");
|
throw new InvalidTokenException("Token has no subject");
|
||||||
}
|
}
|
||||||
|
|
||||||
String group = claims.getStringClaim("group");
|
String application = claims.getStringClaim("group");
|
||||||
|
|
||||||
// Extract roles — may be absent in legacy tokens
|
// Extract roles — may be absent in legacy tokens
|
||||||
List<String> roles;
|
List<String> roles;
|
||||||
@@ -145,7 +145,7 @@ public class JwtServiceImpl implements JwtService {
|
|||||||
roles = List.of();
|
roles = List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new JwtValidationResult(subject, group, roles);
|
return new JwtValidationResult(subject, application, roles);
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
throw new InvalidTokenException("Failed to parse JWT", e);
|
throw new InvalidTokenException("Failed to parse JWT", e);
|
||||||
} catch (JOSEException e) {
|
} catch (JOSEException e) {
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ public class TestSecurityHelper {
|
|||||||
/**
|
/**
|
||||||
* Returns a valid JWT access token with the given roles (no agent registration).
|
* Returns a valid JWT access token with the given roles (no agent registration).
|
||||||
*/
|
*/
|
||||||
public String createToken(String subject, String group, List<String> roles) {
|
public String createToken(String subject, String application, List<String> roles) {
|
||||||
return jwtService.createAccessToken(subject, group, roles);
|
return jwtService.createAccessToken(subject, application, roles);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -38,17 +38,17 @@ class AgentCommandControllerIT extends AbstractPostgresIT {
|
|||||||
operatorJwt = securityHelper.operatorToken();
|
operatorJwt = securityHelper.operatorToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResponseEntity<String> registerAgent(String agentId, String name, String group) {
|
private ResponseEntity<String> registerAgent(String agentId, String name, String application) {
|
||||||
String json = """
|
String json = """
|
||||||
{
|
{
|
||||||
"agentId": "%s",
|
"agentId": "%s",
|
||||||
"name": "%s",
|
"name": "%s",
|
||||||
"group": "%s",
|
"application": "%s",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"routeIds": ["route-1"],
|
"routeIds": ["route-1"],
|
||||||
"capabilities": {}
|
"capabilities": {}
|
||||||
}
|
}
|
||||||
""".formatted(agentId, name, group);
|
""".formatted(agentId, name, application);
|
||||||
|
|
||||||
return restTemplate.postForEntity(
|
return restTemplate.postForEntity(
|
||||||
"/api/v1/agents/register",
|
"/api/v1/agents/register",
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class AgentRegistrationControllerIT extends AbstractPostgresIT {
|
|||||||
{
|
{
|
||||||
"agentId": "%s",
|
"agentId": "%s",
|
||||||
"name": "%s",
|
"name": "%s",
|
||||||
"group": "test-group",
|
"application": "test-group",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"routeIds": ["route-1", "route-2"],
|
"routeIds": ["route-1", "route-2"],
|
||||||
"capabilities": {"tracing": true}
|
"capabilities": {"tracing": true}
|
||||||
|
|||||||
@@ -53,17 +53,17 @@ class AgentSseControllerIT extends AbstractPostgresIT {
|
|||||||
operatorJwt = securityHelper.operatorToken();
|
operatorJwt = securityHelper.operatorToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResponseEntity<String> registerAgent(String agentId, String name, String group) {
|
private ResponseEntity<String> registerAgent(String agentId, String name, String application) {
|
||||||
String json = """
|
String json = """
|
||||||
{
|
{
|
||||||
"agentId": "%s",
|
"agentId": "%s",
|
||||||
"name": "%s",
|
"name": "%s",
|
||||||
"group": "%s",
|
"application": "%s",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"routeIds": ["route-1"],
|
"routeIds": ["route-1"],
|
||||||
"capabilities": {}
|
"capabilities": {}
|
||||||
}
|
}
|
||||||
""".formatted(agentId, name, group);
|
""".formatted(agentId, name, application);
|
||||||
|
|
||||||
return restTemplate.postForEntity(
|
return restTemplate.postForEntity(
|
||||||
"/api/v1/agents/register",
|
"/api/v1/agents/register",
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class BootstrapTokenIT extends AbstractPostgresIT {
|
|||||||
{
|
{
|
||||||
"agentId": "bootstrap-test-agent",
|
"agentId": "bootstrap-test-agent",
|
||||||
"name": "Bootstrap Test",
|
"name": "Bootstrap Test",
|
||||||
"group": "test-group",
|
"application": "test-group",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"routeIds": [],
|
"routeIds": [],
|
||||||
"capabilities": {}
|
"capabilities": {}
|
||||||
@@ -97,7 +97,7 @@ class BootstrapTokenIT extends AbstractPostgresIT {
|
|||||||
{
|
{
|
||||||
"agentId": "bootstrap-test-previous",
|
"agentId": "bootstrap-test-previous",
|
||||||
"name": "Previous Token Test",
|
"name": "Previous Token Test",
|
||||||
"group": "test-group",
|
"application": "test-group",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"routeIds": [],
|
"routeIds": [],
|
||||||
"capabilities": {}
|
"capabilities": {}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class JwtRefreshIT extends AbstractPostgresIT {
|
|||||||
{
|
{
|
||||||
"agentId": "%s",
|
"agentId": "%s",
|
||||||
"name": "Refresh Test Agent",
|
"name": "Refresh Test Agent",
|
||||||
"group": "test-group",
|
"application": "test-group",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"routeIds": [],
|
"routeIds": [],
|
||||||
"capabilities": {}
|
"capabilities": {}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class JwtServiceTest {
|
|||||||
String token = jwtService.createAccessToken("user:admin", "user", roles);
|
String token = jwtService.createAccessToken("user:admin", "user", roles);
|
||||||
JwtService.JwtValidationResult result = jwtService.validateAccessToken(token);
|
JwtService.JwtValidationResult result = jwtService.validateAccessToken(token);
|
||||||
assertEquals("user:admin", result.subject());
|
assertEquals("user:admin", result.subject());
|
||||||
assertEquals("user", result.group());
|
assertEquals("user", result.application());
|
||||||
assertEquals(roles, result.roles());
|
assertEquals(roles, result.roles());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ class JwtServiceTest {
|
|||||||
String token = jwtService.createRefreshToken("agent-1", "default", roles);
|
String token = jwtService.createRefreshToken("agent-1", "default", roles);
|
||||||
JwtService.JwtValidationResult result = jwtService.validateRefreshToken(token);
|
JwtService.JwtValidationResult result = jwtService.validateRefreshToken(token);
|
||||||
assertEquals("agent-1", result.subject());
|
assertEquals("agent-1", result.subject());
|
||||||
assertEquals("default", result.group());
|
assertEquals("default", result.application());
|
||||||
assertEquals(roles, result.roles());
|
assertEquals(roles, result.roles());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class RegistrationSecurityIT extends AbstractPostgresIT {
|
|||||||
{
|
{
|
||||||
"agentId": "%s",
|
"agentId": "%s",
|
||||||
"name": "Security Test Agent",
|
"name": "Security Test Agent",
|
||||||
"group": "test-group",
|
"application": "test-group",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"routeIds": [],
|
"routeIds": [],
|
||||||
"capabilities": {}
|
"capabilities": {}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class SseSigningIT extends AbstractPostgresIT {
|
|||||||
{
|
{
|
||||||
"agentId": "%s",
|
"agentId": "%s",
|
||||||
"name": "SSE Signing Test Agent",
|
"name": "SSE Signing Test Agent",
|
||||||
"group": "test-group",
|
"application": "test-group",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"routeIds": ["route-1"],
|
"routeIds": ["route-1"],
|
||||||
"capabilities": {}
|
"capabilities": {}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import java.util.Map;
|
|||||||
*
|
*
|
||||||
* @param id agent-provided persistent identifier
|
* @param id agent-provided persistent identifier
|
||||||
* @param name human-readable agent name
|
* @param name human-readable agent name
|
||||||
* @param group logical grouping (e.g., "order-service-prod")
|
* @param application application name (e.g., "order-service-prod")
|
||||||
* @param version agent software version
|
* @param version agent software version
|
||||||
* @param routeIds list of Camel route IDs managed by this agent
|
* @param routeIds list of Camel route IDs managed by this agent
|
||||||
* @param capabilities agent-declared capabilities (free-form)
|
* @param capabilities agent-declared capabilities (free-form)
|
||||||
@@ -25,7 +25,7 @@ import java.util.Map;
|
|||||||
public record AgentInfo(
|
public record AgentInfo(
|
||||||
String id,
|
String id,
|
||||||
String name,
|
String name,
|
||||||
String group,
|
String application,
|
||||||
String version,
|
String version,
|
||||||
List<String> routeIds,
|
List<String> routeIds,
|
||||||
Map<String, Object> capabilities,
|
Map<String, Object> capabilities,
|
||||||
@@ -36,28 +36,28 @@ public record AgentInfo(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
public AgentInfo withState(AgentState newState) {
|
public AgentInfo withState(AgentState newState) {
|
||||||
return new AgentInfo(id, name, group, version, routeIds, capabilities,
|
return new AgentInfo(id, name, application, version, routeIds, capabilities,
|
||||||
newState, registeredAt, lastHeartbeat, staleTransitionTime);
|
newState, registeredAt, lastHeartbeat, staleTransitionTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AgentInfo withLastHeartbeat(Instant newLastHeartbeat) {
|
public AgentInfo withLastHeartbeat(Instant newLastHeartbeat) {
|
||||||
return new AgentInfo(id, name, group, version, routeIds, capabilities,
|
return new AgentInfo(id, name, application, version, routeIds, capabilities,
|
||||||
state, registeredAt, newLastHeartbeat, staleTransitionTime);
|
state, registeredAt, newLastHeartbeat, staleTransitionTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AgentInfo withRegisteredAt(Instant newRegisteredAt) {
|
public AgentInfo withRegisteredAt(Instant newRegisteredAt) {
|
||||||
return new AgentInfo(id, name, group, version, routeIds, capabilities,
|
return new AgentInfo(id, name, application, version, routeIds, capabilities,
|
||||||
state, newRegisteredAt, lastHeartbeat, staleTransitionTime);
|
state, newRegisteredAt, lastHeartbeat, staleTransitionTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AgentInfo withStaleTransitionTime(Instant newStaleTransitionTime) {
|
public AgentInfo withStaleTransitionTime(Instant newStaleTransitionTime) {
|
||||||
return new AgentInfo(id, name, group, version, routeIds, capabilities,
|
return new AgentInfo(id, name, application, version, routeIds, capabilities,
|
||||||
state, registeredAt, lastHeartbeat, newStaleTransitionTime);
|
state, registeredAt, lastHeartbeat, newStaleTransitionTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AgentInfo withMetadata(String name, String group, String version,
|
public AgentInfo withMetadata(String name, String application, String version,
|
||||||
List<String> routeIds, Map<String, Object> capabilities) {
|
List<String> routeIds, Map<String, Object> capabilities) {
|
||||||
return new AgentInfo(id, name, group, version, routeIds, capabilities,
|
return new AgentInfo(id, name, application, version, routeIds, capabilities,
|
||||||
state, registeredAt, lastHeartbeat, staleTransitionTime);
|
state, registeredAt, lastHeartbeat, staleTransitionTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,10 +43,10 @@ public class AgentRegistryService {
|
|||||||
* Register a new agent or re-register an existing one.
|
* Register a new agent or re-register an existing one.
|
||||||
* Re-registration updates metadata, transitions state to LIVE, and resets timestamps.
|
* Re-registration updates metadata, transitions state to LIVE, and resets timestamps.
|
||||||
*/
|
*/
|
||||||
public AgentInfo register(String id, String name, String group, String version,
|
public AgentInfo register(String id, String name, String application, String version,
|
||||||
List<String> routeIds, Map<String, Object> capabilities) {
|
List<String> routeIds, Map<String, Object> capabilities) {
|
||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
AgentInfo newAgent = new AgentInfo(id, name, group, version,
|
AgentInfo newAgent = new AgentInfo(id, name, application, version,
|
||||||
List.copyOf(routeIds), Map.copyOf(capabilities),
|
List.copyOf(routeIds), Map.copyOf(capabilities),
|
||||||
AgentState.LIVE, now, now, null);
|
AgentState.LIVE, now, now, null);
|
||||||
|
|
||||||
@@ -55,13 +55,13 @@ public class AgentRegistryService {
|
|||||||
// Re-registration: update metadata, reset to LIVE
|
// Re-registration: update metadata, reset to LIVE
|
||||||
log.info("Agent {} re-registering (was {})", id, existing.state());
|
log.info("Agent {} re-registering (was {})", id, existing.state());
|
||||||
return existing
|
return existing
|
||||||
.withMetadata(name, group, version, List.copyOf(routeIds), Map.copyOf(capabilities))
|
.withMetadata(name, application, version, List.copyOf(routeIds), Map.copyOf(capabilities))
|
||||||
.withState(AgentState.LIVE)
|
.withState(AgentState.LIVE)
|
||||||
.withLastHeartbeat(now)
|
.withLastHeartbeat(now)
|
||||||
.withRegisteredAt(now)
|
.withRegisteredAt(now)
|
||||||
.withStaleTransitionTime(null);
|
.withStaleTransitionTime(null);
|
||||||
}
|
}
|
||||||
log.info("Agent {} registered (name={}, group={})", id, name, group);
|
log.info("Agent {} registered (name={}, application={})", id, name, application);
|
||||||
return newAgent;
|
return newAgent;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -168,11 +168,11 @@ public class AgentRegistryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all agents belonging to the given application group.
|
* Return all agents belonging to the given application.
|
||||||
*/
|
*/
|
||||||
public List<AgentInfo> findByGroup(String group) {
|
public List<AgentInfo> findByApplication(String application) {
|
||||||
return agents.values().stream()
|
return agents.values().stream()
|
||||||
.filter(a -> group.equals(a.group()))
|
.filter(a -> application.equals(a.application()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,21 +14,21 @@ public interface JwtService {
|
|||||||
/**
|
/**
|
||||||
* Validated JWT payload.
|
* Validated JWT payload.
|
||||||
*
|
*
|
||||||
* @param subject the {@code sub} claim (agent ID or {@code user:<username>})
|
* @param subject the {@code sub} claim (agent ID or {@code user:<username>})
|
||||||
* @param group the {@code group} claim
|
* @param application the {@code group} claim (application name)
|
||||||
* @param roles the {@code roles} claim (e.g. {@code ["AGENT"]}, {@code ["ADMIN"]})
|
* @param roles the {@code roles} claim (e.g. {@code ["AGENT"]}, {@code ["ADMIN"]})
|
||||||
*/
|
*/
|
||||||
record JwtValidationResult(String subject, String group, List<String> roles) {}
|
record JwtValidationResult(String subject, String application, List<String> roles) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a signed access JWT with the given subject, group, and roles.
|
* Creates a signed access JWT with the given subject, application, and roles.
|
||||||
*/
|
*/
|
||||||
String createAccessToken(String subject, String group, List<String> roles);
|
String createAccessToken(String subject, String application, List<String> roles);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a signed refresh JWT with the given subject, group, and roles.
|
* Creates a signed refresh JWT with the given subject, application, and roles.
|
||||||
*/
|
*/
|
||||||
String createRefreshToken(String subject, String group, List<String> roles);
|
String createRefreshToken(String subject, String application, List<String> roles);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates an access token and returns the full validation result.
|
* Validates an access token and returns the full validation result.
|
||||||
@@ -46,12 +46,12 @@ public interface JwtService {
|
|||||||
|
|
||||||
// --- Backward-compatible defaults (delegate to role-aware methods) ---
|
// --- Backward-compatible defaults (delegate to role-aware methods) ---
|
||||||
|
|
||||||
default String createAccessToken(String subject, String group) {
|
default String createAccessToken(String subject, String application) {
|
||||||
return createAccessToken(subject, group, List.of());
|
return createAccessToken(subject, application, List.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
default String createRefreshToken(String subject, String group) {
|
default String createRefreshToken(String subject, String application) {
|
||||||
return createRefreshToken(subject, group, List.of());
|
return createRefreshToken(subject, application, List.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
default String validateAndExtractAgentId(String token) {
|
default String validateAndExtractAgentId(String token) {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class AgentRegistryServiceTest {
|
|||||||
assertThat(agent).isNotNull();
|
assertThat(agent).isNotNull();
|
||||||
assertThat(agent.id()).isEqualTo("agent-1");
|
assertThat(agent.id()).isEqualTo("agent-1");
|
||||||
assertThat(agent.name()).isEqualTo("Order Agent");
|
assertThat(agent.name()).isEqualTo("Order Agent");
|
||||||
assertThat(agent.group()).isEqualTo("order-svc");
|
assertThat(agent.application()).isEqualTo("order-svc");
|
||||||
assertThat(agent.version()).isEqualTo("1.0.0");
|
assertThat(agent.version()).isEqualTo("1.0.0");
|
||||||
assertThat(agent.routeIds()).containsExactly("route1", "route2");
|
assertThat(agent.routeIds()).containsExactly("route1", "route2");
|
||||||
assertThat(agent.capabilities()).containsEntry("feature", "tracing");
|
assertThat(agent.capabilities()).containsEntry("feature", "tracing");
|
||||||
@@ -52,7 +52,7 @@ class AgentRegistryServiceTest {
|
|||||||
|
|
||||||
assertThat(updated.id()).isEqualTo("agent-1");
|
assertThat(updated.id()).isEqualTo("agent-1");
|
||||||
assertThat(updated.name()).isEqualTo("New Name");
|
assertThat(updated.name()).isEqualTo("New Name");
|
||||||
assertThat(updated.group()).isEqualTo("new-group");
|
assertThat(updated.application()).isEqualTo("new-group");
|
||||||
assertThat(updated.version()).isEqualTo("2.0.0");
|
assertThat(updated.version()).isEqualTo("2.0.0");
|
||||||
assertThat(updated.routeIds()).containsExactly("route1", "route2");
|
assertThat(updated.routeIds()).containsExactly("route1", "route2");
|
||||||
assertThat(updated.capabilities()).containsEntry("new", "cap");
|
assertThat(updated.capabilities()).containsEntry("new", "cap");
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -3,12 +3,12 @@ import { api } from '../client';
|
|||||||
import { config } from '../../config';
|
import { config } from '../../config';
|
||||||
import { useAuthStore } from '../../auth/auth-store';
|
import { useAuthStore } from '../../auth/auth-store';
|
||||||
|
|
||||||
export function useAgents(status?: string, group?: string) {
|
export function useAgents(status?: string, application?: string) {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['agents', status, group],
|
queryKey: ['agents', status, application],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data, error } = await api.GET('/agents', {
|
const { data, error } = await api.GET('/agents', {
|
||||||
params: { query: { ...(status ? { status } : {}), ...(group ? { group } : {}) } },
|
params: { query: { ...(status ? { status } : {}), ...(application ? { application } : {}) } },
|
||||||
});
|
});
|
||||||
if (error) throw new Error('Failed to load agents');
|
if (error) throw new Error('Failed to load agents');
|
||||||
return data!;
|
return data!;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function useDiagramByRoute(application: string | undefined, routeId: stri
|
|||||||
queryKey: ['diagrams', 'byRoute', application, routeId],
|
queryKey: ['diagrams', 'byRoute', application, routeId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data, error } = await api.GET('/diagrams', {
|
const { data, error } = await api.GET('/diagrams', {
|
||||||
params: { query: { group: application!, routeId: routeId! } },
|
params: { query: { application: application!, routeId: routeId! } },
|
||||||
});
|
});
|
||||||
if (error) throw new Error('Failed to load diagram for route');
|
if (error) throw new Error('Failed to load diagram for route');
|
||||||
return data!;
|
return data!;
|
||||||
|
|||||||
20
ui/src/api/schema.d.ts
vendored
20
ui/src/api/schema.d.ts
vendored
@@ -625,10 +625,10 @@ export interface paths {
|
|||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Find diagram by application group and route ID
|
* Find diagram by application and route ID
|
||||||
* @description Resolves group to agent IDs and finds the latest diagram for the route
|
* @description Resolves application to agent IDs and finds the latest diagram for the route
|
||||||
*/
|
*/
|
||||||
get: operations["findByGroupAndRoute"];
|
get: operations["findByApplicationAndRoute"];
|
||||||
put?: never;
|
put?: never;
|
||||||
post?: never;
|
post?: never;
|
||||||
delete?: never;
|
delete?: never;
|
||||||
@@ -683,7 +683,7 @@ export interface paths {
|
|||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* List all agents
|
* List all agents
|
||||||
* @description Returns all registered agents with runtime metrics, optionally filtered by status and/or group
|
* @description Returns all registered agents with runtime metrics, optionally filtered by status and/or application
|
||||||
*/
|
*/
|
||||||
get: operations["listAgents"];
|
get: operations["listAgents"];
|
||||||
put?: never;
|
put?: never;
|
||||||
@@ -1158,7 +1158,7 @@ export interface components {
|
|||||||
agentId: string;
|
agentId: string;
|
||||||
name: string;
|
name: string;
|
||||||
/** @default default */
|
/** @default default */
|
||||||
group: string;
|
application: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
routeIds?: string[];
|
routeIds?: string[];
|
||||||
capabilities?: {
|
capabilities?: {
|
||||||
@@ -1384,7 +1384,7 @@ export interface components {
|
|||||||
AgentInstanceResponse: {
|
AgentInstanceResponse: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
group: string;
|
application: string;
|
||||||
status: string;
|
status: string;
|
||||||
routeIds: string[];
|
routeIds: string[];
|
||||||
/** Format: date-time */
|
/** Format: date-time */
|
||||||
@@ -3133,10 +3133,10 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
findByGroupAndRoute: {
|
findByApplicationAndRoute: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query: {
|
query: {
|
||||||
group: string;
|
application: string;
|
||||||
routeId: string;
|
routeId: string;
|
||||||
};
|
};
|
||||||
header?: never;
|
header?: never;
|
||||||
@@ -3154,7 +3154,7 @@ export interface operations {
|
|||||||
"*/*": components["schemas"]["DiagramLayout"];
|
"*/*": components["schemas"]["DiagramLayout"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** @description No diagram found for the given group and route */
|
/** @description No diagram found for the given application and route */
|
||||||
404: {
|
404: {
|
||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
@@ -3239,7 +3239,7 @@ export interface operations {
|
|||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
status?: string;
|
status?: string;
|
||||||
group?: string;
|
application?: string;
|
||||||
};
|
};
|
||||||
header?: never;
|
header?: never;
|
||||||
path?: never;
|
path?: never;
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ function AgentOverviewContent({ agent }: { agent: any }) {
|
|||||||
<dl className={styles.detailList}>
|
<dl className={styles.detailList}>
|
||||||
<div className={styles.detailRow}>
|
<div className={styles.detailRow}>
|
||||||
<dt>Application</dt>
|
<dt>Application</dt>
|
||||||
<dd><MonoText>{agent.group ?? '—'}</MonoText></dd>
|
<dd><MonoText>{agent.application ?? '—'}</MonoText></dd>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.detailRow}>
|
<div className={styles.detailRow}>
|
||||||
<dt>Version</dt>
|
<dt>Version</dt>
|
||||||
@@ -175,7 +175,7 @@ export default function AgentHealth() {
|
|||||||
const agentsByApp = useMemo(() => {
|
const agentsByApp = useMemo(() => {
|
||||||
const map: Record<string, any[]> = {};
|
const map: Record<string, any[]> = {};
|
||||||
(agents || []).forEach((a: any) => {
|
(agents || []).forEach((a: any) => {
|
||||||
const g = a.group;
|
const g = a.application;
|
||||||
if (!map[g]) map[g] = [];
|
if (!map[g]) map[g] = [];
|
||||||
map[g].push(a);
|
map[g].push(a);
|
||||||
});
|
});
|
||||||
@@ -185,7 +185,7 @@ export default function AgentHealth() {
|
|||||||
const liveCount = (agents || []).filter((a: any) => a.status === 'LIVE').length;
|
const liveCount = (agents || []).filter((a: any) => a.status === 'LIVE').length;
|
||||||
const staleCount = (agents || []).filter((a: any) => a.status === 'STALE').length;
|
const staleCount = (agents || []).filter((a: any) => a.status === 'STALE').length;
|
||||||
const deadCount = (agents || []).filter((a: any) => a.status === 'DEAD').length;
|
const deadCount = (agents || []).filter((a: any) => a.status === 'DEAD').length;
|
||||||
const uniqueApps = new Set((agents || []).map((a: any) => a.group)).size;
|
const uniqueApps = new Set((agents || []).map((a: any) => a.application)).size;
|
||||||
const activeRoutes = (agents || []).filter((a: any) => a.status === 'LIVE').reduce((sum: number, a: any) => sum + (a.activeRoutes || 0), 0);
|
const activeRoutes = (agents || []).filter((a: any) => a.status === 'LIVE').reduce((sum: number, a: any) => sum + (a.activeRoutes || 0), 0);
|
||||||
const totalTps = (agents || []).filter((a: any) => a.status === 'LIVE').reduce((sum: number, a: any) => sum + (a.tps || 0), 0);
|
const totalTps = (agents || []).filter((a: any) => a.status === 'LIVE').reduce((sum: number, a: any) => sum + (a.tps || 0), 0);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user