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

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new ff851fe1 feat(metrics): bundle Prometheus alert rule YAML templates 
(#933)
ff851fe1 is described below

commit ff851fe11ef2851e33bec14d406a1b15ca40355a
Author: zhaohai <[email protected]>
AuthorDate: Tue Aug 4 18:05:21 2026 +0800

    feat(metrics): bundle Prometheus alert rule YAML templates (#933)
    
    * feat(metrics): bundle Prometheus alert rule YAML templates (METRICS-01)
    
    Add 23 Prometheus alert rule YAML assets under resources/alerts and expose
    them through AlertRuleAssetService/Controller (/api/alert-rules/assets):
    list, get raw yaml, and export as attachment. 
AlertService.defaultPrometheusRules()
    now seeds from these bundled assets when the in-memory repository is empty,
    satisfying the "load as default" requirement. Frontend adds a template 
browser
    (nav + page + list component) with mock/real data mode and export download,
    plus api/service/component unit tests.
    
    Backend: 10 tests (service 5, controller 3, default-seed 2).
    Frontend: 13 tests (api 3, service 7, component 3); tsc + eslint clean.
    
    Co-Authored-By: WorkBuddy <[email protected]>
    
    * fix(ci): resolve tsc -b unused React import in alert rule asset frontend
    
    AlertRuleAssetList.tsx: drop unused default `React` import (automatic JSX
    runtime). The page component uses `React.FC` so it is left untouched. Fixes
    the TS6133 failure under `tsc -b` in the Docker build.
    
    Co-Authored-By: WorkBuddy <[email protected]>
    
    * test: wire AlertRuleAssetService into AlertServiceTest after template 
refactor
    
    ---------
    
    Co-authored-by: WorkBuddy <[email protected]>
    Co-authored-by: lizhimins <[email protected]>
---
 server/scripts/gen_alert_rule_yaml.py              | 115 +++++++++++
 .../studio/ops/alert/AlertRuleAssetController.java |  71 +++++++
 .../studio/ops/alert/AlertRuleAssetInfo.java       |  26 +++
 .../studio/ops/alert/AlertRuleAssetService.java    | 180 ++++++++++++++++++
 .../rocketmq/studio/ops/alert/AlertService.java    |  41 +---
 .../studio/ops/alert/PrometheusAlertRule.java      |  32 ++++
 .../resources/alerts/rocketmq-broker-cpu-high.yaml |  16 ++
 .../alerts/rocketmq-broker-disk-high.yaml          |  16 ++
 .../resources/alerts/rocketmq-broker-down.yaml     |  16 ++
 .../alerts/rocketmq-broker-memory-high.yaml        |  16 ++
 .../alerts/rocketmq-broker-replication-lag.yaml    |  16 ++
 .../alerts/rocketmq-client-connection-drop.yaml    |  16 ++
 .../resources/alerts/rocketmq-client-timeout.yaml  |  16 ++
 .../alerts/rocketmq-consumer-group-empty.yaml      |  16 ++
 .../alerts/rocketmq-consumer-lag-critical.yaml     |  16 ++
 .../alerts/rocketmq-consumer-lag-high.yaml         |  16 ++
 .../alerts/rocketmq-consumer-rebalance.yaml        |  16 ++
 .../resources/alerts/rocketmq-dlq-resend-high.yaml |  16 ++
 .../resources/alerts/rocketmq-exception-rate.yaml  |  16 ++
 .../resources/alerts/rocketmq-jvm-gc-cpu-high.yaml |  16 ++
 .../alerts/rocketmq-producer-failure.yaml          |  16 ++
 .../alerts/rocketmq-producer-latency-high.yaml     |  16 ++
 .../alerts/rocketmq-producer-tps-drop.yaml         |  16 ++
 .../main/resources/alerts/rocketmq-proxy-down.yaml |  16 ++
 .../alerts/rocketmq-proxy-latency-high.yaml        |  16 ++
 .../alerts/rocketmq-threadpool-reject.yaml         |  16 ++
 .../alerts/rocketmq-topic-accumulation.yaml        |  16 ++
 .../alerts/rocketmq-topic-dispatch-latency.yaml    |  16 ++
 .../resources/alerts/rocketmq-topic-in-drop.yaml   |  16 ++
 .../ops/alert/AlertRuleAssetControllerTest.java    |  81 ++++++++
 .../ops/alert/AlertRuleAssetServiceTest.java       |  84 +++++++++
 .../ops/alert/AlertServiceDefaultRulesTest.java    |  68 +++++++
 .../studio/ops/alert/AlertServiceTest.java         |  11 +-
 web/src/App.tsx                                    |   2 +
 web/src/api/alertRuleAssets.test.ts                |  71 +++++++
 web/src/api/alertRuleAssets.ts                     |  42 +++++
 web/src/components/AlertRuleAssetList.tsx          | 197 +++++++++++++++++++
 .../__tests__/AlertRuleAssetList.test.tsx          | 127 +++++++++++++
 web/src/i18n/translations.ts                       |  10 +
 web/src/layouts/MainLayout.tsx                     |   7 +
 web/src/mock/alertRuleAssets.ts                    | 210 +++++++++++++++++++++
 web/src/pages/studio/AlertRuleAssets.tsx           |  44 +++++
 web/src/services/alertRuleAssetService.test.ts     | 101 ++++++++++
 web/src/services/alertRuleAssetService.ts          |  39 ++++
 44 files changed, 1884 insertions(+), 43 deletions(-)

diff --git a/server/scripts/gen_alert_rule_yaml.py 
b/server/scripts/gen_alert_rule_yaml.py
new file mode 100644
index 00000000..cf1b0d3a
--- /dev/null
+++ b/server/scripts/gen_alert_rule_yaml.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python3
+"""Generate RocketMQ Prometheus alert rule YAML assets.
+
+Each asset is a standalone Prometheus rule file (one alert per file) covering
+broker, consumer, producer, topic, client, proxy and error conditions. The
+dashboard loads these as the default alert rule set.
+"""
+import os
+
+OUT_DIR = os.path.join(os.path.dirname(__file__), "..", "src", "main", 
"resources", "alerts")
+os.makedirs(OUT_DIR, exist_ok=True)
+
+# (file_slug, alert, group, expr, for_, severity, team, summary, description)
+RULES = [
+    ("rocketmq-broker-down", "RocketMQBrokerDown", "rocketmq-broker.rules",
+     'up{job=~".*rocketmq.*broker.*"} == 0', "1m", "critical", "broker",
+     "RocketMQ broker is down", "A RocketMQ broker scrape target has been 
unavailable for more than 1 minute."),
+    ("rocketmq-broker-cpu-high", "RocketMQBrokerCPUHigh", 
"rocketmq-broker.rules",
+     '100 * (1 - avg 
by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m]))) > 85', "5m", 
"warning", "broker",
+     "Broker CPU usage high", "Broker CPU usage has stayed above 85% for 5 
minutes."),
+    ("rocketmq-broker-memory-high", "RocketMQBrokerMemoryHigh", 
"rocketmq-broker.rules",
+     'jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"} * 
100 > 85', "5m", "warning", "broker",
+     "Broker JVM heap high", "Broker JVM heap usage is above 85%."),
+    ("rocketmq-broker-disk-high", "RocketMQBrokerDiskHigh", 
"rocketmq-broker.rules",
+     'rocketmq_disk_use_ratio > 85', "5m", "critical", "broker",
+     "Broker disk usage high", "Broker disk usage ratio is above 85%."),
+    ("rocketmq-broker-replication-lag", "RocketMQBrokerReplicationLag", 
"rocketmq-broker.rules",
+     'rocketmq_broker_replication_fall_behind_bytes > 1073741824', "5m", 
"warning", "broker",
+     "Replication lag high", "Master-slave replication fall-behind is above 
1GiB."),
+    ("rocketmq-consumer-lag-high", "RocketMQConsumerLagHigh", 
"rocketmq-consumer.rules",
+     'rocketmq_consumer_lag_messages > 100000', "5m", "warning", "consumer",
+     "Consumer lag high", "A consumer group lag is above 100000 messages."),
+    ("rocketmq-consumer-lag-critical", "RocketMQConsumerLagCritical", 
"rocketmq-consumer.rules",
+     'rocketmq_consumer_lag_messages > 1000000', "5m", "critical", "consumer",
+     "Consumer lag critical", "A consumer group lag is above 1 million 
messages."),
+    ("rocketmq-consumer-rebalance", "RocketMQConsumerRebalance", 
"rocketmq-consumer.rules",
+     'increase(rocketmq_consumer_rebalance_times[5m]) > 10', "5m", "warning", 
"consumer",
+     "Frequent consumer rebalances", "More than 10 consumer rebalances 
happened in 5 minutes."),
+    ("rocketmq-consumer-group-empty", "RocketMQConsumerGroupEmpty", 
"rocketmq-consumer.rules",
+     'absent(rocketmq_consumer_lag_messages) == 1', "10m", "info", "consumer",
+     "Consumer group missing", "No consumer lag metrics observed for a 
consumer group."),
+    ("rocketmq-producer-latency-high", "RocketMQProducerSendLatencyHigh", 
"rocketmq-client.rules",
+     'rocketmq_producer_send_to_back_rt > 1000', "5m", "warning", "client",
+     "Producer send latency high", "Producer send-to-broker latency is above 
1000 ms."),
+    ("rocketmq-producer-failure", "RocketMQProducerSendFailure", 
"rocketmq-client.rules",
+     'rate(rocketmq_producer_send_failure_count[5m]) > 0', "5m", "critical", 
"client",
+     "Producer send failures", "Producer message send failures have been 
observed."),
+    ("rocketmq-producer-tps-drop", "RocketMQProducerTPSDrop", 
"rocketmq-client.rules",
+     'rate(rocketmq_messages_in_total[5m]) < 0.1 * 
rate(rocketmq_messages_in_total[1h] offset 1h)', "10m", "warning", "client",
+     "Producer TPS dropped", "Ingress TPS dropped by more than 90% compared 
with one hour ago."),
+    ("rocketmq-topic-in-drop", "RocketMQTopicMessageInDrop", 
"rocketmq-topic.rules",
+     'rate(rocketmq_messages_in_total[10m]) == 0', "10m", "info", "topic",
+     "No incoming messages", "No incoming messages have been observed for 10 
minutes."),
+    ("rocketmq-topic-accumulation", "RocketMQTopicAccumulation", 
"rocketmq-topic.rules",
+     'rocketmq_dispatch_behind_bytes > 1073741824', "5m", "warning", "topic",
+     "Topic dispatch backlog high", "Topic dispatch behind bytes is above 
1GiB."),
+    ("rocketmq-topic-dispatch-latency", "RocketMQTopicDispatchLatency", 
"rocketmq-topic.rules",
+     'histogram_quantile(0.99, rate(rocketmq_dispatch_latency_bucket[5m])) > 
1', "5m", "warning", "topic",
+     "Dispatch latency high", "99th percentile dispatch latency is above 1 
second."),
+    ("rocketmq-client-connection-drop", "RocketMQClientConnectionDrop", 
"rocketmq-client.rules",
+     'changes(rocketmq_producer_count[5m]) < -5', "5m", "warning", "client",
+     "Client connections dropped", "More than 5 producer connections dropped 
in 5 minutes."),
+    ("rocketmq-client-timeout", "RocketMQClientTimeout", 
"rocketmq-client.rules",
+     'rocketmq_send_to_client_latency > 3000', "5m", "warning", "client",
+     "Client push latency high", "Push-to-client latency is above 3000 ms."),
+    ("rocketmq-proxy-down", "RocketMQProxyDown", "rocketmq-proxy.rules",
+     'up{job=~".*rocketmq.*proxy.*"} == 0', "1m", "critical", "proxy",
+     "RocketMQ proxy is down", "A RocketMQ 5.x proxy target has been down for 
more than 1 minute."),
+    ("rocketmq-proxy-latency-high", "RocketMQProxyLatencyHigh", 
"rocketmq-proxy.rules",
+     'histogram_quantile(0.99, rate(rocketmq_proxy_process_time_bucket[5m])) > 
1', "5m", "warning", "proxy",
+     "Proxy latency high", "99th percentile proxy process time is above 1 
second."),
+    ("rocketmq-exception-rate", "RocketMQBrokerExceptions", 
"rocketmq-errors.rules",
+     'rate(rocketmq_broker_exception_count[5m]) > 0', "5m", "critical", 
"broker",
+     "Broker exceptions", "Broker runtime exceptions have been observed."),
+    ("rocketmq-dlq-resend-high", "RocketMQDLQResendHigh", 
"rocketmq-errors.rules",
+     'rate(rocketmq_dlq_resend_count[5m]) > 10', "5m", "warning", "consumer",
+     "DLQ resends high", "More than 10 dead-letter queue resends occurred in 5 
minutes."),
+    ("rocketmq-threadpool-reject", "RocketMQThreadPoolReject", 
"rocketmq-broker.rules",
+     'increase(rocketmq_threadpool_reject_count[5m]) > 0', "5m", "critical", 
"broker",
+     "Thread pool rejections", "The broker thread pool rejected tasks, 
indicating saturation."),
+    ("rocketmq-jvm-gc-cpu-high", "RocketMQJVMCpuHigh", "rocketmq-broker.rules",
+     'rate(jvm_gc_pause_seconds_count[5m]) * 
avg(rate(jvm_gc_pause_seconds_sum[5m])) > 0.3', "5m", "warning", "broker",
+     "JVM GC CPU high", "The broker spends more than 30% of CPU time in GC 
pauses."),
+]
+
+TEMPLATE = """\
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: {group}
+    rules:
+      - alert: {alert}
+        expr: {expr}
+        for: {for_}
+        labels:
+          severity: {severity}
+          team: {team}
+        annotations:
+          summary: "{summary}"
+          description: "{description}"
+"""
+
+for slug, alert, group, expr, for_, severity, team, summary, description in 
RULES:
+    content = TEMPLATE.format(
+        group=group, alert=alert, expr=expr, for_=for_,
+        severity=severity, team=team, summary=summary, description=description,
+    )
+    path = os.path.join(OUT_DIR, f"{slug}.yaml")
+    with open(path, "w", encoding="utf-8") as f:
+        f.write(content)
+    print(f"wrote {path}")
+
+print(f"TOTAL alert rule assets: {len(RULES)}")
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetController.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetController.java
new file mode 100644
index 00000000..aa991290
--- /dev/null
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetController.java
@@ -0,0 +1,71 @@
+/*
+ * 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.rocketmq.studio.ops.alert;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import lombok.RequiredArgsConstructor;
+import org.apache.rocketmq.studio.common.domain.Result;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/alert-rules/assets")
+@RequiredArgsConstructor
+public class AlertRuleAssetController {
+
+    private final AlertRuleAssetService alertRuleAssetService;
+
+    @Operation(summary = "List bundled alert rule assets",
+            description = "Returns metadata for every RocketMQ Prometheus 
alert rule YAML shipped with the dashboard")
+    @ApiResponse(responseCode = "200", description = "Assets listed 
successfully", useReturnTypeSchema = true)
+    @GetMapping
+    public Result<List<AlertRuleAssetInfo>> listAssets() {
+        return Result.ok(alertRuleAssetService.listAssets());
+    }
+
+    @Operation(summary = "Get an alert rule asset",
+            description = "Returns the raw Prometheus alert rule YAML for the 
given asset name")
+    @ApiResponse(responseCode = "200", description = "Asset returned 
successfully", useReturnTypeSchema = true)
+    @ApiResponse(responseCode = "404", description = "Asset name is unknown")
+    @GetMapping("/{name}")
+    public Result<String> getAsset(@PathVariable("name") String name) {
+        return Result.ok(alertRuleAssetService.getAssetYaml(name));
+    }
+
+    @Operation(summary = "Export an alert rule asset",
+            description = "Returns the raw Prometheus alert rule YAML as a 
downloadable attachment")
+    @ApiResponse(responseCode = "200", description = "Asset YAML returned")
+    @ApiResponse(responseCode = "404", description = "Asset name is unknown")
+    @GetMapping("/{name}/export")
+    public ResponseEntity<byte[]> exportAsset(@PathVariable("name") String 
name) {
+        String yaml = alertRuleAssetService.getAssetYaml(name);
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.parseMediaType("application/x-yaml"));
+        headers.setContentDispositionFormData("attachment", name + ".yaml");
+        return new ResponseEntity<>(yaml.getBytes(StandardCharsets.UTF_8), 
headers, HttpStatus.OK);
+    }
+}
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetInfo.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetInfo.java
new file mode 100644
index 00000000..ea7a144d
--- /dev/null
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetInfo.java
@@ -0,0 +1,26 @@
+/*
+ * 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.rocketmq.studio.ops.alert;
+
+import java.util.List;
+
+/**
+ * Metadata describing a bundled alert rule YAML asset. The {@code name} 
matches
+ * the file name (without the {@code .yaml} suffix).
+ */
+public record AlertRuleAssetInfo(String name, String group, int ruleCount, 
List<String> severities) {
+}
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetService.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetService.java
new file mode 100644
index 00000000..3c2d173f
--- /dev/null
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetService.java
@@ -0,0 +1,180 @@
+/*
+ * 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.rocketmq.studio.ops.alert;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.rocketmq.studio.common.exception.BusinessException;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Loads the Prometheus alert rule YAML assets bundled under
+ * {@code classpath*:alerts/*.yaml} and exposes them for listing, viewing,
+ * exporting and as the default alert rule set.
+ */
+@Slf4j
+@Service
+public class AlertRuleAssetService {
+
+    private static final String LOCATION_PATTERN = "classpath*:alerts/*.yaml";
+
+    private final ObjectMapper yamlMapper = new ObjectMapper(new 
YAMLFactory());
+    private final ResourcePatternResolver resourceResolver = new 
PathMatchingResourcePatternResolver();
+
+    /**
+     * Lists metadata for every bundled alert rule asset.
+     */
+    public List<AlertRuleAssetInfo> listAssets() {
+        List<AlertRuleAssetInfo> infos = new ArrayList<>();
+        for (Resource resource : resolveResources()) {
+            String name = nameOf(resource);
+            if (name == null) {
+                continue;
+            }
+            try (InputStream in = resource.getInputStream()) {
+                JsonNode root = yamlMapper.readTree(in);
+                List<PrometheusAlertRule> rules = parseRules(root);
+                Set<String> severities = new LinkedHashSet<>();
+                String group = rules.isEmpty() ? "" : rules.get(0).group();
+                rules.forEach(rule -> severities.add(rule.severity()));
+                infos.add(new AlertRuleAssetInfo(name, group, rules.size(), 
new ArrayList<>(severities)));
+            } catch (IOException e) {
+                log.warn("Skipping unreadable alert rule asset {}: {}", 
resource, e.getMessage());
+            }
+        }
+        infos.sort((a, b) -> a.name().compareTo(b.name()));
+        return infos;
+    }
+
+    /**
+     * Returns the raw YAML content for the given asset name.
+     *
+     * @throws BusinessException with code 404 when the asset is unknown
+     */
+    public String getAssetYaml(String name) {
+        Resource resource = findResource(name);
+        if (resource == null) {
+            throw new BusinessException(404, "Alert rule asset not found: " + 
name);
+        }
+        try (InputStream in = resource.getInputStream()) {
+            return new String(in.readAllBytes(), StandardCharsets.UTF_8);
+        } catch (IOException e) {
+            throw new BusinessException(500, "Failed to read alert rule asset: 
" + name);
+        }
+    }
+
+    /**
+     * Loads every rule from every bundled asset as the default alert set.
+     */
+    public List<PrometheusAlertRule> loadDefaultRules() {
+        List<PrometheusAlertRule> rules = new ArrayList<>();
+        for (Resource resource : resolveResources()) {
+            if (nameOf(resource) == null) {
+                continue;
+            }
+            try (InputStream in = resource.getInputStream()) {
+                rules.addAll(parseRules(yamlMapper.readTree(in)));
+            } catch (IOException e) {
+                log.warn("Skipping unreadable alert rule asset {}: {}", 
resource, e.getMessage());
+            }
+        }
+        return rules;
+    }
+
+    private List<PrometheusAlertRule> parseRules(JsonNode root) {
+        List<PrometheusAlertRule> rules = new ArrayList<>();
+        JsonNode groups = root.get("groups");
+        if (groups == null || !groups.isArray()) {
+            return rules;
+        }
+        for (JsonNode group : groups) {
+            String groupName = textOr(group, "name", "");
+            JsonNode ruleNodes = group.get("rules");
+            if (ruleNodes == null || !ruleNodes.isArray()) {
+                continue;
+            }
+            for (JsonNode rule : ruleNodes) {
+                if (!rule.isObject()) {
+                    continue;
+                }
+                String alert = textOr(rule, "alert", "");
+                String expr = textOr(rule, "expr", "");
+                String duration = textOr(rule, "for", "5m");
+                String severity = textOr(labelsOf(rule), "severity", 
"warning");
+                String team = textOr(labelsOf(rule), "team", "broker");
+                String summary = textOr(annotationsOf(rule), "summary", alert);
+                String description = textOr(annotationsOf(rule), 
"description", "");
+                rules.add(new PrometheusAlertRule(groupName, alert, expr, 
duration, severity, team, summary, description));
+            }
+        }
+        return rules;
+    }
+
+    private Resource findResource(String name) {
+        for (Resource resource : resolveResources()) {
+            if (name.equals(nameOf(resource))) {
+                return resource;
+            }
+        }
+        return null;
+    }
+
+    private Resource[] resolveResources() {
+        try {
+            return resourceResolver.getResources(LOCATION_PATTERN);
+        } catch (IOException e) {
+            log.warn("Unable to resolve alert rule assets: {}", 
e.getMessage());
+            return new Resource[0];
+        }
+    }
+
+    private static String nameOf(Resource resource) {
+        String filename = resource.getFilename();
+        if (filename == null || !filename.endsWith(".yaml")) {
+            return null;
+        }
+        return filename.substring(0, filename.length() - ".yaml".length());
+    }
+
+    private static String textOr(JsonNode node, String field, String fallback) 
{
+        JsonNode value = node.get(field);
+        return value != null && value.isTextual() ? value.asText() : fallback;
+    }
+
+    private static JsonNode labelsOf(JsonNode rule) {
+        JsonNode labels = rule.get("labels");
+        return labels != null && labels.isObject() ? labels : rule;
+    }
+
+    private static JsonNode annotationsOf(JsonNode rule) {
+        JsonNode annotations = rule.get("annotations");
+        return annotations != null && annotations.isObject() ? annotations : 
rule;
+    }
+}
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertService.java 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertService.java
index 418a7fc0..cd405ebc 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertService.java
@@ -21,7 +21,6 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
@@ -31,6 +30,7 @@ import java.util.UUID;
 public class AlertService {
 
     private final AlertRepository alertRepository;
+    private final AlertRuleAssetService alertRuleAssetService;
 
 
     public List<AlertRuleVO> listRules() {
@@ -126,40 +126,7 @@ public class AlertService {
     }
 
     private List<PrometheusAlertRule> defaultPrometheusRules() {
-        List<PrometheusAlertRule> rules = new ArrayList<>();
-        rules.add(rule("rocketmq-broker.rules", "RocketMQBrokerDown", 
"up{job=~\".*rocketmq.*\"} == 0", "1m",
-                "critical", "broker", "RocketMQ broker is down",
-                "A RocketMQ broker scrape target has been unavailable for more 
than 1 minute."));
-        rules.add(rule("rocketmq-consumer.rules", "RocketMQConsumerLagHigh",
-                "rocketmq_consumer_lag_messages > 100000", "5m",
-                "warning", "consumer", "Consumer lag is high",
-                "A consumer group has accumulated more than 100000 
messages."));
-        rules.add(rule("rocketmq-consumer.rules", 
"RocketMQConsumerLagCritical",
-                "rocketmq_consumer_lag_messages > 1000000", "5m",
-                "critical", "consumer", "Consumer lag is critical",
-                "A consumer group has accumulated more than 1000000 
messages."));
-        rules.add(rule("rocketmq-client.rules", 
"RocketMQProducerSendLatencyHigh",
-                "rocketmq_producer_send_to_back_rt > 1000", "5m",
-                "warning", "client", "Producer send latency is high",
-                "Producer send-to-broker latency has stayed above 1000 ms."));
-        rules.add(rule("rocketmq-broker.rules", 
"RocketMQProcessorWatermarkHigh",
-                "rocketmq_processor_watermark > 80", "5m",
-                "warning", "broker", "Processor watermark is high",
-                "Broker processor watermark is above 80 percent."));
-        rules.add(rule("rocketmq-topic.rules", "RocketMQMessageInDrop",
-                "rate(rocketmq_messages_in_total[5m]) == 0", "10m",
-                "info", "topic", "No incoming messages",
-                "No incoming messages have been observed for 10 minutes."));
-        rules.add(rule("rocketmq-consumer.rules", "RocketMQMessageOutDrop",
-                "rate(rocketmq_messages_out_total[5m]) == 0", "10m",
-                "info", "consumer", "No outgoing messages",
-                "No outgoing messages have been observed for 10 minutes."));
-        return rules;
-    }
-
-    private PrometheusAlertRule rule(String group, String alert, String expr, 
String duration,
-                                     String severity, String team, String 
summary, String description) {
-        return new PrometheusAlertRule(group, alert, expr, duration, severity, 
team, summary, description);
+        return alertRuleAssetService.loadDefaultRules();
     }
 
     private PrometheusAlertRule toPrometheusRule(AlertRuleVO rule) {
@@ -301,10 +268,6 @@ public class AlertService {
         return value != null && !value.trim().isEmpty();
     }
 
-    private record PrometheusAlertRule(String group, String alert, String 
expr, String duration,
-                                       String severity, String team, String 
summary, String description) {
-    }
-
     private void validateRuleId(String id) {
         if (id == null || id.isBlank()) {
             throw new BusinessException(400, "Alert rule ID is required");
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/PrometheusAlertRule.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/PrometheusAlertRule.java
new file mode 100644
index 00000000..8ad18d62
--- /dev/null
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/PrometheusAlertRule.java
@@ -0,0 +1,32 @@
+/*
+ * 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.rocketmq.studio.ops.alert;
+
+/**
+ * A single Prometheus alerting rule in its serialized form.
+ */
+public record PrometheusAlertRule(
+        String group,
+        String alert,
+        String expr,
+        String duration,
+        String severity,
+        String team,
+        String summary,
+        String description
+) {
+}
diff --git a/server/src/main/resources/alerts/rocketmq-broker-cpu-high.yaml 
b/server/src/main/resources/alerts/rocketmq-broker-cpu-high.yaml
new file mode 100644
index 00000000..ce0d1443
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-broker-cpu-high.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-broker.rules
+    rules:
+      - alert: RocketMQBrokerCPUHigh
+        expr: 100 * (1 - avg 
by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m]))) > 85
+        for: 5m
+        labels:
+          severity: warning
+          team: broker
+        annotations:
+          summary: "Broker CPU usage high"
+          description: "Broker CPU usage has stayed above 85% for 5 minutes."
diff --git a/server/src/main/resources/alerts/rocketmq-broker-disk-high.yaml 
b/server/src/main/resources/alerts/rocketmq-broker-disk-high.yaml
new file mode 100644
index 00000000..ff71d44e
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-broker-disk-high.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-broker.rules
+    rules:
+      - alert: RocketMQBrokerDiskHigh
+        expr: rocketmq_disk_use_ratio > 85
+        for: 5m
+        labels:
+          severity: critical
+          team: broker
+        annotations:
+          summary: "Broker disk usage high"
+          description: "Broker disk usage ratio is above 85%."
diff --git a/server/src/main/resources/alerts/rocketmq-broker-down.yaml 
b/server/src/main/resources/alerts/rocketmq-broker-down.yaml
new file mode 100644
index 00000000..7a3c9e63
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-broker-down.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-broker.rules
+    rules:
+      - alert: RocketMQBrokerDown
+        expr: up{job=~".*rocketmq.*broker.*"} == 0
+        for: 1m
+        labels:
+          severity: critical
+          team: broker
+        annotations:
+          summary: "RocketMQ broker is down"
+          description: "A RocketMQ broker scrape target has been unavailable 
for more than 1 minute."
diff --git a/server/src/main/resources/alerts/rocketmq-broker-memory-high.yaml 
b/server/src/main/resources/alerts/rocketmq-broker-memory-high.yaml
new file mode 100644
index 00000000..171d5ffb
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-broker-memory-high.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-broker.rules
+    rules:
+      - alert: RocketMQBrokerMemoryHigh
+        expr: jvm_memory_used_bytes{area="heap"} / 
jvm_memory_max_bytes{area="heap"} * 100 > 85
+        for: 5m
+        labels:
+          severity: warning
+          team: broker
+        annotations:
+          summary: "Broker JVM heap high"
+          description: "Broker JVM heap usage is above 85%."
diff --git 
a/server/src/main/resources/alerts/rocketmq-broker-replication-lag.yaml 
b/server/src/main/resources/alerts/rocketmq-broker-replication-lag.yaml
new file mode 100644
index 00000000..271e3989
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-broker-replication-lag.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-broker.rules
+    rules:
+      - alert: RocketMQBrokerReplicationLag
+        expr: rocketmq_broker_replication_fall_behind_bytes > 1073741824
+        for: 5m
+        labels:
+          severity: warning
+          team: broker
+        annotations:
+          summary: "Replication lag high"
+          description: "Master-slave replication fall-behind is above 1GiB."
diff --git 
a/server/src/main/resources/alerts/rocketmq-client-connection-drop.yaml 
b/server/src/main/resources/alerts/rocketmq-client-connection-drop.yaml
new file mode 100644
index 00000000..9857c9c2
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-client-connection-drop.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-client.rules
+    rules:
+      - alert: RocketMQClientConnectionDrop
+        expr: changes(rocketmq_producer_count[5m]) < -5
+        for: 5m
+        labels:
+          severity: warning
+          team: client
+        annotations:
+          summary: "Client connections dropped"
+          description: "More than 5 producer connections dropped in 5 minutes."
diff --git a/server/src/main/resources/alerts/rocketmq-client-timeout.yaml 
b/server/src/main/resources/alerts/rocketmq-client-timeout.yaml
new file mode 100644
index 00000000..5a321b00
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-client-timeout.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-client.rules
+    rules:
+      - alert: RocketMQClientTimeout
+        expr: rocketmq_send_to_client_latency > 3000
+        for: 5m
+        labels:
+          severity: warning
+          team: client
+        annotations:
+          summary: "Client push latency high"
+          description: "Push-to-client latency is above 3000 ms."
diff --git 
a/server/src/main/resources/alerts/rocketmq-consumer-group-empty.yaml 
b/server/src/main/resources/alerts/rocketmq-consumer-group-empty.yaml
new file mode 100644
index 00000000..bfbb5bce
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-consumer-group-empty.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-consumer.rules
+    rules:
+      - alert: RocketMQConsumerGroupEmpty
+        expr: absent(rocketmq_consumer_lag_messages) == 1
+        for: 10m
+        labels:
+          severity: info
+          team: consumer
+        annotations:
+          summary: "Consumer group missing"
+          description: "No consumer lag metrics observed for a consumer group."
diff --git 
a/server/src/main/resources/alerts/rocketmq-consumer-lag-critical.yaml 
b/server/src/main/resources/alerts/rocketmq-consumer-lag-critical.yaml
new file mode 100644
index 00000000..b805efb7
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-consumer-lag-critical.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-consumer.rules
+    rules:
+      - alert: RocketMQConsumerLagCritical
+        expr: rocketmq_consumer_lag_messages > 1000000
+        for: 5m
+        labels:
+          severity: critical
+          team: consumer
+        annotations:
+          summary: "Consumer lag critical"
+          description: "A consumer group lag is above 1 million messages."
diff --git a/server/src/main/resources/alerts/rocketmq-consumer-lag-high.yaml 
b/server/src/main/resources/alerts/rocketmq-consumer-lag-high.yaml
new file mode 100644
index 00000000..92820c3c
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-consumer-lag-high.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-consumer.rules
+    rules:
+      - alert: RocketMQConsumerLagHigh
+        expr: rocketmq_consumer_lag_messages > 100000
+        for: 5m
+        labels:
+          severity: warning
+          team: consumer
+        annotations:
+          summary: "Consumer lag high"
+          description: "A consumer group lag is above 100000 messages."
diff --git a/server/src/main/resources/alerts/rocketmq-consumer-rebalance.yaml 
b/server/src/main/resources/alerts/rocketmq-consumer-rebalance.yaml
new file mode 100644
index 00000000..f7f1b4ac
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-consumer-rebalance.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-consumer.rules
+    rules:
+      - alert: RocketMQConsumerRebalance
+        expr: increase(rocketmq_consumer_rebalance_times[5m]) > 10
+        for: 5m
+        labels:
+          severity: warning
+          team: consumer
+        annotations:
+          summary: "Frequent consumer rebalances"
+          description: "More than 10 consumer rebalances happened in 5 
minutes."
diff --git a/server/src/main/resources/alerts/rocketmq-dlq-resend-high.yaml 
b/server/src/main/resources/alerts/rocketmq-dlq-resend-high.yaml
new file mode 100644
index 00000000..ab0fa3f3
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-dlq-resend-high.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-errors.rules
+    rules:
+      - alert: RocketMQDLQResendHigh
+        expr: rate(rocketmq_dlq_resend_count[5m]) > 10
+        for: 5m
+        labels:
+          severity: warning
+          team: consumer
+        annotations:
+          summary: "DLQ resends high"
+          description: "More than 10 dead-letter queue resends occurred in 5 
minutes."
diff --git a/server/src/main/resources/alerts/rocketmq-exception-rate.yaml 
b/server/src/main/resources/alerts/rocketmq-exception-rate.yaml
new file mode 100644
index 00000000..c497da1f
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-exception-rate.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-errors.rules
+    rules:
+      - alert: RocketMQBrokerExceptions
+        expr: rate(rocketmq_broker_exception_count[5m]) > 0
+        for: 5m
+        labels:
+          severity: critical
+          team: broker
+        annotations:
+          summary: "Broker exceptions"
+          description: "Broker runtime exceptions have been observed."
diff --git a/server/src/main/resources/alerts/rocketmq-jvm-gc-cpu-high.yaml 
b/server/src/main/resources/alerts/rocketmq-jvm-gc-cpu-high.yaml
new file mode 100644
index 00000000..cd9e4a99
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-jvm-gc-cpu-high.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-broker.rules
+    rules:
+      - alert: RocketMQJVMCpuHigh
+        expr: rate(jvm_gc_pause_seconds_count[5m]) * 
avg(rate(jvm_gc_pause_seconds_sum[5m])) > 0.3
+        for: 5m
+        labels:
+          severity: warning
+          team: broker
+        annotations:
+          summary: "JVM GC CPU high"
+          description: "The broker spends more than 30% of CPU time in GC 
pauses."
diff --git a/server/src/main/resources/alerts/rocketmq-producer-failure.yaml 
b/server/src/main/resources/alerts/rocketmq-producer-failure.yaml
new file mode 100644
index 00000000..b3d9d733
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-producer-failure.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-client.rules
+    rules:
+      - alert: RocketMQProducerSendFailure
+        expr: rate(rocketmq_producer_send_failure_count[5m]) > 0
+        for: 5m
+        labels:
+          severity: critical
+          team: client
+        annotations:
+          summary: "Producer send failures"
+          description: "Producer message send failures have been observed."
diff --git 
a/server/src/main/resources/alerts/rocketmq-producer-latency-high.yaml 
b/server/src/main/resources/alerts/rocketmq-producer-latency-high.yaml
new file mode 100644
index 00000000..970cd0bf
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-producer-latency-high.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-client.rules
+    rules:
+      - alert: RocketMQProducerSendLatencyHigh
+        expr: rocketmq_producer_send_to_back_rt > 1000
+        for: 5m
+        labels:
+          severity: warning
+          team: client
+        annotations:
+          summary: "Producer send latency high"
+          description: "Producer send-to-broker latency is above 1000 ms."
diff --git a/server/src/main/resources/alerts/rocketmq-producer-tps-drop.yaml 
b/server/src/main/resources/alerts/rocketmq-producer-tps-drop.yaml
new file mode 100644
index 00000000..bb10bb9f
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-producer-tps-drop.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-client.rules
+    rules:
+      - alert: RocketMQProducerTPSDrop
+        expr: rate(rocketmq_messages_in_total[5m]) < 0.1 * 
rate(rocketmq_messages_in_total[1h] offset 1h)
+        for: 10m
+        labels:
+          severity: warning
+          team: client
+        annotations:
+          summary: "Producer TPS dropped"
+          description: "Ingress TPS dropped by more than 90% compared with one 
hour ago."
diff --git a/server/src/main/resources/alerts/rocketmq-proxy-down.yaml 
b/server/src/main/resources/alerts/rocketmq-proxy-down.yaml
new file mode 100644
index 00000000..68b5b330
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-proxy-down.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-proxy.rules
+    rules:
+      - alert: RocketMQProxyDown
+        expr: up{job=~".*rocketmq.*proxy.*"} == 0
+        for: 1m
+        labels:
+          severity: critical
+          team: proxy
+        annotations:
+          summary: "RocketMQ proxy is down"
+          description: "A RocketMQ 5.x proxy target has been down for more 
than 1 minute."
diff --git a/server/src/main/resources/alerts/rocketmq-proxy-latency-high.yaml 
b/server/src/main/resources/alerts/rocketmq-proxy-latency-high.yaml
new file mode 100644
index 00000000..03d22895
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-proxy-latency-high.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-proxy.rules
+    rules:
+      - alert: RocketMQProxyLatencyHigh
+        expr: histogram_quantile(0.99, 
rate(rocketmq_proxy_process_time_bucket[5m])) > 1
+        for: 5m
+        labels:
+          severity: warning
+          team: proxy
+        annotations:
+          summary: "Proxy latency high"
+          description: "99th percentile proxy process time is above 1 second."
diff --git a/server/src/main/resources/alerts/rocketmq-threadpool-reject.yaml 
b/server/src/main/resources/alerts/rocketmq-threadpool-reject.yaml
new file mode 100644
index 00000000..8967efe1
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-threadpool-reject.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-broker.rules
+    rules:
+      - alert: RocketMQThreadPoolReject
+        expr: increase(rocketmq_threadpool_reject_count[5m]) > 0
+        for: 5m
+        labels:
+          severity: critical
+          team: broker
+        annotations:
+          summary: "Thread pool rejections"
+          description: "The broker thread pool rejected tasks, indicating 
saturation."
diff --git a/server/src/main/resources/alerts/rocketmq-topic-accumulation.yaml 
b/server/src/main/resources/alerts/rocketmq-topic-accumulation.yaml
new file mode 100644
index 00000000..a9b5907e
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-topic-accumulation.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-topic.rules
+    rules:
+      - alert: RocketMQTopicAccumulation
+        expr: rocketmq_dispatch_behind_bytes > 1073741824
+        for: 5m
+        labels:
+          severity: warning
+          team: topic
+        annotations:
+          summary: "Topic dispatch backlog high"
+          description: "Topic dispatch behind bytes is above 1GiB."
diff --git 
a/server/src/main/resources/alerts/rocketmq-topic-dispatch-latency.yaml 
b/server/src/main/resources/alerts/rocketmq-topic-dispatch-latency.yaml
new file mode 100644
index 00000000..cd7bfe57
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-topic-dispatch-latency.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-topic.rules
+    rules:
+      - alert: RocketMQTopicDispatchLatency
+        expr: histogram_quantile(0.99, 
rate(rocketmq_dispatch_latency_bucket[5m])) > 1
+        for: 5m
+        labels:
+          severity: warning
+          team: topic
+        annotations:
+          summary: "Dispatch latency high"
+          description: "99th percentile dispatch latency is above 1 second."
diff --git a/server/src/main/resources/alerts/rocketmq-topic-in-drop.yaml 
b/server/src/main/resources/alerts/rocketmq-topic-in-drop.yaml
new file mode 100644
index 00000000..1fb7ccf2
--- /dev/null
+++ b/server/src/main/resources/alerts/rocketmq-topic-in-drop.yaml
@@ -0,0 +1,16 @@
+# ============================================================================
+# RocketMQ 5.x Monitoring - Alert Rule
+# Compatible with Prometheus / VictoriaMetrics / Thanos / Cortex / Mimir
+# ============================================================================
+groups:
+  - name: rocketmq-topic.rules
+    rules:
+      - alert: RocketMQTopicMessageInDrop
+        expr: rate(rocketmq_messages_in_total[10m]) == 0
+        for: 10m
+        labels:
+          severity: info
+          team: topic
+        annotations:
+          summary: "No incoming messages"
+          description: "No incoming messages have been observed for 10 
minutes."
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetControllerTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetControllerTest.java
new file mode 100644
index 00000000..cad19f82
--- /dev/null
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetControllerTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.rocketmq.studio.ops.alert;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import 
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.web.servlet.MockMvc;
+
+import java.util.List;
+
+import static org.mockito.Mockito.when;
+import static 
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static 
org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
+import static 
org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static 
org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebMvcTest(AlertRuleAssetController.class)
+@AutoConfigureMockMvc(addFilters = false)
+class AlertRuleAssetControllerTest {
+
+    @Autowired
+    private MockMvc mockMvc;
+
+    @MockBean
+    private AlertRuleAssetService alertRuleAssetService;
+
+    @Test
+    void listAssetsShouldReturnMetadatas() throws Exception {
+        when(alertRuleAssetService.listAssets()).thenReturn(List.of(
+                new AlertRuleAssetInfo("rocketmq-broker-down", 
"rocketmq-broker.rules", 1, List.of("critical")),
+                new AlertRuleAssetInfo("rocketmq-consumer-lag-high", 
"rocketmq-consumer.rules", 1, List.of("warning"))
+        ));
+
+        mockMvc.perform(get("/api/alert-rules/assets"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.code").value(200))
+                .andExpect(jsonPath("$.data.length()").value(2))
+                
.andExpect(jsonPath("$.data[0].name").value("rocketmq-broker-down"))
+                .andExpect(jsonPath("$.data[0].ruleCount").value(1));
+    }
+
+    @Test
+    void getAssetShouldReturnRawYaml() throws Exception {
+        when(alertRuleAssetService.getAssetYaml("rocketmq-broker-down"))
+                .thenReturn("groups:\n  - name: rocketmq-broker.rules\n    
rules:\n      - alert: RocketMQBrokerDown\n");
+
+        mockMvc.perform(get("/api/alert-rules/assets/rocketmq-broker-down"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.code").value(200))
+                .andExpect(jsonPath("$.data").exists());
+    }
+
+    @Test
+    void exportAssetShouldReturnAttachment() throws Exception {
+        when(alertRuleAssetService.getAssetYaml("rocketmq-broker-down"))
+                .thenReturn("groups:\n  - name: rocketmq-broker.rules\n");
+
+        
mockMvc.perform(get("/api/alert-rules/assets/rocketmq-broker-down/export"))
+                .andExpect(status().isOk())
+                .andExpect(header().string("Content-Type", 
"application/x-yaml"))
+                .andExpect(header().string("Content-Disposition",
+                        "form-data; name=\"attachment\"; 
filename=\"rocketmq-broker-down.yaml\""));
+    }
+}
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetServiceTest.java
new file mode 100644
index 00000000..ba1016cd
--- /dev/null
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertRuleAssetServiceTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.rocketmq.studio.ops.alert;
+
+import org.apache.rocketmq.studio.common.exception.BusinessException;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class AlertRuleAssetServiceTest {
+
+    private final AlertRuleAssetService service =
+            new AlertRuleAssetService();
+
+    @Test
+    void listAssetsShouldExposeBundledYamlFiles() {
+        List<AlertRuleAssetInfo> assets = service.listAssets();
+
+        assertFalse(assets.isEmpty(), "expected bundled alert rule assets");
+        assertTrue(assets.size() >= 10, "expected at least 10 asset files, got 
" + assets.size());
+        for (AlertRuleAssetInfo asset : assets) {
+            assertFalse(asset.name().isBlank());
+            assertTrue(asset.ruleCount() >= 1, "asset should contain at least 
one rule");
+        }
+    }
+
+    @Test
+    void loadDefaultRulesShouldReturnAtLeastTwentyRules() {
+        List<PrometheusAlertRule> rules = service.loadDefaultRules();
+
+        assertTrue(rules.size() >= 20, "expected at least 20 default alert 
rules, got " + rules.size());
+        for (PrometheusAlertRule rule : rules) {
+            assertFalse(rule.alert().isBlank(), "rule alert name must not be 
blank");
+            assertFalse(rule.expr().isBlank(), "rule expr must not be blank");
+        }
+    }
+
+    @Test
+    void getAssetYamlShouldReturnRawContent() {
+        List<AlertRuleAssetInfo> assets = service.listAssets();
+        String name = assets.get(0).name();
+
+        String yaml = service.getAssetYaml(name);
+
+        assertFalse(yaml.isBlank());
+        assertTrue(yaml.contains("alert:"));
+        assertTrue(yaml.contains("expr:"));
+    }
+
+    @Test
+    void getAssetYamlShouldThrowWhenNameUnknown() {
+        BusinessException exception = assertThrows(BusinessException.class,
+                () -> service.getAssetYaml("no-such-asset"));
+        assertEquals(404, exception.getCode());
+    }
+
+    @Test
+    void parseRulesShouldMapSeverityAndTeamLabels() {
+        List<PrometheusAlertRule> rules = service.loadDefaultRules();
+        boolean hasCritical = rules.stream().anyMatch(r -> 
"critical".equals(r.severity()));
+        boolean hasBroker = rules.stream().anyMatch(r -> 
"broker".equals(r.team()));
+        assertTrue(hasCritical, "expected at least one critical rule");
+        assertTrue(hasBroker, "expected at least one broker rule");
+    }
+}
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertServiceDefaultRulesTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertServiceDefaultRulesTest.java
new file mode 100644
index 00000000..786ebf65
--- /dev/null
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertServiceDefaultRulesTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.rocketmq.studio.ops.alert;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class AlertServiceDefaultRulesTest {
+
+    private static final Pattern ALERT_PATTERN = Pattern.compile("^\\s*- 
alert:");
+
+    @Test
+    void exportUsesBundledAssetsAsDefaultWhenRepositoryIsEmpty() {
+        AlertRepository repository = mock(AlertRepository.class);
+        when(repository.findAllRules()).thenReturn(Collections.emptyList());
+
+        AlertService service = new AlertService(repository, new 
AlertRuleAssetService());
+        String yaml = service.exportPrometheusRulesYaml();
+
+        int ruleCount = countRules(yaml);
+        assertTrue(ruleCount >= 20, "expected at least 20 default alert rules, 
got " + ruleCount);
+    }
+
+    @Test
+    void listRulesDelegatesToRepositoryWhenEmptyUsesDefaultsViaExport() {
+        AlertRepository repository = mock(AlertRepository.class);
+        when(repository.findAllRules()).thenReturn(List.of());
+
+        AlertService service = new AlertService(repository, new 
AlertRuleAssetService());
+        String yaml = service.exportPrometheusRulesYaml();
+
+        assertTrue(yaml.contains("rocketmq-broker.rules"));
+        assertTrue(yaml.contains("RocketMQBrokerDown"));
+    }
+
+    private int countRules(String yaml) {
+        int count = 0;
+        for (String line : yaml.split("\n")) {
+            Matcher matcher = ALERT_PATTERN.matcher(line);
+            if (matcher.find()) {
+                count++;
+            }
+        }
+        return count;
+    }
+}
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertServiceTest.java
index 0e717c3f..3b11ad02 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertServiceTest.java
@@ -23,7 +23,6 @@ import 
org.apache.rocketmq.studio.common.domain.enums.AlertLevel;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 
@@ -45,9 +44,13 @@ class AlertServiceTest {
     @Mock
     private AlertRepository alertRepository;
 
-    @InjectMocks
     private AlertService alertService;
 
+    @org.junit.jupiter.api.BeforeEach
+    void setUp() {
+        alertService = new AlertService(alertRepository, new 
AlertRuleAssetService());
+    }
+
     @Test
     void listRulesShouldReturnAllRules() {
         AlertRuleVO rule1 = AlertRuleVO.builder().id("1").name("High 
CPU").metric("cpu_usage")
@@ -82,8 +85,8 @@ class AlertServiceTest {
 
         assertThat(result)
                 .contains("groups:")
-                .contains("# Rule 1: RocketMQBrokerDown")
-                .contains("up{job=~\".*rocketmq.*\"} == 0")
+                .contains("RocketMQBrokerDown")
+                .contains("up{job=~\".*rocketmq.*broker.*\"} == 0")
                 .contains("rocketmq_consumer_lag_messages > 100000")
                 .contains("rocketmq_producer_send_to_back_rt > 1000")
                 .contains("severity: critical");
diff --git a/web/src/App.tsx b/web/src/App.tsx
index cd9fba6c..db56997c 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -51,6 +51,7 @@ const AlertManagementPage = lazy(() => 
import('./pages/studio/AlertManagement'))
 const GrafanaDashboardsPage = lazy(() => 
import('./pages/studio/GrafanaDashboards'));
 const ProducerPage = lazy(() => import('./pages/studio/Producer'));
 const OpsPage = lazy(() => import('./pages/studio/Ops'));
+const AlertRuleAssetsPage = lazy(() => 
import('./pages/studio/AlertRuleAssets'));
 
 type AuthGateState = 'checking' | 'allowed' | 'denied' | 'error';
 
@@ -174,6 +175,7 @@ function App() {
             <Route path="studio/alert-management" 
element={<AlertManagementPage />} />
             <Route path="studio/producer" element={<ProducerPage />} />
             <Route path="studio/ops" element={<OpsPage />} />
+            <Route path="ops/alert-rule-templates" 
element={<AlertRuleAssetsPage />} />
             <Route path="*" element={<Navigate to="/" replace />} />
           </Route>
         </Route>
diff --git a/web/src/api/alertRuleAssets.test.ts 
b/web/src/api/alertRuleAssets.test.ts
new file mode 100644
index 00000000..ad137784
--- /dev/null
+++ b/web/src/api/alertRuleAssets.test.ts
@@ -0,0 +1,71 @@
+/*
+ * 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 MockAdapter from 'axios-mock-adapter';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+import client from './client';
+import { exportAlertRuleAsset, getAlertRuleAsset, listAlertRuleAssets } from 
'./alertRuleAssets';
+
+const mock = new MockAdapter(client);
+
+describe('alertRuleAssets API', () => {
+  beforeEach(() => {
+    mock.reset();
+    vi.stubGlobal('localStorage', { getItem: vi.fn().mockReturnValue(null) });
+  });
+
+  afterEach(() => {
+    mock.reset();
+    vi.unstubAllGlobals();
+  });
+
+  it('lists alert rule assets', async () => {
+    const assets = [
+      {
+        name: 'rocketmq-broker-down',
+        group: 'rocketmq-broker.rules',
+        ruleCount: 1,
+        severities: ['critical'],
+      },
+      {
+        name: 'rocketmq-consumer-lag-high',
+        group: 'rocketmq-consumer.rules',
+        ruleCount: 1,
+        severities: ['warning'],
+      },
+    ];
+    mock.onGet('/alert-rules/assets').reply(200, { code: 200, data: assets });
+
+    await expect(listAlertRuleAssets()).resolves.toEqual(assets);
+  });
+
+  it('gets a single asset yaml by name', async () => {
+    const yaml = 'groups:\n  - name: rocketmq-broker.rules\n';
+    mock.onGet('/alert-rules/assets/rocketmq-broker-down').reply(200, { code: 
200, data: yaml });
+
+    await 
expect(getAlertRuleAsset('rocketmq-broker-down')).resolves.toBe(yaml);
+  });
+
+  it('exports an asset as a blob', async () => {
+    const blob = new Blob(['groups:\n  - name: rocketmq-broker.rules\n'], { 
type: 'text/yaml' });
+    mock.onGet('/alert-rules/assets/rocketmq-broker-down/export').reply(200, 
blob);
+
+    const result = await exportAlertRuleAsset('rocketmq-broker-down');
+    expect(result).toBeInstanceOf(Blob);
+    expect(result.type).toBe('text/yaml');
+  });
+});
diff --git a/web/src/api/alertRuleAssets.ts b/web/src/api/alertRuleAssets.ts
new file mode 100644
index 00000000..fbf2db94
--- /dev/null
+++ b/web/src/api/alertRuleAssets.ts
@@ -0,0 +1,42 @@
+/*
+ * 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 client from './client';
+
+export interface AlertRuleAssetInfo {
+  name: string;
+  group: string;
+  ruleCount: number;
+  severities: string[];
+}
+
+export async function listAlertRuleAssets(): Promise<AlertRuleAssetInfo[]> {
+  const res = await client.get<{ data: AlertRuleAssetInfo[] 
}>('/alert-rules/assets');
+  return res.data.data;
+}
+
+export async function getAlertRuleAsset(name: string): Promise<string> {
+  const res = await client.get<{ data: string 
}>(`/alert-rules/assets/${encodeURIComponent(name)}`);
+  return res.data.data;
+}
+
+export async function exportAlertRuleAsset(name: string): Promise<Blob> {
+  const res = await 
client.get<Blob>(`/alert-rules/assets/${encodeURIComponent(name)}/export`, {
+    responseType: 'blob',
+  });
+  return res.data;
+}
diff --git a/web/src/components/AlertRuleAssetList.tsx 
b/web/src/components/AlertRuleAssetList.tsx
new file mode 100644
index 00000000..32b2a232
--- /dev/null
+++ b/web/src/components/AlertRuleAssetList.tsx
@@ -0,0 +1,197 @@
+/*
+ * 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 { useEffect, useState } from 'react';
+import { App, Button, Modal, Space, Table, Tag, Typography } from 'antd';
+import type { ColumnsType } from 'antd/es/table';
+import { DownloadSimple, Eye } from '@phosphor-icons/react';
+import { useLang } from '../i18n/LangContext';
+import {
+  exportAlertRuleAsset,
+  getAlertRuleAsset,
+  listAlertRuleAssets,
+} from '../services/alertRuleAssetService';
+import type { AlertRuleAssetInfo } from '../api/alertRuleAssets';
+
+const { Text } = Typography;
+
+const SEVERITY_COLORS: Record<string, string> = {
+  critical: 'red',
+  warning: 'orange',
+  info: 'blue',
+};
+
+export const AlertRuleAssetList: React.FC = () => {
+  const { t } = useLang();
+  const { message } = App.useApp();
+  const [assets, setAssets] = useState<AlertRuleAssetInfo[]>([]);
+  const [loading, setLoading] = useState(true);
+  const [viewing, setViewing] = useState<AlertRuleAssetInfo | null>(null);
+  const [viewContent, setViewContent] = useState('');
+  const [viewLoading, setViewLoading] = useState(false);
+  const [exportingName, setExportingName] = useState<string | null>(null);
+
+  useEffect(() => {
+    let cancelled = false;
+    const load = async () => {
+      try {
+        const data = await listAlertRuleAssets();
+        if (!cancelled) setAssets(data);
+      } catch {
+        if (!cancelled) message.error(t('alertAssets.loadFailed'));
+      } finally {
+        if (!cancelled) setLoading(false);
+      }
+    };
+    void load();
+    return () => {
+      cancelled = true;
+    };
+  }, [t, message]);
+
+  const handleView = async (info: AlertRuleAssetInfo) => {
+    setViewing(info);
+    setViewLoading(true);
+    try {
+      const yaml = await getAlertRuleAsset(info.name);
+      setViewContent(yaml);
+    } catch {
+      message.error(t('alertAssets.loadFailed'));
+    } finally {
+      setViewLoading(false);
+    }
+  };
+
+  const triggerDownload = (name: string, content: Blob | string) => {
+    const blob = typeof content === 'string' ? new Blob([content], { type: 
'text/yaml' }) : content;
+    const url = URL.createObjectURL(blob);
+    const a = document.createElement('a');
+    a.href = url;
+    a.download = `${name}.yaml`;
+    a.click();
+    URL.revokeObjectURL(url);
+  };
+
+  const handleExport = async (info: AlertRuleAssetInfo) => {
+    setExportingName(info.name);
+    try {
+      const blob = await exportAlertRuleAsset(info.name);
+      triggerDownload(info.name, blob);
+      message.success(t('alertAssets.exported'));
+    } catch {
+      message.error(t('alertAssets.exportFailed'));
+    } finally {
+      setExportingName(null);
+    }
+  };
+
+  const columns: ColumnsType<AlertRuleAssetInfo> = [
+    {
+      title: t('alertAssets.name'),
+      dataIndex: 'name',
+      key: 'name',
+    },
+    {
+      title: t('alertAssets.group'),
+      dataIndex: 'group',
+      key: 'group',
+      render: (group: string) => <Tag color="blue">{group}</Tag>,
+    },
+    {
+      title: t('alertAssets.ruleCount'),
+      dataIndex: 'ruleCount',
+      key: 'ruleCount',
+      width: 110,
+    },
+    {
+      title: t('alertAssets.severity'),
+      dataIndex: 'severities',
+      key: 'severities',
+      width: 180,
+      render: (severities: string[]) => (
+        <Space size={[0, 4]} wrap>
+          {(severities || []).map((severity) => (
+            <Tag key={severity} color={SEVERITY_COLORS[severity] || 'default'}>
+              {severity.toUpperCase()}
+            </Tag>
+          ))}
+        </Space>
+      ),
+    },
+    {
+      title: t('common.actions'),
+      key: 'actions',
+      width: 180,
+      render: (_: unknown, record: AlertRuleAssetInfo) => (
+        <Space size="small">
+          <Button size="small" icon={<Eye size={16} />} onClick={() => 
handleView(record)}>
+            {t('common.view')}
+          </Button>
+          <Button
+            size="small"
+            icon={<DownloadSimple size={16} />}
+            loading={exportingName === record.name}
+            onClick={() => handleExport(record)}
+          >
+            {t('common.export')}
+          </Button>
+        </Space>
+      ),
+    },
+  ];
+
+  return (
+    <div>
+      <Table
+        columns={columns}
+        dataSource={assets}
+        loading={loading}
+        rowKey="name"
+        pagination={false}
+        size="small"
+      />
+
+      <Modal
+        title={viewing ? viewing.name : t('alertAssets.title')}
+        open={viewing !== null}
+        footer={<Button onClick={() => 
setViewing(null)}>{t('common.close')}</Button>}
+        onCancel={() => setViewing(null)}
+        width={760}
+        destroyOnHidden
+      >
+        {viewLoading ? (
+          <Text type="secondary">{t('common.loading')}</Text>
+        ) : (
+          <pre
+            style={{
+              maxHeight: 480,
+              overflow: 'auto',
+              background: '#f5f5f5',
+              padding: 16,
+              borderRadius: 6,
+              fontSize: 12,
+            }}
+          >
+            {viewContent}
+          </pre>
+        )}
+      </Modal>
+    </div>
+  );
+};
+
+export default AlertRuleAssetList;
diff --git a/web/src/components/__tests__/AlertRuleAssetList.test.tsx 
b/web/src/components/__tests__/AlertRuleAssetList.test.tsx
new file mode 100644
index 00000000..cf606cdc
--- /dev/null
+++ b/web/src/components/__tests__/AlertRuleAssetList.test.tsx
@@ -0,0 +1,127 @@
+/*
+ * 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 { afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
+import { fireEvent, render, screen, waitFor, within } from 
'@testing-library/react';
+import { App as AntdApp } from 'antd';
+import AlertRuleAssetList from '../AlertRuleAssetList';
+import { LangProvider } from '../../i18n/LangContext';
+import * as alertRuleAssetService from '../../services/alertRuleAssetService';
+
+vi.mock('../../services/alertRuleAssetService', () => ({
+  listAlertRuleAssets: vi.fn(),
+  getAlertRuleAsset: vi.fn(),
+  exportAlertRuleAsset: vi.fn(),
+}));
+
+const sampleAssets = [
+  {
+    name: 'rocketmq-broker-down',
+    group: 'rocketmq-broker.rules',
+    ruleCount: 1,
+    severities: ['critical'],
+  },
+  {
+    name: 'rocketmq-consumer-lag-high',
+    group: 'rocketmq-consumer.rules',
+    ruleCount: 1,
+    severities: ['warning'],
+  },
+];
+
+const renderWithProviders = (ui: React.ReactElement) =>
+  render(
+    <LangProvider>
+      <AntdApp>{ui}</AntdApp>
+    </LangProvider>,
+  );
+
+describe('AlertRuleAssetList', () => {
+  beforeAll(() => {
+    window.matchMedia =
+      window.matchMedia ||
+      ((query: string) =>
+        ({
+          matches: false,
+          media: query,
+          onchange: null,
+          addListener: () => {},
+          removeListener: () => {},
+          addEventListener: () => {},
+          removeEventListener: () => {},
+          dispatchEvent: () => false,
+        }) as unknown as MediaQueryList);
+  });
+
+  afterEach(() => {
+    vi.clearAllMocks();
+    localStorage.clear();
+  });
+
+  it('renders asset rows from the service', async () => {
+    
vi.mocked(alertRuleAssetService.listAlertRuleAssets).mockResolvedValue(sampleAssets);
+
+    renderWithProviders(<AlertRuleAssetList />);
+
+    expect(await 
screen.findByText('rocketmq-broker-down')).toBeInTheDocument();
+    expect(screen.getByText('rocketmq-consumer-lag-high')).toBeInTheDocument();
+  });
+
+  it('opens a modal with yaml content when View is clicked', async () => {
+    
vi.mocked(alertRuleAssetService.listAlertRuleAssets).mockResolvedValue(sampleAssets);
+    vi.mocked(alertRuleAssetService.getAlertRuleAsset).mockResolvedValue(
+      'groups:\n  - name: rocketmq-broker.rules\n    rules:\n      - alert: 
RocketMQBrokerDown\n',
+    );
+
+    renderWithProviders(<AlertRuleAssetList />);
+
+    const viewButtons = await screen.findAllByRole('button', { name: /查看|View/ 
});
+    fireEvent.click(viewButtons[0]);
+
+    const dialog = await screen.findByRole('dialog');
+    
expect(within(dialog).getByText(/rocketmq-broker.rules/)).toBeInTheDocument();
+    
expect(alertRuleAssetService.getAlertRuleAsset).toHaveBeenCalledWith('rocketmq-broker-down');
+  });
+
+  it('downloads the yaml when Export is clicked', async () => {
+    const createObjectURLSpy = vi.spyOn(URL, 
'createObjectURL').mockReturnValue('blob:url');
+    const revokeSpy = vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() 
=> {});
+    const clickSpy = vi.spyOn(HTMLAnchorElement.prototype, 
'click').mockImplementation(() => {});
+
+    
vi.mocked(alertRuleAssetService.listAlertRuleAssets).mockResolvedValue(sampleAssets);
+    vi.mocked(alertRuleAssetService.exportAlertRuleAsset).mockResolvedValue(
+      new Blob(['groups:\n  - name: rocketmq-broker.rules\n'], { type: 
'text/yaml' }),
+    );
+
+    renderWithProviders(<AlertRuleAssetList />);
+
+    const exportButtons = await screen.findAllByRole('button', { name: 
/导出|Export/ });
+    fireEvent.click(exportButtons[0]);
+
+    await waitFor(() =>
+      expect(alertRuleAssetService.exportAlertRuleAsset).toHaveBeenCalledWith(
+        'rocketmq-broker-down',
+      ),
+    );
+    expect(createObjectURLSpy).toHaveBeenCalled();
+    expect(clickSpy).toHaveBeenCalled();
+
+    createObjectURLSpy.mockRestore();
+    revokeSpy.mockRestore();
+    clickSpy.mockRestore();
+  });
+});
diff --git a/web/src/i18n/translations.ts b/web/src/i18n/translations.ts
index 9afb2e54..3838f340 100644
--- a/web/src/i18n/translations.ts
+++ b/web/src/i18n/translations.ts
@@ -34,6 +34,7 @@ const translations: Record<string, Record<Lang, string>> = {
   'nav.clients': { zh: '客户端连接', en: 'Client Connections' },
   'nav.alertEvents': { zh: '告警事件', en: 'Alert Events' },
   'nav.alertRules': { zh: '告警规则', en: 'Alert Rules' },
+  'nav.alertRuleAssets': { zh: '告警规则模板', en: 'Alert Rule Templates' },
   'nav.audit': { zh: '审计日志', en: 'Audit Log' },
   'nav.grafanaDashboards': { zh: 'Grafana 看板', en: 'Grafana Dashboards' },
   'nav.ai': { zh: 'AI 交互', en: 'AI Chat' },
@@ -741,6 +742,15 @@ const translations: Record<string, Record<Lang, string>> = 
{
   'grafana.loadFailed': { zh: '加载看板失败', en: 'Failed to load dashboards' },
   'grafana.exported': { zh: '看板已导出', en: 'Dashboard exported' },
   'grafana.exportFailed': { zh: '导出看板失败', en: 'Failed to export dashboard' },
+  // ─── Alert rule templates ───
+  'alertAssets.title': { zh: '告警规则模板', en: 'Alert Rule Templates' },
+  'alertAssets.name': { zh: '名称', en: 'Name' },
+  'alertAssets.group': { zh: '规则组', en: 'Group' },
+  'alertAssets.ruleCount': { zh: '规则数', en: 'Rules' },
+  'alertAssets.severity': { zh: '级别', en: 'Severity' },
+  'alertAssets.loadFailed': { zh: '加载告警规则失败', en: 'Failed to load alert rules' 
},
+  'alertAssets.exported': { zh: '告警规则已导出', en: 'Alert rule exported' },
+  'alertAssets.exportFailed': { zh: '导出告警规则失败', en: 'Failed to export alert 
rule' },
 
   // ─── Topic (detailed) ───
   'topic.subtitle': {
diff --git a/web/src/layouts/MainLayout.tsx b/web/src/layouts/MainLayout.tsx
index d85024ad..8803cf1e 100644
--- a/web/src/layouts/MainLayout.tsx
+++ b/web/src/layouts/MainLayout.tsx
@@ -38,6 +38,7 @@ import {
   PlugsConnected,
   BellRinging,
   Notebook,
+  Warning,
 } from '@phosphor-icons/react';
 import { useLang } from '../i18n/LangContext';
 import { useTheme } from '../theme/ThemeContext';
@@ -133,6 +134,11 @@ const MainLayout = () => {
         { key: '/ops/alerts', icon: <BellRinging size={16} />, label: 
t('nav.alertRules') },
         { key: '/ops/system-alerts', icon: <BellRinging size={16} />, label: 
t('nav.alertEvents') },
         { key: '/ops/audit', icon: <Notebook size={16} />, label: 
t('nav.audit') },
+        {
+          key: '/ops/alert-rule-templates',
+          icon: <Warning size={16} />,
+          label: t('nav.alertRuleAssets'),
+        },
       ],
     },
     { key: '/ai', icon: <Sparkle size={iconSize} weight="duotone" />, label: 
t('nav.ai') },
@@ -160,6 +166,7 @@ const MainLayout = () => {
     '/ops/system-alerts': t('nav.alertEvents'),
     '/ops/alerts': t('nav.alertRules'),
     '/ops/audit': t('nav.audit'),
+    '/ops/alert-rule-templates': t('nav.alertRuleAssets'),
     '/ai': t('nav.ai'),
     '/settings': t('nav.settings'),
   };
diff --git a/web/src/mock/alertRuleAssets.ts b/web/src/mock/alertRuleAssets.ts
new file mode 100644
index 00000000..5ea80470
--- /dev/null
+++ b/web/src/mock/alertRuleAssets.ts
@@ -0,0 +1,210 @@
+/*
+ * 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 type { AlertRuleAssetInfo } from '../api/alertRuleAssets';
+
+export interface MockAlertRuleAsset extends AlertRuleAssetInfo {
+  yaml: string;
+}
+
+const yaml = (name: string, group: string, alert: string, expr: string, 
severity: string) =>
+  `groups:\n  - name: ${group}\n    rules:\n      - alert: ${alert}\n        
expr: ${expr}\n        for: 5m\n        labels:\n          severity: 
${severity}\n          team: broker\n        annotations:\n          summary: 
"${alert}"\n          description: "Bundled alert rule ${name}"\n`;
+
+export const mockAlertRuleAssets: MockAlertRuleAsset[] = [
+  {
+    name: 'rocketmq-broker-down',
+    group: 'rocketmq-broker.rules',
+    ruleCount: 1,
+    severities: ['critical'],
+    yaml: yaml(
+      'rocketmq-broker-down',
+      'rocketmq-broker.rules',
+      'RocketMQBrokerDown',
+      'up{job=~".*rocketmq.*broker.*"} == 0',
+      'critical',
+    ),
+  },
+  {
+    name: 'rocketmq-broker-disk-high',
+    group: 'rocketmq-broker.rules',
+    ruleCount: 1,
+    severities: ['critical'],
+    yaml: yaml(
+      'rocketmq-broker-disk-high',
+      'rocketmq-broker.rules',
+      'RocketMQBrokerDiskHigh',
+      'rocketmq_disk_use_ratio > 85',
+      'critical',
+    ),
+  },
+  {
+    name: 'rocketmq-consumer-lag-high',
+    group: 'rocketmq-consumer.rules',
+    ruleCount: 1,
+    severities: ['warning'],
+    yaml: yaml(
+      'rocketmq-consumer-lag-high',
+      'rocketmq-consumer.rules',
+      'RocketMQConsumerLagHigh',
+      'rocketmq_consumer_lag_messages > 100000',
+      'warning',
+    ),
+  },
+  {
+    name: 'rocketmq-consumer-lag-critical',
+    group: 'rocketmq-consumer.rules',
+    ruleCount: 1,
+    severities: ['critical'],
+    yaml: yaml(
+      'rocketmq-consumer-lag-critical',
+      'rocketmq-consumer.rules',
+      'RocketMQConsumerLagCritical',
+      'rocketmq_consumer_lag_messages > 1000000',
+      'critical',
+    ),
+  },
+  {
+    name: 'rocketmq-producer-latency-high',
+    group: 'rocketmq-client.rules',
+    ruleCount: 1,
+    severities: ['warning'],
+    yaml: yaml(
+      'rocketmq-producer-latency-high',
+      'rocketmq-client.rules',
+      'RocketMQProducerSendLatencyHigh',
+      'rocketmq_producer_send_to_back_rt > 1000',
+      'warning',
+    ),
+  },
+  {
+    name: 'rocketmq-producer-failure',
+    group: 'rocketmq-client.rules',
+    ruleCount: 1,
+    severities: ['critical'],
+    yaml: yaml(
+      'rocketmq-producer-failure',
+      'rocketmq-client.rules',
+      'RocketMQProducerSendFailure',
+      'rate(rocketmq_producer_send_failure_count[5m]) > 0',
+      'critical',
+    ),
+  },
+  {
+    name: 'rocketmq-topic-in-drop',
+    group: 'rocketmq-topic.rules',
+    ruleCount: 1,
+    severities: ['info'],
+    yaml: yaml(
+      'rocketmq-topic-in-drop',
+      'rocketmq-topic.rules',
+      'RocketMQTopicMessageInDrop',
+      'rate(rocketmq_messages_in_total[10m]) == 0',
+      'info',
+    ),
+  },
+  {
+    name: 'rocketmq-topic-accumulation',
+    group: 'rocketmq-topic.rules',
+    ruleCount: 1,
+    severities: ['warning'],
+    yaml: yaml(
+      'rocketmq-topic-accumulation',
+      'rocketmq-topic.rules',
+      'RocketMQTopicAccumulation',
+      'rocketmq_dispatch_behind_bytes > 1073741824',
+      'warning',
+    ),
+  },
+  {
+    name: 'rocketmq-client-connection-drop',
+    group: 'rocketmq-client.rules',
+    ruleCount: 1,
+    severities: ['warning'],
+    yaml: yaml(
+      'rocketmq-client-connection-drop',
+      'rocketmq-client.rules',
+      'RocketMQClientConnectionDrop',
+      'changes(rocketmq_producer_count[5m]) < -5',
+      'warning',
+    ),
+  },
+  {
+    name: 'rocketmq-proxy-down',
+    group: 'rocketmq-proxy.rules',
+    ruleCount: 1,
+    severities: ['critical'],
+    yaml: yaml(
+      'rocketmq-proxy-down',
+      'rocketmq-proxy.rules',
+      'RocketMQProxyDown',
+      'up{job=~".*rocketmq.*proxy.*"} == 0',
+      'critical',
+    ),
+  },
+  {
+    name: 'rocketmq-exception-rate',
+    group: 'rocketmq-errors.rules',
+    ruleCount: 1,
+    severities: ['critical'],
+    yaml: yaml(
+      'rocketmq-exception-rate',
+      'rocketmq-errors.rules',
+      'RocketMQBrokerExceptions',
+      'rate(rocketmq_broker_exception_count[5m]) > 0',
+      'critical',
+    ),
+  },
+  {
+    name: 'rocketmq-dlq-resend-high',
+    group: 'rocketmq-errors.rules',
+    ruleCount: 1,
+    severities: ['warning'],
+    yaml: yaml(
+      'rocketmq-dlq-resend-high',
+      'rocketmq-errors.rules',
+      'RocketMQDLQResendHigh',
+      'rate(rocketmq_dlq_resend_count[5m]) > 10',
+      'warning',
+    ),
+  },
+  {
+    name: 'rocketmq-threadpool-reject',
+    group: 'rocketmq-broker.rules',
+    ruleCount: 1,
+    severities: ['critical'],
+    yaml: yaml(
+      'rocketmq-threadpool-reject',
+      'rocketmq-broker.rules',
+      'RocketMQThreadPoolReject',
+      'increase(rocketmq_threadpool_reject_count[5m]) > 0',
+      'critical',
+    ),
+  },
+  {
+    name: 'rocketmq-jvm-gc-cpu-high',
+    group: 'rocketmq-broker.rules',
+    ruleCount: 1,
+    severities: ['warning'],
+    yaml: yaml(
+      'rocketmq-jvm-gc-cpu-high',
+      'rocketmq-broker.rules',
+      'RocketMQJVMCpuHigh',
+      'rate(jvm_gc_pause_seconds_count[5m]) * 
avg(rate(jvm_gc_pause_seconds_sum[5m])) > 0.3',
+      'warning',
+    ),
+  },
+];
diff --git a/web/src/pages/studio/AlertRuleAssets.tsx 
b/web/src/pages/studio/AlertRuleAssets.tsx
new file mode 100644
index 00000000..4b55f38e
--- /dev/null
+++ b/web/src/pages/studio/AlertRuleAssets.tsx
@@ -0,0 +1,44 @@
+/*
+ * 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 React from 'react';
+import { Card, Space } from 'antd';
+import { Warning } from '@phosphor-icons/react';
+import { useLang } from '../../i18n/LangContext';
+import AlertRuleAssetList from '../../components/AlertRuleAssetList';
+
+const AlertRuleAssetsPage: React.FC = () => {
+  const { t } = useLang();
+
+  return (
+    <div style={{ padding: '0 0 24px' }}>
+      <Card
+        size="small"
+        title={
+          <Space>
+            <Warning size={18} />
+            <span>{t('alertAssets.title')}</span>
+          </Space>
+        }
+      >
+        <AlertRuleAssetList />
+      </Card>
+    </div>
+  );
+};
+
+export default AlertRuleAssetsPage;
diff --git a/web/src/services/alertRuleAssetService.test.ts 
b/web/src/services/alertRuleAssetService.test.ts
new file mode 100644
index 00000000..281a014b
--- /dev/null
+++ b/web/src/services/alertRuleAssetService.test.ts
@@ -0,0 +1,101 @@
+/*
+ * 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 { describe, expect, it, vi } from 'vitest';
+import type { AlertRuleAssetInfo } from '../api/alertRuleAssets';
+import * as api from '../api/alertRuleAssets';
+import { mockAlertRuleAssets } from '../mock/alertRuleAssets';
+
+const { mode } = vi.hoisted(() => ({ mode: { mock: true } }));
+
+vi.mock('./dataMode', () => ({ isMockMode: () => mode.mock }));
+vi.mock('../api/alertRuleAssets', () => ({
+  listAlertRuleAssets: vi.fn(),
+  getAlertRuleAsset: vi.fn(),
+  exportAlertRuleAsset: vi.fn(),
+}));
+
+import {
+  exportAlertRuleAsset,
+  getAlertRuleAsset,
+  listAlertRuleAssets,
+} from './alertRuleAssetService';
+
+describe('alertRuleAssetService (mock mode)', () => {
+  it('maps mock assets to AlertRuleAssetInfo list', async () => {
+    mode.mock = true;
+    const assets = await listAlertRuleAssets();
+    expect(assets.length).toBe(mockAlertRuleAssets.length);
+    expect(assets[0]).toEqual({
+      name: mockAlertRuleAssets[0].name,
+      group: mockAlertRuleAssets[0].group,
+      ruleCount: mockAlertRuleAssets[0].ruleCount,
+      severities: mockAlertRuleAssets[0].severities,
+    });
+  });
+
+  it('returns yaml for a known asset', async () => {
+    mode.mock = true;
+    const yaml = await getAlertRuleAsset('rocketmq-broker-down');
+    expect(yaml).toContain('RocketMQBrokerDown');
+  });
+
+  it('throws for an unknown asset', async () => {
+    mode.mock = true;
+    await expect(getAlertRuleAsset('does-not-exist')).rejects.toThrow();
+  });
+
+  it('exports a yaml blob', async () => {
+    mode.mock = true;
+    const blob = await exportAlertRuleAsset('rocketmq-broker-down');
+    expect(blob).toBeInstanceOf(Blob);
+    expect(blob.type).toBe('text/yaml');
+  });
+});
+
+describe('alertRuleAssetService (real mode)', () => {
+  it('delegates list to the api module', async () => {
+    mode.mock = false;
+    const data: AlertRuleAssetInfo[] = [
+      { name: 'a', group: 'g', ruleCount: 1, severities: ['info'] },
+    ];
+    vi.mocked(api.listAlertRuleAssets).mockResolvedValue(data);
+
+    const result = await listAlertRuleAssets();
+    expect(api.listAlertRuleAssets).toHaveBeenCalled();
+    expect(result).toEqual(data);
+  });
+
+  it('delegates get to the api module', async () => {
+    mode.mock = false;
+    vi.mocked(api.getAlertRuleAsset).mockResolvedValue('yaml-content');
+
+    const result = await getAlertRuleAsset('a');
+    expect(api.getAlertRuleAsset).toHaveBeenCalledWith('a');
+    expect(result).toBe('yaml-content');
+  });
+
+  it('delegates export to the api module', async () => {
+    mode.mock = false;
+    const blob = new Blob(['x']);
+    vi.mocked(api.exportAlertRuleAsset).mockResolvedValue(blob);
+
+    const result = await exportAlertRuleAsset('a');
+    expect(api.exportAlertRuleAsset).toHaveBeenCalledWith('a');
+    expect(result).toBe(blob);
+  });
+});
diff --git a/web/src/services/alertRuleAssetService.ts 
b/web/src/services/alertRuleAssetService.ts
new file mode 100644
index 00000000..cd82389f
--- /dev/null
+++ b/web/src/services/alertRuleAssetService.ts
@@ -0,0 +1,39 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.
+
+import { isMockMode } from './dataMode';
+import * as alertRuleAssetsApi from '../api/alertRuleAssets';
+import type { AlertRuleAssetInfo } from '../api/alertRuleAssets';
+import { mockAlertRuleAssets } from '../mock/alertRuleAssets';
+
+export async function listAlertRuleAssets(): Promise<AlertRuleAssetInfo[]> {
+  if (isMockMode()) {
+    return mockAlertRuleAssets.map(({ name, group, ruleCount, severities }) => 
({
+      name,
+      group,
+      ruleCount,
+      severities,
+    }));
+  }
+  return alertRuleAssetsApi.listAlertRuleAssets();
+}
+
+export async function getAlertRuleAsset(name: string): Promise<string> {
+  if (isMockMode()) {
+    const found = mockAlertRuleAssets.find((asset) => asset.name === name);
+    if (!found) {
+      throw new Error(`Alert rule asset not found: ${name}`);
+    }
+    return found.yaml;
+  }
+  return alertRuleAssetsApi.getAlertRuleAsset(name);
+}
+
+export async function exportAlertRuleAsset(name: string): Promise<Blob> {
+  if (isMockMode()) {
+    const found = mockAlertRuleAssets.find((asset) => asset.name === name);
+    const yaml = found ? found.yaml : '';
+    return new Blob([yaml], { type: 'text/yaml' });
+  }
+  return alertRuleAssetsApi.exportAlertRuleAsset(name);
+}

Reply via email to