From f4d26935613f1cba09fd9c052b69468a4b085766 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Mon, 23 Mar 2026 18:13:37 +0100 Subject: [PATCH] feat: enrich AgentInstanceResponse with version/capabilities, add password reset endpoint Co-Authored-By: Claude Sonnet 4.6 --- .../server/app/controller/UserAdminController.java | 14 ++++++++++++++ .../server/app/dto/AgentInstanceResponse.java | 5 +++++ .../server/app/dto/SetPasswordRequest.java | 7 +++++++ ui/src/api/queries/admin/rbac.ts | 13 +++++++++++++ 4 files changed, 39 insertions(+) create mode 100644 cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/SetPasswordRequest.java diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/UserAdminController.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/UserAdminController.java index 766484ba..6d9666fc 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/UserAdminController.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/UserAdminController.java @@ -1,5 +1,6 @@ package com.cameleer3.server.app.controller; +import com.cameleer3.server.app.dto.SetPasswordRequest; import com.cameleer3.server.core.admin.AuditCategory; import com.cameleer3.server.core.admin.AuditResult; import com.cameleer3.server.core.admin.AuditService; @@ -12,6 +13,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; @@ -172,6 +174,18 @@ public class UserAdminController { return ResponseEntity.noContent().build(); } + @PostMapping("/{userId}/password") + @Operation(summary = "Reset user password") + @ApiResponse(responseCode = "204", description = "Password reset") + public ResponseEntity resetPassword( + @PathVariable String userId, + @Valid @RequestBody SetPasswordRequest request, + HttpServletRequest httpRequest) { + userRepository.setPassword(userId, passwordEncoder.encode(request.password())); + auditService.log("reset_password", AuditCategory.USER_MGMT, userId, null, AuditResult.SUCCESS, httpRequest); + return ResponseEntity.noContent().build(); + } + public record CreateUserRequest(String username, String displayName, String email, String password) {} public record UpdateUserRequest(String displayName, String email) {} } diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/AgentInstanceResponse.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/AgentInstanceResponse.java index 31552bfd..e3232da5 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/AgentInstanceResponse.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/AgentInstanceResponse.java @@ -7,6 +7,7 @@ import jakarta.validation.constraints.NotNull; import java.time.Duration; import java.time.Instant; import java.util.List; +import java.util.Map; @Schema(description = "Agent instance summary with runtime metrics") public record AgentInstanceResponse( @@ -17,6 +18,8 @@ public record AgentInstanceResponse( @NotNull List routeIds, @NotNull Instant registeredAt, @NotNull Instant lastHeartbeat, + String version, + Map capabilities, double tps, double errorRate, int activeRoutes, @@ -29,6 +32,7 @@ public record AgentInstanceResponse( info.id(), info.name(), info.group(), info.state().name(), info.routeIds(), info.registeredAt(), info.lastHeartbeat(), + info.version(), info.capabilities(), 0.0, 0.0, 0, info.routeIds() != null ? info.routeIds().size() : 0, uptime @@ -38,6 +42,7 @@ public record AgentInstanceResponse( public AgentInstanceResponse withMetrics(double tps, double errorRate, int activeRoutes) { return new AgentInstanceResponse( id, name, group, status, routeIds, registeredAt, lastHeartbeat, + version, capabilities, tps, errorRate, activeRoutes, totalRoutes, uptimeSeconds ); } diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/SetPasswordRequest.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/SetPasswordRequest.java new file mode 100644 index 00000000..6795575c --- /dev/null +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/SetPasswordRequest.java @@ -0,0 +1,7 @@ +package com.cameleer3.server.app.dto; + +import jakarta.validation.constraints.NotBlank; + +public record SetPasswordRequest( + @NotBlank String password +) {} diff --git a/ui/src/api/queries/admin/rbac.ts b/ui/src/api/queries/admin/rbac.ts index d11f3b44..f50b8ec9 100644 --- a/ui/src/api/queries/admin/rbac.ts +++ b/ui/src/api/queries/admin/rbac.ts @@ -198,6 +198,19 @@ export function useDeleteUser() { }); } +export function useSetPassword() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: async ({ userId, password }: { userId: string; password: string }) => { + await adminFetch(`/users/${userId}/password`, { + method: 'POST', + body: JSON.stringify({ password }), + }); + }, + onSuccess: () => qc.invalidateQueries({ queryKey: ['admin', 'users'] }), + }); +} + export function useAssignRoleToUser() { const qc = useQueryClient(); return useMutation({