From 98e0f2064a67811f705827d1cb5b32af093d3dd2 Mon Sep 17 00:00:00 2001 From: amacharla15 Date: Tue, 10 Feb 2026 20:47:57 -0800 Subject: [PATCH 1/2] Add InstancesController integration tests; map invalid registration to 400 --- .../config/AdminServerWebConfiguration.java | 7 ++ .../web/AdminControllerExceptionHandler.java | 42 +++++++ .../InstancesControllerIntegrationTest.java | 116 ++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/AdminControllerExceptionHandler.java diff --git a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/config/AdminServerWebConfiguration.java b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/config/AdminServerWebConfiguration.java index 52972b72efd..9b3dd61b555 100644 --- a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/config/AdminServerWebConfiguration.java +++ b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/config/AdminServerWebConfiguration.java @@ -31,6 +31,7 @@ import de.codecentric.boot.admin.server.services.ApplicationRegistry; import de.codecentric.boot.admin.server.services.InstanceRegistry; import de.codecentric.boot.admin.server.utils.jackson.AdminServerModule; +import de.codecentric.boot.admin.server.web.AdminControllerExceptionHandler; import de.codecentric.boot.admin.server.web.ApplicationsController; import de.codecentric.boot.admin.server.web.InstancesController; import de.codecentric.boot.admin.server.web.client.InstanceWebClient; @@ -62,6 +63,12 @@ public ApplicationsController applicationsController(ApplicationRegistry applica return new ApplicationsController(applicationRegistry, applicationEventPublisher); } + @Bean + @ConditionalOnMissingBean + public AdminControllerExceptionHandler adminControllerExceptionHandler() { + return new AdminControllerExceptionHandler(); + } + @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) public static class ReactiveRestApiConfiguration { diff --git a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/AdminControllerExceptionHandler.java b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/AdminControllerExceptionHandler.java new file mode 100644 index 00000000000..9819fe65c59 --- /dev/null +++ b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/web/AdminControllerExceptionHandler.java @@ -0,0 +1,42 @@ +/* + * Copyright 2014-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.codecentric.boot.admin.server.web; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.server.ServerWebInputException; +import reactor.core.Exceptions; + +@RestControllerAdvice(annotations = AdminController.class) +public class AdminControllerExceptionHandler { + + @ExceptionHandler(ServerWebInputException.class) + public ResponseEntity handleWebInput(ServerWebInputException ex) { + Throwable cause = Exceptions.unwrap(ex); + if (cause instanceof IllegalArgumentException) { + return ResponseEntity.badRequest().build(); + } + return ResponseEntity.badRequest().build(); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgument(IllegalArgumentException ex) { + return ResponseEntity.badRequest().build(); + } + +} diff --git a/spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/InstancesControllerIntegrationTest.java b/spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/InstancesControllerIntegrationTest.java index 664c03d3b90..98ab80165b8 100644 --- a/spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/InstancesControllerIntegrationTest.java +++ b/spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/InstancesControllerIntegrationTest.java @@ -95,6 +95,105 @@ void should_return_not_found_when_get_unknown_instance() { this.client.get().uri("/instances/unknown").exchange().expectStatus().isNotFound(); } + @Test + void should_reject_invalid_json() { + this.client.post() + .uri("/instances") + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue("{") + .exchange() + .expectStatus() + .isBadRequest(); + } + + @Test + void should_reject_missing_name() { + String body = "{ \"healthUrl\": \"http://localhost:" + localPort + "/application/health\" }"; + + this.client.post() + .uri("/instances") + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(body) + .exchange() + .expectStatus() + .isBadRequest(); + } + + @Test + void should_reject_missing_health_url() { + String body = "{ \"name\": \"noname\" }"; + + this.client.post() + .uri("/instances") + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(body) + .exchange() + .expectStatus() + .isBadRequest(); + } + + @Test + void should_create_distinct_ids_for_different_health_urls() { + String id1 = register(); + + String other = "{ \"name\": \"other\", \"healthUrl\": \"http://localhost:" + localPort + "/other/health\" }"; + String id2 = registerWithBody(other); + + assertThat(id2).isNotEqualTo(id1); + + assertInstanceById(id1); + assertInstanceById(id2); + } + + @Test + void should_remove_instance_after_delete() { + String id = register(); + + this.client.delete().uri(getLocation(id)).exchange().expectStatus().isNoContent(); + + this.client.get().uri(getLocation(id)).exchange().expectStatus().isNotFound(); + + this.client.get() + .uri("/instances") + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentType(MediaType.APPLICATION_JSON) + .expectBody(String.class) + .consumeWith((response) -> { + DocumentContext json = JsonPath.parse(response.getResponseBody()); + List ids = json.read("$[?(@.id == '" + id + "')].id"); + assertThat(ids).isEmpty(); + }); + + } + + @Test + void should_not_create_duplicate_instance_on_reregister() { + String id = register(); + + registerSecondTime(id); + + this.client.get() + .uri("/instances") + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .contentType(MediaType.APPLICATION_JSON) + .expectBody(String.class) + .consumeWith((response) -> { + DocumentContext json = JsonPath.parse(response.getResponseBody()); + List ids = json.read("$[?(@.id == '" + id + "')].id"); + assertThat(ids).hasSize(1); + }); + + } + @Test void should_return_empty_list() { this.client.get() @@ -280,6 +379,23 @@ private Mono register() { //@formatter:on } + private String registerWithBody(String body) { + //@formatter:off + EntityExchangeResult> result = client.post() + .uri("/instances") + .accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON) + .bodyValue(body) + .exchange() + .expectStatus().isCreated() + .expectHeader().contentType(MediaType.APPLICATION_JSON) + .expectHeader().valueMatches("location", "http://localhost:" + localPort + "/instances/[0-9a-f]+") + .expectBody(responseType) + .returnResult(); + //@formatter:on + assertThat(result.getResponseBody()).containsKeys("id"); + return result.getResponseBody().get("id").toString(); + } + private String getLocation(String id) { return "http://localhost:" + localPort + "/instances/" + id; From 12cb3648f1e215a59f4f828be96a80149b92bcc7 Mon Sep 17 00:00:00 2001 From: amacharla15 Date: Sun, 1 Mar 2026 04:35:20 -0800 Subject: [PATCH 2/2] Fix InstancesControllerIntegrationTest for upstream master --- mappings.json | 1 + .../__tests__/click_affordance.test.ts | 50 +++++++++++++++++++ .../InstancesControllerIntegrationTest.java | 8 +-- 3 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 mappings.json create mode 100644 spring-boot-admin-server-ui/src/main/frontend/__tests__/click_affordance.test.ts diff --git a/mappings.json b/mappings.json new file mode 100644 index 00000000000..974edce4372 --- /dev/null +++ b/mappings.json @@ -0,0 +1 @@ +{"contexts":{"spring-boot-admin-sample-servlet":{"mappings":{"dispatcherServlets":{"dispatcherServlet":[{"predicate":"{GET [/actuator/health/**], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'health-path'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/health/**"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/sbom/{id}], produces [application/octet-stream]}","handler":"Actuator web endpoint 'sbom-id'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/sbom/{id}"],"produces":[{"mediaType":"application/octet-stream","negated":false}]}}},{"predicate":"{POST [/actuator/shutdown], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'shutdown'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["POST"],"params":[],"patterns":["/actuator/shutdown"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/threaddump], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'threaddump'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/threaddump"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{DELETE [/actuator/sessions/{sessionId}]}","handler":"Actuator web endpoint 'sessions-sessionId'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["DELETE"],"params":[],"patterns":["/actuator/sessions/{sessionId}"],"produces":[]}}},{"predicate":"{POST [/actuator/resume], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'resume'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["POST"],"params":[],"patterns":["/actuator/resume"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/custom], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'custom'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/custom"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/metrics/{requiredMetricName}], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'metrics-requiredMetricName'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/metrics/{requiredMetricName}"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/loggers], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'loggers'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/loggers"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/configprops], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'configprops'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/configprops"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{DELETE [/actuator/caches/{cache}], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'caches-cache'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["DELETE"],"params":[],"patterns":["/actuator/caches/{cache}"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/auditevents], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'auditevents'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/auditevents"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/info], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'info'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/info"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/env/{toMatch}], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'env-toMatch'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/env/{toMatch}"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/mappings], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'mappings'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/mappings"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/logfile], produces [text/plain;charset=UTF-8]}","handler":"Actuator web endpoint 'logfile'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/logfile"],"produces":[{"mediaType":"text/plain;charset=UTF-8","negated":false}]}}},{"predicate":"{POST [/actuator/restart], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'restart'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["POST"],"params":[],"patterns":["/actuator/restart"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/caches/{cache}], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'caches-cache'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/caches/{cache}"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{POST [/actuator/startup], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'startup'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["POST"],"params":[],"patterns":["/actuator/startup"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/conditions], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'conditions'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/conditions"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/sbom], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'sbom'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/sbom"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/caches], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'caches'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/caches"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/threaddump], produces [text/plain;charset=UTF-8]}","handler":"Actuator web endpoint 'threaddump'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/threaddump"],"produces":[{"mediaType":"text/plain;charset=UTF-8","negated":false}]}}},{"predicate":"{GET [/actuator/jolokia], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'jolokia'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/jolokia"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/configprops/{prefix}], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'configprops-prefix'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/configprops/{prefix}"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/features], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'features'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/features"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{POST [/actuator/refresh], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'refresh'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["POST"],"params":[],"patterns":["/actuator/refresh"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/scheduledtasks], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'scheduledtasks'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/scheduledtasks"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{POST [/actuator/pause], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'pause'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["POST"],"params":[],"patterns":["/actuator/pause"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/sessions], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'sessions'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/sessions"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{POST [/actuator/loggers/{name}], consumes [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'loggers-name'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}],"headers":[],"methods":["POST"],"params":[],"patterns":["/actuator/loggers/{name}"],"produces":[]}}},{"predicate":"{DELETE [/actuator/env], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'env'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["DELETE"],"params":[],"patterns":["/actuator/env"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/startup], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'startup'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/startup"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/sessions/{sessionId}], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'sessions-sessionId'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/sessions/{sessionId}"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/health], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'health'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/health"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/httpexchanges], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'httpexchanges'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/httpexchanges"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator root web endpoint","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping.WebMvcLinksHandler","name":"links","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljakarta/servlet/http/HttpServletResponse;)Ljava/util/Map;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/env], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'env'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/env"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{DELETE [/actuator/caches]}","handler":"Actuator web endpoint 'caches'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["DELETE"],"params":[],"patterns":["/actuator/caches"],"produces":[]}}},{"predicate":"{GET [/actuator/beans], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'beans'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/beans"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/loggers/{name}], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'loggers-name'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/loggers/{name}"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{POST [/actuator/env], consumes [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'env'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}],"headers":[],"methods":["POST"],"params":[],"patterns":["/actuator/env"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/actuator/metrics], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}","handler":"Actuator web endpoint 'metrics'","details":{"handlerMethod":{"className":"org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler","name":"handle","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljava/util/Map;)Ljava/lang/Object;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/actuator/metrics"],"produces":[{"mediaType":"application/vnd.spring-boot.actuator.v3+json","negated":false},{"mediaType":"application/vnd.spring-boot.actuator.v2+json","negated":false},{"mediaType":"application/json","negated":false}]}}},{"predicate":"{ [/error]}","handler":"org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)","details":{"handlerMethod":{"className":"org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController","name":"error","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;)Lorg/springframework/http/ResponseEntity;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":[],"params":[],"patterns":["/error"],"produces":[]}}},{"predicate":"{ [/error], produces [text/html]}","handler":"org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse)","details":{"handlerMethod":{"className":"org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController","name":"errorHtml","descriptor":"(Ljakarta/servlet/http/HttpServletRequest;Ljakarta/servlet/http/HttpServletResponse;)Lorg/springframework/web/servlet/ModelAndView;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":[],"params":[],"patterns":["/error"],"produces":[{"mediaType":"text/html","negated":false}]}}},{"predicate":"{DELETE [/applications/{name}]}","handler":"de.codecentric.boot.admin.server.web.ApplicationsController#unregister(String)","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.web.ApplicationsController","name":"unregister","descriptor":"(Ljava/lang/String;)Lreactor/core/publisher/Mono;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["DELETE"],"params":[],"patterns":["/applications/{name}"],"produces":[]}}},{"predicate":"{[GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS] [/applications/{applicationName}/actuator/**]}","handler":"de.codecentric.boot.admin.server.web.servlet.InstancesProxyController#endpointProxy(String, HttpServletRequest)","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.web.servlet.InstancesProxyController","name":"endpointProxy","descriptor":"(Ljava/lang/String;Ljakarta/servlet/http/HttpServletRequest;)Lreactor/core/publisher/Flux;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET","HEAD","POST","PUT","PATCH","DELETE","OPTIONS"],"params":[],"patterns":["/applications/{applicationName}/actuator/**"],"produces":[]}}},{"predicate":"{POST [/notifications/filters], produces [application/json]}","handler":"de.codecentric.boot.admin.server.notify.filter.web.NotificationFilterController#addFilter(String, String, Long)","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.notify.filter.web.NotificationFilterController","name":"addFilter","descriptor":"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;)Lorg/springframework/http/ResponseEntity;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["POST"],"params":[],"patterns":["/notifications/filters"],"produces":[{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/sba-settings.js], produces [application/javascript]}","handler":"de.codecentric.boot.admin.server.ui.web.UiController#sbaSettings()","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.ui.web.UiController","name":"sbaSettings","descriptor":"()Ljava/lang/String;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/sba-settings.js"],"produces":[{"mediaType":"application/javascript","negated":false}]}}},{"predicate":"{GET [/], produces [text/html]}","handler":"de.codecentric.boot.admin.server.ui.web.UiController#index()","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.ui.web.UiController","name":"index","descriptor":"()Ljava/lang/String;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/"],"produces":[{"mediaType":"text/html","negated":false}]}}},{"predicate":"{GET [/instances], params [name], produces [application/json]}","handler":"de.codecentric.boot.admin.server.web.InstancesController#instances(String)","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.web.InstancesController","name":"instances","descriptor":"(Ljava/lang/String;)Lreactor/core/publisher/Flux;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[{"name":"name","negated":false}],"patterns":["/instances"],"produces":[{"mediaType":"application/json","negated":false}]}}},{"predicate":"{DELETE [/notifications/filters/{id}]}","handler":"de.codecentric.boot.admin.server.notify.filter.web.NotificationFilterController#deleteFilter(String)","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.notify.filter.web.NotificationFilterController","name":"deleteFilter","descriptor":"(Ljava/lang/String;)Lorg/springframework/http/ResponseEntity;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["DELETE"],"params":[],"patterns":["/notifications/filters/{id}"],"produces":[]}}},{"predicate":"{POST [/applications], produces [application/json]}","handler":"de.codecentric.boot.admin.server.web.ApplicationsController#refreshApplications()","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.web.ApplicationsController","name":"refreshApplications","descriptor":"()V"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["POST"],"params":[],"patterns":["/applications"],"produces":[{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/instances/events], produces [text/event-stream]}","handler":"de.codecentric.boot.admin.server.web.InstancesController#eventStream()","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.web.InstancesController","name":"eventStream","descriptor":"()Lreactor/core/publisher/Flux;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/instances/events"],"produces":[{"mediaType":"text/event-stream","negated":false}]}}},{"predicate":"{GET [/instances/events], produces [application/json]}","handler":"de.codecentric.boot.admin.server.web.InstancesController#events()","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.web.InstancesController","name":"events","descriptor":"()Lreactor/core/publisher/Flux;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/instances/events"],"produces":[{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/instances/{id}], produces [application/json]}","handler":"de.codecentric.boot.admin.server.web.InstancesController#instance(String)","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.web.InstancesController","name":"instance","descriptor":"(Ljava/lang/String;)Lreactor/core/publisher/Mono;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/instances/{id}"],"produces":[{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/instances/{id}], produces [text/event-stream]}","handler":"de.codecentric.boot.admin.server.web.InstancesController#instanceStream(String)","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.web.InstancesController","name":"instanceStream","descriptor":"(Ljava/lang/String;)Lreactor/core/publisher/Flux;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/instances/{id}"],"produces":[{"mediaType":"text/event-stream","negated":false}]}}},{"predicate":"{GET [/applications/{name}], produces [application/json]}","handler":"de.codecentric.boot.admin.server.web.ApplicationsController#application(String)","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.web.ApplicationsController","name":"application","descriptor":"(Ljava/lang/String;)Lreactor/core/publisher/Mono;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/applications/{name}"],"produces":[{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/instances], produces [application/json]}","handler":"de.codecentric.boot.admin.server.web.InstancesController#instances()","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.web.InstancesController","name":"instances","descriptor":"()Lreactor/core/publisher/Flux;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/instances"],"produces":[{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/notifications/filters], produces [application/json]}","handler":"de.codecentric.boot.admin.server.notify.filter.web.NotificationFilterController#getFilters()","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.notify.filter.web.NotificationFilterController","name":"getFilters","descriptor":"()Ljava/util/Collection;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/notifications/filters"],"produces":[{"mediaType":"application/json","negated":false}]}}},{"predicate":"{GET [/login], produces [text/html]}","handler":"de.codecentric.boot.admin.server.ui.web.UiController#login()","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.ui.web.UiController","name":"login","descriptor":"()Ljava/lang/String;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/login"],"produces":[{"mediaType":"text/html","negated":false}]}}},{"predicate":"{GET [/variables.css], produces [text/css]}","handler":"de.codecentric.boot.admin.server.ui.web.UiController#variablesCss()","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.ui.web.UiController","name":"variablesCss","descriptor":"()Ljava/lang/String;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/variables.css"],"produces":[{"mediaType":"text/css","negated":false}]}}},{"predicate":"{GET [/applications], produces [text/event-stream]}","handler":"de.codecentric.boot.admin.server.web.ApplicationsController#applicationsStream()","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.web.ApplicationsController","name":"applicationsStream","descriptor":"()Lreactor/core/publisher/Flux;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/applications"],"produces":[{"mediaType":"text/event-stream","negated":false}]}}},{"predicate":"{[GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS] [/instances/{instanceId}/actuator/**]}","handler":"de.codecentric.boot.admin.server.web.servlet.InstancesProxyController#instanceProxy(String, HttpServletRequest)","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.web.servlet.InstancesProxyController","name":"instanceProxy","descriptor":"(Ljava/lang/String;Ljakarta/servlet/http/HttpServletRequest;)V"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET","HEAD","POST","PUT","PATCH","DELETE","OPTIONS"],"params":[],"patterns":["/instances/{instanceId}/actuator/**"],"produces":[]}}},{"predicate":"{GET [/applications], produces [application/json]}","handler":"de.codecentric.boot.admin.server.web.ApplicationsController#applications()","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.web.ApplicationsController","name":"applications","descriptor":"()Lreactor/core/publisher/Flux;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["GET"],"params":[],"patterns":["/applications"],"produces":[{"mediaType":"application/json","negated":false}]}}},{"predicate":"{DELETE [/instances/{id}]}","handler":"de.codecentric.boot.admin.server.web.InstancesController#unregister(String)","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.web.InstancesController","name":"unregister","descriptor":"(Ljava/lang/String;)Lreactor/core/publisher/Mono;"},"requestMappingConditions":{"consumes":[],"headers":[],"methods":["DELETE"],"params":[],"patterns":["/instances/{id}"],"produces":[]}}},{"predicate":"{POST [/instances], consumes [application/json]}","handler":"de.codecentric.boot.admin.server.web.InstancesController#register(Registration, UriComponentsBuilder)","details":{"handlerMethod":{"className":"de.codecentric.boot.admin.server.web.InstancesController","name":"register","descriptor":"(Lde/codecentric/boot/admin/server/domain/values/Registration;Lorg/springframework/web/util/UriComponentsBuilder;)Lreactor/core/publisher/Mono;"},"requestMappingConditions":{"consumes":[{"mediaType":"application/json","negated":false}],"headers":[],"methods":["POST"],"params":[],"patterns":["/instances"],"produces":[]}}},{"predicate":"/webjars/**","handler":"ResourceHttpRequestHandler [classpath [META-INF/resources/webjars/]]"},{"predicate":"/**","handler":"ResourceHttpRequestHandler [classpath [META-INF/spring-boot-admin-server-ui/]]"},{"predicate":"/extensions/**","handler":"ResourceHttpRequestHandler [classpath [META-INF/spring-boot-admin-server-ui/extensions/]]"}]},"servletFilters":[{"servletNameMappings":[],"urlPatternMappings":["/*"],"name":"requestContextFilter","className":"org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter"},{"servletNameMappings":[],"urlPatternMappings":["/*"],"name":"homepageForwardFilter","className":"de.codecentric.boot.admin.server.ui.web.servlet.HomepageForwardingFilter"},{"servletNameMappings":[],"urlPatternMappings":["/*"],"name":"webMvcObservationFilter","className":"org.springframework.web.filter.ServerHttpObservationFilter"},{"servletNameMappings":[],"urlPatternMappings":["/*"],"name":"Tomcat WebSocket (JSR356) Filter","className":"org.apache.tomcat.websocket.server.WsFilter"},{"servletNameMappings":[],"urlPatternMappings":["/*"],"name":"springSessionRepositoryFilter","className":"org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean$1"},{"servletNameMappings":[],"urlPatternMappings":["/*"],"name":"characterEncodingFilter","className":"org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter"},{"servletNameMappings":[],"urlPatternMappings":["/*"],"name":"springSecurityFilterChain","className":"org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean$1"},{"servletNameMappings":[],"urlPatternMappings":["/*"],"name":"httpExchangesFilter","className":"org.springframework.boot.actuate.web.exchanges.servlet.HttpExchangesFilter"},{"servletNameMappings":[],"urlPatternMappings":["/*"],"name":"formContentFilter","className":"org.springframework.boot.web.servlet.filter.OrderedFormContentFilter"}],"servlets":[{"mappings":["/"],"name":"dispatcherServlet","className":"org.springframework.web.servlet.DispatcherServlet"},{"mappings":["/actuator/jolokia/*"],"name":"jolokia","className":"org.jolokia.server.core.http.AgentServlet"}]}}}} diff --git a/spring-boot-admin-server-ui/src/main/frontend/__tests__/click_affordance.test.ts b/spring-boot-admin-server-ui/src/main/frontend/__tests__/click_affordance.test.ts new file mode 100644 index 00000000000..57bb2b3e6b4 --- /dev/null +++ b/spring-boot-admin-server-ui/src/main/frontend/__tests__/click_affordance.test.ts @@ -0,0 +1,50 @@ +import { describe, it, expect } from "vitest"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +function walk(dir: string, out: string[]) { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + for (const e of entries) { + const p = path.join(dir, e.name); + if (e.isDirectory()) { + walk(p, out); + } else { + out.push(p); + } + } +} + +describe("ui click affordance", () => { + it("applications/instances list should have pointer cursor rule", () => { + const here = path.dirname(fileURLToPath(import.meta.url)); + const root = path.resolve(here, ".."); + const files: string[] = []; + walk(root, files); + + const rxCursor = /cursor\s*:\s*pointer/i; + const rxKw = /(applications|instances|instance|application)/i; + + let hit = false; + for (const f of files) { + const lower = f.toLowerCase(); + if ( + lower.endsWith(".css") || + lower.endsWith(".scss") || + lower.endsWith(".vue") || + lower.endsWith(".ts") || + lower.endsWith(".tsx") || + lower.endsWith(".js") || + lower.endsWith(".jsx") + ) { + const text = fs.readFileSync(f, "utf8"); + if (rxCursor.test(text) && rxKw.test(text)) { + hit = true; + break; + } + } + } + + expect(hit).toBe(true); + }); +}); diff --git a/spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/InstancesControllerIntegrationTest.java b/spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/InstancesControllerIntegrationTest.java index 98ab80165b8..b67ba4350b4 100644 --- a/spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/InstancesControllerIntegrationTest.java +++ b/spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/web/InstancesControllerIntegrationTest.java @@ -17,6 +17,7 @@ package de.codecentric.boot.admin.server.web; import java.time.Duration; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; @@ -33,6 +34,7 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.EntityExchangeResult; import org.springframework.test.web.reactive.server.WebTestClient; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -137,7 +139,7 @@ void should_reject_missing_health_url() { @Test void should_create_distinct_ids_for_different_health_urls() { - String id1 = register(); + String id1 = register().block(); String other = "{ \"name\": \"other\", \"healthUrl\": \"http://localhost:" + localPort + "/other/health\" }"; String id2 = registerWithBody(other); @@ -150,7 +152,7 @@ void should_create_distinct_ids_for_different_health_urls() { @Test void should_remove_instance_after_delete() { - String id = register(); + String id = register().block(); this.client.delete().uri(getLocation(id)).exchange().expectStatus().isNoContent(); @@ -174,7 +176,7 @@ void should_remove_instance_after_delete() { @Test void should_not_create_duplicate_instance_on_reregister() { - String id = register(); + String id = register().block(Duration.ofSeconds(5)); registerSecondTime(id);