This is an automated email from the ASF dual-hosted git repository.

tomsun28 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git


The following commit(s) were added to refs/heads/master by this push:
     new 3ab68b5814 [improve] degrade observability consoles gracefully when 
storage lacks log/trace/metric support (#4233)
3ab68b5814 is described below

commit 3ab68b581448c1f05fc58e04b41941c830bdf9c9
Author: Duansg <[email protected]>
AuthorDate: Fri Jul 24 00:54:05 2026 +0800

    [improve] degrade observability consoles gracefully when storage lacks 
log/trace/metric support (#4233)
    
    Co-authored-by: Tomsun28 <[email protected]>
---
 .../dto/observability/ObservabilityCapability.java | 28 +++++++
 .../src/test/resources/sureness.yml                |  1 +
 .../log/controller/LogQueryController.java         | 21 +++++-
 .../ObservabilityCapabilityController.java         | 65 +++++++++++++++++
 .../log/controller/LogQueryControllerTest.java     | 15 ++++
 .../ObservabilityCapabilityControllerTest.java     | 85 ++++++++++++++++++++++
 hertzbeat-manager/src/test/resources/sureness.yml  |  1 +
 hertzbeat-startup/src/main/resources/sureness.yml  |  1 +
 .../store/history/tsdb/HistoryDataReader.java      |  7 ++
 .../tsdb/greptime/GreptimeDbDataStorage.java       |  5 ++
 .../hertzbeat-mysql-iotdb/conf/sureness.yml        |  1 +
 .../hertzbeat-mysql-tdengine/conf/sureness.yml     |  1 +
 .../conf/sureness.yml                              |  1 +
 .../conf/sureness.yml                              |  1 +
 .../conf/sureness.yml                              |  1 +
 script/sureness.yml                                |  1 +
 web-app/src/app/core/startup/startup.service.ts    |  8 +-
 .../log/log-manage/log-manage.component.html       |  3 +-
 .../log/log-manage/log-manage.component.spec.ts    |  3 +-
 .../routes/log/log-manage/log-manage.component.ts  | 23 +++++-
 .../routes/metrics/metrics-manage.component.html   |  3 +-
 .../metrics/metrics-manage.component.spec.ts       |  3 +-
 .../app/routes/metrics/metrics-manage.component.ts | 23 +++++-
 .../app/routes/observability-navigation.spec.ts    |  5 +-
 .../routes/observability/signal-query-context.ts   | 10 +++
 .../signal-storage-guide.component.ts              | 47 ++++++++++++
 .../app/routes/trace/trace-manage.component.html   |  3 +-
 .../routes/trace/trace-manage.component.spec.ts    |  6 +-
 .../src/app/routes/trace/trace-manage.component.ts | 23 +++++-
 web-app/src/assets/i18n/en-US.json                 |  3 +
 web-app/src/assets/i18n/ja-JP.json                 |  3 +
 web-app/src/assets/i18n/ko-KR.json                 |  3 +
 web-app/src/assets/i18n/pt-BR.json                 |  3 +
 web-app/src/assets/i18n/zh-CN.json                 |  3 +
 web-app/src/assets/i18n/zh-TW.json                 |  3 +
 35 files changed, 391 insertions(+), 22 deletions(-)

diff --git 
a/hertzbeat-common-core/src/main/java/org/apache/hertzbeat/common/entity/dto/observability/ObservabilityCapability.java
 
b/hertzbeat-common-core/src/main/java/org/apache/hertzbeat/common/entity/dto/observability/ObservabilityCapability.java
new file mode 100644
index 0000000000..75ed6847f2
--- /dev/null
+++ 
b/hertzbeat-common-core/src/main/java/org/apache/hertzbeat/common/entity/dto/observability/ObservabilityCapability.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.hertzbeat.common.entity.dto.observability;
+
+/**
+ * Which observability query consoles the current storage backend supports.
+ *
+ * @param log    log query console supported
+ * @param trace  trace query console supported
+ * @param metric OTLP metric query console supported
+ */
+public record ObservabilityCapability(boolean log, boolean trace, boolean 
metric) {
+}
diff --git a/hertzbeat-e2e/hertzbeat-log-e2e/src/test/resources/sureness.yml 
b/hertzbeat-e2e/hertzbeat-log-e2e/src/test/resources/sureness.yml
index 64228f740d..cdda55e93a 100644
--- a/hertzbeat-e2e/hertzbeat-log-e2e/src/test/resources/sureness.yml
+++ b/hertzbeat-e2e/hertzbeat-log-e2e/src/test/resources/sureness.yml
@@ -85,6 +85,7 @@ excludedResource:
   - /api/account/auth/**===*
   - /api/i18n/**===get
   - /api/apps/hierarchy===get
+  - /api/observability/capability===get
   - /api/push/**===*
   - /api/status/page/public/**===*
   - /api/manager/sse/**===*
diff --git 
a/hertzbeat-log/src/main/java/org/apache/hertzbeat/log/controller/LogQueryController.java
 
b/hertzbeat-log/src/main/java/org/apache/hertzbeat/log/controller/LogQueryController.java
index 07fb225d10..7cde958b43 100644
--- 
a/hertzbeat-log/src/main/java/org/apache/hertzbeat/log/controller/LogQueryController.java
+++ 
b/hertzbeat-log/src/main/java/org/apache/hertzbeat/log/controller/LogQueryController.java
@@ -24,6 +24,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Supplier;
 
 import org.apache.hertzbeat.warehouse.store.history.tsdb.HistoryDataReader;
 import org.springframework.data.domain.Page;
@@ -32,6 +33,7 @@ import org.springframework.data.domain.PageRequest;
 import org.springframework.data.domain.Sort;
 
 import lombok.extern.slf4j.Slf4j;
+import org.apache.hertzbeat.common.constants.CommonConstants;
 import org.apache.hertzbeat.common.entity.dto.Message;
 import org.apache.hertzbeat.common.entity.dto.observability.LogQueryFilter;
 import org.apache.hertzbeat.common.entity.log.LogEntry;
@@ -54,6 +56,9 @@ public class LogQueryController {
 
     private static final int MAX_PAGE_SIZE = 200;
 
+    private static final String LOG_STORAGE_UNAVAILABLE_MSG =
+            "Log query is not supported by the current storage, please enable 
a log capable storage such as GreptimeDB";
+
     private final HistoryDataReader historyDataReader;
     private final SignalWorkloadGuard workloadGuard;
 
@@ -88,7 +93,7 @@ public class LogQueryController {
             @RequestParam(value = "pageIndex", required = false, defaultValue 
= "0") Integer pageIndex,
             @Parameter(description = "Number of items per page", example = 
"20")
             @RequestParam(value = "pageSize", required = false, defaultValue = 
"20") Integer pageSize) {
-        return workloadGuard.execute(Workload.LOG_LIST, () -> {
+        return executeIfLogSupported(Workload.LOG_LIST, () -> {
             LogQueryFilter filter = filter(start, end, traceId, spanId, 
severityNumber, severityText, search,
                     serviceName, serviceNamespace, environment, resource);
             Page<LogEntry> result = getPagedLogs(filter, pageIndex, pageSize);
@@ -118,7 +123,7 @@ public class LogQueryController {
             @RequestParam(value = "serviceNamespace", required = false) String 
serviceNamespace,
             @RequestParam(value = "environment", required = false) String 
environment,
             @RequestParam(value = "resource", required = false) String 
resource) {
-        return workloadGuard.execute(Workload.LOG_AGGREGATE,
+        return executeIfLogSupported(Workload.LOG_AGGREGATE,
                 () -> doOverviewStats(filter(start, end, traceId, spanId, 
severityNumber, severityText, search,
                         serviceName, serviceNamespace, environment, 
resource)));
     }
@@ -149,7 +154,7 @@ public class LogQueryController {
             @RequestParam(value = "serviceNamespace", required = false) String 
serviceNamespace,
             @RequestParam(value = "environment", required = false) String 
environment,
             @RequestParam(value = "resource", required = false) String 
resource) {
-        return workloadGuard.execute(Workload.LOG_AGGREGATE,
+        return executeIfLogSupported(Workload.LOG_AGGREGATE,
                 () -> doTraceCoverageStats(filter(start, end, traceId, spanId, 
severityNumber, severityText, search,
                         serviceName, serviceNamespace, environment, 
resource)));
     }
@@ -182,7 +187,7 @@ public class LogQueryController {
             @RequestParam(value = "serviceNamespace", required = false) String 
serviceNamespace,
             @RequestParam(value = "environment", required = false) String 
environment,
             @RequestParam(value = "resource", required = false) String 
resource) {
-        return workloadGuard.execute(Workload.LOG_AGGREGATE,
+        return executeIfLogSupported(Workload.LOG_AGGREGATE,
                 () -> doTrendStats(filter(start, end, traceId, spanId, 
severityNumber, severityText, search,
                         serviceName, serviceNamespace, environment, 
resource)));
     }
@@ -193,6 +198,14 @@ public class LogQueryController {
         return ResponseEntity.ok(Message.success(result));
     }
 
+    private <T> ResponseEntity<Message<T>> executeIfLogSupported(Workload 
workload,
+                                                                 
Supplier<ResponseEntity<Message<T>>> operation) {
+        if (!historyDataReader.supportsLogQuery()) {
+            return ResponseEntity.ok(Message.fail(CommonConstants.FAIL_CODE, 
LOG_STORAGE_UNAVAILABLE_MSG));
+        }
+        return workloadGuard.execute(workload, operation);
+    }
+
     private LogQueryFilter filter(Long start, Long end, String traceId, String 
spanId, Integer severityNumber,
                                   String severityText, String search, String 
serviceName, String serviceNamespace,
                                   String environment, String resource) {
diff --git 
a/hertzbeat-log/src/main/java/org/apache/hertzbeat/log/controller/ObservabilityCapabilityController.java
 
b/hertzbeat-log/src/main/java/org/apache/hertzbeat/log/controller/ObservabilityCapabilityController.java
new file mode 100644
index 0000000000..055167f403
--- /dev/null
+++ 
b/hertzbeat-log/src/main/java/org/apache/hertzbeat/log/controller/ObservabilityCapabilityController.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.hertzbeat.log.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.apache.hertzbeat.common.entity.dto.Message;
+import 
org.apache.hertzbeat.common.entity.dto.observability.ObservabilityCapability;
+import org.apache.hertzbeat.log.service.ThreeSignalQueryService;
+import org.apache.hertzbeat.warehouse.store.history.tsdb.HistoryDataReader;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Report which observability query features the current storage backend 
supports,
+ * so the UI can render guidance instead of firing queries that cannot be 
served.
+ *
+ * <p>The three capabilities are probed through two different mechanisms on 
purpose: log query
+ * is dispatched per storage via {@link HistoryDataReader#supportsLogQuery()} 
(multiple storages
+ * implement log read/write), while trace/metric consoles are served only by 
the conditionally
+ * registered {@link ThreeSignalQueryService} (GreptimeDB only for now).
+ * TODO: once trace/metric query grows storage-dispatched implementations like 
logs (or log query
+ *  is absorbed into the three-signal contract), unify the probes on a single 
abstraction.
+ */
+@RestController
+@RequestMapping(path = "/api/observability", produces = "application/json")
+@Tag(name = "Observability Capability Controller")
+public class ObservabilityCapabilityController {
+
+    private final HistoryDataReader historyDataReader;
+    private final ObjectProvider<ThreeSignalQueryService> 
threeSignalQueryService;
+
+    public ObservabilityCapabilityController(HistoryDataReader 
historyDataReader,
+                                             
ObjectProvider<ThreeSignalQueryService> threeSignalQueryService) {
+        this.historyDataReader = historyDataReader;
+        this.threeSignalQueryService = threeSignalQueryService;
+    }
+
+    @GetMapping("/capability")
+    @Operation(summary = "Query observability capability",
+            description = "Return whether log, trace and OTLP metric query 
consoles are supported by the current storage backend.")
+    public ResponseEntity<Message<ObservabilityCapability>> capability() {
+        boolean traceAndMetricSupported = 
threeSignalQueryService.getIfAvailable() != null;
+        return ResponseEntity.ok(Message.success(new ObservabilityCapability(
+                historyDataReader.supportsLogQuery(), traceAndMetricSupported, 
traceAndMetricSupported)));
+    }
+}
diff --git 
a/hertzbeat-log/src/test/java/org/apache/hertzbeat/log/controller/LogQueryControllerTest.java
 
b/hertzbeat-log/src/test/java/org/apache/hertzbeat/log/controller/LogQueryControllerTest.java
index 6aba088ddf..8dd39de96d 100644
--- 
a/hertzbeat-log/src/test/java/org/apache/hertzbeat/log/controller/LogQueryControllerTest.java
+++ 
b/hertzbeat-log/src/test/java/org/apache/hertzbeat/log/controller/LogQueryControllerTest.java
@@ -19,6 +19,7 @@ package org.apache.hertzbeat.log.controller;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static 
org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
@@ -57,12 +58,26 @@ class LogQueryControllerTest {
 
     @BeforeEach
     void setUp() {
+        lenient().when(historyDataReader.supportsLogQuery()).thenReturn(true);
         this.logQueryController = new LogQueryController(historyDataReader, 
new SignalWorkloadGuard());
         this.mockMvc = MockMvcBuilders.standaloneSetup(logQueryController)
                 .setControllerAdvice(new SignalWorkloadExceptionHandler())
                 .build();
     }
 
+    @Test
+    void shouldReturnFriendlyFailureWhenLogQueryUnsupported() throws Exception 
{
+        when(historyDataReader.supportsLogQuery()).thenReturn(false);
+
+        for (String path : List.of("/api/logs/list", 
"/api/logs/stats/overview",
+                "/api/logs/stats/trace-coverage", "/api/logs/stats/trend")) {
+            mockMvc.perform(MockMvcRequestBuilders.get(path))
+                    .andExpect(status().isOk())
+                    .andExpect(jsonPath("$.code").value((int) 
CommonConstants.FAIL_CODE))
+                    .andExpect(jsonPath("$.msg").isNotEmpty());
+        }
+    }
+
     @Test
     void testListLogsWithAllFilters() throws Exception {
         // Mock data
diff --git 
a/hertzbeat-log/src/test/java/org/apache/hertzbeat/log/controller/ObservabilityCapabilityControllerTest.java
 
b/hertzbeat-log/src/test/java/org/apache/hertzbeat/log/controller/ObservabilityCapabilityControllerTest.java
new file mode 100644
index 0000000000..e406213d11
--- /dev/null
+++ 
b/hertzbeat-log/src/test/java/org/apache/hertzbeat/log/controller/ObservabilityCapabilityControllerTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.hertzbeat.log.controller;
+
+import static org.mockito.Mockito.when;
+import static 
org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static 
org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.log.service.ThreeSignalQueryService;
+import org.apache.hertzbeat.warehouse.store.history.tsdb.HistoryDataReader;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+/**
+ * Unit test for {@link ObservabilityCapabilityController}
+ */
+@ExtendWith(MockitoExtension.class)
+class ObservabilityCapabilityControllerTest {
+
+    private MockMvc mockMvc;
+
+    @Mock
+    private HistoryDataReader historyDataReader;
+
+    @Mock
+    private ObjectProvider<ThreeSignalQueryService> threeSignalQueryService;
+
+    @Mock
+    private ThreeSignalQueryService queryService;
+
+    @BeforeEach
+    void setUp() {
+        this.mockMvc = MockMvcBuilders.standaloneSetup(
+                new ObservabilityCapabilityController(historyDataReader, 
threeSignalQueryService)).build();
+    }
+
+    @Test
+    void shouldReportAllUnsupportedWithoutCapableStorage() throws Exception {
+        when(historyDataReader.supportsLogQuery()).thenReturn(false);
+        when(threeSignalQueryService.getIfAvailable()).thenReturn(null);
+
+        
mockMvc.perform(MockMvcRequestBuilders.get("/api/observability/capability"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.code").value((int) 
CommonConstants.SUCCESS_CODE))
+                .andExpect(jsonPath("$.data.log").value(false))
+                .andExpect(jsonPath("$.data.trace").value(false))
+                .andExpect(jsonPath("$.data.metric").value(false));
+    }
+
+    @Test
+    void shouldReportAllSupportedWithCapableStorage() throws Exception {
+        when(historyDataReader.supportsLogQuery()).thenReturn(true);
+        
when(threeSignalQueryService.getIfAvailable()).thenReturn(queryService);
+
+        
mockMvc.perform(MockMvcRequestBuilders.get("/api/observability/capability"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.code").value((int) 
CommonConstants.SUCCESS_CODE))
+                .andExpect(jsonPath("$.data.log").value(true))
+                .andExpect(jsonPath("$.data.trace").value(true))
+                .andExpect(jsonPath("$.data.metric").value(true));
+    }
+}
diff --git a/hertzbeat-manager/src/test/resources/sureness.yml 
b/hertzbeat-manager/src/test/resources/sureness.yml
index 64228f740d..cdda55e93a 100644
--- a/hertzbeat-manager/src/test/resources/sureness.yml
+++ b/hertzbeat-manager/src/test/resources/sureness.yml
@@ -85,6 +85,7 @@ excludedResource:
   - /api/account/auth/**===*
   - /api/i18n/**===get
   - /api/apps/hierarchy===get
+  - /api/observability/capability===get
   - /api/push/**===*
   - /api/status/page/public/**===*
   - /api/manager/sse/**===*
diff --git a/hertzbeat-startup/src/main/resources/sureness.yml 
b/hertzbeat-startup/src/main/resources/sureness.yml
index b4e8e33d86..15e2a2b102 100644
--- a/hertzbeat-startup/src/main/resources/sureness.yml
+++ b/hertzbeat-startup/src/main/resources/sureness.yml
@@ -88,6 +88,7 @@ excludedResource:
   - /api/account/auth/**===*
   - /api/i18n/**===get
   - /api/apps/hierarchy===get
+  - /api/observability/capability===get
   - /api/push/**===*
   - /api/status/page/public/**===*
   - /api/manager/sse/**===*
diff --git 
a/hertzbeat-warehouse/src/main/java/org/apache/hertzbeat/warehouse/store/history/tsdb/HistoryDataReader.java
 
b/hertzbeat-warehouse/src/main/java/org/apache/hertzbeat/warehouse/store/history/tsdb/HistoryDataReader.java
index 468e6e0557..08a42bb919 100644
--- 
a/hertzbeat-warehouse/src/main/java/org/apache/hertzbeat/warehouse/store/history/tsdb/HistoryDataReader.java
+++ 
b/hertzbeat-warehouse/src/main/java/org/apache/hertzbeat/warehouse/store/history/tsdb/HistoryDataReader.java
@@ -33,6 +33,13 @@ public interface HistoryDataReader {
      */
     boolean isServerAvailable();
 
+    /**
+     * @return whether this storage supports observability log query
+     */
+    default boolean supportsLogQuery() {
+        return false;
+    }
+
     /**
      * query history range metrics data from tsdb
      *
diff --git 
a/hertzbeat-warehouse/src/main/java/org/apache/hertzbeat/warehouse/store/history/tsdb/greptime/GreptimeDbDataStorage.java
 
b/hertzbeat-warehouse/src/main/java/org/apache/hertzbeat/warehouse/store/history/tsdb/greptime/GreptimeDbDataStorage.java
index ae953720de..d0132de5b1 100644
--- 
a/hertzbeat-warehouse/src/main/java/org/apache/hertzbeat/warehouse/store/history/tsdb/greptime/GreptimeDbDataStorage.java
+++ 
b/hertzbeat-warehouse/src/main/java/org/apache/hertzbeat/warehouse/store/history/tsdb/greptime/GreptimeDbDataStorage.java
@@ -662,6 +662,11 @@ public class GreptimeDbDataStorage extends 
AbstractHistoryDataStorage {
         }
     }
 
+    @Override
+    public boolean supportsLogQuery() {
+        return true;
+    }
+
     @Override
     public Map<String, Object> queryLogOverviewAggregate(LogQueryFilter 
filter) {
         try {
diff --git a/script/docker-compose/hertzbeat-mysql-iotdb/conf/sureness.yml 
b/script/docker-compose/hertzbeat-mysql-iotdb/conf/sureness.yml
index 282e93928a..70609bf26e 100644
--- a/script/docker-compose/hertzbeat-mysql-iotdb/conf/sureness.yml
+++ b/script/docker-compose/hertzbeat-mysql-iotdb/conf/sureness.yml
@@ -81,6 +81,7 @@ excludedResource:
   - /api/account/auth/**===*
   - /api/i18n/**===get
   - /api/apps/hierarchy===get
+  - /api/observability/capability===get
   - /api/push/**===*
   - /api/status/page/public/**===*
   - /api/manager/sse/**===*
diff --git a/script/docker-compose/hertzbeat-mysql-tdengine/conf/sureness.yml 
b/script/docker-compose/hertzbeat-mysql-tdengine/conf/sureness.yml
index 282e93928a..70609bf26e 100644
--- a/script/docker-compose/hertzbeat-mysql-tdengine/conf/sureness.yml
+++ b/script/docker-compose/hertzbeat-mysql-tdengine/conf/sureness.yml
@@ -81,6 +81,7 @@ excludedResource:
   - /api/account/auth/**===*
   - /api/i18n/**===get
   - /api/apps/hierarchy===get
+  - /api/observability/capability===get
   - /api/push/**===*
   - /api/status/page/public/**===*
   - /api/manager/sse/**===*
diff --git 
a/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/sureness.yml 
b/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/sureness.yml
index 282e93928a..70609bf26e 100644
--- a/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/sureness.yml
+++ b/script/docker-compose/hertzbeat-mysql-victoria-metrics/conf/sureness.yml
@@ -81,6 +81,7 @@ excludedResource:
   - /api/account/auth/**===*
   - /api/i18n/**===get
   - /api/apps/hierarchy===get
+  - /api/observability/capability===get
   - /api/push/**===*
   - /api/status/page/public/**===*
   - /api/manager/sse/**===*
diff --git 
a/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/sureness.yml 
b/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/sureness.yml
index 93d7529310..edc94a4ef4 100644
--- a/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/sureness.yml
+++ b/script/docker-compose/hertzbeat-postgresql-greptimedb/conf/sureness.yml
@@ -85,6 +85,7 @@ excludedResource:
   - /api/account/auth/**===*
   - /api/i18n/**===get
   - /api/apps/hierarchy===get
+  - /api/observability/capability===get
   - /api/push/**===*
   - /api/status/page/public/**===*
   - /api/manager/sse/**===*
diff --git 
a/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/sureness.yml 
b/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/sureness.yml
index 282e93928a..70609bf26e 100644
--- 
a/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/sureness.yml
+++ 
b/script/docker-compose/hertzbeat-postgresql-victoria-metrics/conf/sureness.yml
@@ -81,6 +81,7 @@ excludedResource:
   - /api/account/auth/**===*
   - /api/i18n/**===get
   - /api/apps/hierarchy===get
+  - /api/observability/capability===get
   - /api/push/**===*
   - /api/status/page/public/**===*
   - /api/manager/sse/**===*
diff --git a/script/sureness.yml b/script/sureness.yml
index 23d0f4d143..47b0270e4c 100644
--- a/script/sureness.yml
+++ b/script/sureness.yml
@@ -85,6 +85,7 @@ excludedResource:
   - /api/account/auth/**===*
   - /api/i18n/**===get
   - /api/apps/hierarchy===get
+  - /api/observability/capability===get
   - /api/push/**===*
   - /api/status/page/public/**===*
   - /api/manager/sse/**===*
diff --git a/web-app/src/app/core/startup/startup.service.ts 
b/web-app/src/app/core/startup/startup.service.ts
index 8ababf88b6..dcddafd5de 100644
--- a/web-app/src/app/core/startup/startup.service.ts
+++ b/web-app/src/app/core/startup/startup.service.ts
@@ -6,7 +6,7 @@ import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
 import { ALAIN_I18N_TOKEN, Menu, MenuService, SettingsService, TitleService } 
from '@delon/theme';
 import type { NzSafeAny } from 'ng-zorro-antd/core/types';
 import { NzIconService } from 'ng-zorro-antd/icon';
-import { Observable, zip } from 'rxjs';
+import { Observable, of, zip } from 'rxjs';
 import { catchError, map } from 'rxjs/operators';
 
 import { ICONS } from '../../../style-icons';
@@ -41,14 +41,15 @@ export class StartupService {
     return zip(
       this.i18n.loadLangData(defaultLang),
       this.httpClient.get('./assets/app-data.json', { headers: headers }),
-      this.httpClient.get(`/apps/hierarchy?lang=${defaultLang}`)
+      this.httpClient.get(`/apps/hierarchy?lang=${defaultLang}`),
+      this.httpClient.get('/observability/capability').pipe(catchError(() => 
of(null)))
     ).pipe(
       catchError((res: NzSafeAny) => {
         console.warn(`StartupService.load: Network request failed`, res);
         setTimeout(() => this.router.navigateByUrl(`/exception/500`));
         return [];
       }),
-      map(([langData, appData, menuData]: [Record<string, string>, NzSafeAny, 
NzSafeAny]) => {
+      map(([langData, appData, menuData, capabilityData]: [Record<string, 
string>, NzSafeAny, NzSafeAny, NzSafeAny]) => {
         // setting language data
         this.i18n.use(defaultLang, langData);
         // Application information: including site name, description, year
@@ -85,6 +86,7 @@ export class StartupService {
             item.hide = true;
           }
         });
+        this.storageService.putData('observabilityCapability', 
capabilityData?.data ?? null);
         this.storageService.putData('hierarchy', menuData.data);
         this.menuService.resume();
         this.titleService.suffix = appData.app.name;
diff --git a/web-app/src/app/routes/log/log-manage/log-manage.component.html 
b/web-app/src/app/routes/log/log-manage/log-manage.component.html
index c2a4439eee..d70c4a7fd0 100644
--- a/web-app/src/app/routes/log/log-manage/log-manage.component.html
+++ b/web-app/src/app/routes/log/log-manage/log-manage.component.html
@@ -14,7 +14,8 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<section class="signal-page">
+<app-signal-storage-guide *ngIf="!storageSupported"></app-signal-storage-guide>
+<section class="signal-page" *ngIf="storageSupported">
   <header class="page-heading"
     ><div
       ><h1>{{ 'observability.logs.title' | i18n }}</h1
diff --git a/web-app/src/app/routes/log/log-manage/log-manage.component.spec.ts 
b/web-app/src/app/routes/log/log-manage/log-manage.component.spec.ts
index 8a262b27df..b73477ac82 100644
--- a/web-app/src/app/routes/log/log-manage/log-manage.component.spec.ts
+++ b/web-app/src/app/routes/log/log-manage/log-manage.component.spec.ts
@@ -20,6 +20,7 @@
 import { ActivatedRoute, Router } from '@angular/router';
 
 import { LogService } from '../../../service/log.service';
+import { MemoryStorageService } from '../../../service/memory-storage.service';
 import { LogManageComponent } from './log-manage.component';
 
 describe('LogManageComponent', () => {
@@ -27,7 +28,7 @@ describe('LogManageComponent', () => {
     const logs = jasmine.createSpyObj<LogService>('LogService', ['list', 
'overviewStats', 'trendStats']);
     const route = { snapshot: { queryParamMap: { get: () => null }, data: {} } 
} as unknown as ActivatedRoute;
     const router = jasmine.createSpyObj<Router>('Router', ['navigate']);
-    const component = new LogManageComponent(logs, route, router);
+    const component = new LogManageComponent(logs, route, router, new 
MemoryStorageService());
     component.timeRange = [new Date(1_000), new Date(2_000)];
 
     component.setMode('stream');
diff --git a/web-app/src/app/routes/log/log-manage/log-manage.component.ts 
b/web-app/src/app/routes/log/log-manage/log-manage.component.ts
index 0c5f5281a4..97cda52845 100644
--- a/web-app/src/app/routes/log/log-manage/log-manage.component.ts
+++ b/web-app/src/app/routes/log/log-manage/log-manage.component.ts
@@ -33,9 +33,16 @@ import { Subject, debounceTime, finalize, forkJoin, 
switchMap, takeUntil } from
 
 import { LogEntry } from '../../../pojo/LogEntry';
 import { LogOverviewStats, LogQueryOptions, LogService } from 
'../../../service/log.service';
+import { MemoryStorageService } from '../../../service/memory-storage.service';
 import { SignalContext } from '../../../service/observability.service';
 import { SignalNavigationComponent } from 
'../../observability/signal-navigation.component';
-import { moveSignalTimeRangeToNow, readSignalTimeRange, toSignalTimeContext } 
from '../../observability/signal-query-context';
+import {
+  moveSignalTimeRangeToNow,
+  readSignalCapability,
+  readSignalTimeRange,
+  toSignalTimeContext
+} from '../../observability/signal-query-context';
+import { SignalStorageGuideComponent } from 
'../../observability/signal-storage-guide.component';
 import { SignalTimeRangeComponent } from 
'../../observability/signal-time-range.component';
 import { LogStreamComponent } from '../log-stream/log-stream.component';
 
@@ -63,6 +70,7 @@ export function trendBucketMillis(value: string): number {
     NzTableModule,
     NzTagModule,
     SignalNavigationComponent,
+    SignalStorageGuideComponent,
     SignalTimeRangeComponent,
     LogStreamComponent
   ],
@@ -95,9 +103,20 @@ export class LogManageComponent implements OnInit, 
OnDestroy {
   private readonly queryChanges = new Subject<void>();
   private readonly destroyed = new Subject<void>();
 
-  constructor(private logsService: LogService, private route: ActivatedRoute, 
private router: Router) {}
+  storageSupported = true;
+
+  constructor(
+    private logsService: LogService,
+    private route: ActivatedRoute,
+    private router: Router,
+    private storage: MemoryStorageService
+  ) {}
 
   ngOnInit(): void {
+    this.storageSupported = readSignalCapability(this.storage, 'log');
+    if (!this.storageSupported) {
+      return;
+    }
     const params = this.route.snapshot.queryParamMap;
     this.timeRange = readSignalTimeRange(params);
     this.mode = params.get('view') === 'stream' || 
this.route.snapshot.data['logMode'] === 'stream' ? 'stream' : 'query';
diff --git a/web-app/src/app/routes/metrics/metrics-manage.component.html 
b/web-app/src/app/routes/metrics/metrics-manage.component.html
index 55af41e68b..03dc7be18f 100644
--- a/web-app/src/app/routes/metrics/metrics-manage.component.html
+++ b/web-app/src/app/routes/metrics/metrics-manage.component.html
@@ -14,7 +14,8 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<section class="signal-page">
+<app-signal-storage-guide *ngIf="!storageSupported"></app-signal-storage-guide>
+<section class="signal-page" *ngIf="storageSupported">
   <header class="page-heading">
     <div>
       <h1>{{ 'observability.metrics.title' | i18n }}</h1>
diff --git a/web-app/src/app/routes/metrics/metrics-manage.component.spec.ts 
b/web-app/src/app/routes/metrics/metrics-manage.component.spec.ts
index 304d49fd13..5a3eaf9768 100644
--- a/web-app/src/app/routes/metrics/metrics-manage.component.spec.ts
+++ b/web-app/src/app/routes/metrics/metrics-manage.component.spec.ts
@@ -21,6 +21,7 @@ import { fakeAsync, tick } from '@angular/core/testing';
 import { ActivatedRoute, Router, convertToParamMap } from '@angular/router';
 import { of } from 'rxjs';
 
+import { MemoryStorageService } from '../../service/memory-storage.service';
 import { ObservabilityService } from '../../service/observability.service';
 import { MetricsManageComponent } from './metrics-manage.component';
 
@@ -53,7 +54,7 @@ describe('MetricsManageComponent', () => {
       snapshot: { queryParamMap: convertToParamMap({ start: 1_000, end: 2_000, 
step: 15 }) }
     } as unknown as ActivatedRoute;
     const router = jasmine.createSpyObj<Router>('Router', ['navigate']);
-    const component = new MetricsManageComponent(observability, route, router);
+    const component = new MetricsManageComponent(observability, route, router, 
new MemoryStorageService());
 
     component.ngOnInit();
     tick(250);
diff --git a/web-app/src/app/routes/metrics/metrics-manage.component.ts 
b/web-app/src/app/routes/metrics/metrics-manage.component.ts
index e68ce0b97f..a6d8dfe069 100644
--- a/web-app/src/app/routes/metrics/metrics-manage.component.ts
+++ b/web-app/src/app/routes/metrics/metrics-manage.component.ts
@@ -31,9 +31,16 @@ import { NzTableModule } from 'ng-zorro-antd/table';
 import { NgxEchartsModule } from 'ngx-echarts';
 import { Subject, debounceTime, finalize, switchMap, takeUntil } from 'rxjs';
 
+import { MemoryStorageService } from '../../service/memory-storage.service';
 import { MetricSeries, ObservabilityService, SignalContext } from 
'../../service/observability.service';
 import { SignalNavigationComponent } from 
'../observability/signal-navigation.component';
-import { moveSignalTimeRangeToNow, readSignalTimeRange, toSignalTimeContext } 
from '../observability/signal-query-context';
+import {
+  moveSignalTimeRangeToNow,
+  readSignalCapability,
+  readSignalTimeRange,
+  toSignalTimeContext
+} from '../observability/signal-query-context';
+import { SignalStorageGuideComponent } from 
'../observability/signal-storage-guide.component';
 import { SignalTimeRangeComponent } from 
'../observability/signal-time-range.component';
 
 interface MetricTableRow {
@@ -56,6 +63,7 @@ interface MetricTableRow {
     NzDrawerModule,
     NzAutocompleteModule,
     SignalNavigationComponent,
+    SignalStorageGuideComponent,
     SignalTimeRangeComponent
   ],
   templateUrl: './metrics-manage.component.html',
@@ -82,9 +90,20 @@ export class MetricsManageComponent implements OnInit, 
OnDestroy {
   private readonly queryChanges = new Subject<void>();
   private readonly destroyed = new Subject<void>();
 
-  constructor(private observability: ObservabilityService, private route: 
ActivatedRoute, private router: Router) {}
+  storageSupported = true;
+
+  constructor(
+    private observability: ObservabilityService,
+    private route: ActivatedRoute,
+    private router: Router,
+    private storage: MemoryStorageService
+  ) {}
 
   ngOnInit(): void {
+    this.storageSupported = readSignalCapability(this.storage, 'metric');
+    if (!this.storageSupported) {
+      return;
+    }
     const params = this.route.snapshot.queryParamMap;
     this.timeRange = readSignalTimeRange(params);
     this.serviceName = params.get('serviceName') || '';
diff --git a/web-app/src/app/routes/observability-navigation.spec.ts 
b/web-app/src/app/routes/observability-navigation.spec.ts
index 47bbd76503..6ce9160e2d 100644
--- a/web-app/src/app/routes/observability-navigation.spec.ts
+++ b/web-app/src/app/routes/observability-navigation.spec.ts
@@ -20,6 +20,7 @@
 import { ActivatedRoute, Router, convertToParamMap } from '@angular/router';
 
 import { LogService } from '../service/log.service';
+import { MemoryStorageService } from '../service/memory-storage.service';
 import { ObservabilityService } from '../service/observability.service';
 import { LogManageComponent } from './log/log-manage/log-manage.component';
 import { routes } from './routes-routing.module';
@@ -42,7 +43,7 @@ describe('Observability transition navigation', () => {
     ]);
     const route = { snapshot: { queryParamMap: convertToParamMap({}) } } as 
unknown as ActivatedRoute;
     const router = jasmine.createSpyObj<Router>('Router', ['navigate']);
-    const component = new TraceManageComponent(observability, route, router);
+    const component = new TraceManageComponent(observability, route, router, 
new MemoryStorageService());
     component.selected = {
       summary: {
         traceId: 'trace-1',
@@ -72,7 +73,7 @@ describe('Observability transition navigation', () => {
     const logs = jasmine.createSpyObj<LogService>('LogService', ['list', 
'overviewStats', 'trendStats']);
     const route = { snapshot: { queryParamMap: convertToParamMap({}) } } as 
unknown as ActivatedRoute;
     const router = jasmine.createSpyObj<Router>('Router', ['navigate']);
-    const component = new LogManageComponent(logs, route, router);
+    const component = new LogManageComponent(logs, route, router, new 
MemoryStorageService());
     component.timeRange = [new Date(1_000), new Date(2_000)];
 
     component.openTrace({ traceId: 'trace-2', resource: { 'service.name': 
'catalog' } });
diff --git a/web-app/src/app/routes/observability/signal-query-context.ts 
b/web-app/src/app/routes/observability/signal-query-context.ts
index 7c757f72f2..92cdb0d6cf 100644
--- a/web-app/src/app/routes/observability/signal-query-context.ts
+++ b/web-app/src/app/routes/observability/signal-query-context.ts
@@ -19,11 +19,21 @@
 
 import { ParamMap } from '@angular/router';
 
+import { MemoryStorageService } from '../../service/memory-storage.service';
+
 export interface SignalTimeContext {
   start?: number;
   end?: number;
 }
 
+export type SignalCapabilityKey = 'log' | 'trace' | 'metric';
+
+/** Reads the capability probe cached at startup; fail-open when the probe is 
missing. */
+export function readSignalCapability(storage: MemoryStorageService, key: 
SignalCapabilityKey): boolean {
+  const capability = storage.getData('observabilityCapability');
+  return capability == null || capability[key] !== false;
+}
+
 export type SignalTimePreset = '15m' | '30m' | '1h' | '3h' | '6h' | '12h' | 
'24h' | '7d';
 
 export const SIGNAL_TIME_PRESETS: ReadonlyArray<{ value: SignalTimePreset; 
durationMs: number }> = [
diff --git 
a/web-app/src/app/routes/observability/signal-storage-guide.component.ts 
b/web-app/src/app/routes/observability/signal-storage-guide.component.ts
new file mode 100644
index 0000000000..3d5720dd5b
--- /dev/null
+++ b/web-app/src/app/routes/observability/signal-storage-guide.component.ts
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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.
+ */
+
+import { Component } from '@angular/core';
+import { I18nPipe } from '@delon/theme';
+import { NzButtonModule } from 'ng-zorro-antd/button';
+import { NzResultModule } from 'ng-zorro-antd/result';
+
+/**
+ * Guidance rendered by the observability consoles when the current
+ * storage backend cannot serve log/trace/metric queries.
+ */
+@Component({
+  selector: 'app-signal-storage-guide',
+  standalone: true,
+  imports: [I18nPipe, NzButtonModule, NzResultModule],
+  template: `
+    <nz-result
+      nzStatus="info"
+      [nzTitle]="'observability.storage.unsupported.title' | i18n"
+      [nzSubTitle]="'observability.storage.unsupported.description' | i18n"
+    >
+      <div nz-result-extra>
+        <a nz-button nzType="primary" 
href="https://hertzbeat.apache.org/docs/start/greptime-init"; target="_blank">
+          {{ 'observability.storage.unsupported.link' | i18n }}
+        </a>
+      </div>
+    </nz-result>
+  `
+})
+export class SignalStorageGuideComponent {}
diff --git a/web-app/src/app/routes/trace/trace-manage.component.html 
b/web-app/src/app/routes/trace/trace-manage.component.html
index ab75188840..d0745946c7 100644
--- a/web-app/src/app/routes/trace/trace-manage.component.html
+++ b/web-app/src/app/routes/trace/trace-manage.component.html
@@ -14,7 +14,8 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<section class="signal-page">
+<app-signal-storage-guide *ngIf="!storageSupported"></app-signal-storage-guide>
+<section class="signal-page" *ngIf="storageSupported">
   <header class="page-heading">
     <div
       ><h1>{{ 'observability.traces.title' | i18n }}</h1
diff --git a/web-app/src/app/routes/trace/trace-manage.component.spec.ts 
b/web-app/src/app/routes/trace/trace-manage.component.spec.ts
index 044a995975..19cfd58f55 100644
--- a/web-app/src/app/routes/trace/trace-manage.component.spec.ts
+++ b/web-app/src/app/routes/trace/trace-manage.component.spec.ts
@@ -21,6 +21,7 @@ import { fakeAsync, tick } from '@angular/core/testing';
 import { ActivatedRoute, Router, convertToParamMap } from '@angular/router';
 import { of } from 'rxjs';
 
+import { MemoryStorageService } from '../../service/memory-storage.service';
 import { ObservabilityService, TraceListItem, TraceSpanNode } from 
'../../service/observability.service';
 import { TraceManageComponent } from './trace-manage.component';
 
@@ -62,7 +63,8 @@ describe('TraceManageComponent', () => {
     const component = new TraceManageComponent(
       jasmine.createSpyObj<ObservabilityService>('ObservabilityService', 
['queryTraces', 'traceOverview', 'traceDetail']),
       { snapshot: { queryParamMap: convertToParamMap({}) } } as unknown as 
ActivatedRoute,
-      jasmine.createSpyObj<Router>('Router', ['navigate'])
+      jasmine.createSpyObj<Router>('Router', ['navigate']),
+      new MemoryStorageService()
     );
     const root = span('root', '', 'STATUS_CODE_OK');
     const child = span('child', 'root', 'STATUS_CODE_ERROR');
@@ -101,7 +103,7 @@ describe('TraceManageComponent', () => {
       }
     } as unknown as ActivatedRoute;
     const router = jasmine.createSpyObj<Router>('Router', ['navigate']);
-    const component = new TraceManageComponent(observability, route, router);
+    const component = new TraceManageComponent(observability, route, router, 
new MemoryStorageService());
 
     component.ngOnInit();
     tick(250);
diff --git a/web-app/src/app/routes/trace/trace-manage.component.ts 
b/web-app/src/app/routes/trace/trace-manage.component.ts
index 405d2f31d9..4476c0ab25 100644
--- a/web-app/src/app/routes/trace/trace-manage.component.ts
+++ b/web-app/src/app/routes/trace/trace-manage.component.ts
@@ -29,6 +29,7 @@ import { NzTableModule } from 'ng-zorro-antd/table';
 import { NzTagModule } from 'ng-zorro-antd/tag';
 import { Subject, debounceTime, finalize, forkJoin, switchMap, takeUntil } 
from 'rxjs';
 
+import { MemoryStorageService } from '../../service/memory-storage.service';
 import {
   ObservabilityService,
   SignalContext,
@@ -38,7 +39,13 @@ import {
   TraceSpanNode
 } from '../../service/observability.service';
 import { SignalNavigationComponent } from 
'../observability/signal-navigation.component';
-import { moveSignalTimeRangeToNow, readSignalTimeRange, toSignalTimeContext } 
from '../observability/signal-query-context';
+import {
+  moveSignalTimeRangeToNow,
+  readSignalCapability,
+  readSignalTimeRange,
+  toSignalTimeContext
+} from '../observability/signal-query-context';
+import { SignalStorageGuideComponent } from 
'../observability/signal-storage-guide.component';
 import { SignalTimeRangeComponent } from 
'../observability/signal-time-range.component';
 
 @Component({
@@ -54,6 +61,7 @@ import { SignalTimeRangeComponent } from 
'../observability/signal-time-range.com
     NzTableModule,
     NzTagModule,
     SignalNavigationComponent,
+    SignalStorageGuideComponent,
     SignalTimeRangeComponent
   ],
   templateUrl: './trace-manage.component.html',
@@ -84,9 +92,20 @@ export class TraceManageComponent implements OnInit, 
OnDestroy {
   private readonly queryChanges = new Subject<void>();
   private readonly destroyed = new Subject<void>();
 
-  constructor(private observability: ObservabilityService, private route: 
ActivatedRoute, private router: Router) {}
+  storageSupported = true;
+
+  constructor(
+    private observability: ObservabilityService,
+    private route: ActivatedRoute,
+    private router: Router,
+    private storage: MemoryStorageService
+  ) {}
 
   ngOnInit(): void {
+    this.storageSupported = readSignalCapability(this.storage, 'trace');
+    if (!this.storageSupported) {
+      return;
+    }
     const params = this.route.snapshot.queryParamMap;
     this.timeRange = readSignalTimeRange(params);
     this.traceId = params.get('traceId') || '';
diff --git a/web-app/src/assets/i18n/en-US.json 
b/web-app/src/assets/i18n/en-US.json
index c6b21da67f..1c4cc97836 100644
--- a/web-app/src/assets/i18n/en-US.json
+++ b/web-app/src/assets/i18n/en-US.json
@@ -739,6 +739,9 @@
   "observability.traces.title": "Traces",
   "observability.traces.total": "Total traces",
   "observability.traces.results": "Trace results",
+  "observability.storage.unsupported.title": "Observability storage not 
enabled",
+  "observability.storage.unsupported.description": "The current storage does 
not support log, trace and metric queries. Please enable GreptimeDB 
(warehouse.store.greptime) as the storage backend to use this feature.",
+  "observability.storage.unsupported.link": "How to enable GreptimeDB",
   "common.button.query": "Query",
   "log.stream.apply-filters": "Apply Filters",
   "log.stream.apply-filters-tooltip": "Apply Filters",
diff --git a/web-app/src/assets/i18n/ja-JP.json 
b/web-app/src/assets/i18n/ja-JP.json
index ab8970d8bb..e641b6252b 100644
--- a/web-app/src/assets/i18n/ja-JP.json
+++ b/web-app/src/assets/i18n/ja-JP.json
@@ -698,6 +698,9 @@
   "observability.traces.title": "Traces",
   "observability.traces.total": "Total traces",
   "observability.traces.results": "Trace results",
+  "observability.storage.unsupported.title": "Observability storage not 
enabled",
+  "observability.storage.unsupported.description": "The current storage does 
not support log, trace and metric queries. Please enable GreptimeDB 
(warehouse.store.greptime) as the storage backend to use this feature.",
+  "observability.storage.unsupported.link": "How to enable GreptimeDB",
   "common.button.query": "Query",
   "log.stream.apply-filters": "フィルターを適用",
   "log.stream.apply-filters-tooltip": "フィルターを適用",
diff --git a/web-app/src/assets/i18n/ko-KR.json 
b/web-app/src/assets/i18n/ko-KR.json
index f267512bdf..f9d570a991 100644
--- a/web-app/src/assets/i18n/ko-KR.json
+++ b/web-app/src/assets/i18n/ko-KR.json
@@ -915,6 +915,9 @@
   "monitor.favorite.empty.history": "즐겨찾기한 히스토리 차트가 없습니다",
   "monitor.favorite.add": "즐겨찾기 추가",
   "monitor.favorite.remove": "즐겨찾기 제거",
+  "observability.storage.unsupported.title": "Observability storage not 
enabled",
+  "observability.storage.unsupported.description": "The current storage does 
not support log, trace and metric queries. Please enable GreptimeDB 
(warehouse.store.greptime) as the storage backend to use this feature.",
+  "observability.storage.unsupported.link": "How to enable GreptimeDB",
   "placeholder.key": "키",
   "placeholder.value": "값",
   "plugin.delete": "플러그인 삭제",
diff --git a/web-app/src/assets/i18n/pt-BR.json 
b/web-app/src/assets/i18n/pt-BR.json
index 119e33ec80..d2e92d6230 100644
--- a/web-app/src/assets/i18n/pt-BR.json
+++ b/web-app/src/assets/i18n/pt-BR.json
@@ -467,6 +467,9 @@
   "observability.traces.title": "Traces",
   "observability.traces.total": "Total traces",
   "observability.traces.results": "Trace results",
+  "observability.storage.unsupported.title": "Observability storage not 
enabled",
+  "observability.storage.unsupported.description": "The current storage does 
not support log, trace and metric queries. Please enable GreptimeDB 
(warehouse.store.greptime) as the storage backend to use this feature.",
+  "observability.storage.unsupported.link": "How to enable GreptimeDB",
   "common.button.query": "Query",
   "log.stream.apply-filters": "Aplicar filtros",
   "log.stream.apply-filters-tooltip": "Aplicar filtros",
diff --git a/web-app/src/assets/i18n/zh-CN.json 
b/web-app/src/assets/i18n/zh-CN.json
index 222d5a91c6..d0b49fc070 100644
--- a/web-app/src/assets/i18n/zh-CN.json
+++ b/web-app/src/assets/i18n/zh-CN.json
@@ -742,6 +742,9 @@
   "observability.traces.title": "链路",
   "observability.traces.total": "链路总数",
   "observability.traces.results": "链路结果",
+  "observability.storage.unsupported.title": "可观测性存储未启用",
+  "observability.storage.unsupported.description": "当前存储不支持日志、链路与指标查询,请启用 
GreptimeDB(warehouse.store.greptime)作为存储后再使用该功能。",
+  "observability.storage.unsupported.link": "如何启用 GreptimeDB",
   "common.button.query": "查询",
   "log.stream.apply-filters": "应用过滤器",
   "log.stream.apply-filters-tooltip": "应用过滤器",
diff --git a/web-app/src/assets/i18n/zh-TW.json 
b/web-app/src/assets/i18n/zh-TW.json
index 6d55e53bc7..fc76f38dd2 100644
--- a/web-app/src/assets/i18n/zh-TW.json
+++ b/web-app/src/assets/i18n/zh-TW.json
@@ -702,6 +702,9 @@
   "observability.traces.title": "鏈路",
   "observability.traces.total": "鏈路總數",
   "observability.traces.results": "鏈路結果",
+  "observability.storage.unsupported.title": "可觀測性儲存未啟用",
+  "observability.storage.unsupported.description": "目前儲存不支援日誌、鏈路與指標查詢,請啟用 
GreptimeDB(warehouse.store.greptime)作為儲存後再使用該功能。",
+  "observability.storage.unsupported.link": "如何啟用 GreptimeDB",
   "common.button.query": "查詢",
   "log.stream.apply-filters": "應用過濾器",
   "log.stream.apply-filters-tooltip": "應用過濾器",


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to