From d108d5fc2f5abcc91140c4b0cc6bb51d20c0ec81 Mon Sep 17 00:00:00 2001 From: "igor.petrenko" Date: Wed, 22 Jul 2026 14:37:03 +0300 Subject: [PATCH] CE-179 simple ui for JPath --- .../main/java/oap/http/test/HttpAsserts.java | 5 + oap-jpath/README.md | 5 + oap-ws/oap-ws-admin-ws/README.md | 30 +++ oap-ws/oap-ws-admin-ws/pom.xml | 4 + .../main/java/oap/ws/admin/InspectorWS.java | 208 ++++++++++++++++++ .../main/resources/META-INF/oap-module.oap | 11 + .../ws/admin/inspector-inspect-part.html.vm | 60 +++++ .../oap/ws/admin/inspector-service.html.vm | 47 ++++ .../oap/ws/admin/inspector-ui.html.vm | 22 ++ .../oap/ws/admin/inspector-value.html.vm | 21 ++ .../java/oap/ws/admin/InspectorWSTest.java | 141 ++++++++++++ pom.xml | 2 +- 12 files changed, 555 insertions(+), 1 deletion(-) create mode 100644 oap-ws/oap-ws-admin-ws/src/main/java/oap/ws/admin/InspectorWS.java create mode 100644 oap-ws/oap-ws-admin-ws/src/main/resources/oap/ws/admin/inspector-inspect-part.html.vm create mode 100644 oap-ws/oap-ws-admin-ws/src/main/resources/oap/ws/admin/inspector-service.html.vm create mode 100644 oap-ws/oap-ws-admin-ws/src/main/resources/oap/ws/admin/inspector-ui.html.vm create mode 100644 oap-ws/oap-ws-admin-ws/src/main/resources/oap/ws/admin/inspector-value.html.vm create mode 100644 oap-ws/oap-ws-admin-ws/src/test/java/oap/ws/admin/InspectorWSTest.java diff --git a/oap-http/oap-http-test/src/main/java/oap/http/test/HttpAsserts.java b/oap-http/oap-http-test/src/main/java/oap/http/test/HttpAsserts.java index dbd6750897..ca8e60ae21 100644 --- a/oap-http/oap-http-test/src/main/java/oap/http/test/HttpAsserts.java +++ b/oap-http/oap-http-test/src/main/java/oap/http/test/HttpAsserts.java @@ -382,6 +382,11 @@ public HttpAssertion bodyContains( String values ) { return this; } + public HttpAssertion bodyDoesNotContain( String values ) { + assertString( response.contentString() ).doesNotContain( values ); + return this; + } + public HttpAssertion bodyContainsPattern( Pattern pattern ) { assertString( response.contentString() ).containsPattern( pattern ); return this; diff --git a/oap-jpath/README.md b/oap-jpath/README.md index 9879cf27b1..7078a326bb 100644 --- a/oap-jpath/README.md +++ b/oap-jpath/README.md @@ -134,3 +134,8 @@ The `Pointer` passed to `write` is one of: |---|---| | `PathNotFoundException` | A field or method named in the expression does not exist on the target object | | `ReflectionException` | Reflection access fails (e.g., module access denied) | + +## See also + +- [`JPathWS`](../oap-ws/oap-ws-admin-ws/README.md#jpath-query--get-systemadminjpath) — exposes JPath evaluation over the live Kernel service tree as a JSON HTTP endpoint. +- [`InspectorWS`](../oap-ws/oap-ws-admin-ws/README.md#inspector-ui--get-systemadmininspector) — browsable HTML UI built on top of the same JPath queries. diff --git a/oap-ws/oap-ws-admin-ws/README.md b/oap-ws/oap-ws-admin-ws/README.md index edbfe96393..281c097e18 100644 --- a/oap-ws/oap-ws-admin-ws/README.md +++ b/oap-ws/oap-ws-admin-ws/README.md @@ -4,6 +4,13 @@ Built-in administration endpoints for OAP applications. All endpoints are mounte Depends on: `oap-ws` +## Menu + +- [Log level control](#log-level-control--get-systemadminlogs) +- [JPath query](#jpath-query--get-systemadminjpath) +- [JSON schema](#json-schema--get-systemadminschema) +- [Inspector UI](#inspector-ui--get-systemadmininspector) + ## Endpoints ### Log level control — `GET /system/admin/logs` @@ -55,6 +62,29 @@ GET /system/admin/schema/?path=/oap/ws/file/schema/data.conf --- +### Inspector UI — GET /system/admin/inspector + +Browsable HTML UI over the live Kernel service tree, built on the same JPath query engine as `/system/admin/jpath`. + +| Endpoint | Description | +|---|---| +| `GET /system/admin/inspector/ui` | Lists all `module.service` names as links, with a client-side filter box | +| `GET /system/admin/inspector/ui/{moduleName}.{serviceName}` | Service details (implementation, enabled, dependsOn, supervision, listen, link, parameters) plus fields and methods tables, drillable via the value page | +| `GET /system/admin/inspector/ui/value?query=...&mode=inspect\|json` | Evaluates a JPath query (same grammar as `/system/admin/jpath`). `mode=inspect` (default) shows fields/methods tables for the resulting object when it's not a leaf value (String/primitive/number); `mode=json` pretty-prints the JSON result via `Binder.json.marshal`. Either mode shows the stack trace if evaluation throws | + +```bash +# service list with filter box +curl http://localhost:8081/system/admin/inspector/ui + +# inspect a single service +curl http://localhost:8081/system/admin/inspector/ui/oap-ws.session-manager + +# evaluate a JPath query, pretty-printed +curl "http://localhost:8081/system/admin/inspector/ui/value?query=oap-ws.session-manager.expirationTime" +``` + +--- + ## OAP Module Integration ```hocon diff --git a/oap-ws/oap-ws-admin-ws/pom.xml b/oap-ws/oap-ws-admin-ws/pom.xml index cc0a711c48..97b30ca24c 100644 --- a/oap-ws/oap-ws-admin-ws/pom.xml +++ b/oap-ws/oap-ws-admin-ws/pom.xml @@ -45,5 +45,9 @@ org.projectlombok lombok + + org.apache.velocity + velocity-engine-core + diff --git a/oap-ws/oap-ws-admin-ws/src/main/java/oap/ws/admin/InspectorWS.java b/oap-ws/oap-ws-admin-ws/src/main/java/oap/ws/admin/InspectorWS.java new file mode 100644 index 0000000000..ded3d5b017 --- /dev/null +++ b/oap-ws/oap-ws-admin-ws/src/main/java/oap/ws/admin/InspectorWS.java @@ -0,0 +1,208 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) Open Application Platform Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package oap.ws.admin; + +import com.google.common.base.Throwables; +import lombok.extern.slf4j.Slf4j; +import oap.application.Kernel; +import oap.application.ModuleItem; +import oap.json.Binder; +import oap.reflect.Reflect; +import oap.reflect.Reflection; +import oap.ws.WsMethod; +import oap.ws.WsParam; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; + +import java.io.StringWriter; +import java.lang.reflect.Modifier; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import static oap.http.server.nio.HttpServerExchange.HttpMethod.GET; +import static oap.ws.WsParam.From.PATH; +import static oap.ws.WsParam.From.QUERY; +import static org.apache.velocity.runtime.RuntimeConstants.RESOURCE_LOADER; + +@Slf4j +public class InspectorWS { + private final JPathWS jPathWS; + private final Kernel kernel; + private final VelocityEngine engine = new VelocityEngine(); + + public InspectorWS( JPathWS jPathWS, Kernel kernel ) { + this.jPathWS = jPathWS; + this.kernel = kernel; + engine.setProperty( RESOURCE_LOADER, "classpath" ); + engine.setProperty( "classpath.resource.loader.class", ClasspathResourceLoader.class.getName() ); + engine.init(); + } + + @WsMethod( method = GET, path = "/ui", produces = "text/html" ) + public String ui() { + List services = jPathWS.listServices( "*" ); + VelocityContext context = new VelocityContext(); + context.put( "services", services ); + StringWriter writer = new StringWriter(); + engine.getTemplate( "oap/ws/admin/inspector-ui.html.vm" ).merge( context, writer ); + return writer.toString(); + } + + @WsMethod( method = GET, path = "/ui/{serviceReference}", produces = "text/html" ) + public String service( @WsParam( from = PATH ) String serviceReference ) { + int dot = serviceReference.indexOf( '.' ); + ModuleItem.ServiceItem item = dot < 0 ? null + : kernel.services.get( serviceReference.substring( 0, dot ), serviceReference.substring( dot + 1 ) ); + + VelocityContext context = new VelocityContext(); + context.put( "found", item != null ); + context.put( "reference", serviceReference ); + if( item != null ) { + context.put( "module", item.getModuleName() ); + context.put( "serviceName", item.serviceName ); + context.put( "implementation", item.service.implementation ); + context.put( "enabled", item.enabled.toString() ); + context.put( "dependsOn", item.dependsOn.stream() + .map( d -> d.serviceItem.toString() + ( d.required ? "" : " (optional)" ) ) + .collect( Collectors.toList() ) ); + context.put( "supervision", item.service.supervision.toString() ); + context.put( "listen", item.service.listen ); + context.put( "link", item.service.link ); + context.put( "parameters", item.service.parameters ); + + putInspectTables( context, item.instance, item.getReflection(), + item.getModuleName() + "." + item.serviceName + ".instance" ); + } + StringWriter writer = new StringWriter(); + engine.getTemplate( "oap/ws/admin/inspector-service.html.vm" ).merge( context, writer ); + return writer.toString(); + } + + @WsMethod( method = GET, path = "/ui/value", produces = "text/html" ) + public String value( @WsParam( from = QUERY ) String query, @WsParam( from = QUERY ) Optional mode ) { + String effectiveMode = mode.filter( m -> !m.isEmpty() ).orElse( "inspect" ); + VelocityContext context = new VelocityContext(); + context.put( "query", query ); + context.put( "mode", effectiveMode ); + context.put( "encodedQuery", URLEncoder.encode( query, StandardCharsets.UTF_8 ) ); + try { + Object result = jPathWS.evaluatePath( query ); + context.put( "error", false ); + context.put( "json", Binder.json.marshal( result, true ) ); + + boolean inspectable = result != null && !isLeaf( result.getClass() ); + context.put( "inspectable", inspectable ); + if( inspectable ) { + putInspectTables( context, result, Reflect.reflect( result.getClass() ), query ); + } + } catch( Exception e ) { + log.error( e.getMessage(), e ); + context.put( "error", true ); + context.put( "stackTrace", Throwables.getStackTraceAsString( e ) ); + } + StringWriter writer = new StringWriter(); + engine.getTemplate( "oap/ws/admin/inspector-value.html.vm" ).merge( context, writer ); + return writer.toString(); + } + + private static boolean isLeaf( Class type ) { + return type.isPrimitive() + || type == String.class + || Number.class.isAssignableFrom( type ) + || type == Boolean.class + || type == Character.class + || type.getName().startsWith( "java." ) + || type.getName().startsWith( "javax." ); + } + + private void putInspectTables( VelocityContext context, Object instance, Reflection reflection, String queryPrefix ) { + List> fields = reflection.fields.values().stream() + .sorted() + .map( f -> { + Class type = f.underlying.getType(); + String value = ""; + if( type.isPrimitive() || type == String.class ) { + try { + value = String.valueOf( f.get( instance ) ); + } catch( Exception e ) { + value = ""; + } + } + return Map.of( + "name", f.name(), + "modifier", Modifier.toString( f.underlying.getModifiers() ), + "type", type.getName(), + "query", queryPrefix + "." + f.name(), + "value", value + ); + } ) + .collect( Collectors.toList() ); + context.put( "fields", fields ); + + Set> jpathLiteralTypes = Set.of( String.class, int.class, Integer.class, long.class, Long.class ); + List> methods = reflection.methods.stream() + .filter( m -> m.underlying.getDeclaringClass() != Object.class ) + .filter( m -> m.parameters.stream().allMatch( p -> jpathLiteralTypes.contains( p.underlying.getType() ) ) ) + .collect( Collectors.toMap( + m -> m.name() + "(" + m.parameters.stream() + .map( p -> p.underlying.getType().getName() ) + .collect( Collectors.joining( "," ) ) + ")", + m -> m, + ( a, b ) -> a, + LinkedHashMap::new + ) ) + .values().stream() + .sorted( Comparator.comparing( m -> m.name() ) ) + .map( m -> { + List> params = m.parameters.stream() + .map( p -> Map.of( + "name", p.name(), + "kind", p.underlying.getType() == String.class ? "string" : "number" + ) ) + .collect( Collectors.toList() ); + Map row = new LinkedHashMap<>(); + row.put( "name", m.name() ); + row.put( "modifier", Modifier.toString( m.underlying.getModifiers() ) ); + row.put( "returnType", m.underlying.getReturnType().getName() ); + row.put( "parameters", m.parameters.stream() + .map( p -> p.underlying.getType().getSimpleName() + " " + p.name() ) + .collect( Collectors.joining( ", " ) ) ); + row.put( "zeroArg", m.parameters.isEmpty() ); + row.put( "query", m.parameters.isEmpty() ? queryPrefix + "." + m.name() + "()" : queryPrefix + "." + m.name() ); + row.put( "params", params ); + return row; + } ) + .collect( Collectors.toList() ); + context.put( "methods", methods ); + } +} diff --git a/oap-ws/oap-ws-admin-ws/src/main/resources/META-INF/oap-module.oap b/oap-ws/oap-ws-admin-ws/src/main/resources/META-INF/oap-module.oap index 6a7b240ea3..3e783dec8f 100644 --- a/oap-ws/oap-ws-admin-ws/src/main/resources/META-INF/oap-module.oap +++ b/oap-ws/oap-ws-admin-ws/src/main/resources/META-INF/oap-module.oap @@ -18,6 +18,17 @@ services { port = httpprivate } } + ws-inspector { + implementation = oap.ws.admin.InspectorWS + parameters { + jPathWS = + kernel = + } + ws-service { + path = system/admin/inspector + port = httpprivate + } + } ws-schema { implementation = oap.ws.admin.SchemaWS ws-service { diff --git a/oap-ws/oap-ws-admin-ws/src/main/resources/oap/ws/admin/inspector-inspect-part.html.vm b/oap-ws/oap-ws-admin-ws/src/main/resources/oap/ws/admin/inspector-inspect-part.html.vm new file mode 100644 index 0000000000..3d0672a0fe --- /dev/null +++ b/oap-ws/oap-ws-admin-ws/src/main/resources/oap/ws/admin/inspector-inspect-part.html.vm @@ -0,0 +1,60 @@ +

Fields

+ + + #foreach( $f in $fields ) + + + + + + + #end +
namemodifiertypevalue
$f.name$f.modifier$f.type + #if( $f.value != "" ) + $f.value + #else + inspect + #end +
+

Methods

+ + + #foreach( $m in $methods ) + + + + + + + + #end +
namemodifierreturn typeparameters
$m.name$m.modifier$m.returnType$m.parameters + #if( $m.zeroArg ) + inspect + #else +
+ #foreach( $p in $m.params ) + + #end + +
+ #end +
+ diff --git a/oap-ws/oap-ws-admin-ws/src/main/resources/oap/ws/admin/inspector-service.html.vm b/oap-ws/oap-ws-admin-ws/src/main/resources/oap/ws/admin/inspector-service.html.vm new file mode 100644 index 0000000000..c11e1e1ac8 --- /dev/null +++ b/oap-ws/oap-ws-admin-ws/src/main/resources/oap/ws/admin/inspector-service.html.vm @@ -0,0 +1,47 @@ + +Service: $reference + +

« back to list

+#if( $found ) +

$reference

+
    +
  • module: $module
  • +
  • service: $serviceName
  • +
  • implementation: $implementation
  • +
  • enabled: $enabled
  • +
  • dependsOn: +
      + #foreach( $d in $dependsOn ) +
    • $d
    • + #end +
    +
  • +
  • supervision: $supervision
  • +
  • listen: +
      + #foreach( $e in $listen.entrySet() ) +
    • $e.key = $e.value
    • + #end +
    +
  • +
  • link: +
      + #foreach( $e in $link.entrySet() ) +
    • $e.key = $e.value
    • + #end +
    +
  • +
  • parameters: +
      + #foreach( $e in $parameters.entrySet() ) +
    • $e.key = $e.value
    • + #end +
    +
  • +
+ #parse( "oap/ws/admin/inspector-inspect-part.html.vm" ) +#else +

service not found: $reference

+#end + + diff --git a/oap-ws/oap-ws-admin-ws/src/main/resources/oap/ws/admin/inspector-ui.html.vm b/oap-ws/oap-ws-admin-ws/src/main/resources/oap/ws/admin/inspector-ui.html.vm new file mode 100644 index 0000000000..0c7e749717 --- /dev/null +++ b/oap-ws/oap-ws-admin-ws/src/main/resources/oap/ws/admin/inspector-ui.html.vm @@ -0,0 +1,22 @@ + +Kernel Services + +

Kernel Services

+ +
    +#foreach( $service in $services ) +
  • $service
  • +#end +
+ + + diff --git a/oap-ws/oap-ws-admin-ws/src/main/resources/oap/ws/admin/inspector-value.html.vm b/oap-ws/oap-ws-admin-ws/src/main/resources/oap/ws/admin/inspector-value.html.vm new file mode 100644 index 0000000000..bba504ccc5 --- /dev/null +++ b/oap-ws/oap-ws-admin-ws/src/main/resources/oap/ws/admin/inspector-value.html.vm @@ -0,0 +1,21 @@ + +Value: $query + +

« back

+

$query

+#if( !$error ) +

+ inspect + | + json +

+#end +#if( $error ) +
$stackTrace
+#elseif( $mode == "inspect" && $inspectable ) + #parse( "oap/ws/admin/inspector-inspect-part.html.vm" ) +#else +
$json
+#end + + diff --git a/oap-ws/oap-ws-admin-ws/src/test/java/oap/ws/admin/InspectorWSTest.java b/oap-ws/oap-ws-admin-ws/src/test/java/oap/ws/admin/InspectorWSTest.java new file mode 100644 index 0000000000..c972b1d9f4 --- /dev/null +++ b/oap-ws/oap-ws-admin-ws/src/test/java/oap/ws/admin/InspectorWSTest.java @@ -0,0 +1,141 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) Open Application Platform Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package oap.ws.admin; + +import oap.application.testng.KernelFixture; +import oap.testng.Fixtures; +import oap.testng.TestDirectoryFixture; +import org.testng.annotations.Test; + +import static oap.http.Http.ContentType.TEXT_HTML; +import static oap.http.test.HttpAsserts.assertGet; +import static oap.io.Resources.urlOrThrow; + +public class InspectorWSTest extends Fixtures { + private final KernelFixture kernel; + + public InspectorWSTest() { + TestDirectoryFixture testDirectoryFixture = fixture( new TestDirectoryFixture() ); + kernel = fixture( new KernelFixture( testDirectoryFixture, urlOrThrow( getClass(), "/application.test.conf" ) ) ); + } + + @Test + public void testUi() { + assertGet( kernel.httpUrl( "/system/admin/inspector/ui" ) ) + .isOk() + .hasContentType( TEXT_HTML ) + .bodyContains( "oap-ws-admin-ws-test.test-service" ) + .bodyContains( "id=\"filter\"" ); + } + + @Test + public void testServiceUi() { + kernel.service( "oap-ws-admin-ws-test", TestService.class ).setV2( "testv" ); + + assertGet( kernel.httpUrl( "/system/admin/inspector/ui/oap-ws-admin-ws-test.test-service" ) ) + .isOk() + .hasContentType( TEXT_HTML ) + .bodyContains( "implementation" ) + .bodyContains( TestService.class.getName() ) + .bodyContains( "modifier" ) + .bodyContains( "testv" ); + } + + @Test + public void testServiceUiMethods() { + assertGet( kernel.httpUrl( "/system/admin/inspector/ui/oap-ws-admin-ws-test.test-service" ) ) + .isOk() + .hasContentType( TEXT_HTML ) + .bodyContains( "getV2" ) + .bodyContains( "value?query=oap-ws-admin-ws-test.test-service.instance.getV2()" ) + .bodyDoesNotContain( "getClass" ); + } + + @Test + public void testServiceUiMethodsWithParams() { + assertGet( kernel.httpUrl( "/system/admin/inspector/ui/oap-ws-admin-ws-test.test-service" ) ) + .isOk() + .hasContentType( TEXT_HTML ) + .bodyContains( "setV2" ) + .bodyContains( "data-query=\"oap-ws-admin-ws-test.test-service.instance.setV2\"" ) + .bodyContains( "data-kind=\"string\"" ); + } + + @Test + public void testServiceUiNotFound() { + assertGet( kernel.httpUrl( "/system/admin/inspector/ui/unknown-module.unknown-service" ) ) + .isOk() + .hasContentType( TEXT_HTML ) + .bodyContains( "not found" ); + } + + @Test + public void testValuePage() { + kernel.service( "oap-ws-admin-ws-test", TestService.class ).setV2( "testv" ); + + assertGet( kernel.httpUrl( "/system/admin/inspector/ui/value?query=oap-ws-admin-ws-test.test-service.instance.value" ) ) + .isOk() + .hasContentType( TEXT_HTML ) + .bodyContains( "testv" ); + } + + @Test + public void testValuePageError() { + assertGet( kernel.httpUrl( "/system/admin/inspector/ui/value?query=oap-ws-admin-ws-test.test-service.instance.doesNotExist()" ) ) + .isOk() + .hasContentType( TEXT_HTML ) + .bodyContains( "at " ); + } + + @Test + public void testValuePageInspectMode() { + assertGet( kernel.httpUrl( "/system/admin/inspector/ui/value?query=oap-ws-admin-ws-test.test-service.instance" ) ) + .isOk() + .hasContentType( TEXT_HTML ) + .bodyContains( "Methods" ) + .bodyContains( "setV2" ) + .bodyContains( "mode=json" ); + } + + @Test + public void testValuePageJsonMode() { + assertGet( kernel.httpUrl( "/system/admin/inspector/ui/value?query=oap-ws-admin-ws-test.test-service.instance&mode=json" ) ) + .isOk() + .hasContentType( TEXT_HTML ) + .bodyContains( "\"value\"" ) + .bodyContains( "mode=inspect" ); + } + + @Test + public void testValuePageLeafDefaultsToJson() { + kernel.service( "oap-ws-admin-ws-test", TestService.class ).setV2( "testv" ); + + assertGet( kernel.httpUrl( "/system/admin/inspector/ui/value?query=oap-ws-admin-ws-test.test-service.instance.value" ) ) + .isOk() + .hasContentType( TEXT_HTML ) + .bodyContains( "testv" ) + .bodyDoesNotContain( "Methods" ); + } +} diff --git a/pom.xml b/pom.xml index 49fbf74d6f..e4d9df3bd8 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,7 @@ - 25.9.3 + 25.9.4 25.0.1 25.0.0