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

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


The following commit(s) were added to refs/heads/master by this push:
     new 99b5460dd1 [feature] Support Huawei Cloud `Cloud Eye` alert source 
(#3443)
99b5460dd1 is described below

commit 99b5460dd13c4be85b53264a553c0ad77c2c39e6
Author: Duansg <[email protected]>
AuthorDate: Mon Jun 9 00:22:31 2025 +0800

    [feature] Support Huawei Cloud `Cloud Eye` alert source (#3443)
    
    Co-authored-by: tomsun28 <[email protected]>
---
 .../alert/dto/HuaweiCloudExternAlert.java          | 237 ++++++++++++++
 .../impl/HuaweiCloudExternAlertService.java        | 343 +++++++++++++++++++++
 .../org/apache/hertzbeat/alert/util/DateUtil.java  |  16 +
 .../service/HuaweiCloudExternAlertServiceTest.java | 154 +++++++++
 .../alert-integration.component.ts                 |   5 +
 .../doc/alert-integration/huaweicloud-ces.en-US.md |  77 +++++
 .../doc/alert-integration/huaweicloud-ces.zh-CN.md |  80 +++++
 .../doc/alert-integration/huaweicloud-ces.zh-TW.md |  77 +++++
 web-app/src/assets/i18n/en-US.json                 |   1 +
 web-app/src/assets/i18n/ja-JP.json                 |   1 +
 web-app/src/assets/i18n/pt-BR.json                 |   1 +
 web-app/src/assets/i18n/zh-CN.json                 |   1 +
 web-app/src/assets/i18n/zh-TW.json                 |   1 +
 web-app/src/assets/img/integration/huaweicloud.svg |   1 +
 14 files changed, 995 insertions(+)

diff --git 
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/dto/HuaweiCloudExternAlert.java
 
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/dto/HuaweiCloudExternAlert.java
new file mode 100644
index 0000000000..a82157ef31
--- /dev/null
+++ 
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/dto/HuaweiCloudExternAlert.java
@@ -0,0 +1,237 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * Huawei Cloud (CES) alert content entity.
+ *
+ * @see <a 
href="https://support.huaweicloud.com/usermanual-ces/ces_01_0218.html"/>
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class HuaweiCloudExternAlert {
+
+    public static final String FIELD_MESSAGE = "message";
+    public static final String FIELD_MESSAGE_ID = "message_id";
+    public static final String FIELD_TIMESTAMP = "timestamp";
+    public static final String FIELD_TOPIC_URN = "topic_urn";
+    public static final String FIELD_TYPE = "type";
+    public static final String FIELD_SUBJECT = "subject";
+    public static final String FIELD_SUBSCRIBE_URL = "subscribe_url";
+
+    /**
+     * Signature information.
+     */
+    private String signature;
+
+    /**
+     * Subject
+     */
+    private String subject;
+
+    /**
+     * The unique identifier of a topic, indicating the topic to which the 
message belongs.
+     */
+    @JsonProperty("topic_urn")
+    private String topicUrn;
+
+    /**
+     * Message unique identifier.
+     */
+    @JsonProperty("message_id")
+    private String messageId;
+
+    /**
+     * Message
+     */
+    private String message;
+
+    /**
+     * message types, the message types are respectively:
+     * SubscriptionConfirmation、Notification、UnsubscribeConfirmation
+     */
+    private String type;
+
+    /**
+     * Subscription confirms the URL that needs to be accessed
+     */
+    @JsonProperty("subscribe_url")
+    private String subscribeUrl;
+
+    /**
+     * The certificate URL used for message signing, which does not require 
authentication and can be accessed directly.
+     */
+    @JsonProperty("signing_cert_url")
+    private String signingCertUrl;
+
+    /**
+     * The timestamp of when the message was first sent.
+     */
+    private String timestamp;
+
+    /**
+     * Alert message
+     */
+    @Data
+    @Builder
+    @AllArgsConstructor
+    @NoArgsConstructor
+    public static class AlertMessage {
+
+        private String version;
+
+        private AlertData data;
+    }
+
+    /**
+     * Alert data
+     */
+    @Data
+    @Builder
+    @AllArgsConstructor
+    @NoArgsConstructor
+    public static class AlertData {
+
+        /**
+         * Whether an alarm occurs.
+         * Note: Empty and false are both recovery notifications.
+         * Note: There are no recovery notifications for event types
+         */
+        @JsonProperty("IsAlarm")
+        private Boolean alarm;
+
+        /**
+         * Alarm time
+         */
+        @JsonProperty("AlarmTime")
+        private String alarmTime;
+
+        /**
+         * Resource ID
+         */
+        @JsonProperty("ResourceId")
+        private String resourceId;
+
+        /**
+         * The name of the metric
+         */
+        @JsonProperty("MetricName")
+        private String metricName;
+
+        /**
+         * Specifies the alarm severity, which can be Critical, Major, Minor, 
or Informational.
+         */
+        @JsonProperty("AlarmLevel")
+        private String alarmLevel;
+
+        /**
+         * Namespace
+         */
+        @JsonProperty("Namespace")
+        private String namespace;
+
+        /**
+         * Region
+         */
+        @JsonProperty("Region")
+        private String region;
+
+        /**
+         * Dimension name
+         */
+        @JsonProperty("DimensionName")
+        private String dimensionName;
+
+        /**
+         * Resource name
+         */
+        @JsonProperty("ResourceName")
+        private String resourceName;
+
+        /**
+         * Alarm record ID
+         */
+        @JsonProperty("AlarmRecordID")
+        private String alarmRecordId;
+
+        /**
+         * Current data
+         */
+        @JsonProperty("CurrentData")
+        private String currentData;
+
+        /**
+         * The comparison conditions for the alarm thresholds can be >, =, <, 
>=, <=.
+         */
+        @JsonProperty("ComparisonOperator")
+        private String comparisonOperator;
+
+        /**
+         * Alarm value
+         */
+        @JsonProperty("Value")
+        private String value;
+
+        /**
+         * Number of consecutive occurrences of triggered alarms
+         */
+        @JsonProperty("Count")
+        private int count;
+
+    }
+
+    /**
+     * Huawei cloud alert type
+     */
+    public enum AlertType {
+
+        SUBSCRIPTION("SubscriptionConfirmation"),
+
+        UNSUBSCRIBE("UnsubscribeConfirmation"),
+
+        NOTIFICATION("Notification");
+
+        private final String type;
+
+        AlertType(String type) {
+            this.type = type;
+        }
+
+        public static boolean valid(String type) {
+            if (null == type || type.isEmpty()) {
+                return false;
+            }
+            return Arrays.stream(AlertType.values()).anyMatch(alertType -> 
alertType.getType().equals(type));
+        }
+
+        public String getType() {
+            return type;
+        }
+    }
+
+}
\ No newline at end of file
diff --git 
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/HuaweiCloudExternAlertService.java
 
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/HuaweiCloudExternAlertService.java
new file mode 100644
index 0000000000..2c538aeb48
--- /dev/null
+++ 
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/HuaweiCloudExternAlertService.java
@@ -0,0 +1,343 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.service.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert;
+import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
+import org.apache.hertzbeat.alert.service.ExternAlertService;
+import org.apache.hertzbeat.alert.util.DateUtil;
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
+import org.apache.hertzbeat.common.support.exception.IgnoreException;
+import org.apache.hertzbeat.common.util.JsonUtil;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.springframework.stereotype.Service;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.security.Signature;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.text.MessageFormat;
+import java.time.Instant;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import static 
org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.AlertType.NOTIFICATION;
+import static 
org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.AlertType.SUBSCRIPTION;
+import static 
org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.AlertType.UNSUBSCRIBE;
+import static 
org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.FIELD_MESSAGE;
+import static 
org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.FIELD_MESSAGE_ID;
+import static 
org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.FIELD_SUBJECT;
+import static 
org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.FIELD_SUBSCRIBE_URL;
+import static 
org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.FIELD_TIMESTAMP;
+import static 
org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.FIELD_TOPIC_URN;
+import static org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert.FIELD_TYPE;
+
+/**
+ * Huawei cloud external alarm service impl
+ */
+@Slf4j
+@Service
+public class HuaweiCloudExternAlertService implements ExternAlertService {
+
+    private static final String CERTIFICATE_TYPE = "X.509";
+
+    private static final String CHARSET_UTF8 = StandardCharsets.UTF_8.name();
+
+    private final AlarmCommonReduce alarmCommonReduce;
+
+    public HuaweiCloudExternAlertService(AlarmCommonReduce alarmCommonReduce) {
+        this.alarmCommonReduce = alarmCommonReduce;
+    }
+
+    @Override
+    public void addExternAlert(String content) {
+        HuaweiCloudExternAlert externAlert = JsonUtil.fromJson(content, 
HuaweiCloudExternAlert.class);
+        if (externAlert == null || 
StringUtils.isBlank(externAlert.getMessage())) {
+            log.warn("Failure to parse external alert content. content: {}", 
content);
+            return;
+        }
+        if (!isMessageValid(externAlert)) {
+            log.warn("Huawei cloud alert verify failed. content: {}", content);
+            return;
+        }
+        process(externAlert);
+    }
+
+    /**
+     * Process according to different types
+     *
+     * @param externAlert alert content entity
+     */
+    private void process(HuaweiCloudExternAlert externAlert) {
+        if (NOTIFICATION.getType().equals(externAlert.getType())) {
+            
Optional.ofNullable(buildSendAlert(externAlert)).ifPresent(alarmCommonReduce::reduceAndSendAlarm);
+        } else if (SUBSCRIPTION.getType().equals(externAlert.getType())) {
+            autoSubscribeForUrl(externAlert.getSubscribeUrl());
+        } else if (UNSUBSCRIBE.getType().equals(externAlert.getType())) {
+            log.warn("Huawei cloud notifies the recipient of the notification 
to cancel the subscription.");
+        }
+    }
+
+    /**
+     * Build single alert.
+     *
+     * @param externAlert alert content entity
+     * @return single alert
+     */
+    private SingleAlert buildSendAlert(HuaweiCloudExternAlert externAlert) {
+        HuaweiCloudExternAlert.AlertMessage message = 
JsonUtil.fromJson(externAlert.getMessage(), 
HuaweiCloudExternAlert.AlertMessage.class);
+        if (null == message || null == message.getData()) {
+            log.warn("Failure to parse external alert message. message: {}", 
externAlert.getMessage());
+            return null;
+        }
+        // Note: Empty and false are both recovery notifications.
+        // Note: There are no recovery notifications for event types
+        boolean isAlarm = null != message.getData().getAlarm() && 
message.getData().getAlarm();
+        Long alarmTime = 
DateUtil.getZonedTimeStampFromFormat(message.getData().getAlarmTime(), 
"yyyy/MM/dd HH:mm:ss 'GMT'XXX");
+        return SingleAlert.builder()
+                .triggerTimes(1)
+                .status(isAlarm ? CommonConstants.ALERT_STATUS_FIRING : 
CommonConstants.ALERT_STATUS_RESOLVED)
+                .startAt(alarmTime)
+                .activeAt(Instant.now().toEpochMilli())
+                .endAt(isAlarm ? null : alarmTime)
+                .labels(buildLabels(message.getData()))
+                .annotations(buildAnnotations(message.getData()))
+                .content(formatContent(externAlert.getSubject(), 
message.getData()))
+                .build();
+    }
+
+    /**
+     * Build basic annotations and fill annotations for huawei cloud.
+     *
+     * @param alertData alert content entity
+     * @return annotations
+     */
+    private Map<String, String> 
buildAnnotations(HuaweiCloudExternAlert.AlertData alertData) {
+        Map<String, String> annotations = new HashMap<>(8);
+        if (null != alertData) {
+            putIfNotBlank(annotations, "region", alertData.getRegion());
+            putIfNotBlank(annotations, "dimensionName", 
alertData.getDimensionName());
+            putIfNotBlank(annotations, "resourceName", 
alertData.getResourceName());
+            putIfNotBlank(annotations, "alarmRecordId", 
alertData.getAlarmRecordId());
+        }
+        return annotations;
+    }
+
+    /**
+     * Build basic labels and fill labels for huawei cloud.
+     *
+     * @param alertData alert content entity
+     * @return labels
+     */
+    private Map<String, String> buildLabels(HuaweiCloudExternAlert.AlertData 
alertData) {
+        Map<String, String> labels = new HashMap<>(8);
+        labels.put("__source__", "huaweicloud-ces");
+        if (null != alertData) {
+            putIfNotBlank(labels, "namespace", alertData.getNamespace());
+            putIfNotBlank(labels, "metricName", alertData.getMetricName());
+            putIfNotBlank(labels, "resourceId", alertData.getResourceId());
+            putIfNotBlank(labels, "level", alertData.getAlarmLevel());
+        }
+        return labels;
+    }
+
+    /**
+     * todo i18n
+     *
+     * @param subject alert subject
+     * @param alertData alert content entity
+     * @return content
+     */
+    private String formatContent(String subject, 
HuaweiCloudExternAlert.AlertData alertData) {
+        if (null == alertData) {
+            return subject;
+        }
+        return MessageFormat.format(
+                "{0} threshold:{1}{2}, current:{3}",
+                subject,
+                alertData.getComparisonOperator(),
+                alertData.getValue(),
+                alertData.getCurrentData()
+        );
+    }
+
+    /**
+     * Automatic subscription url.
+     *
+     * @param subscribeUrl subscribeUrl
+     */
+    public void autoSubscribeForUrl(String subscribeUrl) {
+        if (StringUtils.isBlank(subscribeUrl)) {
+            return;
+        }
+        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+            HttpGet httpGet = new HttpGet(subscribeUrl);
+            try (CloseableHttpResponse response = httpClient.execute(httpGet)) 
{
+                int statusCode = response.getStatusLine().getStatusCode();
+                String responseBody = 
EntityUtils.toString(response.getEntity());
+
+                if (statusCode != 200) {
+                    log.error("Subscribe url request failed with status code: 
" + statusCode + ", response: " + responseBody);
+                    return;
+                }
+                JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
+                if (jsonResponse == null) {
+                    throw new IgnoreException("Subscribe url failed with 
status code: " + statusCode + ", response: " + responseBody);
+                }
+                JsonNode surnNode = jsonResponse.get("subscription_urn");
+                if (surnNode == null || 
StringUtils.isBlank(surnNode.asText())) {
+                    throw new IgnoreException("Subscribe url failed with 
status code: " + statusCode + ", response: " + responseBody);
+                }
+                log.info("Successfully subscribed to Huawei Cloud(SMN) url.");
+            }
+        } catch (Exception e) {
+            log.error("Failed to subscribe url request: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * Verifying the signature of huawei cloud alert message.
+     *
+     * @param externAlert alert content entity
+     * @return verification result
+     * @throws SecurityException thrown when validation fails
+     */
+    private boolean isMessageValid(HuaweiCloudExternAlert externAlert) {
+        try {
+            String signMessage = buildSignMessage(externAlert);
+            if (StringUtils.isBlank(signMessage)) {
+                throw new SecurityException("Verify sign message is null");
+            }
+            X509Certificate cert = 
getCertificate(externAlert.getSigningCertUrl());
+            return verifySignature(signMessage, cert, 
externAlert.getSignature());
+        } catch (Exception e) {
+            log.error("Failed to verify message signature: ", e);
+            return false;
+        }
+    }
+
+    /**
+     * Build sign message.
+     *
+     * @param externAlert alert content entity
+     * @return sign message
+     */
+    private String buildSignMessage(HuaweiCloudExternAlert externAlert) {
+        if (NOTIFICATION.getType().equals(externAlert.getType())) {
+            return buildNotificationMessage(externAlert);
+        } else if (SUBSCRIPTION.getType().equals(externAlert.getType()) || 
UNSUBSCRIBE.getType().equals(externAlert.getType())){
+            return buildSubscriptionMessage(externAlert);
+        }
+        return null;
+    }
+
+    /**
+     * Building sign message of 'Notification' type
+     *
+     * @param externAlert alert content entity
+     * @return sign message
+     */
+    private String buildNotificationMessage(HuaweiCloudExternAlert 
externAlert) {
+        StringBuilder message = new StringBuilder();
+        appendField(message, FIELD_MESSAGE, externAlert.getMessage());
+        appendField(message, FIELD_MESSAGE_ID, externAlert.getMessageId());
+        if (StringUtils.isNotBlank(externAlert.getSubject())) {
+            appendField(message, FIELD_SUBJECT, externAlert.getSubject());
+        }
+        appendField(message, FIELD_TIMESTAMP, externAlert.getTimestamp());
+        appendField(message, FIELD_TOPIC_URN, externAlert.getTopicUrn());
+        appendField(message, FIELD_TYPE, externAlert.getType());
+        return message.toString();
+    }
+
+    /**
+     * Building sign message of 'SubscriptionConfirmation' or 
'UnsubscribeConfirmation' type
+     *
+     * @param externAlert alert content entity
+     * @return sign message
+     */
+    private String buildSubscriptionMessage(HuaweiCloudExternAlert 
externAlert) {
+        StringBuilder message = new StringBuilder();
+        appendField(message, FIELD_MESSAGE, externAlert.getMessage());
+        appendField(message, FIELD_MESSAGE_ID, externAlert.getMessageId());
+        appendField(message, FIELD_SUBSCRIBE_URL, 
externAlert.getSubscribeUrl());
+        appendField(message, FIELD_TIMESTAMP, externAlert.getTimestamp());
+        appendField(message, FIELD_TOPIC_URN, externAlert.getTopicUrn());
+        appendField(message, FIELD_TYPE, externAlert.getType());
+        return message.toString();
+    }
+
+    /**
+     * Obtain certificate
+     *
+     * @param signCertUrl sign cert url
+     * @return X509 certificate
+     * @throws Exception Thrown when certificate acquisition fails
+     */
+    private X509Certificate getCertificate(String signCertUrl) throws 
Exception {
+        URL url = new URL(signCertUrl);
+        try (InputStream in = url.openStream()) {
+            CertificateFactory cf = 
CertificateFactory.getInstance(CERTIFICATE_TYPE);
+            return (X509Certificate) cf.generateCertificate(in);
+        }
+    }
+
+    /**
+     * Verify signature
+     *
+     * @param message sign message
+     * @param cert cert
+     * @param signature signature
+     * @return verification result
+     * @throws Exception thrown when an error occurs in the validation process
+     */
+    private boolean verifySignature(String message, X509Certificate cert, 
String signature) throws Exception {
+        Signature sig = Signature.getInstance(cert.getSigAlgName());
+        sig.initVerify(cert.getPublicKey());
+        sig.update(message.getBytes(CHARSET_UTF8));
+        return sig.verify(Base64.getDecoder().decode(signature));
+    }
+
+    private void putIfNotBlank(Map<String, String> map, String key, String 
value) {
+        if (StringUtils.isNotBlank(value)){
+            map.put(key, value);
+        }
+    }
+
+    private void appendField(StringBuilder builder, String fieldName, String 
value) {
+        builder.append(fieldName).append("\n").append(value).append("\n");
+    }
+
+    @Override
+    public String supportSource() {
+        return "huaweicloud-ces";
+    }
+
+}
\ No newline at end of file
diff --git 
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/util/DateUtil.java 
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/util/DateUtil.java
index 55b0104eb9..13c357369e 100644
--- 
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/util/DateUtil.java
+++ 
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/util/DateUtil.java
@@ -19,6 +19,7 @@ package org.apache.hertzbeat.alert.util;
 
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatterBuilder;
 import java.util.Optional;
@@ -80,4 +81,19 @@ public final class DateUtil {
         return Optional.empty();
     }
 
+    /**
+     * convert format data to timestamp
+     */
+    public static Long getZonedTimeStampFromFormat(String dateStr, String 
format) {
+        try {
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
+            // the parsed zoned date-time, not null
+            return ZonedDateTime.parse(dateStr, 
formatter).toInstant().toEpochMilli();
+        } catch (Exception e) {
+            log.error("Error parsing date '{}' with format '{}': {}",
+                    dateStr, format, e.getMessage());
+        }
+        return null;
+    }
+
 }
diff --git 
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/HuaweiCloudExternAlertServiceTest.java
 
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/HuaweiCloudExternAlertServiceTest.java
new file mode 100644
index 0000000000..7167a5dcca
--- /dev/null
+++ 
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/HuaweiCloudExternAlertServiceTest.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.service;
+
+import org.apache.hertzbeat.alert.dto.HuaweiCloudExternAlert;
+import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
+import org.apache.hertzbeat.alert.service.impl.HuaweiCloudExternAlertService;
+import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
+import org.apache.hertzbeat.common.util.JsonUtil;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+/**
+ * unit test for {@link AlibabaCloudSlsExternAlertServiceTest }
+ */
+@Disabled
+@ExtendWith(MockitoExtension.class)
+public class HuaweiCloudExternAlertServiceTest {
+
+    @Mock
+    private AlarmCommonReduce alarmCommonReduce;
+
+    @InjectMocks
+    private HuaweiCloudExternAlertService externAlertService;
+
+    @BeforeEach
+    void setUp() {
+        assertEquals("huaweicloud-ces", externAlertService.supportSource());
+    }
+
+    @Test
+    void testAddExternAlertWithInvalidContent() {
+        String invalidContent = "invalid json";
+        externAlertService.addExternAlert(invalidContent);
+        verify(alarmCommonReduce, 
never()).reduceAndSendAlarm(any(SingleAlert.class));
+    }
+
+    @Test
+    void testMessageValidFailed() {
+        HuaweiCloudExternAlert externAlert = new HuaweiCloudExternAlert();
+        externAlert.setMessageId("d3672d737bb742cf8c2aa3f0fd72d4d1");
+        externAlert.setType("failedType");
+        externAlert.setMessage("failedMessage");
+        externAlert.setTimestamp("2025-06-07T15:12:09Z");
+        externAlertService.addExternAlert(JsonUtil.toJson(externAlert));
+        verify(alarmCommonReduce, 
never()).reduceAndSendAlarm(any(SingleAlert.class));
+    }
+
+    @Test
+    void testCertFailed() {
+        HuaweiCloudExternAlert externAlert = new HuaweiCloudExternAlert();
+        
externAlert.setSignature("TImrLoeb0tV1JZJSPyA0rpC9mNqH3MmhwQ4tgpuHHa+JztfGVZFvkU//OthKKhzpDAoYiXOYG9DbzXCLb"
+                + 
"vaGePIRITakoynYyYr9zZIpdx9jXhQNlgF8np1+t0JxNeoIq0DYWgH52tsodwqOm+OnmkcHwCRo/1rFv85KrKAaX2gy3sNwX"
+                + 
"w1hKnAwAw0mJlxHHSf/N3+7j6GoxCNV7fN9K4CpJiLMGNvUa7zVmG0U9mPvt/7Lac155kPPQ9lYyeL7vVI0e4sfRbuQruz3E"
+                + 
"0ZP40TKx0afoeR0/Bx/IoZzRP1La7pKlbEISvkcM7TqW/IOGQTkhVsQ32RFRxZWO2snw==");
+        externAlert.setSubject("[华为云][紧急告警恢复]云监控通知:分布式缓存服务-DCS Redis实例 
“dcs-h4tv” 的每秒并发操作数已恢复正常。");
+        
externAlert.setTopicUrn("urn:smn:cn-north-4:477a784601d744e4ab9ab83986502d31:CES_notification_group_bngJ2aMpX");
+        externAlert.setMessageId("d3672d737bb742cf8c2aa3f0fd72d4d1");
+        externAlert.setType("Notification");
+        externAlert.setMessage("{}");
+        
externAlert.setSigningCertUrl("https://smn.cn-north-4.myhuaweicloud.com/failedUrl";);
+        externAlert.setTimestamp("2025-06-07T15:12:09Z");
+        externAlertService.addExternAlert(JsonUtil.toJson(externAlert));
+        verify(alarmCommonReduce, 
never()).reduceAndSendAlarm(any(SingleAlert.class));
+    }
+
+    @Test
+    void testAddExternAlert() {
+        HuaweiCloudExternAlert externAlert = new HuaweiCloudExternAlert();
+        
externAlert.setSignature("Igs0bBhzw0JGmlgBH+9ejw2xWfPTXjAatAEsKDkkWcC5bZ/jveckdRZdgp/S0JER9eiJfMF427YDABufIN0sv/vBRXaRQKfRBLTJYbSTl+AQpEbIW5yUfJSRLEG3HNEhUDjASolbrW7zdPCoGkkqjifE23FCvw"
+                + 
"+4tewMzqmHnfJHcFBq3W89CJzdPBjwO1UcY9C39moUZgqZk+qDVLpxb4bHSrEYAwPOSrOPR7TZpETJ30UOgFYajJydQk692edfs0NeVutHoQiOJ5/YC83ULHft0aXhichjtfZE4KF69nROAKez0ubk3l"
+                + "Ey/mBIM9Ylbxn5b84OIrzzZQrIWe8Syw==");
+        externAlert.setSubject("[华为云][紧急告警]云监控通知:分布式缓存服务-DCS Redis实例 
“dcs-h4tv” 的每秒并发操作数已触发告警。");
+        
externAlert.setTopicUrn("urn:smn:cn-north-4:477a784601d744e4ab9ab83986502d31:CES_notification_group_bngJ2aMpX");
+        externAlert.setMessageId("1565df032a19494590d61e05f7b0dc0e");
+        externAlert.setType("Notification");
+        
externAlert.setMessage("{\"version\":\"v1\",\"data\":{\"AccountName\":\"hid_hk6tij5o1v-95zn\",\"Namespace\":\"分布式缓存服务\",\"DimensionName\":\"DCS
 Redis实例\",\"ResourceName\""
+                + 
":\"dcs-h4tv\",\"MetricName\":\"每秒并发操作数\",\"IsAlarm\":true,\"AlarmLevel\":\"紧急\",\"Region\":\"华东-上海一\",\"RegionId\":\"cn-east-3\",\"ResourceId\":\"3dc7b9ea"
+                + 
"-70b4-4c38-942d-e2636e6d844c\",\"PrivateIp\":\"192.168.0.54\",\"CurrentData\":\"6.00
 count\",\"AlarmTime\":\"2025/06/02 22:56:15 GMT+08:00\","
+                + "\"AlarmRecordID\":\"ah1748876175242njvndyzMZ\","
+                + 
"\"AlarmRuleName\":\"alarm-c5jj\",\"IsOriginalValue\":true,\"Filter\":\"原始值\",\"ComparisonOperator\":\"\\u003e\",\"Value\":\"5
 count\",\"Unit\":\"count\",\"Count\":2}}");
+        
externAlert.setSigningCertUrl("https://smn.cn-north-4.myhuaweicloud.com/smn/SMN_cn-north-4_b98100ca131b4116ab8ee7ccedbaae99.pem";);
+        externAlert.setTimestamp("2025-06-02T14:56:17Z");
+        externAlertService.addExternAlert(JsonUtil.toJson(externAlert));
+        verify(alarmCommonReduce, 
times(1)).reduceAndSendAlarm(any(SingleAlert.class));
+    }
+
+    @Test
+    void testSubscriptionUrl() {
+        HuaweiCloudExternAlert externAlert = new HuaweiCloudExternAlert();
+        
externAlert.setSubscribeUrl("https://console.huaweicloud.com/smn/subscription/confirm?token=477a784601d744e4ab9ab83986502d31c4b938";
+                + 
"0ec0b64392b134e517c3aa17eb7b3a12dc9f3b4ab495e61c4dee654b435d7223ea934345bf8ae8901cef912b1d&topic_urn=urn:smn:cn-north-4"
+                + 
":477a784601d744e4ab9ab83986502d31:CES_notification_group_bngJ2aMpX&region=cn-north-4");
+        
externAlert.setSignature("ottf37C/2RdDgqimRQMIBU6i7XjUfPPMU760jJn71wwP3825YPoIT22uw2A9399rkm9Jrt1qUEFrDLuA5yHFLd5n/XoM4FghIgyFn7VIfgpuVM31a+co78s"
+                + 
"YBiZ1egOCE/AwFm2oygRhfIceUj9Kw9vmc06el9TXY6RtE5tAEF6qEmICtTh45KwtCO/WRs3DY72dQi5hm0w7/tktS4WFZ1iP4LHt5eCwFvnH0u29Y96cJNI0fLUQxI5MkhgjK"
+                + 
"77JkFK7UT6ZYJZhzgSp/B7OQGStOQx+3Duvx4T4CzccZQM3sca81Z0B0GFGWeVXuEHyCPLsayY/Iz+5Tco51elT8w==");
+        
externAlert.setTopicUrn("urn:smn:cn-north-4:477a784601d744e4ab9ab83986502d31:CES_notification_group_bngJ2aMpX");
+        externAlert.setMessageId("242fac183d3a4936b5ead6c725a32ed0");
+        externAlert.setType("SubscriptionConfirmation");
+        externAlert.setMessage("You are invited to subscribe to topic: 
urn:smn:cn-north-4:477a784601d744e4ab9ab83986502d31:"
+                + "CES_notification_group_bngJ2aMpX. To confirm this 
subscription, please visit the subscribe_url included in this message. The 
subscribe_url is valid only within 48 hours.");
+        
externAlert.setSigningCertUrl("https://smn.cn-north-4.myhuaweicloud.com/smn/SMN_cn-north-4_b98100ca131b4116ab8ee7ccedbaae99.pem";);
+        externAlert.setTimestamp("2025-06-07T15:07:14Z");
+        externAlertService.addExternAlert(JsonUtil.toJson(externAlert));
+        verify(alarmCommonReduce, 
never()).reduceAndSendAlarm(any(SingleAlert.class));
+    }
+
+    @Test
+    void testUnsubscribe() {
+        HuaweiCloudExternAlert externAlert = new HuaweiCloudExternAlert();
+        
externAlert.setSignature("TImrLoeb0tV1JZJSPyA0rpC9mNqH3MmhwQ4tgpuHHa+JztfGVZFvkU//OthKKhzpDAoYiXOYG9DbzXCLbvaGePIRITakoynYyYr9zZIpdx9jXhQNlgF8np"
+                + 
"1+t0JxNeoIq0DYWgH52tsodwqOm+OnmkcHwCRo/1rFv85KrKAaX2gy3sNwXw1hKnAwAw0mJlxHHSf/N3+7j6GoxCNV7fN9K4CpJiLMGNvUa7zVmG0U9mPvt/7Lac155kPPQ9l"
+                + 
"YyeL7vVI0e4sfRbuQruz3E0+ZP40TKx0afoeR0/Bx/IoZzRP1La7pKlbEISvkcM7TqW/IOGQTkhVsQ32RFRxZWO2snw==");
+        externAlert.setSubject("[华为云][紧急告警恢复]云监控通知:分布式缓存服务-DCS Redis实例 
“dcs-h4tv” 的每秒并发操作数已恢复正常。");
+        
externAlert.setTopicUrn("urn:smn:cn-north-4:477a784601d744e4ab9ab83986502d31:CES_notification_group_bngJ2aMpX");
+        externAlert.setMessageId("d3672d737bb742cf8c2aa3f0fd72d4d1");
+        externAlert.setType("UnsubscribeConfirmation");
+        externAlert.setMessage("{}");
+        
externAlert.setSigningCertUrl("https://smn.cn-north-4.myhuaweicloud.com/smn/SMN_cn-north-4_b98100ca131b4116ab8ee7ccedbaae99.pem";);
+        externAlert.setTimestamp("2025-06-07T15:12:09Z");
+        externAlertService.addExternAlert(JsonUtil.toJson(externAlert));
+        verify(alarmCommonReduce, 
never()).reduceAndSendAlarm(any(SingleAlert.class));
+    }
+
+
+
+
+}
\ No newline at end of file
diff --git 
a/web-app/src/app/routes/alert/alert-integration/alert-integration.component.ts 
b/web-app/src/app/routes/alert/alert-integration/alert-integration.component.ts
index efcec7b9b1..baf5bf657f 100644
--- 
a/web-app/src/app/routes/alert/alert-integration/alert-integration.component.ts
+++ 
b/web-app/src/app/routes/alert/alert-integration/alert-integration.component.ts
@@ -87,6 +87,11 @@ export class AlertIntegrationComponent implements OnInit {
       id: 'alibabacloud-sls',
       name: this.i18nSvc.fanyi('alert.integration.source.alibabacloud-sls'),
       icon: 'assets/img/integration/alibabacloud.svg'
+    },
+    {
+      id: 'huaweicloud-ces',
+      name: this.i18nSvc.fanyi('alert.integration.source.huaweicloud-ces'),
+      icon: 'assets/img/integration/huaweicloud.svg'
     }
   ];
 
diff --git a/web-app/src/assets/doc/alert-integration/huaweicloud-ces.en-US.md 
b/web-app/src/assets/doc/alert-integration/huaweicloud-ces.en-US.md
new file mode 100644
index 0000000000..b847dd670d
--- /dev/null
+++ b/web-app/src/assets/doc/alert-integration/huaweicloud-ces.en-US.md
@@ -0,0 +1,77 @@
+>Send Huawei Cloud `Cloud Eye` alerts to the HertzBeat alert platform via 
Webhook.
+
+### Step 1: Configure Notification Template in `Cloud Eye` Console
+1. Log in to Huawei Cloud `Cloud Eye` Console
+2. Select **Alarm Management** > **Alarm Notifications** > **Notification 
Templates** > **Create**
+3. Protocol: Set to HTTP/HTTPS, Notification Type: Choose as needed, Format: 
JSON
+4. Ensure the following JSON preview is correct:
+```json
+{
+  "version": "v1",
+  "data": {
+    "AccountName": "RDS_test",
+    "Namespace": "Elastic Cloud Server",
+    "DimensionName": "Cloud Server",
+    "ResourceName": "ecs-test",
+    "MetricName": "CPU Usage",
+    "IsAlarm": true,
+    "AlarmLevel": "Important",
+    "Region": "North China-Ulanqab-203",
+    "RegionId": "cn-north-4",
+    "ResourceId": "xxxx-xxxx",
+    "PrivateIp": "127.0.0.0",
+    "PublicIp": "100.0.0.0",
+    "CurrentData": "1.06%",
+    "AlarmTime": "2024/08/0514:45:16GMT+08:00",
+    "AlarmRecordID": "ah1722xxxxxx",
+    "AlarmRuleName": "test-xxx",
+    "IsOriginalValue": true,
+    "Filter": "Original Value",
+    "ComparisonOperator": "u003e=",
+    "Value": "0%",
+    "Unit": "%",
+    "Count": 1,
+    "EpName": "default"
+  }
+}
+```
+
+### Step 2: Configure Notification Recipients in `Cloud Eye` Console
+1. Log in to Huawei Cloud `Cloud Eye` Console
+2. Select **Alarm Management** > **Alarm Notifications** > **Recipients** > 
**Create**
+3. Select Channel: HTTP or HTTPS
+4. Add HertzBeat as the alert receiver configuration
+   - Request URL: 
`http://{hertzbeat_host}:1157/api/alerts/report/huaweicloud-ces`
+
+### Step 3: Configure Notification Groups in `Cloud Eye` Console
+1. Log in to Huawei Cloud `Cloud Eye` Console
+2. Select **Alarm Management** > **Alarm Notifications** > **Notification 
Groups** > **Create**
+3. Select Recipients: The **Recipients** set in Step 2
+   - You can also add the **NoRecipients** to existing notification groups
+
+### Step 4: Configure Notification Policies in `Cloud Eye` Console
+1. Log in to Huawei Cloud `Cloud Eye` Console
+2. Select **Alarm Management** > **Alarm Notifications** > **Notification 
Policies** > **Create**
+3. Select **Notification Scope** > **Recipients** > **Notification Group** > 
Choose the **Notification Group** set in Step 3
+4. Select **Notification Templates** > **Templates for Metric Monitoring** and 
**Templates for Event Monitoring** -> Choose the **Notification Recipients** 
set in Step 1
+5. Configure other settings as needed
+
+### Common Issues
+
+#### Alerts Not Triggering
+- Ensure the Webhook URL is accessible by Huawei Cloud Monitor Service (CES) 
notifications
+- Verify the correctness of **Notification Policies**, **Notification 
Groups**, **Recipients**, and **Notification Templates** settings
+- Verify the correctness/enablement of **Alarm Management** > **Alarm Rules**, 
check **Alarm Records** for triggered alarms
+- Note: After adding a created **Recipients** to a **Notification Groups**, 
the **Simple Message Notification (SMN)** will send a subscription confirmation 
message to the subscription endpoint, which must be confirmed before receiving 
alarm notifications.
+  - After creating a notification group, a topic will be automatically created 
in **Simple Message Notification (SMN)** > **Topic Management** > **Topics**, 
and a subscription will be created in **Simple Message Notification (SMN)** > 
**Topic Management** > **Subscriptions**. HertzBeat has added automatic 
subscription functionality. If the status is not (Confirmed), please manually 
request the subscription
+- Note: If multiple **Recipientss** are created with different names but the 
same notification channel object, you will only receive one subscription 
confirmation message.
+
+#### Other Notes
+- HertzBeat has added automatic subscription functionality after being added 
to a **Notification Groups**.
+- For security purposes, HertzBeat supports **Message Signature Verification** 
to verify the legitimacy of messages through signature strings.
+
+#### For More Information, Please Refer to
+- [Alarm 
Management](https://support.huaweicloud.com/intl/en-us/usermanual-ces/ces_01_0067.html)
+- [Message Signature 
Verification](https://support.huaweicloud.com/intl/en-us/usermanual-smn/smn_ug_a9003.html)
+- [Requesting Subscription 
Confirmation](https://support.huaweicloud.com/intl/en-us/usermanual-smn/smn_ug_0009.html)
+- [HTTP or HTTPS Message 
Format](https://support.huaweicloud.com/intl/en-us/usermanual-smn/smn_ug_a9002.html)
diff --git a/web-app/src/assets/doc/alert-integration/huaweicloud-ces.zh-CN.md 
b/web-app/src/assets/doc/alert-integration/huaweicloud-ces.zh-CN.md
new file mode 100644
index 0000000000..419ca39651
--- /dev/null
+++ b/web-app/src/assets/doc/alert-integration/huaweicloud-ces.zh-CN.md
@@ -0,0 +1,80 @@
+>将华为云监控服务(CES)的告警通过 Webhook 方式发送到 HertzBeat 告警平台。
+
+### 步骤一: 在云监控服务控制台设置通知模板
+1. 登录华为云监控服务控制台
+2. 选择 **告警** > **告警通知** > **通知内容模板** > **创建通知内容模板**
+3. 渠道类型:设置为 HTTP/HTTPS 、通知类型:按需选择、数据格式: JSON
+4. 确保如下 JSON 正确预览
+```
+{
+  "version": "v1",
+  "data": {
+    "AccountName": "RDS_test",
+    "Namespace": "弹性云服务器",
+    "DimensionName": "云服务器",
+    "ResourceName": "ecs-test",
+    "MetricName": "CPU使用率",
+    "IsAlarm": true,
+    "AlarmLevel": "重要",
+    "Region": "华北-乌兰察布-二零三",
+    "RegionId": "cn-north-4",
+    "ResourceId": "xxxx-xxxx",
+    "PrivateIp": "127.0.0.0",
+    "PublicIp": "100.0.0.0",
+    "CurrentData": "1.06%",
+    "AlarmTime": "2024/08/0514:45:16GMT+08:00",
+    "AlarmRecordID": "ah1722xxxxxx",
+    "AlarmRuleName": "test-xxx",
+    "IsOriginalValue": true,
+    "Filter": "原始值",
+    "ComparisonOperator": "u003e=",
+    "Value": "0%",
+    "Unit": "%",
+    "Count": 1,
+    "EpName": "default"
+  }
+}
+```
+
+### 步骤二: 在云监控服务控制台设置通知对象
+1. 登录华为云监控服务控制台
+2. 选择 **告警** > **告警通知** > **通知对象** > **创建通知对象**
+3. 选择渠道:HTTP 或者 HTTPS
+4. 添加 HertzBeat 作为告警接收端配置
+- 请求地址: http://{hertzbeat_host}:1157/api/alerts/report/huaweicloud-ces
+
+### 步骤三: 在云监控服务控制台设置通知组
+1. 登录华为云监控服务控制台
+2. 选择 **告警** > **告警通知** > **通知组** > **创建通知组**
+3. 选择通知对象:步骤二设置的 **通知对象**
+- 您也可以在已有的通知组添加 **通知对象**
+
+### 步骤四: 在云监控服务控制台设置通知策略
+1. 登录华为云监控服务控制台
+2. 选择 **告警** > **告警通知** > **通知策略** > **创建通知策略**
+3. 选择 **通知范围** > **接收对象** > **通知组** > 选择步骤三设置的 **通知组**
+4. 选择 **通知内容模板** > **指标模板** 跟 **事件模板** -> 选择步骤一设置的 **通知模板**
+5. 其他的请按需选择配置
+
+### 常见问题
+
+#### 告警未触发
+- 确保 Webhook URL 可以被 华为云监控服务(CES) 通知访问
+- 确保 **通知策略**、**通知组**、**通知对象**、**通知内容模板** 设置的正确性
+- 确保 **告警** > **告警规则** 设置的正确性/是否已启用,可查阅 **告警记录** 是否有触发告警
+- 注意:已创建的 **通知对象** 加入到 **通知组** 后,**消息通知服务(SMN)** 会向订阅终端发送订阅确认信息,需确认后方可收到告警通知。
+  - 创建完通知组以后,会在 **消息通知服务(SMN)** > **主题管理** > **主题** 中同步创建主题,并在 **消息通知服务(SMN)** 
> **主题管理** > **订阅** 中创建订阅信息。HertzBeat 添加了自动订阅的功能,如果状态不是(已确认),请手动请求订阅
+- 注意:若多个 **通知对象** 创建名称不一致,但通知渠道的对象一致,则只会收到一次订阅确认信息。
+
+#### 其他
+- HertzBeat 添加了加入到 **通知组** 后自动订阅的功能。
+- 为了确保安全,HertzBeat 支持了 **消息签名认证**,通过签名串验证消息的合法性。
+
+#### 更多信息请参考
+- [告警](https://support.huaweicloud.com/usermanual-ces/ces_01_0067.html)
+- [校验消息签名](https://support.huaweicloud.com/usermanual-smn/smn_ug_a9003.html)
+- [请求订阅](https://support.huaweicloud.com/usermanual-smn/smn_ug_0046.html)
+- 
[HTTP(S)消息格式](https://support.huaweicloud.com/usermanual-smn/smn_ug_a9002.html)
+
+
+
diff --git a/web-app/src/assets/doc/alert-integration/huaweicloud-ces.zh-TW.md 
b/web-app/src/assets/doc/alert-integration/huaweicloud-ces.zh-TW.md
new file mode 100644
index 0000000000..882d24a5ba
--- /dev/null
+++ b/web-app/src/assets/doc/alert-integration/huaweicloud-ces.zh-TW.md
@@ -0,0 +1,77 @@
+>將華為雲監控服務(CES)的告警通過 Webhook 方式發送到 HertzBeat 告警平臺。
+
+### 步驟一: 在雲監控服務控制檯設置通知模板
+1. 登錄華為雲監控服務控制檯
+2. 選擇 **告警** > **告警通知** > **通知內容模板** > **創建通知內容模板**
+3. 渠道類型:設置為 HTTP/HTTPS 、通知類型:按需選擇、數據格式: JSON
+4. 確保如下 JSON 正確預覽
+```
+{
+  "version": "v1",
+  "data": {
+    "AccountName": "RDS_test",
+    "Namespace": "彈性雲服務器",
+    "DimensionName": "雲服務器",
+    "ResourceName": "ecs-test",
+    "MetricName": "CPU使用率",
+    "IsAlarm": true,
+    "AlarmLevel": "重要",
+    "Region": "華北-烏蘭察布-二零三",
+    "RegionId": "cn-north-4",
+    "ResourceId": "xxxx-xxxx",
+    "PrivateIp": "127.0.0.0",
+    "PublicIp": "100.0.0.0",
+    "CurrentData": "1.06%",
+    "AlarmTime": "2024/08/0514:45:16GMT+08:00",
+    "AlarmRecordID": "ah1722xxxxxx",
+    "AlarmRuleName": "test-xxx",
+    "IsOriginalValue": true,
+    "Filter": "原始值",
+    "ComparisonOperator": "u003e=",
+    "Value": "0%",
+    "Unit": "%",
+    "Count": 1,
+    "EpName": "default"
+  }
+}
+```
+
+### 步驟二: 在雲監控服務控制檯設置通知對象
+1. 登錄華為雲監控服務控制檯
+2. 選擇 **告警** > **告警通知** > **通知對象** > **創建通知對象**
+3. 選擇渠道:HTTP 或者 HTTPS
+4. 添加 HertzBeat 作為告警接收端配置
+- 請求地址: http://{hertzbeat_host}:1157/api/alerts/report/huaweicloud-ces
+
+### 步驟三: 在雲監控服務控制檯設置通知組
+1. 登錄華為雲監控服務控制檯
+2. 選擇 **告警** > **告警通知** > **通知組** > **創建通知組**
+3. 選擇通知對象:步驟二設置的 **通知對象**
+- 您也可以在已有的通知組添加 **通知對象**
+
+### 步驟四: 在雲監控服務控制檯設置通知策略
+1. 登錄華為雲監控服務控制檯
+2. 選擇 **告警** > **告警通知** > **通知策略** > **創建通知策略**
+3. 選擇 **通知範圍** > **接收對象** > **通知組** > 選擇步驟三設置的 **通知組**
+4. 選擇 **通知內容模板** > **指標模板** 跟 **事件模板** -> 選擇步驟一設置的 **通知模板**
+5. 其他的請按需選擇配置
+
+### 常見問題
+
+#### 告警未觸發
+- 確保 Webhook URL 可以被 華為雲監控服務(CES) 通知訪問
+- 確保 **通知策略**、**通知組**、**通知對象**、**通知內容模板** 設置的正確性
+- 確保 **告警** > **告警規則** 設置的正確性/是否已啟用,可查閱 **告警記錄** 是否有觸發告警
+- 注意:已創建的 **通知對象** 加入到 **通知組** 後,**消息通知服務(SMN)** 會向訂閱終端發送訂閱確認信息,需確認後方可收到告警通知。
+  - 創建完通知組以後,會在 **消息通知服務(SMN)** > **主題管理** > **主題** 中同步創建主題,並在 **消息通知服務(SMN)** 
> **主題管理** > **訂閱** 中創建訂閱信息。HertzBeat 添加了自動訂閱的功能,如果狀態不是(已確認),請手動請求訂閱
+- 注意:若多個 **通知對象** 創建名稱不一致,但通知渠道的對象一致,則只會收到一次訂閱確認信息。
+
+#### 其他
+- HertzBeat 添加了加入到 **通知組** 後自動訂閱的功能。
+- 為了確保安全,HertzBeat 支持了 **消息簽名認證**,通過簽名串驗證消息的合法性。
+
+#### 更多信息請參考
+- [告警](https://support.huaweicloud.com/usermanual-ces/ces_01_0067.html)
+- [校驗消息簽名](https://support.huaweicloud.com/usermanual-smn/smn_ug_a9003.html)
+- [請求訂閱](https://support.huaweicloud.com/usermanual-smn/smn_ug_0046.html)
+- 
[HTTP(S)消息格式](https://support.huaweicloud.com/usermanual-smn/smn_ug_a9002.html)
diff --git a/web-app/src/assets/i18n/en-US.json 
b/web-app/src/assets/i18n/en-US.json
index ce74374127..a8d0f44931 100644
--- a/web-app/src/assets/i18n/en-US.json
+++ b/web-app/src/assets/i18n/en-US.json
@@ -101,6 +101,7 @@
   "alert.integration.source.uptime-kuma": "Uptime Kuma",
   "alert.integration.source.zabbix": "Zabbix",
   "alert.integration.source.alibabacloud-sls": "AlibabaCloud-SLS",
+  "alert.integration.source.huaweicloud-ces": "Huawei Cloud Eye",
   "alert.integration.token.desc": "Token you generated that can be used to 
access the HertzBeat API.",
   "alert.integration.token.new": "Click to Generate Token",
   "alert.integration.token.notice": "Token only be displayed once. Please keep 
your token secure. Do not share it with others.",
diff --git a/web-app/src/assets/i18n/ja-JP.json 
b/web-app/src/assets/i18n/ja-JP.json
index 6781ebbf39..6a0b1c28c3 100644
--- a/web-app/src/assets/i18n/ja-JP.json
+++ b/web-app/src/assets/i18n/ja-JP.json
@@ -101,6 +101,7 @@
   "alert.integration.source.uptime-kuma": "Uptime Kuma",
   "alert.integration.source.zabbix": "Zabbix",
   "alert.integration.source.alibabacloud-sls": "AlibabaCloud-SLS",
+  "alert.integration.source.huaweicloud-ces": "Huawei Cloud Eye",
   "alert.integration.token.desc": "HertzBeat APIにアクセスするために生成したトークン。",
   "alert.integration.token.new": "トークンを生成するにはクリック",
   "alert.integration.token.notice": 
"トークンは一度だけ表示されます。トークンを安全に保管し、他人と共有しないでください。",
diff --git a/web-app/src/assets/i18n/pt-BR.json 
b/web-app/src/assets/i18n/pt-BR.json
index bd25d96d6b..0d0455959a 100644
--- a/web-app/src/assets/i18n/pt-BR.json
+++ b/web-app/src/assets/i18n/pt-BR.json
@@ -246,6 +246,7 @@
     "alert.integration.source.tencent": "Monitoramento de nuvem Tencent",
     "alert.integration.source.webhook": "PadrãoWebhook",
     "alert.integration.source.alibabacloud-sls": "AlibabaCloud-SLS",
+    "alert.integration.source.huaweicloud-ces": "Huawei Cloud Eye",
     "alert.integration.token.desc": "O token gerado pode ser usado para 
acessar a API HertzBeat",
     "alert.integration.token.new": "Clique para gerar token",
     "alert.integration.token.notice": "Este conteúdo será exibido apenas uma 
vez, por favor guarde seu token adequadamente e não o divulgue a terceiros.",
diff --git a/web-app/src/assets/i18n/zh-CN.json 
b/web-app/src/assets/i18n/zh-CN.json
index 4fa296ae88..aa60387170 100644
--- a/web-app/src/assets/i18n/zh-CN.json
+++ b/web-app/src/assets/i18n/zh-CN.json
@@ -101,6 +101,7 @@
   "alert.integration.source.uptime-kuma": "Uptime Kuma",
   "alert.integration.source.zabbix": "Zabbix",
   "alert.integration.source.alibabacloud-sls": "阿里云日志服务 SLS",
+  "alert.integration.source.huaweicloud-ces": "华为云监控服务",
   "alert.integration.token.desc": "生成的 Token 可用于访问 HertzBeat API",
   "alert.integration.token.new": "点击生成 Token",
   "alert.integration.token.notice": "此内容只会展示一次,请妥善保管您的 Token,不要泄露给他人",
diff --git a/web-app/src/assets/i18n/zh-TW.json 
b/web-app/src/assets/i18n/zh-TW.json
index fc8b7e116c..3c75766e11 100644
--- a/web-app/src/assets/i18n/zh-TW.json
+++ b/web-app/src/assets/i18n/zh-TW.json
@@ -101,6 +101,7 @@
   "alert.integration.source.uptime-kuma": "Uptime Kuma",
   "alert.integration.source.zabbix": "Zabbix",
   "alert.integration.source.alibabacloud-sls": "阿里雲端日誌服務 SLS",
+  "alert.integration.source.huaweicloud-ces": "華為雲監控服務",
   "alert.integration.token.desc": "生成的 Token 可用于访问 HertzBeat API",
   "alert.integration.token.new": "点击生成 Token",
   "alert.integration.token.notice": "此内容只会展示一次,请妥善保管您的 Token,不要泄露给他人",
diff --git a/web-app/src/assets/img/integration/huaweicloud.svg 
b/web-app/src/assets/img/integration/huaweicloud.svg
new file mode 100644
index 0000000000..0807b90573
--- /dev/null
+++ b/web-app/src/assets/img/integration/huaweicloud.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"; viewBox="0 0 396.53 
299.12"><title>Huawei logo</title><path 
d="M539.79,595.81a1.46,1.46,0,0,0,1.61.11c17.44-8.72,90.1-45.64,120.13-75,0,0,19.05-15.29,20-39.94,2.15-34.12-32.19-65.77-32.19-65.77s-63.51,77-110.12,178.69a2,2,0,0,0,.54,1.94m167-79.44S603.19,572,549.59,608.19a1.59,1.59,0,0,0-.65,1.72s.54,1,1.29,1c19.16,0,91.71.11,93.65-.22a70.13,70.13,0,0,0,21-4.84s25.83-8.18,39.18-37.57c0,0,11.95-23.9,2.69-51.88M552.28,620a2.07,2.07,0,0,0-1.4,1,1.72,1.
 [...]
\ No newline at end of file


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

Reply via email to