mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-09-17 18:19:02 +00:00
Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf21538062 | ||
|
|
3688974610 | ||
|
|
af35043663 | ||
|
|
84c28229b2 | ||
|
|
1a3e614209 | ||
|
|
3df0e56da9 | ||
|
|
b5b4771e1a | ||
|
|
b8c5ae4d40 | ||
|
|
207f2d958c | ||
|
|
398a40bba4 | ||
|
|
bb14681621 | ||
|
|
d3a080fbaf | ||
|
|
ce6f9feac6 | ||
|
|
7d18118bbd | ||
|
|
d5d2b6bc71 | ||
|
|
e11a4da2ac |
+9
-2
@@ -19,6 +19,7 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
@@ -30,6 +31,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
/**
|
||||
* SSE manager for alert
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AlertSseManager {
|
||||
private final Map<Long, SseEmitter> emitters = new ConcurrentHashMap<>();
|
||||
@@ -38,6 +40,7 @@ public class AlertSseManager {
|
||||
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
|
||||
emitter.onCompletion(() -> removeEmitter(clientId));
|
||||
emitter.onTimeout(() -> removeEmitter(clientId));
|
||||
emitter.onError((ex) -> removeEmitter(clientId));
|
||||
emitters.put(clientId, emitter);
|
||||
return emitter;
|
||||
}
|
||||
@@ -50,7 +53,11 @@ public class AlertSseManager {
|
||||
.id(String.valueOf(System.currentTimeMillis()))
|
||||
.name("ALERT_EVENT")
|
||||
.data(data));
|
||||
} catch (IOException e) {
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
emitter.complete();
|
||||
removeEmitter(clientId);
|
||||
} catch (Exception exception) {
|
||||
log.error("Failed to broadcast alert data to client: {}", exception.getMessage());
|
||||
emitter.complete();
|
||||
removeEmitter(clientId);
|
||||
}
|
||||
@@ -60,4 +67,4 @@ public class AlertSseManager {
|
||||
private void removeEmitter(Long clientId) {
|
||||
emitters.remove(clientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-2
@@ -17,9 +17,30 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Alibaba Cloud SMS properties
|
||||
* Alibaba Cloud SMS configuration properties
|
||||
*/
|
||||
@Data
|
||||
public class AlibabaSmsProperties {
|
||||
// todo add properties
|
||||
/**
|
||||
* Alibaba Cloud access key id
|
||||
*/
|
||||
private String accessKeyId;
|
||||
|
||||
/**
|
||||
* Alibaba Cloud access key secret
|
||||
*/
|
||||
private String accessKeySecret;
|
||||
|
||||
/**
|
||||
* SMS signature
|
||||
*/
|
||||
private String signName;
|
||||
|
||||
/**
|
||||
* SMS template Code
|
||||
*/
|
||||
private String templateCode;
|
||||
}
|
||||
|
||||
@@ -49,4 +49,9 @@ public class SmsConfig {
|
||||
*/
|
||||
private AlibabaSmsProperties alibaba;
|
||||
|
||||
/**
|
||||
* UniSMS configuration
|
||||
*/
|
||||
private UniSmsProperties unisms;
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* UniSMS properties
|
||||
*/
|
||||
@Data
|
||||
public class UniSmsProperties {
|
||||
/**
|
||||
* UniSMS access key id
|
||||
*/
|
||||
private String accessKeyId;
|
||||
|
||||
/**
|
||||
* UniSMS access key secret, required for HMAC mode
|
||||
*/
|
||||
private String accessKeySecret;
|
||||
|
||||
/**
|
||||
* SMS signature
|
||||
*/
|
||||
private String signature;
|
||||
|
||||
/**
|
||||
* SMS template ID
|
||||
*/
|
||||
private String templateId;
|
||||
|
||||
/**
|
||||
* Authentication mode: simple or hmac, default is simple
|
||||
*/
|
||||
private String authMode = "simple";
|
||||
}
|
||||
+7
-1
@@ -21,6 +21,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.SmsConfig;
|
||||
import org.apache.hertzbeat.alert.service.impl.TencentSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.UniSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.AlibabaSmsClientImpl;
|
||||
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
|
||||
import org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum;
|
||||
import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
|
||||
@@ -30,6 +32,7 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.ALIBABA;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.TENCENT;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.UNISMS;
|
||||
|
||||
/**
|
||||
* SMS client factory
|
||||
@@ -124,8 +127,11 @@ public class SmsClientFactory {
|
||||
case TENCENT:
|
||||
currentSmsClient = new TencentSmsClientImpl(smsConfig.getTencent());
|
||||
break;
|
||||
case UNISMS:
|
||||
currentSmsClient = new UniSmsClientImpl(smsConfig.getUnisms());
|
||||
break;
|
||||
case ALIBABA:
|
||||
// TODO: implement Alibaba SMS client
|
||||
currentSmsClient = new AlibabaSmsClientImpl(smsConfig.getAlibaba());
|
||||
break;
|
||||
default:
|
||||
log.warn("[SmsClientFactory] Unsupported SMS provider type: {}", smsConfig.getType());
|
||||
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* 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.hertzbeat.alert.config.AlibabaSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.alert.util.CryptoUtils;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.apache.hertzbeat.common.support.exception.SendMessageException;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.SimpleTimeZone;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.ALIBABA;
|
||||
|
||||
/**
|
||||
* Alibaba Cloud SMS Client Implementation<br>
|
||||
* API doc: <a href="https://next.api.aliyun.com/document/Dysmsapi/2017-05-25/SendSms">https://next.api.aliyun.com/document/Dysmsapi/2017-05-25/SendSms</a><br>
|
||||
* Singnature doc: <a href="https://help.aliyun.com/zh/sdk/product-overview/v3-request-structure-and-signature">https://help.aliyun.com/zh/sdk/product-overview/v3-request-structure-and-signature</a>
|
||||
*/
|
||||
@Slf4j
|
||||
public class AlibabaSmsClientImpl implements SmsClient {
|
||||
|
||||
private static final String API_VERSION = "2017-05-25";
|
||||
private static final String ACTION = "SendSms";
|
||||
private static final String HOST = "dysmsapi.aliyuncs.com";
|
||||
private static final String ALGORITHM = "ACS3-HMAC-SHA256";
|
||||
|
||||
private final String accessKeyId;
|
||||
private final String accessKeySecret;
|
||||
private final String signName;
|
||||
private final String templateCode;
|
||||
|
||||
public AlibabaSmsClientImpl(AlibabaSmsProperties config) {
|
||||
if (config != null) {
|
||||
this.accessKeyId = config.getAccessKeyId();
|
||||
this.accessKeySecret = config.getAccessKeySecret();
|
||||
this.signName = config.getSignName();
|
||||
this.templateCode = config.getTemplateCode();
|
||||
} else {
|
||||
this.accessKeyId = "";
|
||||
this.accessKeySecret = "";
|
||||
this.signName = "";
|
||||
this.templateCode = "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
|
||||
// Extract alert info
|
||||
String instance = null;
|
||||
String priority = null;
|
||||
String content = null;
|
||||
if (alert.getCommonLabels() != null) {
|
||||
instance = alert.getCommonLabels().get("instance");
|
||||
priority = alert.getCommonLabels().get("priority");
|
||||
content = alert.getCommonAnnotations().get("summary");
|
||||
content = content == null ? alert.getCommonAnnotations().get("description") : content;
|
||||
if (content == null) {
|
||||
content = alert.getCommonAnnotations().values().stream().findFirst().orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Build template parameters
|
||||
Map<String, String> templateParam = new HashMap<>();
|
||||
templateParam.put("instance", instance == null ? alert.getGroupKey() : instance);
|
||||
templateParam.put("priority", priority == null ? "unknown" : priority);
|
||||
templateParam.put("content", content);
|
||||
|
||||
sendSms(receiver.getPhone(), JsonUtil.toJson(templateParam));
|
||||
}
|
||||
|
||||
private void sendSms(String phoneNumber, String templateParam) {
|
||||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||
// Build query parameters
|
||||
Map<String, String> queryParams = new TreeMap<>();
|
||||
queryParams.put("PhoneNumbers", phoneNumber);
|
||||
queryParams.put("SignName", signName);
|
||||
queryParams.put("TemplateCode", templateCode);
|
||||
queryParams.put("TemplateParam", templateParam);
|
||||
|
||||
// Build canonical query string
|
||||
StringBuilder canonicalQueryString = new StringBuilder();
|
||||
queryParams.forEach((key, value) -> {
|
||||
if (canonicalQueryString.length() > 0) {
|
||||
canonicalQueryString.append("&");
|
||||
}
|
||||
canonicalQueryString.append(percentEncode(key))
|
||||
.append("=")
|
||||
.append(percentEncode(value));
|
||||
});
|
||||
|
||||
// Generate timestamp and nonce
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||
sdf.setTimeZone(new SimpleTimeZone(0, "GMT"));
|
||||
String timestamp = sdf.format(new Date());
|
||||
String nonce = UUID.randomUUID().toString();
|
||||
|
||||
// Calculate signature
|
||||
String authorization = calculateAuthorization(
|
||||
canonicalQueryString.toString(),
|
||||
timestamp,
|
||||
nonce
|
||||
);
|
||||
|
||||
// Build URL
|
||||
String url = "https://" + HOST + "/?" + canonicalQueryString;
|
||||
|
||||
// Build HTTP request
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
httpPost.setHeader("Content-Type", "application/json");
|
||||
httpPost.setHeader("Host", HOST);
|
||||
httpPost.setHeader("Authorization", authorization);
|
||||
httpPost.setHeader("x-acs-action", ACTION);
|
||||
httpPost.setHeader("x-acs-version", API_VERSION);
|
||||
httpPost.setHeader("x-acs-date", timestamp);
|
||||
httpPost.setHeader("x-acs-signature-nonce", nonce);
|
||||
httpPost.setHeader("x-acs-content-sha256",
|
||||
CryptoUtils.sha256Hex(""));
|
||||
|
||||
log.info("Sending Alibaba SMS request to {}", url + ", params: " + templateParam + "headers: " + Arrays.toString(httpPost.getAllHeaders()));
|
||||
|
||||
// Send request and handle response
|
||||
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
String responseBody = EntityUtils.toString(response.getEntity());
|
||||
|
||||
log.info("SMS response status: {}, body: {}", statusCode, responseBody);
|
||||
|
||||
if (statusCode != 200) {
|
||||
throw new SendMessageException("HTTP request failed with status code: " + statusCode + ", response: " + responseBody);
|
||||
}
|
||||
|
||||
JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
|
||||
String code = jsonResponse.get("Code").asText();
|
||||
if (!"OK".equals(code)) {
|
||||
String message = jsonResponse.get("Message").asText();
|
||||
throw new SendMessageException(code + ":" + message);
|
||||
}
|
||||
|
||||
log.info("Successfully sent SMS to phone: {}", phoneNumber);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to send SMS: {}", e.getMessage());
|
||||
throw new SendMessageException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String calculateAuthorization(String canonicalQueryString, String timestamp, String nonce) {
|
||||
try {
|
||||
// Step 1: Build canonical request
|
||||
String canonicalRequest = buildCanonicalRequest(canonicalQueryString, timestamp, nonce);
|
||||
|
||||
// Step 2: Build string to sign
|
||||
String stringToSign = ALGORITHM + "\n" + CryptoUtils.sha256Hex(canonicalRequest);
|
||||
|
||||
// Step 3: Calculate signature
|
||||
String signature = CryptoUtils.hmacSha256Hex(accessKeySecret, stringToSign);
|
||||
|
||||
// Step 4: Build authorization header
|
||||
return ALGORITHM + " Credential=" + accessKeyId + ",SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;" + "x-acs-signature-nonce;x-acs-version,Signature=" + signature;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to calculate authorization", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildCanonicalRequest(String canonicalQueryString, String timestamp, String nonce) {
|
||||
return "POST\n"
|
||||
+ "/\n"
|
||||
+ canonicalQueryString + "\n"
|
||||
+ "host:" + HOST + "\n"
|
||||
+ "x-acs-action:" + ACTION + "\n"
|
||||
+ "x-acs-content-sha256:" + CryptoUtils.sha256Hex("") + "\n"
|
||||
+ "x-acs-date:" + timestamp + "\n"
|
||||
+ "x-acs-signature-nonce:" + nonce + "\n"
|
||||
+ "x-acs-version:" + API_VERSION + "\n\n"
|
||||
+ "host;x-acs-action;x-acs-content-sha256;x-acs-date;"
|
||||
+ "x-acs-signature-nonce;x-acs-version\n"
|
||||
+ CryptoUtils.sha256Hex("");
|
||||
}
|
||||
|
||||
private String percentEncode(String value) {
|
||||
try {
|
||||
return java.net.URLEncoder.encode(value, StandardCharsets.UTF_8)
|
||||
.replace("+", "%20")
|
||||
.replace("*", "%2A")
|
||||
.replace("%7E", "~");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to encode value", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return ALIBABA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkConfig() {
|
||||
return !(accessKeyId.isBlank() || accessKeySecret.isBlank() || signName.isBlank() || templateCode.isBlank());
|
||||
}
|
||||
}
|
||||
+44
-2
@@ -20,7 +20,7 @@ package org.apache.hertzbeat.alert.service.impl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.TencentSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.alert.util.TencentCloudApiSignV3;
|
||||
import org.apache.hertzbeat.alert.util.CryptoUtils;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
@@ -34,9 +34,14 @@ import org.apache.http.util.EntityUtils;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.TENCENT;
|
||||
|
||||
@@ -53,6 +58,7 @@ public class TencentSmsClientImpl implements SmsClient {
|
||||
private static final String API_VERSION = "2021-01-11";
|
||||
private static final String ACTION = "SendSms";
|
||||
private static final String HOST = "sms.tencentcloudapi.com";
|
||||
private static final Charset UTF8 = StandardCharsets.UTF_8;
|
||||
|
||||
private String appId;
|
||||
private String signName;
|
||||
@@ -113,7 +119,7 @@ public class TencentSmsClientImpl implements SmsClient {
|
||||
String payload = JsonUtil.toJson(params);
|
||||
|
||||
// calculate request signature
|
||||
String authorization = TencentCloudApiSignV3.calculateAuthorization(
|
||||
String authorization = calculateAuthorization(
|
||||
secretId, secretKey, "sms", HOST, REGION,
|
||||
ACTION, API_VERSION, payload);
|
||||
|
||||
@@ -178,4 +184,40 @@ public class TencentSmsClientImpl implements SmsClient {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static String calculateAuthorization(String secretId, String secretKey,
|
||||
String service, String host, String region,
|
||||
String action, String version, String payload) throws Exception {
|
||||
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));
|
||||
|
||||
// Step 1: Construct the canonical request string
|
||||
String httpRequestMethod = "POST";
|
||||
String canonicalUri = "/";
|
||||
String canonicalQueryString = "";
|
||||
String canonicalHeaders = "content-type:application/json; charset=utf-8\n" + "host:" + host + "\n";
|
||||
String signedHeaders = "content-type;host";
|
||||
String hashedRequestPayload = CryptoUtils.sha256Hex(payload);
|
||||
String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n"
|
||||
+ canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload;
|
||||
|
||||
// Step 2: Construct the string to sign
|
||||
String algorithm = "TC3-HMAC-SHA256";
|
||||
String credentialScope = date + "/" + service + "/" + "tc3_request";
|
||||
String hashedCanonicalRequest = CryptoUtils.sha256Hex(canonicalRequest);
|
||||
String stringToSign = algorithm + "\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
|
||||
|
||||
// Step 3: Calculate the signature
|
||||
byte[] secretDate = CryptoUtils.hmac256(("TC3" + secretKey).getBytes(UTF8), date);
|
||||
byte[] secretService = CryptoUtils.hmac256(secretDate, service);
|
||||
byte[] secretSigning = CryptoUtils.hmac256(secretService, "tc3_request");
|
||||
String signature = DatatypeConverter.printHexBinary(
|
||||
CryptoUtils.hmac256(secretSigning, stringToSign)).toLowerCase();
|
||||
|
||||
// Step 4: Construct the Authorization header
|
||||
return algorithm + " " + "Credential=" + secretId + "/" + credentialScope + ", "
|
||||
+ "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature;
|
||||
}
|
||||
}
|
||||
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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 lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.UniSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.apache.hertzbeat.common.support.exception.SendMessageException;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.apache.hertzbeat.alert.util.CryptoUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.UNISMS;
|
||||
|
||||
/**
|
||||
* UniSMS client implementation <br/>
|
||||
* doc:<a href="https://unisms.apistd.com/docs/api/send">https://unisms.apistd.com/docs/api/send</a>
|
||||
*/
|
||||
@Slf4j
|
||||
public class UniSmsClientImpl implements SmsClient {
|
||||
|
||||
private static final String API_URL = "https://uni.apistd.com";
|
||||
private static final String ACTION = "sms.message.send";
|
||||
private static final String SUCCESS_CODE = "0";
|
||||
private static final String HMAC_ALGORITHM = "hmac-sha256";
|
||||
|
||||
private final UniSmsProperties config;
|
||||
|
||||
public UniSmsClientImpl(UniSmsProperties config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
|
||||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||
// build request parameters
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("to", receiver.getPhone());
|
||||
params.put("signature", config.getSignature());
|
||||
params.put("templateId", config.getTemplateId());
|
||||
|
||||
// build template data
|
||||
Map<String, String> templateData = new HashMap<>();
|
||||
String instance = alert.getCommonLabels().getOrDefault("instance", alert.getGroupKey());
|
||||
String priority = alert.getCommonLabels().getOrDefault("priority", "unknown");
|
||||
String content = alert.getCommonAnnotations().get("summary");
|
||||
content = content == null ? alert.getCommonAnnotations().get("description") : content;
|
||||
if (content == null) {
|
||||
content = alert.getCommonAnnotations().values().stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
templateData.put("instance", instance);
|
||||
templateData.put("priority", priority);
|
||||
templateData.put("content", content);
|
||||
params.put("templateData", templateData);
|
||||
|
||||
// build URL and request headers
|
||||
String url;
|
||||
if ("hmac".equalsIgnoreCase(config.getAuthMode())) {
|
||||
url = buildHmacUrl();
|
||||
} else {
|
||||
url = buildSimpleUrl();
|
||||
}
|
||||
|
||||
// send HTTP request
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
httpPost.setHeader("Content-Type", "application/json");
|
||||
httpPost.setHeader("Accept", "application/json");
|
||||
|
||||
String payload = JsonUtil.toJson(params);
|
||||
httpPost.setEntity(new StringEntity(payload, StandardCharsets.UTF_8));
|
||||
|
||||
log.info("Sending SMS request to UniSMS, payload: {}, url: {}", payload, url);
|
||||
|
||||
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
|
||||
handleResponse(response, receiver.getPhone());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send SMS via UniSMS: {}", e.getMessage());
|
||||
throw new SendMessageException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String buildSimpleUrl() {
|
||||
return String.format("%s/?action=%s&accessKeyId=%s",
|
||||
API_URL, ACTION, config.getAccessKeyId());
|
||||
}
|
||||
|
||||
private String buildHmacUrl() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
String nonce = generateNonce();
|
||||
|
||||
// build query parameters
|
||||
Map<String, String> params = new TreeMap<>();
|
||||
params.put("accessKeyId", config.getAccessKeyId());
|
||||
params.put("action", ACTION);
|
||||
params.put("algorithm", HMAC_ALGORITHM);
|
||||
params.put("nonce", nonce);
|
||||
params.put("timestamp", String.valueOf(timestamp));
|
||||
|
||||
// build sign text
|
||||
String signText = params.entrySet().stream()
|
||||
.map(entry -> entry.getKey() + "=" + entry.getValue())
|
||||
.collect(Collectors.joining("&"));
|
||||
|
||||
// calculate signature
|
||||
String signature = CryptoUtils.hmacSha256Base64(config.getAccessKeySecret(), signText);
|
||||
return String.format("%s/?action=%s&accessKeyId=%s&algorithm=%s×tamp=%d&nonce=%s&signature=%s",
|
||||
API_URL, ACTION, config.getAccessKeyId(), HMAC_ALGORITHM, timestamp, nonce, signature);
|
||||
}
|
||||
|
||||
private String generateNonce() {
|
||||
return UUID.randomUUID().toString().replace("-", "").substring(0, 16);
|
||||
}
|
||||
|
||||
private void handleResponse(CloseableHttpResponse response, String phone) throws IOException {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
String responseBody = EntityUtils.toString(response.getEntity());
|
||||
|
||||
log.info("UniSMS response status: {}, body: {}", statusCode, responseBody);
|
||||
|
||||
if (statusCode != 200) {
|
||||
throw new SendMessageException("HTTP request failed with status code: " + statusCode + ", response: " + responseBody);
|
||||
}
|
||||
|
||||
JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
|
||||
String code = jsonResponse.get("code").asText();
|
||||
if (!SUCCESS_CODE.equals(code)) {
|
||||
String message = jsonResponse.get("message").asText();
|
||||
throw new SendMessageException(code + ":" + message);
|
||||
}
|
||||
|
||||
log.info("Successfully sent SMS to phone: {}", phone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return UNISMS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkConfig() {
|
||||
if (config == null
|
||||
|| config.getAccessKeyId() == null
|
||||
|| config.getAccessKeyId().isBlank()
|
||||
|| config.getSignature() == null
|
||||
|| config.getSignature().isBlank()
|
||||
|| config.getTemplateId() == null
|
||||
|| config.getTemplateId().isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// HMAC mode requires additional check for accessKeySecret
|
||||
if ("hmac".equalsIgnoreCase(config.getAuthMode())
|
||||
&& (config.getAccessKeySecret() == null || config.getAccessKeySecret().isBlank())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* General encryption utility class
|
||||
*/
|
||||
@Slf4j
|
||||
public class CryptoUtils {
|
||||
|
||||
private static final Charset UTF8 = StandardCharsets.UTF_8;
|
||||
|
||||
private CryptoUtils() {}
|
||||
|
||||
/**
|
||||
* Calculate HMAC-SHA256 signature
|
||||
* @param key secret key
|
||||
* @param msg message to be signed
|
||||
* @return signed byte array
|
||||
*/
|
||||
public static byte[] hmac256(byte[] key, String msg) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
|
||||
mac.init(secretKeySpec);
|
||||
return mac.doFinal(msg.getBytes(UTF8));
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to calculate HMAC-SHA256: {}", e.getMessage());
|
||||
throw new RuntimeException("Failed to calculate HMAC-SHA256", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate SHA256 hash and convert to lowercase hexadecimal string
|
||||
* @param data data to be hashed
|
||||
* @return lowercase hexadecimal string
|
||||
*/
|
||||
public static String sha256Hex(String data) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
byte[] digest = md.digest(data.getBytes(UTF8));
|
||||
return DatatypeConverter.printHexBinary(digest).toLowerCase();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to calculate SHA256: {}", e.getMessage());
|
||||
throw new RuntimeException("Failed to calculate SHA256", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate HMAC-SHA256 signature and convert to Base64 string
|
||||
* @param key secret key
|
||||
* @param data data to be signed
|
||||
* @return Base64 encoded signature string
|
||||
*/
|
||||
public static String hmacSha256Base64(String key, String data) {
|
||||
byte[] hmacResult = hmac256(key.getBytes(UTF8), data);
|
||||
return Base64.getEncoder().encodeToString(hmacResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate HMAC-SHA256 signature and convert to lowercase hexadecimal string
|
||||
* @param key secret key
|
||||
* @param data data to be signed
|
||||
* @return lowercase hexadecimal string
|
||||
*/
|
||||
public static String hmacSha256Hex(String key, String data) {
|
||||
byte[] hmacResult = hmac256(key.getBytes(UTF8), data);
|
||||
return DatatypeConverter.printHexBinary(hmacResult).toLowerCase();
|
||||
}
|
||||
}
|
||||
-84
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
|
||||
/**
|
||||
* used to calculate the signature of the Tencent Cloud API.
|
||||
*/
|
||||
public class TencentCloudApiSignV3 {
|
||||
private static final Charset UTF8 = StandardCharsets.UTF_8;
|
||||
|
||||
public static String calculateAuthorization(String secretId, String secretKey,
|
||||
String service, String host, String region,
|
||||
String action, String version, String payload) throws Exception {
|
||||
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));
|
||||
|
||||
// Step 1: Construct the canonical request string
|
||||
String httpRequestMethod = "POST";
|
||||
String canonicalUri = "/";
|
||||
String canonicalQueryString = "";
|
||||
String canonicalHeaders = "content-type:application/json; charset=utf-8\n" + "host:" + host + "\n";
|
||||
String signedHeaders = "content-type;host";
|
||||
String hashedRequestPayload = sha256Hex(payload);
|
||||
String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n"
|
||||
+ canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload;
|
||||
|
||||
// Step 2: Construct the string to sign
|
||||
String algorithm = "TC3-HMAC-SHA256";
|
||||
String credentialScope = date + "/" + service + "/" + "tc3_request";
|
||||
String hashedCanonicalRequest = sha256Hex(canonicalRequest);
|
||||
String stringToSign = algorithm + "\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
|
||||
|
||||
// Step 3: Calculate the signature
|
||||
byte[] secretDate = hmac256(("TC3" + secretKey).getBytes(UTF8), date);
|
||||
byte[] secretService = hmac256(secretDate, service);
|
||||
byte[] secretSigning = hmac256(secretService, "tc3_request");
|
||||
String signature = DatatypeConverter.printHexBinary(hmac256(secretSigning, stringToSign)).toLowerCase();
|
||||
|
||||
// Step 4: Construct the Authorization header
|
||||
return algorithm + " " + "Credential=" + secretId + "/" + credentialScope + ", "
|
||||
+ "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature;
|
||||
}
|
||||
|
||||
public static byte[] hmac256(byte[] key, String msg) throws Exception {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
|
||||
mac.init(secretKeySpec);
|
||||
return mac.doFinal(msg.getBytes(UTF8));
|
||||
}
|
||||
|
||||
public static String sha256Hex(String s) throws Exception {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
byte[] d = md.digest(s.getBytes(UTF8));
|
||||
return DatatypeConverter.printHexBinary(d).toLowerCase();
|
||||
}
|
||||
}
|
||||
+4
@@ -223,7 +223,9 @@ public class KafkaCollectImpl extends AbstractCollect {
|
||||
|
||||
@Override
|
||||
public void preCheck(Metrics metrics) throws IllegalArgumentException {
|
||||
Assert.isTrue(metrics != null, "Metrics cannot be null");
|
||||
KafkaProtocol kafkaProtocol = metrics.getKclient();
|
||||
|
||||
// Ensure that metrics and kafkaProtocol are not null
|
||||
Assert.isTrue(metrics != null && kafkaProtocol != null, "Kafka collect must have kafkaProtocol params");
|
||||
// Ensure that host and port are not empty
|
||||
@@ -265,6 +267,8 @@ public class KafkaCollectImpl extends AbstractCollect {
|
||||
break;
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("Kafka collect error: " + e.getMessage());
|
||||
log.error("Kafka collect error", e);
|
||||
}
|
||||
}
|
||||
|
||||
+8
-9
@@ -42,7 +42,7 @@ public class KafkaCollectTest {
|
||||
@Test
|
||||
void preCheck() {
|
||||
// metrics is null
|
||||
assertThrows(NullPointerException.class, () -> {
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
collect.preCheck(null);
|
||||
});
|
||||
|
||||
@@ -51,22 +51,21 @@ public class KafkaCollectTest {
|
||||
collect.preCheck(Metrics.builder().build());
|
||||
});
|
||||
|
||||
KafkaProtocol kafka = new KafkaProtocol();
|
||||
Metrics metric = Metrics.builder().kclient(kafka).build();
|
||||
// kafka srv host is null
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
KafkaProtocol kafka = new KafkaProtocol();
|
||||
collect.preCheck(Metrics.builder().kclient(kafka).build());
|
||||
collect.preCheck(metric);
|
||||
});
|
||||
|
||||
// kafka port is null
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
KafkaProtocol kafka = KafkaProtocol.builder().host("127.0.0.1").build();
|
||||
collect.preCheck(Metrics.builder().kclient(kafka).build());
|
||||
kafka.setHost("127.0.0.1");
|
||||
collect.preCheck(metric);
|
||||
});
|
||||
|
||||
// no exception throw
|
||||
assertDoesNotThrow(() -> {
|
||||
KafkaProtocol kafka = KafkaProtocol.builder().host("127.0.0.1").port("9092").build();
|
||||
collect.preCheck(Metrics.builder().kclient(kafka).build());
|
||||
kafka.setPort("9092");
|
||||
collect.preCheck(metric);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ package org.apache.hertzbeat.common.constants;
|
||||
public interface SmsConstants {
|
||||
// Tencent cloud SMS
|
||||
String TENCENT = "tencent";
|
||||
|
||||
// Alibaba Cloud SMS
|
||||
String ALIBABA = "alibaba";
|
||||
|
||||
// UniSMS
|
||||
String UNISMS = "unisms";
|
||||
}
|
||||
|
||||
+81
@@ -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.hertzbeat.common.entity.dto.query;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Metric History Range Query Data
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Metric Query Data")
|
||||
public class MetricQueryData {
|
||||
|
||||
@Schema(title = "Metric Schema")
|
||||
private MetricSchema schema;
|
||||
|
||||
@Schema(title = "metrics row values, first is the timestamp-ts", example = "[[29,32,44],[32,34,true]]")
|
||||
private List<List<Object>> values;
|
||||
|
||||
/**
|
||||
* Metric Schema
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public static final class MetricSchema {
|
||||
|
||||
@Schema(title = "Metrics Field")
|
||||
private List<MetricField> fields;
|
||||
|
||||
@Schema(title = "Meta Information")
|
||||
private Map<String, String> meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metric Field
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public static final class MetricField {
|
||||
|
||||
@Schema(title = "Metric Field Name")
|
||||
private String name;
|
||||
|
||||
@Schema(title = "Field Type: number, string, time, bool")
|
||||
private String type;
|
||||
|
||||
@Schema(title = "Field Unit: %, Mb, Kbps etc.")
|
||||
private String unit;
|
||||
|
||||
@Schema(title = "Whether is a label")
|
||||
private Boolean label;
|
||||
}
|
||||
}
|
||||
+9
-4
@@ -26,10 +26,15 @@ import lombok.Data;
|
||||
@Data
|
||||
public class SmsAlibabaConfig {
|
||||
|
||||
@NotBlank(message = "SecretId cannot be empty")
|
||||
private String secretId;
|
||||
@NotBlank(message = "AccessKeyId cannot be empty")
|
||||
private String accessKeyId;
|
||||
|
||||
@NotBlank(message = "SecretKey cannot be empty")
|
||||
private String secretKey;
|
||||
@NotBlank(message = "AccessKeySecret cannot be empty")
|
||||
private String accessKeySecret;
|
||||
|
||||
@NotBlank(message = "SignName cannot be empty")
|
||||
private String signName;
|
||||
|
||||
@NotBlank(message = "TemplateCode cannot be null")
|
||||
private String templateCode;
|
||||
}
|
||||
|
||||
+2
@@ -37,5 +37,7 @@ public class SmsNoticeSender {
|
||||
|
||||
private SmsAlibabaConfig alibaba;
|
||||
|
||||
private SmsUniSmsConfig unisms;
|
||||
|
||||
private boolean enable = true;
|
||||
}
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.manager.pojo.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.logging.log4j.core.config.plugins.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* UniSMS configuration
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SmsUniSmsConfig {
|
||||
/**
|
||||
* UniSMS access key id
|
||||
*/
|
||||
@NotBlank(message = "accessKeyId cannot be empty")
|
||||
private String accessKeyId;
|
||||
|
||||
/**
|
||||
* UniSMS access key secret, required for HMAC mode
|
||||
*/
|
||||
private String accessKeySecret;
|
||||
|
||||
/**
|
||||
* SMS signature
|
||||
*/
|
||||
@NotBlank(message = "signature cannot be null")
|
||||
private String signature;
|
||||
|
||||
/**
|
||||
* SMS template ID
|
||||
*/
|
||||
@NotBlank(message = "templateId cannot be null")
|
||||
private String templateId;
|
||||
|
||||
/**
|
||||
* Authentication mode: simple or hmac, default is simple
|
||||
*/
|
||||
@NotBlank(message = "authMode cannot be null")
|
||||
private String authMode = "simple";
|
||||
}
|
||||
@@ -204,7 +204,7 @@ alerter:
|
||||
inhibit:
|
||||
ttl: 14400000
|
||||
sms:
|
||||
enable: true
|
||||
enable: false
|
||||
type: tencent
|
||||
tencent:
|
||||
secret-id:
|
||||
@@ -213,7 +213,18 @@ alerter:
|
||||
sign-name:
|
||||
template-id:
|
||||
alibaba:
|
||||
app-id:
|
||||
access-key-id:
|
||||
access-key-secret:
|
||||
sign-name:
|
||||
template-code:
|
||||
unisms:
|
||||
# auth-mode: simple or hmac
|
||||
auth-mode: simple
|
||||
access-key-id: YOUR_ACCESS_KEY_ID
|
||||
# hmac mode need to fill in access-key-secret
|
||||
access-key-secret: YOUR_ACCESS_KEY_SECRET
|
||||
signature: YOUR_SMS_SIGNATURE
|
||||
template-id: YOUR_TEMPLATE_ID
|
||||
|
||||
scheduler:
|
||||
server:
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
# 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.
|
||||
|
||||
|
||||
# The monitoring type category:service-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
|
||||
category: llm
|
||||
# The monitoring type eg: linux windows tomcat mysql aws...
|
||||
app: deepseek
|
||||
# The monitoring i18n name
|
||||
name:
|
||||
zh-CN: Deepseek
|
||||
en-US: Deepseek
|
||||
# The description and help of this monitoring type
|
||||
help:
|
||||
zh-CN: Hertzbeat 对 Deepseek Api进行测量监控。<br>您可以点击 “<i>新建 Deepseek</i>” 并进行配置,或者选择“<i>更多操作</i>”,导入已有配置。
|
||||
en-US: Hertzbeat monitors Deepseek Api. You could click the "<i>New Deepseek</i>" button and proceed with the configuration or import an existing setup through the "<i>More Actions</i>" menu.
|
||||
zh-TW: Hertzbeat對Deepseek Api進行量測監控。<br>您可以點擊“<i>Deepseek</i>”並進行配寘,或者選擇“<i>更多操作</i>”,導入已有配寘。
|
||||
helpLink:
|
||||
zh-CN: https://hertzbeat.apache.org/zh-cn/docs/help/deepseek
|
||||
en-US: https://hertzbeat.apache.org/docs/help/deepseek
|
||||
# Input params define for monitoring(render web ui by the definition)
|
||||
params:
|
||||
# field-param field key
|
||||
- field: token
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 会话密钥
|
||||
en-US: Session Key
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: text
|
||||
# required-true or false
|
||||
required: true
|
||||
|
||||
# collect metrics config list
|
||||
metrics:
|
||||
# metrics - auth
|
||||
- name: billing
|
||||
i18n:
|
||||
zh-CN: 计费
|
||||
en-US: Billing
|
||||
# metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel
|
||||
# priority 0's metrics is availability metrics, it will be scheduled first, only availability metrics collect success will the scheduling continue
|
||||
priority: 0
|
||||
# collect metrics content
|
||||
fields:
|
||||
# field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field
|
||||
- field: currency
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 货币
|
||||
en-US: Currency
|
||||
- field: total_balance
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 可用余额
|
||||
en-US: Available Balance
|
||||
- field: granted_balance
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 未过期的赠金余额
|
||||
en-US: Unexpired Gift Balance
|
||||
- field: topped_up_balance
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 充值的余额
|
||||
en-US: Topped Up Balance
|
||||
# the protocol used for monitoring, eg: sql, ssh, http, telnet, wmi, snmp, sdk
|
||||
protocol: http
|
||||
# the config content when protocol is http
|
||||
http:
|
||||
# http host: ipv4 ipv6 domain
|
||||
host: api.deepseek.com
|
||||
# http port
|
||||
port: 443
|
||||
# http url
|
||||
url: /user/balance
|
||||
# http method: GET POST PUT DELETE PATCH
|
||||
method: GET
|
||||
# if enabled https
|
||||
ssl: true
|
||||
# http auth
|
||||
authorization:
|
||||
# http auth type: Basic Auth, Digest Auth, Bearer Token
|
||||
type: Bearer Token
|
||||
bearerTokenToken: ^_^token^_^
|
||||
# http response data parse type: default-hertzbeat rule, jsonpath-jsonpath script, website-for website monitoring, prometheus-prometheus exporter rule
|
||||
parseType: jsonPath
|
||||
parseScript: '$.balance_infos.*'
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.warehouse.controller;
|
||||
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.util.List;
|
||||
import org.apache.hertzbeat.common.entity.dto.Message;
|
||||
import org.apache.hertzbeat.common.entity.dto.query.MetricQueryData;
|
||||
import org.apache.hertzbeat.warehouse.service.MetricsDataQueryService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Metrics Data Query API
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(produces = {APPLICATION_JSON_VALUE})
|
||||
@Tag(name = "Metrics Data Query API")
|
||||
public class MetricsDataQueryController {
|
||||
|
||||
|
||||
@Autowired
|
||||
private MetricsDataQueryService queryService;
|
||||
|
||||
|
||||
@GetMapping("/api/warehouse/query")
|
||||
@Operation(summary = "Query Real Time Metrics Data")
|
||||
public ResponseEntity<Message<List<MetricQueryData>>> queryMetricsData(
|
||||
@Parameter(description = "Query PromQL expr list", example = "cpu")
|
||||
@RequestParam List<String> queries,
|
||||
@Parameter(description = "Query timestamp", example = "1725854804451")
|
||||
@RequestParam long time) {
|
||||
return ResponseEntity.ok(Message.success(queryService.query(queries, time)));
|
||||
}
|
||||
|
||||
@GetMapping("/api/warehouse/query/range")
|
||||
@Operation(summary = "Query Range Metrics Data")
|
||||
public ResponseEntity<Message<List<MetricQueryData>>> queryMetricsDataRange(
|
||||
@Parameter(description = "Query PromQL expr list", example = "cpu")
|
||||
@RequestParam List<String> queries,
|
||||
@Parameter(description = "Query start timestamp", example = "1725854804451")
|
||||
@RequestParam long start,
|
||||
@Parameter(description = "Query end timestamp", example = "1733630804452")
|
||||
@RequestParam long end,
|
||||
@Parameter(description = "Query step", example = "4m")
|
||||
@RequestParam String step
|
||||
) {
|
||||
return ResponseEntity.ok(Message.success(queryService.queryRange(queries, start, end, step)));
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.warehouse.service;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.hertzbeat.common.entity.dto.query.MetricQueryData;
|
||||
|
||||
/**
|
||||
* metrics data query service
|
||||
*/
|
||||
public interface MetricsDataQueryService {
|
||||
|
||||
/**
|
||||
* Query metrics data
|
||||
* @param queries query expr
|
||||
* @param time time
|
||||
* @return data
|
||||
*/
|
||||
List<MetricQueryData> query(List<String> queries, long time);
|
||||
|
||||
/**
|
||||
* Query metrics data range
|
||||
* @param queries query expr
|
||||
* @param start start
|
||||
* @param end end
|
||||
* @param step step
|
||||
* @return data
|
||||
*/
|
||||
List<MetricQueryData> queryRange(List<String> queries, long start, long end, String step);
|
||||
}
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* 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.warehouse.service.impl.greptime;
|
||||
|
||||
import io.greptime.GreptimeDB;
|
||||
import io.greptime.models.AuthInfo;
|
||||
import io.greptime.models.DataType;
|
||||
import io.greptime.models.Err;
|
||||
import io.greptime.models.Result;
|
||||
import io.greptime.models.Table;
|
||||
import io.greptime.models.TableSchema;
|
||||
import io.greptime.models.WriteOk;
|
||||
import io.greptime.options.GreptimeOptions;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.time.temporal.TemporalAmount;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.dto.Value;
|
||||
import org.apache.hertzbeat.common.entity.dto.query.MetricQueryData;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.hertzbeat.common.util.TimePeriodUtil;
|
||||
import org.apache.hertzbeat.warehouse.service.MetricsDataQueryService;
|
||||
import org.apache.hertzbeat.warehouse.store.history.greptime.GreptimeProperties;
|
||||
import org.apache.hertzbeat.warehouse.store.history.vm.PromQlQueryContent;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* GreptimeDB data storage, only supports GreptimeDB version >= v0.5
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(prefix = "warehouse.store.greptime", name = "enabled", havingValue = "true")
|
||||
@Slf4j
|
||||
public class GreptimeDbStorage implements MetricsDataQueryService, DisposableBean {
|
||||
|
||||
private static final String BASIC = "Basic";
|
||||
private static final String QUERY_RANGE_PATH = "/v1/prometheus/api/v1/query_range";
|
||||
private static final String LABEL_KEY_NAME = "__name__";
|
||||
private static final String LABEL_KEY_FIELD = "__field__";
|
||||
private static final String LABEL_KEY_INSTANCE = "instance";
|
||||
private static final String SPILT = "_";
|
||||
|
||||
private GreptimeDB greptimeDb;
|
||||
|
||||
private final GreptimeProperties greptimeProperties;
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
public GreptimeDbStorage(GreptimeProperties greptimeProperties, RestTemplate restTemplate) {
|
||||
if (greptimeProperties == null) {
|
||||
log.error("init error, please config Warehouse GreptimeDB props in application.yml");
|
||||
throw new IllegalArgumentException("please config Warehouse GreptimeDB props");
|
||||
}
|
||||
this.restTemplate = restTemplate;
|
||||
this.greptimeProperties = greptimeProperties;
|
||||
initGreptimeDbClient(greptimeProperties);
|
||||
}
|
||||
|
||||
private void initGreptimeDbClient(GreptimeProperties greptimeProperties) {
|
||||
String endpoints = greptimeProperties.grpcEndpoints();
|
||||
GreptimeOptions opts = GreptimeOptions.newBuilder(endpoints.split(","), greptimeProperties.database())
|
||||
.writeMaxRetries(3)
|
||||
.authInfo(new AuthInfo(greptimeProperties.username(), greptimeProperties.password()))
|
||||
.routeTableRefreshPeriodSeconds(30)
|
||||
.build();
|
||||
this.greptimeDb = GreptimeDB.create(opts);
|
||||
}
|
||||
|
||||
public void saveData(CollectRep.MetricsData metricsData) {
|
||||
if (metricsData.getCode() != CollectRep.Code.SUCCESS) {
|
||||
return;
|
||||
}
|
||||
if (metricsData.getValuesList().isEmpty()) {
|
||||
log.info("[warehouse greptime] flush metrics data {} {} is null, ignore.", metricsData.getId(), metricsData.getMetrics());
|
||||
return;
|
||||
}
|
||||
String monitorId = String.valueOf(metricsData.getId());
|
||||
String tableName = getTableName(metricsData.getApp(), metricsData.getMetrics());
|
||||
TableSchema.Builder tableSchemaBuilder = TableSchema.newBuilder(tableName);
|
||||
|
||||
tableSchemaBuilder.addTag("instance", DataType.String)
|
||||
.addTimestamp("ts", DataType.TimestampMillisecond);
|
||||
|
||||
List<CollectRep.Field> fieldsList = metricsData.getFieldsList();
|
||||
for (CollectRep.Field field : fieldsList) {
|
||||
// handle field type
|
||||
if (field.getLabel()) {
|
||||
tableSchemaBuilder.addTag(field.getName(), DataType.String);
|
||||
} else {
|
||||
if (field.getType() == CommonConstants.TYPE_NUMBER) {
|
||||
tableSchemaBuilder.addField(field.getName(), DataType.Float64);
|
||||
} else if (field.getType() == CommonConstants.TYPE_STRING) {
|
||||
tableSchemaBuilder.addField(field.getName(), DataType.String);
|
||||
}
|
||||
}
|
||||
}
|
||||
Table table = Table.from(tableSchemaBuilder.build());
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
Object[] values = new Object[2 + fieldsList.size()];
|
||||
values[0] = monitorId;
|
||||
values[1] = now;
|
||||
for (CollectRep.ValueRow valueRow : metricsData.getValuesList()) {
|
||||
for (int i = 0; i < fieldsList.size(); i++) {
|
||||
if (!CommonConstants.NULL_VALUE.equals(valueRow.getColumns(i))) {
|
||||
CollectRep.Field field = fieldsList.get(i);
|
||||
if (field.getLabel()) {
|
||||
values[2 + i] = valueRow.getColumns(i);
|
||||
} else {
|
||||
if (field.getType() == CommonConstants.TYPE_NUMBER) {
|
||||
values[2 + i] = Double.parseDouble(valueRow.getColumns(i));
|
||||
} else if (field.getType() == CommonConstants.TYPE_STRING) {
|
||||
values[2 + i] = valueRow.getColumns(i);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
values[2 + i] = null;
|
||||
}
|
||||
}
|
||||
table.addRow(values);
|
||||
}
|
||||
CompletableFuture<Result<WriteOk, Err>> writeFuture = greptimeDb.write(table);
|
||||
try {
|
||||
Result<WriteOk, Err> result = writeFuture.get(10, TimeUnit.SECONDS);
|
||||
if (result.isOk()) {
|
||||
log.debug("[warehouse greptime]-Write successful");
|
||||
} else {
|
||||
log.warn("[warehouse greptime]--Write failed: {}", result.getErr());
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
log.error("[warehouse greptime]--Error occurred: {}", throwable.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[warehouse greptime]--Error: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, List<Value>> getHistoryMetricData(Long monitorId, String app, String metrics, String metric,
|
||||
String label, String history) {
|
||||
String name = getTableName(app, metrics);
|
||||
String timeSeriesSelector = LABEL_KEY_NAME + "=\"" + name + "\""
|
||||
+ "," + LABEL_KEY_INSTANCE + "=\"" + monitorId + "\"";
|
||||
if (!CommonConstants.PROMETHEUS.equals(app)) {
|
||||
timeSeriesSelector = timeSeriesSelector + "," + LABEL_KEY_FIELD + "=\"" + metric + "\"";
|
||||
}
|
||||
Map<String, List<Value>> instanceValuesMap = new HashMap<>(8);
|
||||
try {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
|
||||
if (StringUtils.hasText(greptimeProperties.username())
|
||||
&& StringUtils.hasText(greptimeProperties.password())) {
|
||||
String authStr = greptimeProperties.username() + ":" + greptimeProperties.password();
|
||||
String encodedAuth = new String(Base64.encodeBase64(authStr.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
|
||||
headers.add(HttpHeaders.AUTHORIZATION, BASIC + " " + encodedAuth);
|
||||
}
|
||||
Instant now = Instant.now();
|
||||
long start;
|
||||
try {
|
||||
if (NumberUtils.isParsable(history)) {
|
||||
start = NumberUtils.toLong(history);
|
||||
start = (ZonedDateTime.now().toEpochSecond() - start);
|
||||
} else {
|
||||
TemporalAmount temporalAmount = TimePeriodUtil.parseTokenTime(history);
|
||||
assert temporalAmount != null;
|
||||
Instant dateTime = now.minus(temporalAmount);
|
||||
start = dateTime.getEpochSecond();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("history time error: {}. use default: 6h", e.getMessage());
|
||||
start = now.minus(6, ChronoUnit.HOURS).getEpochSecond();
|
||||
}
|
||||
long end = now.getEpochSecond();
|
||||
String step = "60s";
|
||||
if (end - start < Duration.ofDays(7).getSeconds() && end - start > Duration.ofDays(1).getSeconds()) {
|
||||
step = "1h";
|
||||
} else if (end - start >= Duration.ofDays(7).getSeconds()) {
|
||||
step = "4h";
|
||||
}
|
||||
HttpEntity<Void> httpEntity = new HttpEntity<>(headers);
|
||||
URI uri = UriComponentsBuilder.fromHttpUrl(greptimeProperties.httpEndpoint() + QUERY_RANGE_PATH)
|
||||
.queryParam(URLEncoder.encode("query", StandardCharsets.UTF_8), URLEncoder.encode("{" + timeSeriesSelector + "}", StandardCharsets.UTF_8))
|
||||
.queryParam("start", start)
|
||||
.queryParam("end", end)
|
||||
.queryParam("step", step)
|
||||
.build(true).toUri();
|
||||
ResponseEntity<PromQlQueryContent> responseEntity = restTemplate.exchange(uri,
|
||||
HttpMethod.GET, httpEntity, PromQlQueryContent.class);
|
||||
if (responseEntity.getStatusCode().is2xxSuccessful()) {
|
||||
log.debug("query metrics data from victoria-metrics success. {}", uri);
|
||||
if (responseEntity.getBody() != null && responseEntity.getBody().getData() != null
|
||||
&& responseEntity.getBody().getData().getResult() != null) {
|
||||
List<PromQlQueryContent.ContentData.Content> contents = responseEntity.getBody().getData().getResult();
|
||||
for (PromQlQueryContent.ContentData.Content content : contents) {
|
||||
Map<String, String> labels = content.getMetric();
|
||||
labels.remove(LABEL_KEY_NAME);
|
||||
labels.remove(LABEL_KEY_INSTANCE);
|
||||
String labelStr = JsonUtil.toJson(labels);
|
||||
if (content.getValues() != null && !content.getValues().isEmpty()) {
|
||||
List<Value> valueList = instanceValuesMap.computeIfAbsent(labelStr, k -> new LinkedList<>());
|
||||
for (Object[] valueArr : content.getValues()) {
|
||||
long timestamp = ((Double) valueArr[0]).longValue();
|
||||
String value = new BigDecimal(String.valueOf(valueArr[1])).setScale(4, RoundingMode.HALF_UP).stripTrailingZeros().toPlainString();
|
||||
// read timestamp here is s unit
|
||||
valueList.add(new Value(value, timestamp * 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.error("query metrics data from greptime failed. {}", responseEntity);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
return instanceValuesMap;
|
||||
}
|
||||
|
||||
private String getTableName(String app, String metrics) {
|
||||
return app + SPILT + metrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (this.greptimeDb != null) {
|
||||
this.greptimeDb.shutdownGracefully();
|
||||
this.greptimeDb = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetricQueryData> query(List<String> queries, long time) {
|
||||
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetricQueryData> queryRange(List<String> queries, long start, long end, String step) {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ Hi HertzBeat PPMC,
|
||||
|
||||
This is a formal vote about inviting ${NEW_COMMITTER_NAME} as our community new committer.
|
||||
|
||||
Work list: https://github.com/apache/hertzbeat/commits?author=${NEW_COMMITTER_NAME}
|
||||
Work list: https://github.com/apache/hertzbeat/commits?author=xxx
|
||||
|
||||
Here is the list of ${NEW_COMMITTER_NAME}'s contributions in HertzBeat Community:
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
id: deepseek
|
||||
title: Monitoring Deepseek Account Status
|
||||
sidebar_label: Deepseek Account Status
|
||||
keywords: [Open Source Monitoring System, Open Source Network Monitoring, Deepseek Account Monitoring]
|
||||
---
|
||||
|
||||
### Preparation
|
||||
|
||||
#### Obtain Session Key
|
||||
|
||||
Log in to the Deepseek backend and visit the `https://platform.deepseek.com/api_keys` page to obtain the session key.
|
||||
|
||||
### Configuration Parameters
|
||||
|
||||
| Parameter Name | Parameter Description |
|
||||
| ------------- | --------------------- |
|
||||
| Monitoring Host | Enter `api.deepseek.com` here. |
|
||||
| Task Name | The name that identifies this monitoring task, which must be unique. |
|
||||
| Session Key | The session key obtained in the preparation step. |
|
||||
| Collector | Configure which collector is used to schedule data collection for this monitoring. |
|
||||
| Monitoring Interval | The interval for periodically collecting data, in seconds. The minimum interval that can be set is 30 seconds. |
|
||||
| Bound Tags | Tags for categorizing and managing monitoring resources. |
|
||||
| Description/Remarks | Additional remarks to identify and describe this monitoring. Users can add notes here. |
|
||||
|
||||
### Collection Metrics
|
||||
|
||||
#### Metric Set: Billing
|
||||
|
||||
| Metric Name | Metric Unit | Metric Description |
|
||||
| ---------- | ---------- | ----------------- |
|
||||
| Currency | None | Currency, either RMB or USD. |
|
||||
| Available Balance | RMB/USD | Total available balance, including bonus and recharge balance. |
|
||||
| Unexpired Bonus Balance | RMB/USD | Unexpired bonus balance. |
|
||||
| Recharge Balance | RMB/USD | Recharge balance. |
|
||||
@@ -8,52 +8,76 @@ This describes how to configure the SMS server, the number of built-in availabil
|
||||
|
||||
**Configuration file `application.yml` of `hertzbeat`**
|
||||
|
||||
### Configure the configuration file of HertzBeat
|
||||
Configuring the HertzBeat configuration file:
|
||||
|
||||
Modify the configuration file located at `hertzbeat/config/application.yml`
|
||||
Note ⚠️The docker container method needs to mount the application.yml file to the local host
|
||||
The installation package can be decompressed and modified in `hertzbeat/config/application.yml`
|
||||
- Modify the configuration file located at `hertzbeat/config/application.yml`
|
||||
- **Docker Deployment:** ⚠️ When using a Docker container, the `application.yml` file must be mounted to the host machine
|
||||
- **Installation Package Deployment:** Extract the package and modify the configuration file located at `hertzbeat/config/application.yml`
|
||||
|
||||
1. Configure the SMS sending server
|
||||
## 1. Configuring the SMS Sending Service
|
||||
|
||||
> Only when your own SMS server is successfully configured, the alarm SMS triggered in the monitoring tool will be sent normally.
|
||||
Only when you successfully configure your own SMS service will the alert SMS triggered within the monitoring system be sent correctly.
|
||||
HertzBeat provides two ways to configure the SMS service: modifying the `application.yml` configuration file directly or configuring it through the HertzBeat frontend interface (Settings > Message Server Setting).
|
||||
|
||||
Add the following Tencent platform SMS server configuration in `application.yml` (parameters need to be replaced with your SMS server configuration)
|
||||
> ⚠️ Note: Only one method can be effective at a time. If both methods are configured and enabled, HertzBeat will prioritize the SMS service configured in the frontend interface.
|
||||
|
||||
```yaml
|
||||
common:
|
||||
sms:
|
||||
tencent:
|
||||
secret-id: AKIDbQ4VhdMr89wDedFrIcgU2PaaMvOuBCzY
|
||||
secret-key: PaXGl0ziY9UcWFjUyiFlCPMr77rLkJYlyA
|
||||
app-id: 1435441637
|
||||
sign-name: XX Technology
|
||||
template-id: 1343434
|
||||
```
|
||||
### 1.1 Tencent Cloud SMS Configuration
|
||||
|
||||
2. Configure alarm custom parameters
|
||||
Add the following Tencent Cloud SMS server configuration to `application.yml` (replace parameters with your own SMS server configuration):
|
||||
|
||||
```yaml
|
||||
alerter:
|
||||
# Custom console address
|
||||
console-url: https://console.tancloud.io
|
||||
```
|
||||
```yaml
|
||||
alerter:
|
||||
sms:
|
||||
enable: true # Whether to enable
|
||||
type: tencent # SMS provider type, supports "tencent"
|
||||
tencent: # Tencent Cloud SMS configuration
|
||||
secret-id: AKIDbQ4VhdMr89wDedFrIcgU2PaaMvOuBCzY
|
||||
secret-key: PaXGl0ziY9UcWFjUyiFlCPMr77rLkJYlyA
|
||||
app-id: 1435441637
|
||||
sign-name: HertzBeat
|
||||
template-id: 1343434
|
||||
```
|
||||
|
||||
3. Use external redis instead of memory to store real-time metric data
|
||||
1. Create a signature (sign-name) in Tencent Cloud SMS
|
||||

|
||||
|
||||
> By default, the real-time data of our metrics is stored in memory, which can be configured as follows to use redis instead of memory storage.
|
||||
2. Create a message template (template-id) in Tencent Cloud SMS
|
||||
|
||||
Note ⚠️ `memory.enabled: false, redis.enabled: true`
|
||||
```text
|
||||
Monitor: {1}, Alert Level: {2}. Content: {3}
|
||||
```
|
||||
|
||||
```yaml
|
||||
warehouse:
|
||||
store:
|
||||
memory:
|
||||
enabled: false
|
||||
init-size: 1024
|
||||
redis:
|
||||
enabled: true
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
password: 123456
|
||||
```
|
||||

|
||||
|
||||
3. Create an application (app-id) in Tencent Cloud SMS
|
||||

|
||||
|
||||
4. Obtain Tencent Cloud Access Management credentials (secret-id, secret-key)
|
||||

|
||||
|
||||
## 2. Configuring Custom Alert Parameters
|
||||
|
||||
```yaml
|
||||
alerter:
|
||||
# Custom console URL
|
||||
console-url: https://console.tancloud.io
|
||||
```
|
||||
|
||||
## 3. Using an External Redis Instead of In-Memory Storage for Real-Time Metric Data
|
||||
|
||||
> By default, real-time metric data is stored in memory. You can configure Redis as a replacement using the settings below.
|
||||
|
||||
⚠️ Note: Set `memory.enabled: false, redis.enabled: true`
|
||||
|
||||
```yaml
|
||||
warehouse:
|
||||
store:
|
||||
memory:
|
||||
enabled: false
|
||||
init-size: 1024
|
||||
redis:
|
||||
enabled: true
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
password: 123456
|
||||
```
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ Hi HertzBeat PPMC,
|
||||
|
||||
This is a formal vote about inviting ${NEW_COMMITTER_NAME} as our community new committer.
|
||||
|
||||
Work list: https://github.com/apache/hertzbeat/commits?author=${NEW_COMMITTER_NAME}
|
||||
Work list: https://github.com/apache/hertzbeat/commits?author=xxx
|
||||
|
||||
Here is the list of ${NEW_COMMITTER_NAME}'s contributions in HertzBeat Community:
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
id: deepseek
|
||||
title: 监控:Deepseek 账户情况
|
||||
sidebar_label: Deepseek 账户情况
|
||||
keywords: [开源监控系统, 开源网络监控, Deepseek账户监控]
|
||||
---
|
||||
|
||||
### 准备工作
|
||||
|
||||
#### 获取会话密钥
|
||||
|
||||
登录 Deepseek 后台,访问 `https://platform.deepseek.com/api_keys` 页面,获取会话密钥。
|
||||
|
||||
### 配置参数
|
||||
|
||||
| 参数名称 | 参数帮助描述 |
|
||||
|-------|---------------------------------|
|
||||
| 监控Host | 此处填写 api.deepseek.com 。 |
|
||||
| 任务名称 | 标识此监控的名称,名称需要保证唯一性。 |
|
||||
| 会话密钥 | 即准备工作中获取的会话密钥。 |
|
||||
| 采集器 | 配置此监控使用哪台采集器调度采集。 |
|
||||
| 监控周期 | 监控周期性采集数据间隔时间,单位秒,可设置的最小间隔为30秒。 |
|
||||
| 绑定标签 | 对监控资源的分类管理标签。 |
|
||||
| 描述备注 | 更多标识和描述此监控的备注信息,用户可以在这里备注信息。 |
|
||||
|
||||
### 采集指标
|
||||
|
||||
#### 指标集合:计费
|
||||
|
||||
| 指标名称 | 指标单位 | 指标帮助描述 |
|
||||
|---------|--------|-----------|
|
||||
| 货币 | 无 | 货币,人民币或美元 |
|
||||
| 可用余额 | 人民币/美元 | 总的可用余额,包括赠金和充值余额 |
|
||||
| 未过期的赠金余额 | 人民币/美元 | 未过期的赠金余额 |
|
||||
| 充值的余额 | 人民币/美元 | 充值余额 |
|
||||
@@ -23,15 +23,15 @@ keywords: [开源监控系统, 开源网络监控, OpenAI账户监控]
|
||||
|
||||
### 配置参数
|
||||
|
||||
| 参数名称 | 参数帮助描述 |
|
||||
|:-------|---------------------------------|---|
|
||||
| 监控Host | 此处填写 api.openai.com 。 |
|
||||
| 任务名称 | 标识此监控的名称,名称需要保证唯一性。 | |
|
||||
| 会话密钥 | 即准备工作中获取的会话密钥。 | |
|
||||
| 采集器 | 配置此监控使用哪台采集器调度采集。 |
|
||||
| 参数名称 | 参数帮助描述 |
|
||||
|----------|--------------------------------|
|
||||
| 监控Host | 此处填写 api.openai.com 。 |
|
||||
| 任务名称 | 标识此监控的名称,名称需要保证唯一性。 |
|
||||
| 会话密钥 | 即准备工作中获取的会话密钥。 |
|
||||
| 采集器 | 配置此监控使用哪台采集器调度采集。 |
|
||||
| 监控周期 | 监控周期性采集数据间隔时间,单位秒,可设置的最小间隔为30秒。 |
|
||||
| 绑定标签 | 对监控资源的分类管理标签。 |
|
||||
| 描述备注 | 更多标识和描述此监控的备注信息,用户可以在这里备注信息。 |
|
||||
| 绑定标签 | 对监控资源的分类管理标签。 |
|
||||
| 描述备注 | 更多标识和描述此监控的备注信息,用户可以在这里备注信息。 |
|
||||
|
||||
### 采集指标
|
||||
|
||||
|
||||
@@ -4,73 +4,79 @@ title: 常见参数配置
|
||||
sidebar_label: 常见参数配置
|
||||
---
|
||||
|
||||
这里描述了如果配置短信服务器,内置可用性告警触发次数等。
|
||||
这里描述了如何配置短信服务,内置可用性告警触发次数等。
|
||||
|
||||
**`hertzbeat`的配置文件`application.yml`**
|
||||
|
||||
### 配置HertzBeat的配置文件
|
||||
配置HertzBeat的配置文件
|
||||
|
||||
修改位于 `hertzbeat/config/application.yml` 的配置文件
|
||||
注意⚠️docker容器方式需要将application.yml文件挂载到主机本地
|
||||
安装包方式解压修改位于 `hertzbeat/config/application.yml` 即可
|
||||
- 修改位于 `hertzbeat/config/application.yml` 的配置文件
|
||||
- **Docker部署:** ⚠️docker容器方式需要将 `application.yml` 文件挂载到主机本地
|
||||
- **安装包方式:** 解压修改位于 `hertzbeat/config/application.yml` 的配置文件即可
|
||||
|
||||
1. 配置短信发送服务器
|
||||
## 1. 配置短信发送服务
|
||||
|
||||
> 只有成功配置了您自己的短信服务器,监控系统内触发的告警短信才会正常发送。
|
||||
只有成功配置了您自己的短信服务,监控系统内触发的告警短信才会正常发送。
|
||||
hertzbeat有两种方式配置短信服务,一种是直接修改`application.yml`配置文件,另一种是通过hertzbeat前端界面(系统设置 > 消息服务配置)配置。
|
||||
> 注意⚠️:两种方式配置的短信服务只能选择一种生效,当两种方式都配置并且开启时,hertzbeat将会优先使用前端界面配置的短信服务。
|
||||
|
||||
在`application.yml`新增如下腾讯平台短信服务器配置(参数需替换为您的短信服务器配置)
|
||||
### 1.1 腾讯云短信配置
|
||||
|
||||
```yaml
|
||||
common:
|
||||
sms:
|
||||
tencent:
|
||||
secret-id: AKIDbQ4VhdMr89wDedFrIcgU2PaaMvOuBCzY
|
||||
secret-key: PaXGl0ziY9UcWFjUyiFlCPMr77rLkJYlyA
|
||||
app-id: 1435441637
|
||||
sign-name: 赫兹跳动
|
||||
template-id: 1343434
|
||||
```
|
||||
在`application.yml`新增如下腾讯平台短信服务器配置(参数需替换为您的短信服务器配置)
|
||||
|
||||
1.1 腾讯云短信创建签名(sign-name)
|
||||

|
||||
```yaml
|
||||
alerter:
|
||||
sms:
|
||||
enable: true # 是否启用
|
||||
type: tencent # 短信服务商类型,支持tencent、
|
||||
tencent: # 腾讯云短信配置
|
||||
secret-id: AKIDbQ4VhdMr89wDedFrIcgU2PaaMvOuBCzY
|
||||
secret-key: PaXGl0ziY9UcWFjUyiFlCPMr77rLkJYlyA
|
||||
app-id: 1435441637
|
||||
sign-name: 赫兹跳动
|
||||
template-id: 1343434
|
||||
```
|
||||
|
||||
1.2 腾讯云短信创建正文模板(template-id)
|
||||
1. 腾讯云短信创建签名(sign-name)
|
||||

|
||||
|
||||
```text
|
||||
监控:{1},告警级别:{2}。内容:{3}
|
||||
```
|
||||
2. 腾讯云短信创建正文模板(template-id)
|
||||
|
||||

|
||||
```text
|
||||
监控:{1},告警级别:{2}。内容:{3}
|
||||
```
|
||||
|
||||
1.3 腾讯云短信创建应用(app-id)
|
||||

|
||||

|
||||
|
||||
1.4 腾讯云访问管理(secret-id、secret-key)
|
||||

|
||||
3. 腾讯云短信创建应用(app-id)
|
||||

|
||||
|
||||
2. 配置告警自定义参数
|
||||
4. 腾讯云访问管理(secret-id、secret-key)
|
||||

|
||||
|
||||
```yaml
|
||||
alerter:
|
||||
# 自定义控制台地址
|
||||
console-url: https://console.tancloud.io
|
||||
```
|
||||
## 2. 配置告警自定义参数
|
||||
|
||||
3. 使用外置redis代替内存存储实时指标数据
|
||||
```yaml
|
||||
alerter:
|
||||
# 自定义控制台地址
|
||||
console-url: https://console.tancloud.io
|
||||
```
|
||||
|
||||
> 默认我们的指标实时数据存储在内存中,可以配置如下来使用redis代替内存存储。
|
||||
## 3. 使用外置redis代替内存存储实时指标数据
|
||||
|
||||
注意⚠️ `memory.enabled: false, redis.enabled: true`
|
||||
> 默认我们的指标实时数据存储在内存中,可以配置如下来使用redis代替内存存储。
|
||||
|
||||
```yaml
|
||||
warehouse:
|
||||
store:
|
||||
memory:
|
||||
enabled: false
|
||||
init-size: 1024
|
||||
redis:
|
||||
enabled: true
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
password: 123456
|
||||
```
|
||||
注意⚠️ `memory.enabled: false, redis.enabled: true`
|
||||
|
||||
```yaml
|
||||
warehouse:
|
||||
store:
|
||||
memory:
|
||||
enabled: false
|
||||
init-size: 1024
|
||||
redis:
|
||||
enabled: true
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
password: 123456
|
||||
```
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.2.3</version>
|
||||
<version>3.4.2</version>
|
||||
</parent>
|
||||
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
@@ -168,7 +168,7 @@
|
||||
<influxdb.version>2.23</influxdb.version>
|
||||
<spring-cloud-starter-openfeign.version>3.0.5</spring-cloud-starter-openfeign.version>
|
||||
<taos-jdbcdriver.version>3.0.0</taos-jdbcdriver.version>
|
||||
<greptimedb.version>0.9.1</greptimedb.version>
|
||||
<greptimedb.version>0.11.0</greptimedb.version>
|
||||
<mysql-jdbcdriver.version>8.0.33</mysql-jdbcdriver.version>
|
||||
<arrow.version>18.1.0</arrow.version>
|
||||
<snappy-java.version>1.1.10.7</snappy-java.version>
|
||||
|
||||
@@ -202,6 +202,28 @@ alerter:
|
||||
# alert inhibit ttl unit ms, default 14400000(4 hours)
|
||||
inhibit:
|
||||
ttl: 14400000
|
||||
sms:
|
||||
enable: false
|
||||
type: tencent
|
||||
tencent:
|
||||
secret-id:
|
||||
secret-key:
|
||||
app-id:
|
||||
sign-name:
|
||||
template-id:
|
||||
alibaba:
|
||||
access-key-id:
|
||||
access-key-secret:
|
||||
sign-name:
|
||||
template-code:
|
||||
unisms:
|
||||
# auth-mode: simple or hmac
|
||||
auth-mode: simple
|
||||
access-key-id: YOUR_ACCESS_KEY_ID
|
||||
# hmac mode need to fill in access-key-secret
|
||||
access-key-secret: YOUR_ACCESS_KEY_SECRET
|
||||
signature: YOUR_SMS_SIGNATURE
|
||||
template-id: YOUR_TEMPLATE_ID
|
||||
|
||||
scheduler:
|
||||
server:
|
||||
|
||||
@@ -169,6 +169,28 @@ alerter:
|
||||
# alert inhibit ttl unit ms, default 14400000(4 hours)
|
||||
inhibit:
|
||||
ttl: 14400000
|
||||
sms:
|
||||
enable: false
|
||||
type: tencent
|
||||
tencent:
|
||||
secret-id:
|
||||
secret-key:
|
||||
app-id:
|
||||
sign-name:
|
||||
template-id:
|
||||
alibaba:
|
||||
access-key-id:
|
||||
access-key-secret:
|
||||
sign-name:
|
||||
template-code:
|
||||
unisms:
|
||||
# auth-mode: simple or hmac
|
||||
auth-mode: simple
|
||||
access-key-id: YOUR_ACCESS_KEY_ID
|
||||
# hmac mode need to fill in access-key-secret
|
||||
access-key-secret: YOUR_ACCESS_KEY_SECRET
|
||||
signature: YOUR_SMS_SIGNATURE
|
||||
template-id: YOUR_TEMPLATE_ID
|
||||
|
||||
scheduler:
|
||||
server:
|
||||
|
||||
@@ -165,6 +165,28 @@ alerter:
|
||||
# alert inhibit ttl unit ms, default 14400000(4 hours)
|
||||
inhibit:
|
||||
ttl: 14400000
|
||||
sms:
|
||||
enable: false
|
||||
type: tencent
|
||||
tencent:
|
||||
secret-id:
|
||||
secret-key:
|
||||
app-id:
|
||||
sign-name:
|
||||
template-id:
|
||||
alibaba:
|
||||
access-key-id:
|
||||
access-key-secret:
|
||||
sign-name:
|
||||
template-code:
|
||||
unisms:
|
||||
# auth-mode: simple or hmac
|
||||
auth-mode: simple
|
||||
access-key-id: YOUR_ACCESS_KEY_ID
|
||||
# hmac mode need to fill in access-key-secret
|
||||
access-key-secret: YOUR_ACCESS_KEY_SECRET
|
||||
signature: YOUR_SMS_SIGNATURE
|
||||
template-id: YOUR_TEMPLATE_ID
|
||||
|
||||
scheduler:
|
||||
server:
|
||||
|
||||
@@ -163,6 +163,28 @@ alerter:
|
||||
# alert inhibit ttl unit ms, default 14400000(4 hours)
|
||||
inhibit:
|
||||
ttl: 14400000
|
||||
sms:
|
||||
enable: false
|
||||
type: tencent
|
||||
tencent:
|
||||
secret-id:
|
||||
secret-key:
|
||||
app-id:
|
||||
sign-name:
|
||||
template-id:
|
||||
alibaba:
|
||||
access-key-id:
|
||||
access-key-secret:
|
||||
sign-name:
|
||||
template-code:
|
||||
unisms:
|
||||
# auth-mode: simple or hmac
|
||||
auth-mode: simple
|
||||
access-key-id: YOUR_ACCESS_KEY_ID
|
||||
# hmac mode need to fill in access-key-secret
|
||||
access-key-secret: YOUR_ACCESS_KEY_SECRET
|
||||
signature: YOUR_SMS_SIGNATURE
|
||||
template-id: YOUR_TEMPLATE_ID
|
||||
|
||||
scheduler:
|
||||
server:
|
||||
|
||||
@@ -162,6 +162,28 @@ alerter:
|
||||
# alert inhibit ttl unit ms, default 14400000(4 hours)
|
||||
inhibit:
|
||||
ttl: 14400000
|
||||
sms:
|
||||
enable: false
|
||||
type: tencent
|
||||
tencent:
|
||||
secret-id:
|
||||
secret-key:
|
||||
app-id:
|
||||
sign-name:
|
||||
template-id:
|
||||
alibaba:
|
||||
access-key-id:
|
||||
access-key-secret:
|
||||
sign-name:
|
||||
template-code:
|
||||
unisms:
|
||||
# auth-mode: simple or hmac
|
||||
auth-mode: simple
|
||||
access-key-id: YOUR_ACCESS_KEY_ID
|
||||
# hmac mode need to fill in access-key-secret
|
||||
access-key-secret: YOUR_ACCESS_KEY_SECRET
|
||||
signature: YOUR_SMS_SIGNATURE
|
||||
template-id: YOUR_TEMPLATE_ID
|
||||
|
||||
scheduler:
|
||||
server:
|
||||
|
||||
@@ -81,4 +81,11 @@ describe('Service: I18n', () => {
|
||||
expect(lang).toBe('en-US');
|
||||
});
|
||||
});
|
||||
it('should be trigger notify when changed language', () => {
|
||||
genModule();
|
||||
srv.use('pt-BR', {});
|
||||
srv.change.subscribe(lang => {
|
||||
expect(lang).toBe('pt-BR');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import { registerLocaleData } from '@angular/common';
|
||||
import { HttpHeaders } from '@angular/common/http';
|
||||
import ngEn from '@angular/common/locales/en';
|
||||
import ngJa from '@angular/common/locales/ja';
|
||||
import ngPt from '@angular/common/locales/pt';
|
||||
import ngZh from '@angular/common/locales/zh';
|
||||
import ngZhTw from '@angular/common/locales/zh-Hant';
|
||||
import { Injectable } from '@angular/core';
|
||||
@@ -17,9 +18,16 @@ import {
|
||||
ja_JP as delonJaJP
|
||||
} from '@delon/theme';
|
||||
import { AlainConfigService } from '@delon/util/config';
|
||||
import { enUS as dfEn, zhCN as dfZhCn, zhTW as dfZhTw, ja as dfJa } from 'date-fns/locale';
|
||||
import { enUS as dfEn, zhCN as dfZhCn, zhTW as dfZhTw, ja as dfJa, ptBR as dfPtBR } from 'date-fns/locale';
|
||||
import { NzSafeAny } from 'ng-zorro-antd/core/types';
|
||||
import { en_US as zorroEnUS, NzI18nService, zh_CN as zorroZhCN, zh_TW as zorroZhTW, ja_JP as zorroJaJP } from 'ng-zorro-antd/i18n';
|
||||
import {
|
||||
en_US as zorroEnUS,
|
||||
NzI18nService,
|
||||
zh_CN as zorroZhCN,
|
||||
zh_TW as zorroZhTW,
|
||||
ja_JP as zorroJaJP,
|
||||
pt_BR as zorroPtBR
|
||||
} from 'ng-zorro-antd/i18n';
|
||||
import { Observable, zip } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@@ -67,6 +75,14 @@ const LANGS: { [key: string]: LangConfigData } = {
|
||||
date: dfJa,
|
||||
delon: delonJaJP,
|
||||
abbr: '🇯🇵'
|
||||
},
|
||||
'pt-BR': {
|
||||
text: 'Português (Brasil)',
|
||||
ng: ngPt,
|
||||
zorro: zorroPtBR,
|
||||
date: dfPtBR,
|
||||
delon: delonEnUS, // Usando en-US como fallback (ou crie um locale personalizado)
|
||||
abbr: '🇧🇷'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ import { GeneralConfigService } from '../../../service/general-config.service';
|
||||
ngClass="alain-default__nav-item-icon"
|
||||
(click)="toggleMute($event)"
|
||||
nz-tooltip
|
||||
[nzTooltipTitle]="'common.mute' | i18n"
|
||||
[nzTooltipTitle]="(mute.mute ? 'common.unmute' : 'common.mute') | i18n"
|
||||
></i>
|
||||
</nz-badge>
|
||||
</div>
|
||||
@@ -163,6 +163,9 @@ export class HeaderNotifyComponent implements OnInit, OnDestroy {
|
||||
if (this.refreshInterval) {
|
||||
clearInterval(this.refreshInterval);
|
||||
}
|
||||
if (this.eventSource) {
|
||||
this.eventSource.close();
|
||||
}
|
||||
}
|
||||
|
||||
onPopoverVisibleChange(visible: boolean): void {
|
||||
@@ -228,24 +231,6 @@ export class HeaderNotifyComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
updateAlertsStatus(alertIds: Set<number>, status: string) {
|
||||
const markAlertsStatus$ = this.alertSvc.applyGroupAlertsStatus(alertIds, status).subscribe(
|
||||
message => {
|
||||
markAlertsStatus$.unsubscribe();
|
||||
if (message.code === 0) {
|
||||
this.notifySvc.success(this.i18nSvc.fanyi('common.notify.mark-success'), '');
|
||||
this.loadData();
|
||||
} else {
|
||||
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.mark-fail'), message.msg);
|
||||
}
|
||||
},
|
||||
error => {
|
||||
markAlertsStatus$.unsubscribe();
|
||||
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.mark-fail'), error.msg);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
gotoAlertCenter(): void {
|
||||
this.popoverVisible = false;
|
||||
this.router.navigateByUrl(`/alert/center`);
|
||||
@@ -314,7 +299,7 @@ export class HeaderNotifyComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
this.eventSource.onerror = error => {
|
||||
console.error('SSE connection error:', error);
|
||||
setTimeout(() => this.initSSEConnection(), 3000);
|
||||
this.eventSource.close();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,8 @@
|
||||
*/
|
||||
|
||||
export class AlibabaSmsConfig {
|
||||
secretId!: string;
|
||||
secretKey!: string;
|
||||
accessKeyId!: string;
|
||||
accessKeySecret!: string;
|
||||
signName!: string;
|
||||
appId!: string;
|
||||
templateId!: string;
|
||||
templateCode!: string;
|
||||
}
|
||||
|
||||
@@ -19,13 +19,16 @@
|
||||
|
||||
import { AlibabaSmsConfig } from './AlibabaSmsConfig';
|
||||
import { TencentSmsConfig } from './TencentSmsConfig';
|
||||
import { UniSmsConfig } from './UniSmsConfig';
|
||||
import { SmsType } from './enums/sms-type.enum';
|
||||
|
||||
export class SmsNoticeSender {
|
||||
id!: number;
|
||||
type!: string;
|
||||
tencent!: TencentSmsConfig;
|
||||
alibaba!: AlibabaSmsConfig;
|
||||
enable!: boolean;
|
||||
type: SmsType = SmsType.TENCENT;
|
||||
tencent: TencentSmsConfig = new TencentSmsConfig();
|
||||
alibaba: AlibabaSmsConfig = new AlibabaSmsConfig();
|
||||
unisms: UniSmsConfig = new UniSmsConfig();
|
||||
enable: boolean = false;
|
||||
creator!: string;
|
||||
modifier!: string;
|
||||
gmtCreate!: number;
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
export class UniSmsConfig {
|
||||
accessKeyId!: string;
|
||||
accessKeySecret!: string;
|
||||
signature!: string;
|
||||
authMode!: string;
|
||||
templateId!: string;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export enum SmsType {
|
||||
TENCENT = 'tencent',
|
||||
ALIBABA = 'alibaba',
|
||||
UNISMS = 'unisms'
|
||||
}
|
||||
|
||||
export enum UniSmsAuthMode {
|
||||
HMAC = 'hmac',
|
||||
SIMPLE = 'simple'
|
||||
}
|
||||
@@ -38,22 +38,6 @@
|
||||
border-radius: 6px;
|
||||
padding: 4px 11px 4px 40px;
|
||||
}
|
||||
|
||||
.ant-input-prefix {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 16px;
|
||||
margin-right: 8px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.ant-input-affix-wrapper {
|
||||
border-radius: 6px;
|
||||
|
||||
&:hover, &:focus {
|
||||
border-color: #40a9ff;
|
||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,6 +374,59 @@
|
||||
:host {
|
||||
.alert-card {
|
||||
background-color: @common-background-color-dark;
|
||||
.alert-header {
|
||||
.alert-info {
|
||||
.alert-meta-info {
|
||||
color: rgb(214, 214, 214);
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert-details {
|
||||
::ng-deep {
|
||||
.ant-collapse {
|
||||
.ant-collapse-item {
|
||||
.ant-collapse-header {
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.ant-collapse-extra {
|
||||
color: rgb(236, 236, 236);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.detail-section {
|
||||
.section-title {
|
||||
color: #cdcdcd;
|
||||
}
|
||||
.alert-count {
|
||||
color: #ececec;
|
||||
}
|
||||
}
|
||||
|
||||
.alert-annotations {
|
||||
.annotation-item {
|
||||
.annotation-key {
|
||||
color: #dfdede;
|
||||
}
|
||||
.annotation-value {
|
||||
color: #e3e3e3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.time-info {
|
||||
.time-item {
|
||||
.time-label {
|
||||
color: #dfdede;
|
||||
}
|
||||
.time-value {
|
||||
color: #e3e3e3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,8 +54,12 @@
|
||||
groupStyle="width: 250px;"
|
||||
[placeholder]="'alert.group-converge.name' | i18n"
|
||||
[(value)]="search"
|
||||
(valueChange)="onFilterChange()"
|
||||
(keydown.enter)="onFilterChange()"
|
||||
(cleared)="onFilterChange()"
|
||||
/>
|
||||
<button nz-button nzType="primary" (click)="onFilterChange()" class="mobile-hide">
|
||||
{{ 'common.search' | i18n }}
|
||||
</button>
|
||||
</ng-template>
|
||||
</app-toolbar>
|
||||
|
||||
@@ -77,12 +81,13 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th nzAlign="center" nzLeft nzWidth="3%" [(nzChecked)]="checkedAll" (nzCheckedChange)="onAllChecked($event)"></th>
|
||||
<th nzAlign="center" nzWidth="15%">{{ 'alert.group-converge.name' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="20%">{{ 'alert.group-converge.group-labels' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="14%">{{ 'alert.group-converge.name' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="12%">{{ 'alert.group-converge.group-labels' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="10%">{{ 'alert.group-converge.group-wait' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="10%">{{ 'alert.group-converge.group-interval' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="10%">{{ 'alert.group-converge.repeat-interval' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="8%" nzRight>{{ 'alert.group-converge.enable' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="10%" nzRight>{{ 'common.enable' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="14%">{{ 'common.edit-time' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="12%" nzRight>{{ 'common.edit' | i18n }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -101,6 +106,7 @@
|
||||
<td nzAlign="center" nzRight>
|
||||
<nz-switch [(ngModel)]="data.enable" (ngModelChange)="updateGroupConverge(data)" name="enable"></nz-switch>
|
||||
</td>
|
||||
<td nzAlign="center">{{ (data.gmtUpdate ? data.gmtUpdate : data.gmtCreate) | date : 'YYYY-MM-dd HH:mm:ss' }}</td>
|
||||
<td nzAlign="center" nzRight>
|
||||
<div class="actions">
|
||||
<button
|
||||
|
||||
@@ -54,8 +54,12 @@
|
||||
groupStyle="width: 250px;"
|
||||
[placeholder]="'alert.inhibit.name' | i18n"
|
||||
[(value)]="search"
|
||||
(valueChange)="onFilterChange()"
|
||||
(keydown.enter)="onFilterChange()"
|
||||
(cleared)="onFilterChange()"
|
||||
/>
|
||||
<button nz-button nzType="primary" (click)="onFilterChange()" class="mobile-hide">
|
||||
{{ 'common.search' | i18n }}
|
||||
</button>
|
||||
</ng-template>
|
||||
</app-toolbar>
|
||||
|
||||
@@ -78,10 +82,11 @@
|
||||
<tr>
|
||||
<th nzAlign="center" nzLeft nzWidth="3%" [(nzChecked)]="checkedAll" (nzCheckedChange)="onAllChecked($event)"></th>
|
||||
<th nzAlign="center" nzWidth="15%">{{ 'alert.inhibit.name' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="20%">{{ 'alert.inhibit.source_labels' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="20%">{{ 'alert.inhibit.target_labels' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="18%">{{ 'alert.inhibit.source_labels' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="18%">{{ 'alert.inhibit.target_labels' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="15%">{{ 'alert.inhibit.equal_labels' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="8%" nzRight>{{ 'common.enable' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="12%" nzRight>{{ 'common.enable' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="14%">{{ 'common.edit-time' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="12%" nzRight>{{ 'common.edit' | i18n }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -101,6 +106,7 @@
|
||||
<td nzAlign="center" nzRight>
|
||||
<nz-switch [(ngModel)]="data.enable" (ngModelChange)="updateInhibit(data)" name="enable"></nz-switch>
|
||||
</td>
|
||||
<td nzAlign="center">{{ (data.gmtUpdate ? data.gmtUpdate : data.gmtCreate) | date : 'YYYY-MM-dd HH:mm:ss' }}</td>
|
||||
<td nzAlign="center" nzRight>
|
||||
<div class="actions">
|
||||
<button nz-button nzType="primary" (click)="editInhibit(data.id)" nz-tooltip [nzTooltipTitle]="'alert.inhibit.edit' | i18n">
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
{{ 'common.button.copy' | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<div style="padding: 12px; border: 1px solid #d9d9d9; border-radius: 4px; background-color: #f5f5f5; word-wrap: break-word">
|
||||
<div style="padding: 12px; border: 1px solid; border-radius: 4px; word-wrap: break-word">
|
||||
{{ token }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+25
-12
@@ -33,8 +33,12 @@
|
||||
groupStyle="width: 250px;"
|
||||
[placeholder]="'alert.notice.receiver.people.name' | i18n"
|
||||
[(value)]="name"
|
||||
(valueChange)="onSearch()"
|
||||
(keydown.enter)="onSearch()"
|
||||
(cleared)="onSearch()"
|
||||
/>
|
||||
<button nz-button nzType="primary" (click)="onSearch()" class="mobile-hide">
|
||||
{{ 'common.search' | i18n }}
|
||||
</button>
|
||||
</ng-template>
|
||||
</app-toolbar>
|
||||
<nz-table
|
||||
@@ -55,9 +59,9 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th nzAlign="center" nzWidth="15%">{{ 'alert.notice.receiver.people' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="20%">{{ 'alert.notice.receiver.type' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="20%">{{ 'alert.notice.receiver.setting' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="20%">{{ 'common.edit-time' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="15%">{{ 'alert.notice.receiver.type' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="25%">{{ 'alert.notice.receiver.setting' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="15%">{{ 'common.edit-time' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="15%" nzRight>{{ 'common.edit' | i18n }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -152,15 +156,24 @@
|
||||
>
|
||||
<i nz-icon nzTheme="outline" nzType="edit"></i>
|
||||
</button>
|
||||
<button
|
||||
(click)="onDeleteOneNoticeReceiver(data.id)"
|
||||
[nzTooltipTitle]="'alert.notice.receiver.delete' | i18n"
|
||||
nz-button
|
||||
nz-tooltip
|
||||
nzDanger
|
||||
>
|
||||
<i nz-icon nzTheme="outline" nzType="delete"></i>
|
||||
<button nz-button nz-dropdown [nzDropdownMenu]="more_menu">
|
||||
<span nz-icon nzType="ellipsis"></span>
|
||||
</button>
|
||||
<nz-dropdown-menu #more_menu="nzDropdownMenu">
|
||||
<ul nz-menu>
|
||||
<li nz-menu-item>
|
||||
<button
|
||||
(click)="onDeleteOneNoticeReceiver(data.id)"
|
||||
[nzTooltipTitle]="'alert.notice.receiver.delete' | i18n"
|
||||
nz-button
|
||||
nz-tooltip
|
||||
nzDanger
|
||||
>
|
||||
<i nz-icon nzTheme="outline" nzType="delete"></i>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nz-dropdown-menu>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
+27
-14
@@ -33,8 +33,12 @@
|
||||
groupStyle="width: 250px;"
|
||||
[placeholder]="'alert.notice.rule.name' | i18n"
|
||||
[(value)]="name"
|
||||
(valueChange)="onSearch()"
|
||||
(keydown.enter)="onSearch()"
|
||||
(cleared)="onSearch()"
|
||||
/>
|
||||
<button nz-button nzType="primary" (click)="onSearch()" class="mobile-hide">
|
||||
{{ 'common.search' | i18n }}
|
||||
</button>
|
||||
</ng-template>
|
||||
</app-toolbar>
|
||||
<nz-table
|
||||
@@ -54,13 +58,13 @@
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th nzAlign="center" nzWidth="15%">{{ 'alert.notice.rule.name' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="15%">{{ 'alert.notice.receiver.people' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="15%">{{ 'alert.notice.template.name' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="12%">{{ 'alert.notice.rule.name' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="12%">{{ 'alert.notice.receiver.people' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="10%">{{ 'alert.notice.template.name' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="10%">{{ 'alert.notice.rule.all' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="10%">{{ 'common.enable' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="20%">{{ 'common.edit-time' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="15%" nzRight>{{ 'common.edit' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="14%">{{ 'common.edit-time' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="10%" nzRight>{{ 'common.edit' | i18n }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -95,15 +99,24 @@
|
||||
>
|
||||
<i nz-icon nzTheme="outline" nzType="edit"></i>
|
||||
</button>
|
||||
<button
|
||||
(click)="onDeleteOneNoticeRule(data.id)"
|
||||
[nzTooltipTitle]="'alert.notice.rule.delete' | i18n"
|
||||
nz-button
|
||||
nz-tooltip
|
||||
nzDanger
|
||||
>
|
||||
<i nz-icon nzTheme="outline" nzType="delete"></i>
|
||||
<button nz-button nz-dropdown [nzDropdownMenu]="more_menu">
|
||||
<span nz-icon nzType="ellipsis"></span>
|
||||
</button>
|
||||
<nz-dropdown-menu #more_menu="nzDropdownMenu">
|
||||
<ul nz-menu>
|
||||
<li nz-menu-item>
|
||||
<button
|
||||
(click)="onDeleteOneNoticeRule(data.id)"
|
||||
[nzTooltipTitle]="'alert.notice.rule.delete' | i18n"
|
||||
nz-button
|
||||
nz-tooltip
|
||||
nzDanger
|
||||
>
|
||||
<i nz-icon nzTheme="outline" nzType="delete"></i>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nz-dropdown-menu>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
+7
-3
@@ -36,8 +36,12 @@
|
||||
groupStyle="width: 250px;"
|
||||
[placeholder]="'alert.notice.template.name' | i18n"
|
||||
[(value)]="name"
|
||||
(valueChange)="onSearch()"
|
||||
(keydown.enter)="onSearch()"
|
||||
(cleared)="onSearch()"
|
||||
/>
|
||||
<button nz-button nzType="primary" (click)="onSearch()" class="mobile-hide">
|
||||
{{ 'common.search' | i18n }}
|
||||
</button>
|
||||
</ng-template>
|
||||
</app-toolbar>
|
||||
<nz-table
|
||||
@@ -57,7 +61,7 @@
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th nzAlign="center" nzWidth="25%">{{ 'alert.notice.template.name' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="20%">{{ 'alert.notice.template.name' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="15%">{{ 'alert.notice.template.type' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="15%">{{ 'alert.notice.template.preset' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="20%">{{ 'common.edit-time' | i18n }}</th>
|
||||
@@ -244,7 +248,7 @@
|
||||
[(ngModel)]="template.content"
|
||||
id="template_content_example"
|
||||
name="template_content"
|
||||
style="white-space: nowrap; overflow: scroll; width: 100%; height: 200px"
|
||||
class="textarea"
|
||||
type="textarea"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
@import "~src/styles/theme";
|
||||
|
||||
.textarea {
|
||||
white-space: nowrap;
|
||||
overflow: scroll;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
background-color: #c6bfbf;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,9 @@
|
||||
(keydown.enter)="onFilterChange()"
|
||||
(cleared)="onFilterChange()"
|
||||
/>
|
||||
<button nz-button nzType="primary" (click)="onFilterChange()">
|
||||
{{ 'common.search' | i18n }}
|
||||
</button>
|
||||
</ng-template>
|
||||
</app-toolbar>
|
||||
|
||||
@@ -93,12 +96,13 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th nzAlign="center" nzLeft nzWidth="3%" [(nzChecked)]="checkedAll" (nzCheckedChange)="onAllChecked($event)"></th>
|
||||
<th nzAlign="center" nzWidth="10%">{{ 'alert.setting.name' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="12%">{{ 'alert.setting.name' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="8%">{{ 'alert.setting.type' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="24%">{{ 'alert.setting.expr' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="20%">{{ 'alert.setting.template' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="8%">{{ 'label.bind' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="8%" nzRight>{{ 'alert.setting.enable' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="15%">{{ 'alert.setting.expr' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="15%">{{ 'alert.setting.template' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="15%">{{ 'label.bind' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="8%" nzRight>{{ 'common.enable' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="14%">{{ 'common.edit-time' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="8%" nzRight>{{ 'common.edit' | i18n }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -116,16 +120,19 @@
|
||||
<span>{{ 'alert.setting.type.periodic' | i18n }}</span>
|
||||
</nz-tag>
|
||||
</td>
|
||||
<td nzAlign="center">
|
||||
<td nzAlign="center" nzEllipsis nz-tooltip [nzTooltipTitle]="data.expr">
|
||||
<span>{{ data.expr }}</span>
|
||||
</td>
|
||||
<td nzAlign="center">{{ data.template }}</td>
|
||||
<td nzAlign="center" nzEllipsis nz-tooltip [nzTooltipTitle]="data.template">
|
||||
<span>{{ data.template }}</span>
|
||||
</td>
|
||||
<td nzAlign="center">
|
||||
<nz-tag *ngFor="let item of data.labels | keyvalue">{{ item.key }}:{{ item.value }}</nz-tag>
|
||||
</td>
|
||||
<td nzAlign="center" nzRight>
|
||||
<nz-switch [(ngModel)]="data.enable" (ngModelChange)="updateAlertDefine(data)" name="enable"></nz-switch>
|
||||
</td>
|
||||
<td nzAlign="center">{{ (data.gmtUpdate ? data.gmtUpdate : data.gmtCreate) | date : 'YYYY-MM-dd HH:mm:ss' }}</td>
|
||||
<td nzAlign="center" nzRight>
|
||||
<div class="actions">
|
||||
<button
|
||||
@@ -285,12 +292,7 @@
|
||||
<nz-form-item *ngIf="define.type == 'realtime' && cascadeValues.length > 0 && cascadeValues[1] !== 'availability' && !isExpr">
|
||||
<nz-form-label [nzSpan]="7" [nzNoColon]="true"></nz-form-label>
|
||||
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n" [nzValidateStatus]="qbFormCtrl">
|
||||
<ngx-query-builder
|
||||
[classNames]="qbClassNames"
|
||||
[config]="qbConfig"
|
||||
[formControl]="qbFormCtrl"
|
||||
[ngStyle]="{ background: 'ghostwhite', borderRadius: '4px' }"
|
||||
>
|
||||
<ngx-query-builder [classNames]="qbClassNames" [config]="qbConfig" [formControl]="qbFormCtrl" [ngStyle]="{ borderRadius: '4px' }">
|
||||
<ng-container *querySwitchGroup="let rule; let onChange = onChange">
|
||||
<nz-radio-group
|
||||
style="white-space: nowrap"
|
||||
|
||||
@@ -246,8 +246,7 @@
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
padding: 12px;
|
||||
background: #fafafa;
|
||||
border: 1px dashed @border-color;
|
||||
border: 1px dashed fade(@primary-color, 30%);
|
||||
border-radius: 6px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@@ -258,7 +257,6 @@
|
||||
.draggable-tag {
|
||||
padding: 8px 12px;
|
||||
min-width: 140px;
|
||||
background: #f0f5ff;
|
||||
border: 1px solid fade(@primary-color, 30%);
|
||||
border-radius: 4px;
|
||||
cursor: move;
|
||||
|
||||
@@ -54,8 +54,12 @@
|
||||
groupStyle="width: 250px;"
|
||||
[placeholder]="'alert.silence.name' | i18n"
|
||||
[(value)]="search"
|
||||
(valueChange)="onFilterChange()"
|
||||
(keydown.enter)="onFilterChange()"
|
||||
(cleared)="onFilterChange()"
|
||||
/>
|
||||
<button nz-button nzType="primary" (click)="onFilterChange()" class="mobile-hide">
|
||||
{{ 'common.search' | i18n }}
|
||||
</button>
|
||||
</ng-template>
|
||||
</app-toolbar>
|
||||
|
||||
@@ -77,11 +81,12 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th nzAlign="center" nzLeft nzWidth="3%" [(nzChecked)]="checkedAll" (nzCheckedChange)="onAllChecked($event)"></th>
|
||||
<th nzAlign="center" nzWidth="7%">{{ 'alert.silence.name' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="7%">{{ 'alert.silence.type' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="10%">{{ 'alert.silence.times' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="3%" nzRight>{{ 'alert.silence.enable' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="5%" nzRight>{{ 'common.edit' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="14%">{{ 'alert.silence.name' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="12%">{{ 'alert.silence.type' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="12%">{{ 'alert.silence.times' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="12%" nzRight>{{ 'common.enable' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="14%">{{ 'common.edit-time' | i18n }}</th>
|
||||
<th nzAlign="center" nzWidth="12%" nzRight>{{ 'common.edit' | i18n }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -109,6 +114,7 @@
|
||||
<td nzAlign="center" nzRight>
|
||||
<nz-switch [(ngModel)]="data.enable" (ngModelChange)="updateAlertSilence(data)" name="enable"></nz-switch>
|
||||
</td>
|
||||
<td nzAlign="center">{{ (data.gmtUpdate ? data.gmtUpdate : data.gmtCreate) | date : 'YYYY-MM-dd HH:mm:ss' }}</td>
|
||||
<td nzAlign="center" nzRight>
|
||||
<div class="actions">
|
||||
<button
|
||||
|
||||
@@ -75,18 +75,6 @@ export class MonitorDataChartComponent implements OnInit {
|
||||
show: true,
|
||||
orient: 'vertical',
|
||||
feature: {
|
||||
dataZoom: {
|
||||
yAxisIndex: 'none',
|
||||
title: {
|
||||
zoom: this.i18nSvc.fanyi('monitor.detail.chart.zoom'),
|
||||
back: this.i18nSvc.fanyi('monitor.detail.chart.back')
|
||||
},
|
||||
emphasis: {
|
||||
iconStyle: {
|
||||
textPosition: 'left'
|
||||
}
|
||||
}
|
||||
},
|
||||
saveAsImage: {
|
||||
title: this.i18nSvc.fanyi('monitor.detail.chart.save'),
|
||||
emphasis: {
|
||||
@@ -223,7 +211,10 @@ export class MonitorDataChartComponent implements OnInit {
|
||||
{
|
||||
type: 'inside',
|
||||
start: 0,
|
||||
end: 100
|
||||
end: 100,
|
||||
zoomOnMouseWheel: false,
|
||||
moveOnMouseMove: false,
|
||||
moveOnMouseWheel: false
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -122,15 +122,20 @@
|
||||
class="mobile-hide"
|
||||
[placeholder]="'monitor.search.label' | i18n"
|
||||
[(value)]="labels"
|
||||
(valueChange)="onTagChanged()"
|
||||
(keydown.enter)="onFilterSearchMonitors()"
|
||||
(cleared)="onFilterSearchMonitors()"
|
||||
/>
|
||||
<app-multi-func-input
|
||||
groupStyle="width: 180px;"
|
||||
class="mobile-hide"
|
||||
[placeholder]="'monitor.search.placeholder' | i18n"
|
||||
[(value)]="filterContent"
|
||||
(valueChange)="onFilterSearchMonitors()"
|
||||
(keydown.enter)="onFilterSearchMonitors()"
|
||||
(cleared)="onFilterSearchMonitors()"
|
||||
/>
|
||||
<button class="mobile-hide" nz-button nzType="primary" (click)="onFilterSearchMonitors()">
|
||||
{{ 'common.search' | i18n }}
|
||||
</button>
|
||||
</ng-template>
|
||||
</app-toolbar>
|
||||
|
||||
@@ -164,7 +169,7 @@
|
||||
<tbody>
|
||||
<tr *ngFor="let data of fixedTable.data">
|
||||
<td nzAlign="center" nzLeft [nzChecked]="checkedMonitorIds.has(data.id)" (nzCheckedChange)="onItemChecked(data.id, $event)"></td>
|
||||
<td nzAlign="center">
|
||||
<td nzAlign="center" nzEllipsis>
|
||||
<button nz-button nzSize="default" nzType="link" [routerLink]="['/monitors/' + data.id]">
|
||||
{{ data.name }}
|
||||
<span nz-icon nzType="area-chart"></span>
|
||||
@@ -184,7 +189,7 @@
|
||||
<span>{{ 'monitor.status.down' | i18n }}</span>
|
||||
</nz-tag>
|
||||
</td>
|
||||
<td nzAlign="center">
|
||||
<td nzAlign="center" nzEllipsis>
|
||||
<button
|
||||
nz-button
|
||||
nzSize="default"
|
||||
@@ -223,13 +228,7 @@
|
||||
<nz-dropdown-menu #more_menu="nzDropdownMenu">
|
||||
<ul nz-menu>
|
||||
<li nz-menu-item>
|
||||
<button
|
||||
nz-button
|
||||
nzType="primary"
|
||||
(click)="onEditOneMonitor(data.id)"
|
||||
nz-tooltip
|
||||
[nzTooltipTitle]="'monitor.edit-monitor' | i18n"
|
||||
>
|
||||
<button nz-button (click)="onEditOneMonitor(data.id)" nz-tooltip [nzTooltipTitle]="'monitor.edit-monitor' | i18n">
|
||||
<i nz-icon nzType="edit" nzTheme="outline"></i>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -120,14 +120,6 @@ export class MonitorListComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
onTagChanged(): void {
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: { ...this.route.snapshot.queryParams, tag: this.labels },
|
||||
queryParamsHandling: 'merge'
|
||||
});
|
||||
}
|
||||
|
||||
onFilterSearchMonitors() {
|
||||
this.tableLoading = true;
|
||||
this.pageIndex = 1;
|
||||
|
||||
@@ -68,8 +68,12 @@
|
||||
groupStyle="width: 250px;"
|
||||
[placeholder]="'collector.name' | i18n"
|
||||
[(value)]="search"
|
||||
(valueChange)="loadCollectorsTable()"
|
||||
(keydown.enter)="loadCollectorsTable()"
|
||||
(cleared)="loadCollectorsTable()"
|
||||
/>
|
||||
<button nz-button nzType="primary" (click)="loadCollectorsTable()" class="mobile-hide">
|
||||
{{ 'common.search' | i18n }}
|
||||
</button>
|
||||
</ng-template>
|
||||
</app-toolbar>
|
||||
|
||||
@@ -167,16 +171,25 @@
|
||||
>
|
||||
<i nz-icon nzType="down-circle" nzTheme="outline"></i>
|
||||
</button>
|
||||
<button
|
||||
[disabled]="data.collector.name == 'main-default-collector'"
|
||||
nz-button
|
||||
nzDanger
|
||||
(click)="onDeleteOneCollector(data.collector.name)"
|
||||
nz-tooltip
|
||||
[nzTooltipTitle]="'collector.delete' | i18n"
|
||||
>
|
||||
<i nz-icon nzType="delete" nzTheme="outline"></i>
|
||||
<button nz-button nz-dropdown [nzDropdownMenu]="more_menu">
|
||||
<span nz-icon nzType="ellipsis"></span>
|
||||
</button>
|
||||
<nz-dropdown-menu #more_menu="nzDropdownMenu">
|
||||
<ul nz-menu>
|
||||
<li nz-menu-item>
|
||||
<button
|
||||
[disabled]="data.collector.name == 'main-default-collector'"
|
||||
nz-button
|
||||
nzDanger
|
||||
(click)="onDeleteOneCollector(data.collector.name)"
|
||||
nz-tooltip
|
||||
[nzTooltipTitle]="'collector.delete' | i18n"
|
||||
>
|
||||
<i nz-icon nzType="delete" nzTheme="outline"></i>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nz-dropdown-menu>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -55,8 +55,12 @@
|
||||
groupStyle="width: 250px;"
|
||||
[placeholder]="'plugin.search' | i18n"
|
||||
[(value)]="search"
|
||||
(valueChange)="loadPluginsTable()"
|
||||
(keydown.enter)="loadPluginsTable()"
|
||||
(cleared)="loadPluginsTable()"
|
||||
/>
|
||||
<button nz-button nzType="primary" (click)="loadPluginsTable()" class="mobile-hide">
|
||||
{{ 'common.search' | i18n }}
|
||||
</button>
|
||||
</ng-template>
|
||||
</app-toolbar>
|
||||
|
||||
@@ -110,9 +114,18 @@
|
||||
>
|
||||
<i nz-icon nzType="edit" nzTheme="outline"></i>
|
||||
</button>
|
||||
<button nz-button nzDanger (click)="onDeleteOnePlugin(data.id)" nz-tooltip [nzTooltipTitle]="'plugin.delete' | i18n">
|
||||
<i nz-icon nzType="delete" nzTheme="outline"></i>
|
||||
<button nz-button nz-dropdown [nzDropdownMenu]="more_menu">
|
||||
<span nz-icon nzType="ellipsis"></span>
|
||||
</button>
|
||||
<nz-dropdown-menu #more_menu="nzDropdownMenu">
|
||||
<ul nz-menu>
|
||||
<li nz-menu-item>
|
||||
<button nz-button nzDanger (click)="onDeleteOnePlugin(data.id)" nz-tooltip [nzTooltipTitle]="'plugin.delete' | i18n">
|
||||
<i nz-icon nzType="delete" nzTheme="outline"></i>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nz-dropdown-menu>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
+156
-31
@@ -47,25 +47,34 @@
|
||||
<ng-template #smsDesc>
|
||||
{{ 'alert.notice.sender.sms.type' | i18n }}: {{ 'alert.notice.sender.sms.type.' + smsNoticeSender.type | i18n }}
|
||||
<br />
|
||||
<ng-container *ngIf="smsNoticeSender.type === 'tencent'">
|
||||
{{ 'alert.notice.sender.sms.tencent.secretId' | i18n }}: {{ smsNoticeSender.tencent.secretId }}
|
||||
<br />
|
||||
</ng-container>
|
||||
<ng-container *ngIf="smsNoticeSender.type === 'tencent'">
|
||||
{{ 'alert.notice.sender.sms.tencent.secretKey' | i18n }}: {{ smsNoticeSender.tencent.secretKey }}
|
||||
<br />
|
||||
</ng-container>
|
||||
<ng-container *ngIf="smsNoticeSender.type === 'tencent'">
|
||||
{{ 'alert.notice.sender.sms.tencent.signName' | i18n }}: {{ smsNoticeSender.tencent.signName }}
|
||||
<br />
|
||||
</ng-container>
|
||||
<ng-container *ngIf="smsNoticeSender.type === 'tencent'">
|
||||
{{ 'alert.notice.sender.sms.tencent.appId' | i18n }}: {{ smsNoticeSender.tencent.appId }}
|
||||
<br />
|
||||
</ng-container>
|
||||
<ng-container *ngIf="smsNoticeSender.type === 'tencent'">
|
||||
{{ 'alert.notice.sender.sms.tencent.templateId' | i18n }}: {{ smsNoticeSender.tencent.templateId }}
|
||||
<ng-container [ngSwitch]="smsNoticeSender.type">
|
||||
<!-- Tencent SMS -->
|
||||
<ng-container *ngSwitchCase="SmsType.TENCENT">
|
||||
{{ 'alert.notice.sender.sms.tencent.appId' | i18n }}: {{ smsNoticeSender.tencent.appId }}
|
||||
<br />
|
||||
{{ 'alert.notice.sender.sms.tencent.signName' | i18n }}: {{ smsNoticeSender.tencent.signName }}
|
||||
<br />
|
||||
{{ 'alert.notice.sender.sms.tencent.templateId' | i18n }}: {{ smsNoticeSender.tencent.templateId }}
|
||||
</ng-container>
|
||||
|
||||
<!-- Alibaba SMS -->
|
||||
<ng-container *ngSwitchCase="SmsType.ALIBABA">
|
||||
{{ 'alert.notice.sender.sms.alibaba.signName' | i18n }}: {{ smsNoticeSender.alibaba.signName }}
|
||||
<br />
|
||||
{{ 'alert.notice.sender.sms.alibaba.templateCode' | i18n }}: {{ smsNoticeSender.alibaba.templateCode }}
|
||||
</ng-container>
|
||||
|
||||
<!-- UniSMS -->
|
||||
<ng-container *ngSwitchCase="SmsType.UNISMS">
|
||||
{{ 'alert.notice.sender.sms.unisms.signature' | i18n }}: {{ smsNoticeSender.unisms.signature }}
|
||||
<br />
|
||||
{{ 'alert.notice.sender.sms.unisms.templateId' | i18n }}: {{ smsNoticeSender.unisms.templateId }}
|
||||
<br />
|
||||
{{ 'alert.notice.sender.sms.unisms.authMode' | i18n }}: {{ smsNoticeSender.unisms.authMode }}
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<br />
|
||||
{{ 'common.enable' | i18n }}: {{ smsNoticeSender.enable ? ('common.yes' | i18n) : ('common.no' | i18n) }}
|
||||
</ng-template>
|
||||
</nz-list-item-meta>
|
||||
</nz-list-item>
|
||||
@@ -145,27 +154,28 @@
|
||||
<nz-form-item>
|
||||
<nz-form-label nzSpan="7" nzRequired="true">{{ 'alert.notice.sender.sms.type' | i18n }}</nz-form-label>
|
||||
<nz-form-control nzSpan="12">
|
||||
<nz-select [(ngModel)]="smsType" name="type" id="type" (ngModelChange)="onSmsTypeChange(smsType)">
|
||||
<nz-option nzValue="tencent" nzLabel="{{ 'alert.notice.sender.sms.type.tencent' | i18n }}"></nz-option>
|
||||
<nz-option nzValue="alibaba" nzLabel="{{ 'alert.notice.sender.sms.type.alibaba' | i18n }}"></nz-option>
|
||||
<nz-select [(ngModel)]="smsType" name="type" id="type" (ngModelChange)="onSmsTypeChange($event)">
|
||||
<nz-option [nzValue]="SmsType.TENCENT" nzLabel="{{ 'alert.notice.sender.sms.type.tencent' | i18n }}"></nz-option>
|
||||
<nz-option [nzValue]="SmsType.ALIBABA" nzLabel="{{ 'alert.notice.sender.sms.type.alibaba' | i18n }}"></nz-option>
|
||||
<nz-option [nzValue]="SmsType.UNISMS" nzLabel="{{ 'alert.notice.sender.sms.type.unisms' | i18n }}"></nz-option>
|
||||
</nz-select>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<ng-container *ngIf="smsType === 'tencent'">
|
||||
<ng-container *ngIf="smsType === SmsType.TENCENT">
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSpan]="7" nzFor="secretId" nzRequired="true">{{
|
||||
'alert.notice.sender.sms.tencent.secretId' | i18n
|
||||
}}</nz-form-label>
|
||||
<nz-form-label [nzSpan]="7" nzFor="secretId" nzRequired="true">
|
||||
{{ 'alert.notice.sender.sms.tencent.secretId' | i18n }}
|
||||
</nz-form-label>
|
||||
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
|
||||
<input [(ngModel)]="smsNoticeSender.tencent.secretId" nz-input required name="secretId" type="text" id="secretId" />
|
||||
<input [(ngModel)]="smsNoticeSender.tencent.secretId" nz-input required name="secretId" type="password" id="secretId" />
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSpan]="7" nzFor="secretKey" nzRequired="true">{{
|
||||
'alert.notice.sender.sms.tencent.secretKey' | i18n
|
||||
}}</nz-form-label>
|
||||
<nz-form-label [nzSpan]="7" nzFor="secretKey" nzRequired="true">
|
||||
{{ 'alert.notice.sender.sms.tencent.secretKey' | i18n }}
|
||||
</nz-form-label>
|
||||
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
|
||||
<input [(ngModel)]="smsNoticeSender.tencent.secretKey" nz-input required name="secretKey" type="text" id="secretKey" />
|
||||
<input [(ngModel)]="smsNoticeSender.tencent.secretKey" nz-input required name="secretKey" type="password" id="secretKey" />
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
@@ -191,7 +201,122 @@
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="smsType === 'alibaba'"> </ng-container>
|
||||
<ng-container *ngIf="smsType === SmsType.ALIBABA">
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSpan]="7" nzFor="accessKeyId" nzRequired="true">
|
||||
{{ 'alert.notice.sender.sms.alibaba.accessKeyId' | i18n }}
|
||||
</nz-form-label>
|
||||
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
|
||||
<input
|
||||
[(ngModel)]="smsNoticeSender.alibaba.accessKeyId"
|
||||
nz-input
|
||||
required
|
||||
name="accessKeyId"
|
||||
type="password"
|
||||
id="alibabaAccessKeyId"
|
||||
/>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSpan]="7" nzFor="accessKeySecret" nzRequired="true">
|
||||
{{ 'alert.notice.sender.sms.alibaba.accessKeySecret' | i18n }}
|
||||
</nz-form-label>
|
||||
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
|
||||
<input
|
||||
[(ngModel)]="smsNoticeSender.alibaba.accessKeySecret"
|
||||
nz-input
|
||||
required
|
||||
name="accessKeySecret"
|
||||
type="password"
|
||||
id="alibabaAccessKeySecret"
|
||||
/>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSpan]="7" nzFor="signName" nzRequired="true">
|
||||
{{ 'alert.notice.sender.sms.alibaba.signName' | i18n }}
|
||||
</nz-form-label>
|
||||
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
|
||||
<input [(ngModel)]="smsNoticeSender.alibaba.signName" nz-input required name="signName" type="text" id="alibabaSignName" />
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSpan]="7" nzFor="templateCode" nzRequired="true">
|
||||
{{ 'alert.notice.sender.sms.alibaba.templateCode' | i18n }}
|
||||
</nz-form-label>
|
||||
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
|
||||
<input
|
||||
[(ngModel)]="smsNoticeSender.alibaba.templateCode"
|
||||
nz-input
|
||||
required
|
||||
name="templateCode"
|
||||
type="text"
|
||||
id="alibabaTemplateCode"
|
||||
/>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="smsType === SmsType.UNISMS">
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSpan]="7" nzFor="accessKeyId" nzRequired="true">
|
||||
{{ 'alert.notice.sender.sms.unisms.accessKeyId' | i18n }}
|
||||
</nz-form-label>
|
||||
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
|
||||
<input
|
||||
[(ngModel)]="smsNoticeSender.unisms.accessKeyId"
|
||||
nz-input
|
||||
required
|
||||
name="accessKeyId"
|
||||
type="password"
|
||||
id="unismsAccessKeyId"
|
||||
/>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-label nzSpan="7" nzFor="authMode" nzRequired="true">
|
||||
{{ 'alert.notice.sender.sms.unisms.authMode' | i18n }}
|
||||
</nz-form-label>
|
||||
<nz-form-control nzSpan="12">
|
||||
<nz-select [(ngModel)]="smsNoticeSender.unisms.authMode" name="authMode" id="unismsAuthMode" required>
|
||||
<nz-option [nzValue]="uniSmsAuthModes.HMAC" nzLabel="HMAC"></nz-option>
|
||||
<nz-option [nzValue]="uniSmsAuthModes.SIMPLE" nzLabel="Simple"></nz-option>
|
||||
</nz-select>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<!-- accessKeySecret 根据 authMode 动态显示 -->
|
||||
<nz-form-item *ngIf="isAccessKeySecretRequired()">
|
||||
<nz-form-label [nzSpan]="7" nzFor="accessKeySecret" nzRequired="true">
|
||||
{{ 'alert.notice.sender.sms.unisms.accessKeySecret' | i18n }}
|
||||
</nz-form-label>
|
||||
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
|
||||
<input
|
||||
[(ngModel)]="smsNoticeSender.unisms.accessKeySecret"
|
||||
nz-input
|
||||
required
|
||||
name="accessKeySecret"
|
||||
type="password"
|
||||
id="unismsAccessKeySecret"
|
||||
/>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSpan]="7" nzFor="signature" nzRequired="true">
|
||||
{{ 'alert.notice.sender.sms.unisms.signature' | i18n }}
|
||||
</nz-form-label>
|
||||
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
|
||||
<input [(ngModel)]="smsNoticeSender.unisms.signature" nz-input required name="signature" type="text" id="unismsSignature" />
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSpan]="7" nzFor="templateId" nzRequired="true">
|
||||
{{ 'alert.notice.sender.sms.unisms.templateId' | i18n }}
|
||||
</nz-form-label>
|
||||
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
|
||||
<input [(ngModel)]="smsNoticeSender.unisms.templateId" nz-input required name="templateId" type="text" id="unismsTemplateId" />
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
</ng-container>
|
||||
<nz-form-item>
|
||||
<nz-form-label nzSpan="7" nzFor="smsEnable" nzRequired="true">{{ 'common.enable' | i18n }}</nz-form-label>
|
||||
<nz-form-control nzSpan="12">
|
||||
|
||||
+34
-21
@@ -27,6 +27,8 @@ import { finalize } from 'rxjs/operators';
|
||||
import { AlibabaSmsConfig } from 'src/app/pojo/AlibabaSmsConfig';
|
||||
import { SmsNoticeSender } from 'src/app/pojo/SmsNoticeSender';
|
||||
import { TencentSmsConfig } from 'src/app/pojo/TencentSmsConfig';
|
||||
import { UniSmsConfig } from 'src/app/pojo/UniSmsConfig';
|
||||
import { SmsType, UniSmsAuthMode } from 'src/app/pojo/enums/sms-type.enum';
|
||||
|
||||
import { EmailNoticeSender } from '../../../../pojo/EmailNoticeSender';
|
||||
import { GeneralConfigService } from '../../../../service/general-config.service';
|
||||
@@ -49,9 +51,13 @@ export class MessageServerComponent implements OnInit {
|
||||
loading: boolean = false;
|
||||
isEmailServerModalVisible: boolean = false;
|
||||
isSmsServerModalVisible: boolean = false;
|
||||
smsType: string = 'tencent';
|
||||
smsType: SmsType = SmsType.TENCENT;
|
||||
emailSender = new EmailNoticeSender();
|
||||
smsNoticeSender = new SmsNoticeSender();
|
||||
uniSmsAuthModes = UniSmsAuthMode;
|
||||
SmsType = SmsType;
|
||||
private tempSmsType: SmsType = SmsType.TENCENT;
|
||||
private tempSmsNoticeSender = new SmsNoticeSender();
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadEmailSenderServer();
|
||||
@@ -131,12 +137,14 @@ export class MessageServerComponent implements OnInit {
|
||||
this.senderServerLoading = false;
|
||||
if (message.code === 0) {
|
||||
if (message.data) {
|
||||
this.smsNoticeSender = message.data;
|
||||
this.smsType = message.data.type;
|
||||
const newSender = new SmsNoticeSender();
|
||||
this.smsNoticeSender = { ...newSender, ...message.data };
|
||||
this.smsNoticeSender.tencent = { ...new TencentSmsConfig(), ...message.data.tencent };
|
||||
this.smsNoticeSender.alibaba = { ...new AlibabaSmsConfig(), ...message.data.alibaba };
|
||||
this.smsNoticeSender.unisms = { ...new UniSmsConfig(), ...message.data.unisms };
|
||||
this.smsType = message.data.type || 'tencent';
|
||||
} else {
|
||||
this.smsNoticeSender = new SmsNoticeSender();
|
||||
this.smsNoticeSender.type = 'tencent';
|
||||
this.smsNoticeSender.tencent = new TencentSmsConfig();
|
||||
}
|
||||
} else {
|
||||
console.warn(message.msg);
|
||||
@@ -152,23 +160,34 @@ export class MessageServerComponent implements OnInit {
|
||||
}
|
||||
|
||||
onConfigSmsServer() {
|
||||
this.tempSmsType = this.smsType;
|
||||
this.tempSmsNoticeSender = {
|
||||
...this.smsNoticeSender,
|
||||
tencent: { ...this.smsNoticeSender.tencent },
|
||||
alibaba: { ...this.smsNoticeSender.alibaba },
|
||||
unisms: { ...this.smsNoticeSender.unisms }
|
||||
};
|
||||
this.isSmsServerModalVisible = true;
|
||||
}
|
||||
|
||||
onCancelSmsServer() {
|
||||
this.smsType = this.tempSmsType;
|
||||
this.smsNoticeSender = {
|
||||
...this.tempSmsNoticeSender,
|
||||
tencent: { ...this.tempSmsNoticeSender.tencent },
|
||||
alibaba: { ...this.tempSmsNoticeSender.alibaba },
|
||||
unisms: { ...this.tempSmsNoticeSender.unisms }
|
||||
};
|
||||
this.isSmsServerModalVisible = false;
|
||||
}
|
||||
|
||||
onSmsTypeChange(value: string) {
|
||||
if (value === 'tencent') {
|
||||
// tencent sms sender
|
||||
this.smsType = 'tencent';
|
||||
this.smsNoticeSender.type = 'tencent';
|
||||
} else if (value === 'alibaba') {
|
||||
// alibaba sms sender
|
||||
this.smsType = 'alibaba';
|
||||
this.smsNoticeSender.type = 'alibaba';
|
||||
}
|
||||
onSmsTypeChange(value: SmsType) {
|
||||
this.smsType = value;
|
||||
this.smsNoticeSender.type = value;
|
||||
}
|
||||
|
||||
isAccessKeySecretRequired(): boolean {
|
||||
return this.smsNoticeSender.unisms.authMode === UniSmsAuthMode.HMAC;
|
||||
}
|
||||
|
||||
onSaveSmsServer() {
|
||||
@@ -181,12 +200,6 @@ export class MessageServerComponent implements OnInit {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.smsNoticeSender.type === 'tencent') {
|
||||
this.smsNoticeSender.alibaba = new AlibabaSmsConfig();
|
||||
}
|
||||
if (this.smsNoticeSender.type === 'alibaba') {
|
||||
this.smsNoticeSender.tencent = new TencentSmsConfig();
|
||||
}
|
||||
const modalOk$ = this.noticeSenderSvc
|
||||
.saveGeneralConfig(this.smsNoticeSender, 'sms')
|
||||
.pipe(
|
||||
|
||||
@@ -18,27 +18,27 @@
|
||||
-->
|
||||
|
||||
<nz-spin [nzSpinning]="loading">
|
||||
<div nz-row *ngIf="config">
|
||||
<div nz-col nzSpan="12">
|
||||
<form nz-form #objectStoreForm="ngForm" (submit)="onSaveObjectStore()">
|
||||
<div class="main" *ngIf="config">
|
||||
<div class="left">
|
||||
<form nz-form nzLayout="vertical" #objectStoreForm="ngForm" (submit)="onSaveObjectStore()">
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSpan]="8" nzFor="config.type" nzRequired="true">{{ 'settings.object-store.type' | i18n }}</nz-form-label>
|
||||
<nz-select
|
||||
[(ngModel)]="config.type"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
style="text-align: center; font-weight: bolder"
|
||||
[nzDropdownStyle]="{ 'font-weight': 'bolder', 'font-size': 'larger' }"
|
||||
(ngModelChange)="onChange()"
|
||||
>
|
||||
<nz-option [nzValue]="ObjectStoreType.FILE" [nzLabel]="'settings.object-store.type.file' | i18n"></nz-option>
|
||||
<nz-option [nzValue]="ObjectStoreType.DATABASE" [nzLabel]="'settings.object-store.type.database' | i18n"></nz-option>
|
||||
<nz-option [nzValue]="ObjectStoreType.OBS" [nzLabel]="'settings.object-store.type.obs' | i18n"></nz-option>
|
||||
</nz-select>
|
||||
<nz-form-label nzFor="config.type">{{ 'settings.object-store.type' | i18n }}</nz-form-label>
|
||||
<nz-form-control [nzErrorTip]="'validation.required' | i18n">
|
||||
<nz-select
|
||||
[(ngModel)]="config.type"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
style="text-align: center; font-weight: bolder; margin-top: 4px"
|
||||
[nzDropdownStyle]="{ 'font-weight': 'bolder', 'font-size': 'larger' }"
|
||||
(ngModelChange)="onChange()"
|
||||
>
|
||||
<nz-option [nzValue]="ObjectStoreType.FILE" [nzLabel]="'settings.object-store.type.file' | i18n"></nz-option>
|
||||
<nz-option [nzValue]="ObjectStoreType.DATABASE" [nzLabel]="'settings.object-store.type.database' | i18n"></nz-option>
|
||||
<nz-option [nzValue]="ObjectStoreType.OBS" [nzLabel]="'settings.object-store.type.obs' | i18n"></nz-option>
|
||||
</nz-select>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item *ngIf="config.type == ObjectStoreType.OBS">
|
||||
<nz-form-label [nzSpan]="6" nzFor="obs.accessKey" nzRequired="true">{{
|
||||
'settings.object-store.obs.accessKey' | i18n
|
||||
}}</nz-form-label>
|
||||
<nz-form-label nzFor="obs.accessKey" nzRequired="true">{{ 'settings.object-store.obs.accessKey' | i18n }}</nz-form-label>
|
||||
<nz-form-control [nzErrorTip]="'validation.required' | i18n">
|
||||
<input
|
||||
[(ngModel)]="config.config.accessKey"
|
||||
@@ -52,9 +52,7 @@
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item *ngIf="config.type == ObjectStoreType.OBS">
|
||||
<nz-form-label [nzSpan]="6" nzFor="obs.secretKey" nzRequired="true">{{
|
||||
'settings.object-store.obs.secretKey' | i18n
|
||||
}}</nz-form-label>
|
||||
<nz-form-label nzFor="obs.secretKey" nzRequired="true">{{ 'settings.object-store.obs.secretKey' | i18n }}</nz-form-label>
|
||||
<nz-form-control [nzErrorTip]="'validation.required' | i18n">
|
||||
<input
|
||||
[(ngModel)]="config.config.secretKey"
|
||||
@@ -68,9 +66,7 @@
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item *ngIf="config.type == ObjectStoreType.OBS">
|
||||
<nz-form-label [nzSpan]="6" nzFor="obs.bucketName" nzRequired="true">{{
|
||||
'settings.object-store.obs.bucketName' | i18n
|
||||
}}</nz-form-label>
|
||||
<nz-form-label nzFor="obs.bucketName" nzRequired="true">{{ 'settings.object-store.obs.bucketName' | i18n }}</nz-form-label>
|
||||
<nz-form-control [nzErrorTip]="'validation.required' | i18n">
|
||||
<input
|
||||
[(ngModel)]="config.config.bucketName"
|
||||
@@ -84,9 +80,7 @@
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item *ngIf="config.type == ObjectStoreType.OBS">
|
||||
<nz-form-label [nzSpan]="6" nzFor="obs.endpoint" nzRequired="true">{{
|
||||
'settings.object-store.obs.endpoint' | i18n
|
||||
}}</nz-form-label>
|
||||
<nz-form-label nzFor="obs.endpoint" nzRequired="true">{{ 'settings.object-store.obs.endpoint' | i18n }}</nz-form-label>
|
||||
<nz-form-control [nzErrorTip]="'validation.required' | i18n">
|
||||
<input
|
||||
[(ngModel)]="config.config.endpoint"
|
||||
@@ -100,9 +94,7 @@
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item *ngIf="config.type == ObjectStoreType.OBS">
|
||||
<nz-form-label [nzSpan]="6" nzFor="obs.savePath" nzRequired="true">{{
|
||||
'settings.object-store.obs.savePath' | i18n
|
||||
}}</nz-form-label>
|
||||
<nz-form-label nzFor="obs.savePath" nzRequired="true">{{ 'settings.object-store.obs.savePath' | i18n }}</nz-form-label>
|
||||
<nz-form-control [nzErrorTip]="'validation.required' | i18n">
|
||||
<input
|
||||
[(ngModel)]="config.config.savePath"
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { ChangeDetectorRef, Component, Inject, OnInit } from '@angular/core';
|
||||
import { Component, Inject, OnInit, ViewChild } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { I18NService } from '@core';
|
||||
import { ALAIN_I18N_TOKEN } from '@delon/theme';
|
||||
import { NzNotificationService } from 'ng-zorro-antd/notification';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
|
||||
@@ -34,28 +36,19 @@ const key = 'oss';
|
||||
})
|
||||
export class ObjectStoreComponent implements OnInit {
|
||||
constructor(
|
||||
private cdr: ChangeDetectorRef,
|
||||
private notifySvc: NzNotificationService,
|
||||
private configService: GeneralConfigService,
|
||||
@Inject(DOCUMENT) private doc: any
|
||||
private notifySvc: NzNotificationService,
|
||||
@Inject(ALAIN_I18N_TOKEN) private i18nSvc: I18NService
|
||||
) {}
|
||||
|
||||
loading = true;
|
||||
config!: ObjectStore<any>;
|
||||
isObjectStoreModalVisible: boolean = false;
|
||||
@ViewChild('objectStoreForm', { static: false }) ruleForm: NgForm | undefined;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadObjectStore();
|
||||
}
|
||||
|
||||
onConfigObjectStore() {
|
||||
this.isObjectStoreModalVisible = true;
|
||||
}
|
||||
|
||||
onCancelObjectStore() {
|
||||
this.isObjectStoreModalVisible = false;
|
||||
}
|
||||
|
||||
loadObjectStore() {
|
||||
this.loading = true;
|
||||
let configInit$ = this.configService.getGeneralConfig(key).subscribe(
|
||||
@@ -81,6 +74,15 @@ export class ObjectStoreComponent implements OnInit {
|
||||
}
|
||||
|
||||
onSaveObjectStore() {
|
||||
if (this.ruleForm?.invalid) {
|
||||
Object.values(this.ruleForm.controls).forEach(control => {
|
||||
if (control.invalid) {
|
||||
control.markAsDirty();
|
||||
control.updateValueAndValidity({ onlySelf: true });
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
const configOk$ = this.configService
|
||||
.saveGeneralConfig(this.config, key)
|
||||
@@ -93,12 +95,13 @@ export class ObjectStoreComponent implements OnInit {
|
||||
.subscribe(
|
||||
message => {
|
||||
if (message.code === 0) {
|
||||
this.notifySvc.success(this.i18nSvc.fanyi('common.notify.apply-success'), '');
|
||||
} else {
|
||||
// this.notifySvc.error(this.i18nSvc.fanyi('common.notify.apply-fail'), message.msg);
|
||||
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.apply-fail'), message.msg);
|
||||
}
|
||||
},
|
||||
error => {
|
||||
// this.notifySvc.error(this.i18nSvc.fanyi('common.notify.apply-fail'), error.msg);
|
||||
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.apply-fail'), error.msg);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -115,6 +118,5 @@ export class ObjectStoreComponent implements OnInit {
|
||||
}
|
||||
};
|
||||
|
||||
protected readonly ObjectStore = ObjectStore;
|
||||
protected readonly ObjectStoreType = ObjectStoreType;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
<nz-option [nzValue]="'zh_CN'" [nzLabel]="'settings.system-config.locale.zh_CN' | i18n"></nz-option>
|
||||
<nz-option [nzValue]="'zh_TW'" [nzLabel]="'settings.system-config.locale.zh_TW' | i18n"></nz-option>
|
||||
<nz-option [nzValue]="'ja_JP'" [nzLabel]="'settings.system-config.locale.ja-JP' | i18n"></nz-option>
|
||||
<nz-option [nzValue]="'pt_BR'" [nzLabel]="'settings.system-config.locale.pt_BR' | i18n"></nz-option>
|
||||
</nz-select>
|
||||
</se>
|
||||
<se [label]="'settings.system-config.timezone' | i18n" [error]="'validation.required' | i18n">
|
||||
|
||||
@@ -210,15 +210,24 @@
|
||||
>
|
||||
<i nz-icon nzType="edit" nzTheme="outline"></i>
|
||||
</button>
|
||||
<button
|
||||
nz-button
|
||||
nzDanger
|
||||
(click)="onDeleteOneComponent(data.id)"
|
||||
nz-tooltip
|
||||
[nzTooltipTitle]="'status.component.delete' | i18n"
|
||||
>
|
||||
<i nz-icon nzType="delete" nzTheme="outline"></i>
|
||||
<button nz-button nz-dropdown [nzDropdownMenu]="more_menu">
|
||||
<span nz-icon nzType="ellipsis"></span>
|
||||
</button>
|
||||
<nz-dropdown-menu #more_menu="nzDropdownMenu">
|
||||
<ul nz-menu>
|
||||
<li nz-menu-item>
|
||||
<button
|
||||
nz-button
|
||||
nzDanger
|
||||
(click)="onDeleteOneComponent(data.id)"
|
||||
nz-tooltip
|
||||
[nzTooltipTitle]="'status.component.delete' | i18n"
|
||||
>
|
||||
<i nz-icon nzType="delete" nzTheme="outline"></i>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nz-dropdown-menu>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -297,15 +306,24 @@
|
||||
>
|
||||
<i nz-icon nzType="redo" nzTheme="outline"></i>
|
||||
</button>
|
||||
<button
|
||||
nz-button
|
||||
nzDanger
|
||||
(click)="onDeleteOneIncident(data.id)"
|
||||
nz-tooltip
|
||||
[nzTooltipTitle]="'status.incident.delete' | i18n"
|
||||
>
|
||||
<i nz-icon nzType="delete" nzTheme="outline"></i>
|
||||
<button nz-button nz-dropdown [nzDropdownMenu]="more_menu">
|
||||
<span nz-icon nzType="ellipsis"></span>
|
||||
</button>
|
||||
<nz-dropdown-menu #more_menu="nzDropdownMenu">
|
||||
<ul nz-menu>
|
||||
<li nz-menu-item>
|
||||
<button
|
||||
nz-button
|
||||
nzDanger
|
||||
(click)="onDeleteOneIncident(data.id)"
|
||||
nz-tooltip
|
||||
[nzTooltipTitle]="'status.incident.delete' | i18n"
|
||||
>
|
||||
<i nz-icon nzType="delete" nzTheme="outline"></i>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nz-dropdown-menu>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -42,8 +42,12 @@
|
||||
groupStyle="width: 250px;"
|
||||
[placeholder]="'label.search' | i18n"
|
||||
[(value)]="search"
|
||||
(valueChange)="loadTagsTable()"
|
||||
(keydown.enter)="loadTagsTable()"
|
||||
(cleared)="loadTagsTable()"
|
||||
/>
|
||||
<button nz-button nzType="primary" (click)="loadTagsTable()" class="mobile-hide">
|
||||
{{ 'common.search' | i18n }}
|
||||
</button>
|
||||
</ng-template>
|
||||
</app-toolbar>
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"alert.center.filter-priority": "Alert Priority",
|
||||
"alert.center.filter-status": "Alert Status",
|
||||
"alert.center.first-time": "Start Time",
|
||||
"alert.center.labels": "Tags",
|
||||
"alert.center.labels": "Labels",
|
||||
"alert.center.last-time": "Active Time",
|
||||
"alert.center.monitor": "Belong Monitor",
|
||||
"alert.center.no-deal": "Mark Pending",
|
||||
@@ -46,7 +46,6 @@
|
||||
"alert.export.use-type": "Export rules in {{type}} file format",
|
||||
"alert.group-converge.delete": "Delete Converge Strategy",
|
||||
"alert.group-converge.edit": "Edit Converge Strategy",
|
||||
"alert.group-converge.enable": "Enable Converge",
|
||||
"alert.group-converge.group-interval": "Interval Time",
|
||||
"alert.group-converge.group-interval.tip": "Minimum interval for sending grouped alert notifications, avoid too frequent notifications, default 5 minutes",
|
||||
"alert.group-converge.group-labels": "Group Labels",
|
||||
@@ -125,8 +124,8 @@
|
||||
"alert.notice.rule.period.no-limit": "Unlimited",
|
||||
"alert.notice.rule.priority": "Priority Match",
|
||||
"alert.notice.rule.priority.placeholder": "Select Priorities",
|
||||
"alert.notice.rule.tag": "Tag Match",
|
||||
"alert.notice.rule.tag.placeholder": "Select Tags",
|
||||
"alert.notice.rule.tag": "Label Match",
|
||||
"alert.notice.rule.tag.placeholder": "Select Labels",
|
||||
"alert.notice.rule.time": "Notification Time",
|
||||
"alert.notice.rule.time-end": "End time",
|
||||
"alert.notice.rule.time-start": "Start time",
|
||||
@@ -145,9 +144,19 @@
|
||||
"alert.notice.sender.sms.tencent.secretKey": "Tencent Sms SecretKey",
|
||||
"alert.notice.sender.sms.tencent.signName": "Tencent Sms SignName",
|
||||
"alert.notice.sender.sms.tencent.templateId": "Tencent Sms TemplateId",
|
||||
"alert.notice.sender.sms.alibaba.accessKeyId": "Alibaba SMS AccessKeyId",
|
||||
"alert.notice.sender.sms.alibaba.accessKeySecret": "Alibaba SMS AccessKeySecret",
|
||||
"alert.notice.sender.sms.alibaba.signName": "Alibaba SMS Sign Name",
|
||||
"alert.notice.sender.sms.alibaba.templateCode": "Alibaba SMS Template Code",
|
||||
"alert.notice.sender.sms.unisms.accessKeyId": "UniSMS AccessKeyId",
|
||||
"alert.notice.sender.sms.unisms.accessKeySecret": "UniSMS AccessKeySecret",
|
||||
"alert.notice.sender.sms.unisms.signature": "UniSMS Signature",
|
||||
"alert.notice.sender.sms.unisms.templateId": "UniSMS TemplateId",
|
||||
"alert.notice.sender.sms.unisms.authMode": "UniSMS Authentication Mode",
|
||||
"alert.notice.sender.sms.type": "Sms Type",
|
||||
"alert.notice.sender.sms.type.alibaba": "Alibaba Sms",
|
||||
"alert.notice.sender.sms.type.tencent": "Tencent Sms",
|
||||
"alert.notice.sender.sms.type.unisms": "UniSMS",
|
||||
"alert.notice.template": "Notice Template",
|
||||
"alert.notice.template.content": "Template Content",
|
||||
"alert.notice.template.delete": "Delete Template",
|
||||
@@ -301,8 +310,7 @@
|
||||
"alert.severity.all": "All Severity",
|
||||
"alert.silence.delete": "Delete Silence Strategy",
|
||||
"alert.silence.edit": "Edit Silence Strategy",
|
||||
"alert.silence.enable": "Enable Silence",
|
||||
"alert.silence.labels": "Tag Match",
|
||||
"alert.silence.labels": "Label Match",
|
||||
"alert.silence.match-all": "Match All",
|
||||
"alert.silence.name": "Silence Strategy Name",
|
||||
"alert.silence.new": "New Silence Strategy",
|
||||
@@ -424,11 +432,12 @@
|
||||
"common.copy.button": "Copy",
|
||||
"common.disable": "Disable",
|
||||
"common.edit": "Operate",
|
||||
"common.edit-time": "Update Time",
|
||||
"common.edit-time": "Edit Time",
|
||||
"common.enable": "Enable",
|
||||
"common.file.select": "Select File",
|
||||
"common.ignore": "Ignore",
|
||||
"common.mute": "Mute",
|
||||
"common.unmute": "Unmute",
|
||||
"common.name": "Metric Name",
|
||||
"common.new-time": "Create Time",
|
||||
"common.no": "No",
|
||||
@@ -764,6 +773,7 @@
|
||||
"settings.system-config.locale.zh_CN": "Simplified Chinese(zh_CN)",
|
||||
"settings.system-config.locale.zh_TW": "Traditional Chinese(zh_TW)",
|
||||
"settings.system-config.locale.ja-JP": "Japanese(ja_JP)",
|
||||
"settings.system-config.locale.pt_BR": "Portuguese(pt_BR)",
|
||||
"settings.system-config.ok": "Confirm Update",
|
||||
"settings.system-config.theme": "System Theme",
|
||||
"settings.system-config.theme.compact": "Compact Theme",
|
||||
@@ -789,7 +799,7 @@
|
||||
"status.component.state.0": "Normal",
|
||||
"status.component.state.1": "Abnormal",
|
||||
"status.component.state.2": "Unknown",
|
||||
"status.component.tag": "Match Tag",
|
||||
"status.component.tag": "Match Label",
|
||||
"status.component.tag.tip": "Status calculation associates the label, and uses all monitoring availability status associated with the label as data to calculate the service status of this component.",
|
||||
"status.help": "Quickly build a powerful status page based on HertzBeat to easily communicate the real-time status of your services to users. For example, the status page provided by Github <a href='https://www.githubstatus.com'> https://www.githubstatus.com</a>. <br>Supports linkage and synchronization of status page component status and monitoring status, fault event maintenance and management mechanism, etc. Improve your transparency, professionalism and user trust, and reduce communication costs.",
|
||||
"status.help.link": "https://hertzbeat.apache.org/docs/help/status",
|
||||
@@ -838,7 +848,7 @@
|
||||
"status.public.to-component": "Status Page",
|
||||
"status.public.to-incident": "Incident History",
|
||||
"status.public.today": "Today",
|
||||
"tag": "Tag",
|
||||
"tag": "Label",
|
||||
"validation.confirm-password.required": "Please confirm your password!",
|
||||
"validation.date.required": "Please select the start and end date",
|
||||
"validation.email.invalid": "Invalid email!",
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
"alert.export.use-type": "{{type}}ファイル形式で閾値ルールをエクスポート",
|
||||
"alert.group-converge.delete": "収束戦略を削除",
|
||||
"alert.group-converge.edit": "収束戦略を編集",
|
||||
"alert.group-converge.enable": "収束を有効化",
|
||||
"alert.group-converge.group-interval": "間隔時間",
|
||||
"alert.group-converge.group-interval.tip": "グループ化されたアラート通知を送信する最小間隔時間。通知が頻繁になりすぎないように、デフォルトは5分です",
|
||||
"alert.group-converge.group-labels": "グループラベル",
|
||||
@@ -145,9 +144,19 @@
|
||||
"alert.notice.sender.sms.tencent.secretKey": "Tencent Sms SecretKey",
|
||||
"alert.notice.sender.sms.tencent.signName": "Tencent Sms SignName",
|
||||
"alert.notice.sender.sms.tencent.templateId": "Tencent Sms TemplateId",
|
||||
"alert.notice.sender.sms.alibaba.accessKeyId": "Alibaba SMS AccessKeyId",
|
||||
"alert.notice.sender.sms.alibaba.accessKeySecret": "Alibaba SMS AccessKeySecret",
|
||||
"alert.notice.sender.sms.alibaba.signName": "Alibaba SMS SignName",
|
||||
"alert.notice.sender.sms.alibaba.templateCode": "Alibaba SMS TemplateCode",
|
||||
"alert.notice.sender.sms.unisms.accessKeyId": "UniSMS AccessKeyId",
|
||||
"alert.notice.sender.sms.unisms.accessKeySecret": "UniSMS AccessKeySecret",
|
||||
"alert.notice.sender.sms.unisms.signature": "UniSMS Signature",
|
||||
"alert.notice.sender.sms.unisms.templateId": "UniSMS TemplateId",
|
||||
"alert.notice.sender.sms.unisms.authMode": "UniSMS認証モード",
|
||||
"alert.notice.sender.sms.type": "SMSタイプ",
|
||||
"alert.notice.sender.sms.type.alibaba": "Alibaba Sms",
|
||||
"alert.notice.sender.sms.type.tencent": "Tencent Sms",
|
||||
"alert.notice.sender.sms.type.unisms": "UniSMS",
|
||||
"alert.notice.template": "通知テンプレート",
|
||||
"alert.notice.template.content": "テンプレート内容",
|
||||
"alert.notice.template.delete": "テンプレートを削除",
|
||||
@@ -301,7 +310,6 @@
|
||||
"alert.severity.all": "すべての重大度",
|
||||
"alert.silence.delete": "サイレンス戦略を削除",
|
||||
"alert.silence.edit": "サイレンス戦略を編集",
|
||||
"alert.silence.enable": "サイレンスを有効化",
|
||||
"alert.silence.labels": "タグ一致",
|
||||
"alert.silence.match-all": "すべて一致",
|
||||
"alert.silence.name": "サイレンス戦略名",
|
||||
@@ -424,11 +432,12 @@
|
||||
"common.copy.button": "コピー",
|
||||
"common.disable": "無効化",
|
||||
"common.edit": "操作",
|
||||
"common.edit-time": "更新時間",
|
||||
"common.edit-time": "編集時間",
|
||||
"common.enable": "有効化",
|
||||
"common.file.select": "ファイルを選択",
|
||||
"common.ignore": "無視",
|
||||
"common.mute": "ミュート",
|
||||
"common.unmute": "ミュート解除",
|
||||
"common.name": "メトリック名",
|
||||
"common.new-time": "作成時間",
|
||||
"common.no": "いいえ",
|
||||
@@ -764,6 +773,7 @@
|
||||
"settings.system-config.locale.zh_CN": "簡体字中国語(zh_CN)",
|
||||
"settings.system-config.locale.zh_TW": "繁体字中国語(zh_TW)",
|
||||
"settings.system-config.locale.ja-JP": "日本語(ja_JP)",
|
||||
"settings.system-config.locale.pt_BR": "Português(pt_BR)",
|
||||
"settings.system-config.ok": "更新を確認",
|
||||
"settings.system-config.theme": "システムテーマ",
|
||||
"settings.system-config.theme.compact": "コンパクトテーマ",
|
||||
|
||||
@@ -0,0 +1,770 @@
|
||||
{
|
||||
"menu": {
|
||||
"main": "Principal",
|
||||
"lang": "Idioma",
|
||||
"dashboard": "Painel",
|
||||
"search.placeholder": "Pesquisar: Tarefa de Monitoramento: Nome, Host, etc",
|
||||
"fullscreen": "Tela Cheia",
|
||||
"fullscreen.exit": "Sair",
|
||||
"clear.local.storage": "Limpar Armazenamento Local",
|
||||
"monitor": {
|
||||
"": "Monitoramento",
|
||||
"center": "Centro de Monitoramento",
|
||||
"bulletin": "Boletim",
|
||||
"service": "Monitor de Serviço",
|
||||
"db": "Monitor de Banco de Dados",
|
||||
"os": "Monitor de Sistema Operacional",
|
||||
"mid": "Monitor de Middleware",
|
||||
"cn": "Nativo da Nuvem",
|
||||
"network": "Monitor de Rede",
|
||||
"custom": "Monitor Personalizado",
|
||||
"prometheus": "Tarefa Prometheus",
|
||||
"program": "Monitor de Programa",
|
||||
"webserver": "Monitor de Servidor Web",
|
||||
"cache": "Monitor de Cache",
|
||||
"bigdata": "Monitor de Big Data",
|
||||
"promql": "Consulta de Dados",
|
||||
"llm": "AI LLM",
|
||||
"server": "Monitor de Servidor"
|
||||
},
|
||||
"account": {
|
||||
"": "Pessoal",
|
||||
"center": "Centro Pessoal",
|
||||
"settings": "Configurações da Conta",
|
||||
"security": "Configurações de Segurança",
|
||||
"binding": "Vinculação de Conta",
|
||||
"trigger": "Disparar Erro",
|
||||
"logout": "Sair"
|
||||
},
|
||||
"alert": {
|
||||
"": "Alertas",
|
||||
"center": "Centro de Alarmes",
|
||||
"converge": "Convergência de Alarmes",
|
||||
"setting": "Regra de Limite",
|
||||
"silence": "Silenciar Alarme",
|
||||
"dispatch": "Notificação"
|
||||
},
|
||||
"advanced": {
|
||||
"": "Avançado",
|
||||
"collector": "Cluster de Coletores",
|
||||
"tags": "Gerenciar Tags",
|
||||
"define": "Modelo de Monitoramento",
|
||||
"status": "Página de Status",
|
||||
"plugins": "Gerenciar Plugins"
|
||||
},
|
||||
"extras": {
|
||||
"": "Mais",
|
||||
"help": "Centro de Ajuda",
|
||||
"setting": "Configuração",
|
||||
"settings": "Configurações",
|
||||
"about": "Sobre"
|
||||
},
|
||||
"more": "Mais"
|
||||
},
|
||||
"monitor": {
|
||||
"": "Monitor",
|
||||
"availability": "Disponibilidade do Monitor",
|
||||
"name": "Nome da Tarefa",
|
||||
"name.tip": "Nome da tarefa de monitoramento",
|
||||
"host": "Host de Destino",
|
||||
"host.tip": "O IP ou domínio do peer monitorado",
|
||||
"description": "Descrição",
|
||||
"description.tip": "Descrição e observações",
|
||||
"intervals": "Intervalos",
|
||||
"intervals.tip": "Intervalo de tempo para coleta periódica de dados, em segundos",
|
||||
"collector": "Coletor",
|
||||
"collector.tip": "Escolha qual coletor despachar para este monitoramento",
|
||||
"collector.system.default": "Despacho Padrão do Sistema",
|
||||
"collector.status.online": "Online",
|
||||
"collector.status.offline": "Offline",
|
||||
"category": {
|
||||
"": "Categoria",
|
||||
"server": "Monitor de Servidor",
|
||||
"service": "Serviço",
|
||||
"db": "Banco de Dados",
|
||||
"os": "Sistema Operacional",
|
||||
"mid": "Middleware",
|
||||
"cn": "Nativo da Nuvem",
|
||||
"network": "Rede",
|
||||
"custom": "Personalizado",
|
||||
"program": "Aplicação",
|
||||
"webserver": "Servidor Web",
|
||||
"cache": "Cache",
|
||||
"bigdata": "Big Data",
|
||||
"auto": "Tarefa Automática"
|
||||
},
|
||||
"app": {
|
||||
"": "Tipo de Monitor",
|
||||
"website": "Monitor de Site",
|
||||
"api": "API HTTP",
|
||||
"http": "API HTTP",
|
||||
"ping": "Conexão PING",
|
||||
"port": "Porta Disponível",
|
||||
"mysql": "MySQL",
|
||||
"oracle": "Oracle",
|
||||
"redis": "Redis",
|
||||
"fullsite": "Monitor de Mapa do Site"
|
||||
},
|
||||
"status": {
|
||||
"": "Status da Tarefa",
|
||||
"all": "Todos os Status",
|
||||
"up": "Ativo",
|
||||
"down": "Inativo",
|
||||
"unreachable": "Inacessível",
|
||||
"paused": "Pausado"
|
||||
},
|
||||
"grafana": {
|
||||
"enabled.tip": "está habilitado, os dados de monitoramento serão exibidos no Grafana",
|
||||
"enabled.label": "Habilitar Grafana",
|
||||
"upload.tip": "Carregar arquivo de modelo do Grafana, suporta arquivo .json",
|
||||
"upload.label": "Carregar Modelo do Grafana"
|
||||
}
|
||||
},
|
||||
"alert": {
|
||||
"": "Alerta",
|
||||
"status": {
|
||||
"": "Status do Alerta",
|
||||
"all": "Todos os Status",
|
||||
"0": "Pendente",
|
||||
"2": "Restaurado",
|
||||
"3": "Processado"
|
||||
},
|
||||
"priority": {
|
||||
"": "Prioridade do Alarme",
|
||||
"all": "Todas as Prioridades",
|
||||
"0": "Emergência",
|
||||
"1": "Crítico",
|
||||
"2": "Aviso"
|
||||
}
|
||||
},
|
||||
"bulletin": {
|
||||
"new": "Novo Boletim",
|
||||
"edit": "Editar Boletim",
|
||||
"delete": "Excluir Boletim",
|
||||
"batch.delete": "Excluir Boletins em Lote",
|
||||
"name": "Nome do Boletim",
|
||||
"name.placeholder": "Digite um nome personalizado para o boletim",
|
||||
"monitor.type": "Tipo de Monitor",
|
||||
"monitor.name": "Nome da Tarefa de Monitoramento",
|
||||
"monitor.metrics": "Métricas de Monitoramento",
|
||||
"help.content": "Boletim de monitoramento personalizado (beta), exibindo métricas selecionadas de um monitor específico em forma de tabela",
|
||||
"help.link": ""
|
||||
},
|
||||
"question.link": "https://hertzbeat.apache.org/docs/help/issue/",
|
||||
"alert.setting.new": "Nova Regra de Limite",
|
||||
"alert.setting.edit": "Editar Regra de Limite",
|
||||
"alert.setting.delete": "Excluir Regra de Limite",
|
||||
"alert.setting.export": "Exportar Regra",
|
||||
"alert.setting.import": "Importar Regra",
|
||||
"alert.setting.target": "Métrica Alvo",
|
||||
"alert.setting.target.place-holder": "Pesquise ou selecione a métrica alvo",
|
||||
"alert.setting.expr": "Expressão de Disparo do Limite",
|
||||
"alert.setting.trigger": "Disparar alarmes e atualizar o status do monitor",
|
||||
"alert.setting.rule": "Regra de Limite",
|
||||
"alert.setting.number": "Numérico",
|
||||
"alert.setting.string": "Texto",
|
||||
"alert.setting.time": "Tempo",
|
||||
"alert.setting.rule.label": "Configuração gráfica de regras de limite de alarme, suporta múltiplas regras &&",
|
||||
"alert.setting.rule.metric.place-holder": "Selecione a métrica",
|
||||
"alert.setting.rule.switch-expr.0": "Limite de Modelo",
|
||||
"alert.setting.rule.switch-expr.1": "Limite de Codificação",
|
||||
"alert.setting.rule.operator": "Operador",
|
||||
"alert.setting.rule.operator.str-equals": "igual",
|
||||
"alert.setting.rule.operator.str-no-equals": "não igual",
|
||||
"alert.setting.rule.operator.str-contains": "contém",
|
||||
"alert.setting.rule.operator.str-no-contains": "não contém",
|
||||
"alert.setting.rule.operator.str-matches": "corresponde",
|
||||
"alert.setting.rule.operator.str-no-matches": "não corresponde",
|
||||
"alert.setting.rule.operator.exists": "valor existe",
|
||||
"alert.setting.rule.operator.no-exists": "valor não existe",
|
||||
"alert.setting.rule.string-value.place-holder": "Digite o texto",
|
||||
"alert.setting.rule.numeric-value.place-holder": "Digite o número",
|
||||
"alert.setting.times": "Número de Disparos",
|
||||
"alert.setting.times.tip": "Defina quantas vezes o limite deve ser disparado antes de enviar um alerta",
|
||||
"alert.setting.template": "Modelo de Notificação",
|
||||
"alert.setting.template.tip": "Variáveis de ambiente de modelo de notificação suportadas",
|
||||
"alert.setting.template.label": "O modelo de informação de notificação enviado após o disparo do alarme, veja as variáveis de ambiente do modelo acima",
|
||||
"alert.setting.template.example": "Digite o modelo de notificação. Ex: ${app}.${metrics}.${metric} valor está muito alto",
|
||||
"alert.setting.template.monitor-type": "Nome do Tipo de Monitor",
|
||||
"alert.setting.template.metrics-name": "Nome da Métrica",
|
||||
"alert.setting.template.metric-name": "Nome da Métrica",
|
||||
"alert.setting.template.metric-value": "Valor da Métrica",
|
||||
"alert.setting.template.other-value": "Outro Valor da Métrica",
|
||||
"alert.setting.default": "Padrão Global",
|
||||
"alert.setting.default.tip": "Se esta configuração de limite de alarme se aplica a todos os monitoramentos deste tipo globalmente",
|
||||
"alert.setting.enable": "Habilitar Limite",
|
||||
"alert.setting.enable.tip": "Esta configuração de limite de alarme está habilitada ou desabilitada",
|
||||
"alert.setting.recover-notice": "Notificação de Recuperação",
|
||||
"alert.setting.recover-notice.tip": "Se deve enviar a notificação correspondente quando o alarme for resolvido sob esta regra de limite",
|
||||
"alert.setting.connect": "Associar Monitores ao Limite de Alarme",
|
||||
"alert.setting.connect.left": "Não Associado",
|
||||
"alert.setting.connect.right": "Associado",
|
||||
"alert.setting.expr.tip": "Variáveis de ambiente e operadores suportados na expressão de disparo do limite",
|
||||
"alert.setting.expr.label": "Calcule e julgue se o limite foi disparado de acordo com esta expressão. As variáveis de ambiente e operadores da expressão são mostrados acima.",
|
||||
"alert.setting.expr.example": "Calcule se o limite foi disparado de acordo com esta expressão. Ex",
|
||||
"alert.setting.priority.tip": "O nível de alarme que dispara o limite, do baixo para o alto: Aviso, Crítico, Emergência",
|
||||
"alert.setting.target.tip": "O objeto métrico selecionado",
|
||||
"alert.setting.target.other": "Outros objetos métricos da linha",
|
||||
"alert.setting.target.system_value_row_count": "Contagem de linhas de valor do Sistema-Métricas",
|
||||
"alert.setting.operator": "Funções de operador suportadas",
|
||||
"alert.setting.search": "Pesquisar Limite",
|
||||
"alert.silence.new": "Nova Estratégia de Silêncio",
|
||||
"alert.silence.edit": "Editar Estratégia de Silêncio",
|
||||
"alert.silence.delete": "Excluir Estratégia de Silêncio",
|
||||
"alert.silence.name": "Nome da Estratégia de Silêncio",
|
||||
"alert.silence.match-all": "Corresponder a Todos",
|
||||
"alert.silence.priority": "Corresponder Prioridade",
|
||||
"alert.silence.type.once": "Silêncio Único",
|
||||
"alert.silence.type.cyc": "Silêncio Periódico",
|
||||
"alert.silence.type": "Tipo de Silêncio",
|
||||
"alert.silence.tags": "Corresponder Tags",
|
||||
"alert.silence.time": "Período de Silêncio",
|
||||
"alert.silence.times": "Número de Alertas Silenciados",
|
||||
"alert.silence.enable": "Habilitar Silêncio",
|
||||
"alert.converge.new": "Nova Estratégia de Convergência",
|
||||
"alert.converge.edit": "Editar Estratégia de Convergência",
|
||||
"alert.converge.delete": "Excluir Estratégia de Convergência",
|
||||
"alert.converge.name": "Nome da Estratégia",
|
||||
"alert.converge.match-all": "Corresponder a Todos",
|
||||
"alert.converge.priority": "Corresponder Prioridade",
|
||||
"alert.converge.tags": "Corresponder Tags",
|
||||
"alert.converge.repeat": "Critério de Repetição de Alerta",
|
||||
"alert.converge.repeat-rule": "As tags e a prioridade do alerta são as mesmas",
|
||||
"alert.converge.eval-interval": "Intervalo de Convergência de Repetição de Alerta (s)",
|
||||
"alert.converge.enable": "Habilitar Convergência",
|
||||
"alert.center.delete": "Excluir Alertas",
|
||||
"alert.center.clear": "Limpar Tudo",
|
||||
"alert.center.deal": "Marcar como Processado",
|
||||
"alert.center.no-deal": "Marcar como Pendente",
|
||||
"alert.center.search": "Pesquisar Conteúdo do Alerta",
|
||||
"alert.center.filter-status": "Status do Alerta",
|
||||
"alert.center.filter-priority": "Prioridade do Alerta",
|
||||
"alert.center.target": "Métrica Alvo",
|
||||
"alert.center.monitor": "Monitor Pertence",
|
||||
"alert.center.priority": "Prioridade",
|
||||
"alert.center.content": "Conteúdo do Alerta",
|
||||
"alert.center.tags": "Tags",
|
||||
"alert.center.status": "Status",
|
||||
"alert.center.time": "Hora do Alerta",
|
||||
"alert.center.time.tip": "Alertas foram disparados {{times}} vezes durante este período de alerta",
|
||||
"alert.center.first-time": "Hora de Início",
|
||||
"alert.center.last-time": "Última Hora",
|
||||
"alert.center.confirm.delete": "Confirme se deseja excluir!",
|
||||
"alert.center.confirm.clear-all": "Confirme se deseja limpar todos os alertas!",
|
||||
"alert.center.notify.no-mark": "Nenhum item selecionado para marcação!",
|
||||
"alert.center.confirm.mark-done-batch": "Confirme se deseja marcar como processado em lote!",
|
||||
"alert.center.confirm.mark-done": "Confirme se deseja marcar como processado!",
|
||||
"alert.center.confirm.mark-no-batch": "Confirme se deseja marcar como pendente em lote!",
|
||||
"alert.center.confirm.mark-no": "Confirme se deseja marcar como pendente!",
|
||||
"alert.help.notice": "A notificação é usada para configurar o destinatário da mensagem de alarme e o método de recebimento. A mensagem de alarme será enviada ao destinatário de forma especificada (suporta email, discord, webhook, etc). <a href='https://hertzbeat.apache.org/zh-cn/docs/help/alert_webhook'>Clique aqui para ver os passos de configuração.</a>.<br>“<i>Modelo de Notificação</i>” é o modelo de estrutura de conteúdo da mensagem. O modelo embutido é usado por padrão ou você pode personalizar o modelo para personalizar a estrutura de notificação da mensagem.<br><span class='help_module_span'>Nota⚠\uFE0F: Após configurar o “<i>Destinatário</i>”, você também precisa configurar a “<i>Política de Notificação</i>” para especificar quais mensagens são enviadas para quais destinatários.</span><a href='https://hertzbeat.apache.org/docs/help/alert_email'> Clique aqui para ver possíveis problemas</a>.",
|
||||
"alert.help.notice.link": "https://hertzbeat.apache.org/docs/help/alert_email",
|
||||
"alert.help.converge": "A Convergência de Alarmes suporta a deduplicação e convergência de mensagens de alarme repetidas dentro de um período de tempo especificado. <br> Clique em \"<i>Nova Estratégia de Convergência</i>\" e configure o período de tempo para evitar um grande número de alarmes repetitivos que podem anestesiar o destinatário do alarme.",
|
||||
"alert.help.converge.link": "https://hertzbeat.apache.org",
|
||||
"alert.help.center": "O Centro de Alarmes é o centro de processamento de notificações para todas as mensagens de alarme disparadas, incluindo alarmes disparados por limites internos do sistema e informações de alarme acessadas através de canais de alarme externos de terceiros. <br> O Hertzbeat suporta operações em lote, como consulta de alarmes, marcação de processamento, não processados, exclusão de alarmes e limpeza.",
|
||||
"alert.help.center.link": "https://hertzbeat.apache.org/docs/help/guide",
|
||||
"alert.help.setting": "As Regras de Limite são usadas para o gerenciamento de regras de limite de alarme para métricas. Clique em \"<i>Novo Limite</i>\" para configurar o limite de alarme para métricas de monitoramento. O Hertzbeat disparará alarmes com base no limite e nos dados das métricas.<br>Nota⚠\uFE0F: A mensagem de alarme que foi disparada pode ser verificada no [Centro de Alarmes], e você também pode configurar o método de notificação e os destinatários em [Notificação].",
|
||||
"alert.help.setting.link": "https://hertzbeat.apache.org/docs/help/alert_threshold",
|
||||
"alert.help.silence": "O gerenciamento de Silêncio de Alarmes é usado quando você não quer ser perturbado durante a manutenção do sistema ou nos fins de semana. <br> Clique em \"<i>Nova Estratégia de Silêncio</i>\" e configure o período de tempo para bloquear mensagens para que você não seja perturbado durante os intervalos.",
|
||||
"alert.help.silence.link": "https://hertzbeat.apache.org/docs",
|
||||
"alert.notice.template": "Modelo de Notificação",
|
||||
"alert.notice.template.new": "Novo Modelo",
|
||||
"alert.notice.template.edit": "Editar Modelo",
|
||||
"alert.notice.template.show": "Ver Conteúdo do Modelo",
|
||||
"alert.notice.template.delete": "Excluir Modelo",
|
||||
"alert.notice.template.name": "Nome do Modelo",
|
||||
"alert.notice.template.type": "Tipo de Notificação",
|
||||
"alert.notice.template.preset": "Tipo de Modelo",
|
||||
"alert.notice.template.preset.true": "Pré-definido pelo Sistema",
|
||||
"alert.notice.template.preset.false": "Personalizado pelo Usuário",
|
||||
"alert.notice.template.content": "Conteúdo do Modelo",
|
||||
"alert.notice.template.placeholder": "Selecione um modelo",
|
||||
"alert.notice.receiver": "Destinatário da Notificação",
|
||||
"alert.notice.receiver.new": "Novo Destinatário",
|
||||
"alert.notice.receiver.edit": "Editar Destinatário",
|
||||
"alert.notice.receiver.delete": "Excluir Destinatário",
|
||||
"alert.notice.receiver.people": "Destinatário",
|
||||
"alert.notice.receiver.people.placeholder": "Selecione um destinatário",
|
||||
"alert.notice.receiver.people.name": "Nome do Destinatário",
|
||||
"alert.notice.receiver.type": "Tipo de Notificação",
|
||||
"alert.notice.receiver.type.placeholder": "Selecione um tipo de notificação",
|
||||
"alert.notice.receiver.setting": "Configuração",
|
||||
"alert.notice.receiver.next": "Por favor, configure sua [Política de Notificação de Alerta] no próximo passo!",
|
||||
"alert.notice.type.sms": "SMS",
|
||||
"alert.notice.type.phone": "Telefone",
|
||||
"alert.notice.type.email": "Email",
|
||||
"alert.notice.type.userId": "ID do Usuário",
|
||||
"alert.notice.type.url": "URL",
|
||||
"alert.notice.type.wechat": "Abrir WeChat",
|
||||
"alert.notice.type.wechat-id": "WeChat OPENID",
|
||||
"alert.notice.type.WeCom-robot": "Robô WeCom",
|
||||
"alert.notice.type.WeCom-robot-key": "Chave do Robô WeCom",
|
||||
"alert.notice.type.access-token": "Token de Acesso do Robô",
|
||||
"alert.notice.type.ding": "Robô DingDing",
|
||||
"alert.notice.type.fei-shu": "Robô FeiShu",
|
||||
"alert.notice.type.fei-shu-key": "Chave do Robô FeiShu",
|
||||
"alert.notice.type.telegram-bot": "Bot do Telegram",
|
||||
"alert.notice.type.telegram-bot-token": "Token do Bot do Telegram",
|
||||
"alert.notice.type.telegram-bot-user-id": "ID do Usuário do Telegram",
|
||||
"alert.notice.type.slack": "WebHook do Slack",
|
||||
"alert.notice.type.slack-webHook-url": "URL do WebHook do Slack",
|
||||
"alert.notice.type.discord": "Bot do Discord",
|
||||
"alert.notice.type.discord-bot-token": "Token do Bot do Discord",
|
||||
"alert.notice.type.discord-channel-id": "ID do Canal do Discord",
|
||||
"alert.notice.type.WeComApp": "App WeCom",
|
||||
"alert.notice.type.WeComApp-corpId": "ID da Corporação do App WeCom",
|
||||
"alert.notice.type.WeComApp-agentId": "ID do App WeCom",
|
||||
"alert.notice.type.WeComApp-appSecret": "Segredo do App WeCom",
|
||||
"alert.notice.type.WeComApp-userId": "ID do Usuário (separado por |)",
|
||||
"alert.notice.type.WeComApp-partyId": "ID do Partido (separado por |)",
|
||||
"alert.notice.type.WeComApp-tagId": "ID da Tag (separado por |)",
|
||||
"alert.notice.type.smn": "SMN da Nuvem Huawei",
|
||||
"alert.notice.type.smn-ak": "AK",
|
||||
"alert.notice.type.smn-sk": "SK",
|
||||
"alert.notice.type.smn-projectId": "ID do Projeto",
|
||||
"alert.notice.type.smn-region": "Região",
|
||||
"alert.notice.type.smn-topicUrn": "TopicUrn",
|
||||
"alert.notice.type.serverchan": "ServerChan",
|
||||
"alert.notice.type.serverchan-token": "Token do ServerChan",
|
||||
"alert.notice.type.gotify": "Gotify",
|
||||
"alert.notice.type.gotify-token": "Token do Gotify",
|
||||
"alert.notice.rule": "Política de Notificação",
|
||||
"alert.notice.rule.new": "Nova Política de Notificação",
|
||||
"alert.notice.rule.edit": "Editar Política de Notificação",
|
||||
"alert.notice.rule.delete": "Excluir Política de Notificação",
|
||||
"alert.notice.rule.name": "Nome da Política",
|
||||
"alert.notice.rule.all": "Despachar Todos",
|
||||
"alert.notice.rule.enable": "Habilitar",
|
||||
"alert.notice.rule.tag": "Corresponder Tags",
|
||||
"alert.notice.rule.tag.placeholder": "Selecione Tags",
|
||||
"alert.notice.rule.priority": "Corresponder Prioridades",
|
||||
"alert.notice.rule.priority.placeholder": "Selecione Prioridades",
|
||||
"alert.notice.rule.period": "Período de Tempo",
|
||||
"alert.notice.rule.period-chose": "Escolher Data",
|
||||
"alert.notice.rule.period.no-limit": "Ilimitado",
|
||||
"alert.notice.rule.period.custom": "Personalizado",
|
||||
"alert.notice.rule.time": "Hora da Notificação",
|
||||
"alert.notice.rule.time-start": "Hora de Início",
|
||||
"alert.notice.rule.time-end": "Hora de Término",
|
||||
"alert.notice.send-test": "Enviar Mensagem de Teste de Alerta",
|
||||
"alert.notice.send-test.notify.success": "Envio de Teste de Alerta Bem-sucedido!",
|
||||
"alert.notice.send-test.notify.failed": "Envio de Teste de Alerta Falhou!",
|
||||
"alert.notice.sender.enable": "está Habilitado",
|
||||
"alert.notice.sender.mail.host": "Endereço do Servidor de Email",
|
||||
"alert.notice.sender.mail.username": "Conta de Email",
|
||||
"alert.notice.sender.mail.password": "Senha do Email",
|
||||
"alert.notice.sender.mail.port": "Porta do Email",
|
||||
"alert.notice.sender.mail.ssl": "Habilitar SSL",
|
||||
"alert.notice.sender.mail.starttls": "Habilitar STARTTLS",
|
||||
"alert.notice.sender.mail.enable": "Habilitar Configuração de Email",
|
||||
"alert.notice.sender.sms.type": "Tipo de SMS",
|
||||
"alert.notice.sender.sms.type.tencent": "SMS Tencent",
|
||||
"alert.notice.sender.sms.type.alibaba": "SMS Alibaba",
|
||||
"alert.notice.sender.sms.tencent.secretId": "SecretId do SMS Tencent",
|
||||
"alert.notice.sender.sms.tencent.secretKey": "SecretKey do SMS Tencent",
|
||||
"alert.notice.sender.sms.tencent.signName": "Nome de Assinatura do SMS Tencent",
|
||||
"alert.notice.sender.sms.tencent.appId": "AppId do SMS Tencent",
|
||||
"alert.notice.sender.sms.tencent.templateId": "ID do Modelo do SMS Tencent",
|
||||
"alert.export.switch-type": "Selecione o formato do arquivo de exportação!",
|
||||
"alert.export.use-type": "Exportar regras no formato de arquivo {{type}}",
|
||||
"dashboard.alerts.title": "Lista de Alarmes Recentes",
|
||||
"dashboard.alerts.title-no": "Alarmes Pendentes Recentes",
|
||||
"dashboard.alerts.no": "Nenhum Alarme Pendente",
|
||||
"dashboard.alerts.enter": "Ir para o Centro de Alarmes",
|
||||
"dashboard.alerts.distribute": "A Distribuição dos Alarmes",
|
||||
"dashboard.alerts.num": "Número de Alarmes",
|
||||
"dashboard.alerts.deal": "Tratamento de Alarmes",
|
||||
"dashboard.alerts.deal-percent": "Taxa de Tratamento",
|
||||
"dashboard.monitors.total": "Total de Monitores",
|
||||
"dashboard.monitors.title": "Visão Geral do Monitoramento",
|
||||
"dashboard.monitors.sub-title": "A Distribuição dos Monitores",
|
||||
"dashboard.monitors.formatter": " Monitores ",
|
||||
"dashboard.monitors.distribute": "Distribuição do Monitor",
|
||||
"menu.link.question": "FAQ",
|
||||
"menu.link.guild": "Guia do Usuário",
|
||||
"monitor_icon.center": "laptop",
|
||||
"monitor_icon.service": "appstore",
|
||||
"monitor_icon.db": "console-sql",
|
||||
"monitor_icon.os": "windows",
|
||||
"monitor_icon.mid": "cluster",
|
||||
"monitor_icon.cn": "cloud-server",
|
||||
"monitor_icon.network": "global",
|
||||
"monitor_icon.custom": "project",
|
||||
"monitor_icon.program": "code",
|
||||
"monitor_icon.cache": "group",
|
||||
"monitor_icon.bigdata": "dot-chart",
|
||||
"monitor_icon.webserver": "database",
|
||||
"monitors.center.help": "O Centro de Monitoramento é o portal de gerenciamento de recursos de monitoramento do HertzBeat. Exibe os monitores atualmente adicionados em forma de lista e suporta agrupamento por tags, filtragem de consulta e acesso para visualizar detalhes de monitoramento. <br> Você pode adicionar, modificar, excluir, pausar monitoramento, importar/exportar, gerenciar em lote e outras operações nos monitores.",
|
||||
"monitors.center.help.link": "https://hertzbeat.apache.org/docs/",
|
||||
"monitors.center.search.placeholder": "Pesquisar tipo de monitor para adicionar: Linux, Redis",
|
||||
"monitors.list": "Lista de Monitores",
|
||||
"monitors.spinning-tip.detecting": "Detecção Disponível",
|
||||
"monitors.new": "Novo",
|
||||
"monitors.new-monitor": "Novo Monitor",
|
||||
"monitors.new.success": "Novo Monitor Bem-sucedido",
|
||||
"monitors.new.failed": "Novo Monitor Falhou",
|
||||
"monitors.edit": "Editar",
|
||||
"monitors.edit.success": "Atualização do Monitor Bem-sucedida",
|
||||
"monitors.edit.failed": "Atualização do Monitor Falhou",
|
||||
"monitors.not-found": "Este Monitor Não Encontrado",
|
||||
"monitors.delete": "Excluir",
|
||||
"monitors.edit-monitor": "Editar Monitor",
|
||||
"monitors.delete-monitor": "Excluir Monitor",
|
||||
"monitors.enable": "Retomar Monitor",
|
||||
"monitors.cancel": "Pausar Monitor",
|
||||
"monitors.export": "Exportar Monitor",
|
||||
"monitors.export.switch-type": "Selecione o formato do arquivo de exportação!",
|
||||
"monitors.export.use-type": "Exportar monitores no formato de arquivo {{type}}",
|
||||
"monitors.import": "Importar Monitor",
|
||||
"monitors.search.placeholder": "Pesquisar Monitor",
|
||||
"monitors.search.tag": "Filtrar por Tag",
|
||||
"monitors.search.app": "Filtrar por Tipo",
|
||||
"monitors.total": "Total",
|
||||
"monitors.advanced": "Avançado",
|
||||
"monitors.advanced.tip": "Parâmetros de Configuração Avançada",
|
||||
"monitors.detect": "Detectar",
|
||||
"monitors.detect.success": "Detecção Bem-sucedida",
|
||||
"monitors.detect.failed": "Detecção Falhou",
|
||||
"monitors.detect.tip": "Verificar e detectar o status de disponibilidade do monitor",
|
||||
"monitors.detail.time-series.unavailable": "Incapaz de fornecer gráfico histórico, configure o banco de dados de séries temporais",
|
||||
"monitors.detail": "Detalhes do Monitor",
|
||||
"monitors.detail.auto-refresh": "Atualização Automática Após {{time}} s",
|
||||
"monitors.detail.config-refresh": "Definir Atualização Automática para {{time}} s",
|
||||
"monitors.detail.close-refresh": "Fechar Atualização Automática",
|
||||
"monitors.detail.show-basic": "Mostrar Básico do Monitor",
|
||||
"monitors.detail.name": "Nome",
|
||||
"monitors.detail.port": "Porta",
|
||||
"monitors.detail.description": "Descrição",
|
||||
"monitors.detail.status": "Status",
|
||||
"monitors.detail.basic": "Básico do Monitoramento",
|
||||
"monitors.detail.realtime": "Detalhes em Tempo Real do Monitor",
|
||||
"monitors.detail.history": "Detalhes do Gráfico Histórico do Monitor",
|
||||
"monitors.collect.time": "Tempo de Coleta",
|
||||
"monitors.collect.time.tip": "Último Tempo de Coleta",
|
||||
"monitors.detail.chart.zoom": "Ampliar",
|
||||
"monitors.detail.chart.back": "Restaurar Zoom",
|
||||
"monitors.detail.chart.save": "Salvar como Imagem",
|
||||
"monitors.detail.chart.query-1h": "Consultar 1 Hora",
|
||||
"monitors.detail.chart.query-6h": "Consultar 6 Horas",
|
||||
"monitors.detail.chart.query-1d": "Consultar 1 Dia",
|
||||
"monitors.detail.chart.query-1w": "Consultar 1 Semana",
|
||||
"monitors.detail.chart.query-1m": "Consultar 1 Mês",
|
||||
"monitors.detail.chart.query-3m": "Consultar 3 Meses",
|
||||
"monitors.detail.chart.no-data": "Nenhum Dado de Métrica",
|
||||
"monitors.detail.chart.unit": "Unidade",
|
||||
"monitors.detail.value.null": "Nenhum Valor",
|
||||
"common.name": "Nome da Métrica",
|
||||
"common.value": "Valor da Métrica",
|
||||
"common.search": "Pesquisar",
|
||||
"common.refresh": "Atualizar",
|
||||
"common.notice": "Notificação",
|
||||
"common.ignore": "Ignorar",
|
||||
"common.edit-time": "Tempo de Atualização",
|
||||
"common.new-time": "Tempo de Criação",
|
||||
"common.edit": "Operar",
|
||||
"common.total": "Total",
|
||||
"common.yes": "Sim",
|
||||
"common.no": "Não",
|
||||
"common.enable": "Habilitar",
|
||||
"common.disable": "Desabilitar",
|
||||
"common.copy": "Copiar para a Área de Transferência",
|
||||
"common.copy.button": "Copiar",
|
||||
"common.notify.no-select-edit": "Nenhum item selecionado para edição!",
|
||||
"common.notify.one-select-edit": "Apenas uma seleção pode ser editada!",
|
||||
"common.confirm.delete": "Confirme se deseja excluir!",
|
||||
"common.notify.no-select-delete": "Nenhum item selecionado para exclusão!",
|
||||
"common.notify.no-select-export": "Nenhum item selecionado para exportação!",
|
||||
"common.confirm.delete-batch": "Confirme se deseja excluir em lote!",
|
||||
"common.notify.delete-success": "Exclusão Bem-sucedida!",
|
||||
"common.notify.delete-fail": "Exclusão Falhou!",
|
||||
"common.notify.new-success": "Adição Bem-sucedida!",
|
||||
"common.notify.new-fail": "Adição Falhou!",
|
||||
"common.notify.apply-success": "Aplicação Bem-sucedida!",
|
||||
"common.notify.apply-fail": "Aplicação Falhou!",
|
||||
"common.notify.operate-success": "Operação Bem-sucedida!",
|
||||
"common.notify.operate-fail": "Operação Falhou!",
|
||||
"common.notify.monitor-fail": "Consulta do Monitor Falhou!",
|
||||
"common.notify.edit-success": "Edição Bem-sucedida!",
|
||||
"common.notify.edit-fail": "Edição Falhou!",
|
||||
"common.notify.no-select-cancel": "Nenhum item selecionado para cancelamento!",
|
||||
"common.confirm.cancel-batch": "Confirme se deseja cancelar o monitor em lote!",
|
||||
"common.confirm.cancel": "Confirme se deseja cancelar o monitor!",
|
||||
"common.notify.cancel-success": "Cancelamento Bem-sucedido!",
|
||||
"common.notify.cancel-fail": "Cancelamento Falhou!",
|
||||
"common.notify.mark-success": "Marca Bem-sucedida!",
|
||||
"common.notify.mark-fail": "Marca Falhou!",
|
||||
"common.notify.no-select-enable": "Nenhum item selecionado para habilitar!",
|
||||
"common.confirm.enable-batch": "Confirme se deseja habilitar o monitor em lote!",
|
||||
"common.confirm.enable": "Confirme se deseja habilitar o monitor!",
|
||||
"common.notify.enable-success": "Habilitação Bem-sucedida!",
|
||||
"common.notify.enable-fail": "Habilitação Falhou!",
|
||||
"common.confirm.clear-cache": "Confirme se deseja limpar o cache!",
|
||||
"common.notify.clear-success": "Limpeza Bem-sucedida!",
|
||||
"common.notify.clear-fail": "Limpeza Falhou!",
|
||||
"common.notify.export-success": "Exportação Bem-sucedida!",
|
||||
"common.notify.export-fail": "Exportação Falhou!",
|
||||
"common.notify.import-success": "Importação Bem-sucedida!",
|
||||
"common.notify.import-fail": "Importação Falhou!",
|
||||
"common.notify.copy-success": "Cópia Bem-sucedida!",
|
||||
"common.button.ok": "OK",
|
||||
"common.button.cancel": "Cancelar",
|
||||
"common.button.return": "Retornar",
|
||||
"common.button.help": "Ajuda",
|
||||
"common.button.edit": "Editar",
|
||||
"common.button.setting": "Configuração",
|
||||
"common.button.delete": "Excluir",
|
||||
"common.button.detect": "Detectar",
|
||||
"common.week.7": "Domingo",
|
||||
"common.week.1": "Segunda-feira",
|
||||
"common.week.2": "Terça-feira",
|
||||
"common.week.3": "Quarta-feira",
|
||||
"common.week.4": "Quinta-feira",
|
||||
"common.week.5": "Sexta-feira",
|
||||
"common.week.6": "Sábado",
|
||||
"common.time.unit.second": "Segundos",
|
||||
"common.file.select": "Selecionar Arquivo",
|
||||
"validation.email.invalid": "Email inválido!",
|
||||
"validation.phone.invalid": "Número de telefone inválido!",
|
||||
"validation.verification-code.invalid": "Código de verificação inválido, deve ter 6 dígitos!",
|
||||
"validation.required": "Por favor, preencha os campos obrigatórios! ",
|
||||
"app.theme.default": "Tema Claro",
|
||||
"app.theme.dark": "Tema Escuro",
|
||||
"app.theme.compact": "Tema Compacto",
|
||||
"app.role.admin": "administrador",
|
||||
"app.lock": "Desbloquear",
|
||||
"app.lock.placeholder": "Digite qualquer coisa para desbloquear",
|
||||
"app.passport.desc": "Um Sistema de Monitoramento em Tempo Real de Código Aberto",
|
||||
"app.passport.intro-1": "Código Aberto, Distribuído",
|
||||
"app.passport.intro-2": "Monitoramento em Tempo Real",
|
||||
"app.login.message-need-identifier": "Por favor, insira seu nome de usuário",
|
||||
"app.login.message-need-credential": "Por favor, insira a senha",
|
||||
"app.login.message-invalid-credentials": "Nome de usuário ou senha inválidos",
|
||||
"app.login.need-change-password": "Por favor, atualize a senha inicial padrão em tempo hábil!",
|
||||
"app.login.tab-login-credentials": "Entrar no HertzBeat",
|
||||
"app.login.remember-me": "Lembrar de mim",
|
||||
"app.login.login": "Entrar",
|
||||
"app.login.notify": "Por favor, faça login!",
|
||||
"app.login.explore.cloud": "Explorar Nuvem",
|
||||
"app.login.explore.cloud.detail": "Clique para Explorar a Nuvem HertzBeat",
|
||||
"tag.new": "Nova Tag",
|
||||
"tag.edit": "Editar Tag",
|
||||
"tag.search": "Pesquisar Tag",
|
||||
"tag.delete": "Excluir Tag",
|
||||
"tag": "Tag",
|
||||
"tag.setting": "Tags",
|
||||
"tag.id": "ID",
|
||||
"tag.name": "Nome da Tag",
|
||||
"tag.value": "Valor da Tag",
|
||||
"tag.color": "Cor",
|
||||
"tag.description": "Descrição",
|
||||
"tag.update-time": "Tempo de Atualização",
|
||||
"tag.display": "Exibir",
|
||||
"tag.bind": "Vincular Tags",
|
||||
"tag.bind.tip": "Você pode usar tags para gerenciamento de classificação. Ex: atribuir tags a recursos em ambiente de produção e teste.",
|
||||
"tag.help": "As tags estão em todos os lugares no HertzBeat. Podemos aplicar tags no agrupamento de recursos, correspondência de tags sob regras e outros. [Gerenciamento de Tags] é usado para gerenciamento unificado de tags, incluindo adição, exclusão, edição, etc. <br>Você pode usar tags para classificar e gerenciar recursos de monitoramento, como vincular etiquetas para ambientes de produção e teste separadamente.",
|
||||
"tag.help.link": "https://hertzbeat.apache.org/zh-cn/docs/",
|
||||
"plugin.help": "No HertzBeat, podemos usar o mecanismo de plugin para realizar algumas outras operações após o alarme, exceto notificação. O gerenciamento de plugins é usado para gerenciamento unificado de plugins, incluindo upload e operações de habilitar/desabilitar.<br>Por exemplo, você pode usar o mecanismo de plugin para executar scripts específicos ou SQL após a ocorrência do alarme.",
|
||||
"plugin.help.link": "https://hertzbeat.apache.org/docs/help/plugin",
|
||||
"plugin.upload": "Carregar Plugin",
|
||||
"plugin.name": "Nome do Plugin",
|
||||
"plugin.type": "Tipo do Plugin",
|
||||
"plugin.status": "Status Habilitado",
|
||||
"plugin.jar.file": "Arquivo Jar",
|
||||
"plugin.delete": "Excluir Plugin",
|
||||
"plugin.type.POST_ALERT": "PÓS ALERTA",
|
||||
"plugin.type.POST_COLLECT": "PÓS COLETA",
|
||||
"plugin.search": "Pesquisar plugins",
|
||||
"plugin.edit": "Editar plugin",
|
||||
"plugin.param.edit": "Editar Parâmetros",
|
||||
"define.help": "Os modelos de monitoramento definem cada tipo de monitoramento, variável de parâmetro, informações de métricas, protocolo de coleta, etc. Você pode selecionar um modelo de monitoramento existente no menu suspenso e fazer modificações de acordo com suas próprias necessidades. A área inferior esquerda é a área de comparação e a área inferior direita é o local de edição. <br> Você também pode clicar em \"Novo Tipo de Monitor\" para definir um novo tipo personalizado. Atualmente, os protocolos suportados incluem<a href='https://hertzbeat.apache.org/zh-cn/docs/advanced/extend-http'> HTTP</a>, <a href='https://hertzbeat.apache.org/zh-cn/docs/advanced/extend-jdbc'>JDBC</a>, <a href='https://hertzbeat.apache.org/zh-cn/docs/advanced/extend-ssh'>SSH</a>, <a href='https://hertzbeat.apache.org/zh-cn/docs/advanced/extend-jmx'>JMX</a>, <a href='https://hertzbeat.apache.org/zh-cn/docs/advanced/extend-snmp'> SNMP</a>. <a class='help_module_content' href='https://hertzbeat.apache.org/zh-cn/docs/template'>Modelos de Monitoramento</a>.",
|
||||
"define.help.link": "https://hertzbeat.apache.org/zh-cn/docs/advanced/extend-point/",
|
||||
"define.save-apply": "Salvar e Aplicar",
|
||||
"define.delete": "Excluir {{app}}",
|
||||
"define.delete.confirm": "Confirme se deseja excluir o tipo de monitoramento {{app}}? Este tipo de monitoramento não poderá ser adicionado após a exclusão.",
|
||||
"define.new": "Novo Tipo de Monitor",
|
||||
"define.new.code": "# Por favor, defina um novo tipo de monitoramento escrevendo o conteúdo YML aqui, consulte o documento: https://hertzbeat.apache.org/docs/advanced/extend-point ",
|
||||
"define.save-apply.no-code": "O conteúdo da definição do tipo de monitoramento não pode estar vazio.",
|
||||
"define.save-apply.confirm": "Confirme se deseja atualizar e aplicar a definição do monitor? Isso afetará o que você monitora.",
|
||||
"define.hide-true.tip": "Não exibido no menu principal, se deseja exibi-lo",
|
||||
"define.hide-true.confirm": "Confirme se deseja exibir este menu?",
|
||||
"define.hide-false.tip": "Já exibido no menu principal, se deseja ocultá-lo",
|
||||
"define.hide-false.confirm": "Confirme se deseja ocultar este menu?",
|
||||
"settings.server": "Configuração do Servidor de Mensagens",
|
||||
"settings.server.email": "Servidor de Email",
|
||||
"settings.server.email.setting": "Configurar Servidor de Email",
|
||||
"settings.server.sms": "Servidor de SMS",
|
||||
"settings.server.sms.setting": "Configurar Servidor de SMS",
|
||||
"settings.system-config": "Configuração do Sistema",
|
||||
"settings.system-config.locale": "Idioma do Sistema",
|
||||
"settings.system-config.locale.zh_CN": "Chinês Simplificado(zh_CN)",
|
||||
"settings.system-config.locale.zh_TW": "Chinês Tradicional(zh_TW)",
|
||||
"settings.system-config.locale.en_US": "Inglês(en_US)",
|
||||
"settings.system-config.locale.pt_BR": "Português(pt_BR)",
|
||||
"settings.system-config.timezone": "Fuso Horário do Sistema",
|
||||
"settings.system-config.theme": "Tema do Sistema",
|
||||
"settings.system-config.theme.default": "Tema Padrão",
|
||||
"settings.system-config.theme.dark": "Tema Escuro",
|
||||
"settings.system-config.theme.compact": "Tema Compacto",
|
||||
"settings.system-config.ok": "Confirmar Atualização",
|
||||
"settings.object-store": "Configuração do Servidor de Arquivos",
|
||||
"settings.object-store.type": "Provedor do Servidor de Arquivos",
|
||||
"settings.object-store.type.file": "Arquivo local (padrão)",
|
||||
"settings.object-store.type.database": "Banco de dados local",
|
||||
"settings.object-store.type.obs": "HUAWEI CLOUD OBS",
|
||||
"settings.object-store.obs.accessKey": "AccessKey",
|
||||
"settings.object-store.obs.accessKey.placeholder": "Access Key ID da HUAWEI CLOUD",
|
||||
"settings.object-store.obs.secretKey": "SecretKey",
|
||||
"settings.object-store.obs.secretKey.placeholder": "Access Key Secret da HUAWEI CLOUD",
|
||||
"settings.object-store.obs.bucketName": "Bucket",
|
||||
"settings.object-store.obs.bucketName.placeholder": "O nome do bucket que você criou na HUAWEI CLOUD OBS",
|
||||
"settings.object-store.obs.endpoint": "EndPoint",
|
||||
"settings.object-store.obs.endpoint.placeholder": "Domínio da HUAWEI CLOUD OBS, excluindo o nome do bucket",
|
||||
"settings.object-store.obs.savePath": "Caminho de Salvamento",
|
||||
"settings.object-store.obs.savePath.placeholder": "Caminho para salvar o arquivo de backup, O valor padrão é hertzbeat.",
|
||||
"collector": "Coletor",
|
||||
"collector.name": "Nome do Coletor",
|
||||
"collector.name.placeholder": "Por favor, configure o nome único do coletor",
|
||||
"collector.status": "Status Online",
|
||||
"collector.mode": "Modo de Execução",
|
||||
"collector.mode.public": "Cluster Público",
|
||||
"collector.mode.private": "Nuvem-Private Edge",
|
||||
"collector.task": "Total de Tarefas",
|
||||
"collector.start-time": "Hora de Início",
|
||||
"collector.ip": "Endereço IP",
|
||||
"collector.version": "Versão",
|
||||
"collector.node": "Nome do Nó",
|
||||
"collector.pinned": "Tarefas Fixadas",
|
||||
"collector.dispatched": "Tarefas Despachadas",
|
||||
"collector.delete": "Excluir Coletor",
|
||||
"collector.deploy": "Implantar Coletor",
|
||||
"collector.deploy.identity": "Token do Coletor (IDENTIDADE)",
|
||||
"collector.deploy.identity.tip": "Por favor, note que este token é único, mantenha-o seguro.",
|
||||
"collector.deploy.ok": "Gerar Token e Comandos de Implantação",
|
||||
"collector.deploy.close": "Fechar - Eu Salvei o Token",
|
||||
"collector.deploy.docker": "Implantar via Docker",
|
||||
"collector.deploy.docker.help": "# Execute o seguinte comando no ambiente Docker",
|
||||
"collector.deploy.docker.help.1": "# docker run -d : Inicie um contêiner em execução em segundo plano através do Docker",
|
||||
"collector.deploy.docker.help.2": "# -e IDENTITY=xxx : Defina o token de identidade do coletor",
|
||||
"collector.deploy.docker.help.3": "# -e MANAGER_HOST=127.0.0.1 : Defina o endereço do serviço principal do HertzBeat para conexão",
|
||||
"collector.deploy.docker.help.4": "# -e MODE=public : Defina o modo de execução (público ou privado), cluster público ou nuvem-private edge.",
|
||||
"collector.deploy.docker.help.5": "# --name hertzbeat-collector: Nomeie o contêiner como hertzbeat-collector",
|
||||
"collector.deploy.docker.help.6": "# apache/hertzbeat-collector: Fonte oficial da imagem do aplicativo, ou use quay.io/tancloud/hertzbeat-collector quando houver timeout",
|
||||
"collector.deploy.package": "Implantar via Pacote",
|
||||
"collector.deploy.package.github": "Baixar do Github",
|
||||
"collector.deploy.package.gitee": "Baixar do Gitee",
|
||||
"collector.deploy.package.help": "# Baixe o pacote de lançamento correspondente ao sistema hertzbeat-collector-xx.tar.gz",
|
||||
"collector.deploy.package.help.1": "# Descompacte e configure o arquivo hertzbeat-collector/config/application.yml",
|
||||
"collector.deploy.package.help.2": "# Substitua os parâmetros IDENTITY MANAGER_HOST MODE no arquivo como segue.",
|
||||
"collector.deploy.package.help.3": "# Execute bin/startup.sh ou startup.bat(windows) para iniciar o coletor.",
|
||||
"collector.online": "Coletor Online",
|
||||
"collector.offline": "Coletor Offline",
|
||||
"collector.confirm.online": "Confirme se deseja colocar este coletor online!",
|
||||
"collector.confirm.offline": "Confirme se deseja colocar este coletor offline!",
|
||||
"collector.confirm.online-batch": "Confirme se deseja colocar o coletor online em lote!",
|
||||
"collector.confirm.offline-batch": "Confirme se deseja colocar o coletor offline em lote!",
|
||||
"collector.notify.no-select-online": "Nenhum item selecionado para coletor online!",
|
||||
"collector.notify.no-select-offline": "Nenhum item selecionado para coletor offline!",
|
||||
"collector.help": "O Cluster de Coletores é usado para gerenciar nós de cluster de coletores registrados, exibir o status atual do coletor e a distribuição de tarefas de agendamento, e suporta operações como implantação, exclusão, offline de coletores.<br>Além de usar o coletor embutido, você pode registrar vários coletores para uso em cenários de <strong>Cluster de Alto Desempenho</strong> ou <strong>Colaboração Nuvem-Edge</strong>.",
|
||||
"collector.help.link": "https://hertzbeat.apache.org/docs/help/guide",
|
||||
"about.title": "Um sistema de monitoramento em tempo real de código aberto, sem agente, cluster, compatível com prometheus, personalizado e página de status.",
|
||||
"about.point.1": "Monitoramento-alerta-notificação tudo em um, suporta web, banco de dados, sistema operacional, middleware, rede, etc.",
|
||||
"about.point.2": "Fácil de usar, operações totalmente baseadas na web com apenas um clique do mouse.",
|
||||
"about.point.3": "Capacidades poderosas de modelo de monitoramento, monitoramento personalizado de qualquer métrica que você desejar.",
|
||||
"about.point.4": "Alto desempenho, suporta cluster de coletores, rede multi-isolada e nuvem-edge.",
|
||||
"about.point.5": "Regras de limite de alarme flexíveis, notificação oportuna via discord, slack, telegram, etc.",
|
||||
"about.point.6": "Construa facilmente uma página de status poderosa para comunicar o status em tempo real aos usuários.",
|
||||
"about.help": "A poderosa personalização do HertzBeat, suporte a vários tipos, alto desempenho e fácil expansão, visa ajudar os usuários a construir rapidamente seu próprio sistema de monitoramento.",
|
||||
"about.github": "Github",
|
||||
"about.gitee": "Gitee",
|
||||
"about.issue": "Feedback",
|
||||
"about.pr": "Contribuir",
|
||||
"about.discuss": "Discutir",
|
||||
"about.doc": "Documento",
|
||||
"about.upgrade": "Atualizar",
|
||||
"about.star": "Estrela",
|
||||
"status.page": "Página de Status",
|
||||
"status.org.name": "Nome da Organização",
|
||||
"status.org.name.tip": "O nome da equipe da organização exibido na página de status, como TanCloud",
|
||||
"status.org.desc": "Introdução",
|
||||
"status.org.desc.tip": "Introdução às informações da organização exibidas na página de status",
|
||||
"status.org.logo": "Imagem do Logo",
|
||||
"status.org.logo.tip": "Por favor, configure o endereço URL da imagem do logo da organização, sugere svg",
|
||||
"status.org.home": "Link do Site",
|
||||
"status.org.home.tip": "Por favor, configure o endereço da página inicial do site da organização",
|
||||
"status.org.feedback": "Link de Feedback",
|
||||
"status.org.feedback.tip": "Por favor, configure o endereço de contato de feedback dos usuários",
|
||||
"status.org.color": "Cor do Tema",
|
||||
"status.org.color.tip": "Por favor, configure a cor do tema da página de status",
|
||||
"status.org.state": "Estado da Organização",
|
||||
"status.public.feedback": "FEEDBACK DE PROBLEMAS",
|
||||
"status.public.org.state.0": "Todos os Sistemas Operacionais",
|
||||
"status.public.org.state.1": "Alguns Sistemas Anormais",
|
||||
"status.public.org.state.2": "Todos os Sistemas Anormais",
|
||||
"status.public.today": "Hoje",
|
||||
"status.public.30-day": "30 dias atrás",
|
||||
"status.public.power-by": "Desenvolvido por Apache HertzBeat. Dê-nos uma estrela!",
|
||||
"status.public.to-incident": "Histórico de Incidentes",
|
||||
"status.public.to-component": "Página de Status",
|
||||
"status.incident": "Incidente",
|
||||
"status.incident.name": "Nome do Incidente",
|
||||
"status.incident.name.tip": "Por favor, descreva brevemente o título do incidente atual",
|
||||
"status.incident.history": "Histórico de Incidentes",
|
||||
"status.incident.state": "Status do Incidente",
|
||||
"status.incident.state.0": "Investigando",
|
||||
"status.incident.state.1": "Identificado",
|
||||
"status.incident.state.2": "Monitorando",
|
||||
"status.incident.state.3": "Resolvido",
|
||||
"status.incident.message": "Publicar Mensagem",
|
||||
"status.incident.message.tip.0": "Estamos investigando este incidente, por favor, verifique novamente mais tarde para atualizações.",
|
||||
"status.incident.message.tip.1": "Confirmamos a causa da falha e estamos processando.",
|
||||
"status.incident.message.tip.2": "Estamos monitorando este evento e observando o efeito do processamento",
|
||||
"status.incident.message.tip.3": "Este incidente foi resolvido, obrigado pela compreensão e apoio.",
|
||||
"status.incident.message-latest": "Última Mensagem",
|
||||
"status.incident.component": "Componente Afetado",
|
||||
"status.incident.new": "Publicar Incidente",
|
||||
"status.incident.update": "Atualizar Incidente",
|
||||
"status.incident.delete": "Excluir Incidente",
|
||||
"status.incident.public.start-at": "Iniciar em",
|
||||
"status.incident.public.update-at": "Atualizar em",
|
||||
"status.incident.public.process-time": "Tempo de Processamento",
|
||||
"status.component": "Componente",
|
||||
"status.component.name": "Componente de Serviço",
|
||||
"status.component.name.tip": "Configurar o nome do componente de serviço exibido, como Gateway",
|
||||
"status.component.desc": "Descrição do Componente",
|
||||
"status.component.desc.tip": "Configurar informações de descrição do componente de serviço",
|
||||
"status.component.state": "Estado do Componente",
|
||||
"status.component.new": "Novo Componente",
|
||||
"status.component.edit": "Editar Componente",
|
||||
"status.component.delete": "Excluir Componente",
|
||||
"status.component.tag": "Corresponder Tag",
|
||||
"status.component.tag.tip": "O cálculo do status associa a etiqueta e usa todos os status de disponibilidade de monitoramento associados à etiqueta como dados para calcular o status do serviço deste componente.",
|
||||
"status.component.method": "Modo de Cálculo do Status",
|
||||
"status.component.method.tip": "A forma de calcular o status do serviço do componente é calcular automaticamente a disponibilidade com base no monitoramento de associação de tags ou configurar manualmente o status.",
|
||||
"status.component.method.0": "Cálculo Automático",
|
||||
"status.component.method.1": "Configuração Manual",
|
||||
"status.component.config-state": "Estado de Configuração",
|
||||
"status.component.config-state.tip": "Quando o modo de cálculo do status é manual, o estado do serviço do componente configurado.",
|
||||
"status.component.state.0": "Normal",
|
||||
"status.component.state.1": "Anormal",
|
||||
"status.component.state.2": "Desconhecido",
|
||||
"status.component.notify.need-org": "Por favor, configure as informações da sua organização primeiro!",
|
||||
"status.help": "Construa rapidamente uma página de status poderosa com base no HertzBeat para comunicar facilmente o status em tempo real dos seus serviços aos usuários. Por exemplo, a página de status fornecida pelo Github <a href='https://www.githubstatus.com'> https://www.githubstatus.com</a>. <br>Suporta vinculação e sincronização do status do componente da página de status e status de monitoramento, manutenção e gerenciamento de eventos de falha, etc. Melhore sua transparência, profissionalismo e confiança do usuário, e reduza os custos de comunicação.",
|
||||
"status.help.link": "https://hertzbeat.apache.org/docs/help/status",
|
||||
"validation.email.required": "Por favor, insira seu email!",
|
||||
"validation.email.wrong-format": "O endereço de email está no formato errado!",
|
||||
"validation.password.required": "Por favor, insira sua senha!",
|
||||
"validation.password.twice": "As senhas inseridas duas vezes não coincidem!",
|
||||
"validation.password.strength.msg": "Por favor, insira pelo menos 6 caracteres e não use senhas fáceis de adivinhar.",
|
||||
"validation.password.strength.strong": "Força: forte",
|
||||
"validation.password.strength.medium": "Força: média",
|
||||
"validation.password.strength.short": "Força: muito curta",
|
||||
"validation.confirm-password.required": "Por favor, confirme sua senha!",
|
||||
"validation.phone-number.required": "Por favor, insira seu número de telefone!",
|
||||
"validation.phone-number.wrong-format": "Número de telefone mal formatado!",
|
||||
"validation.verification-code.required": "Por favor, insira o código de verificação!",
|
||||
"validation.title.required": "Por favor, insira um título",
|
||||
"validation.date.required": "Por favor, selecione a data de início e término",
|
||||
"validation.goal.required": "Por favor, insira uma descrição do objetivo",
|
||||
"validation.standard.required": "Por favor, insira uma métrica",
|
||||
"expand": "Expandir",
|
||||
"collapse": "Recolher"
|
||||
}
|
||||
@@ -46,7 +46,6 @@
|
||||
"alert.export.use-type": "以 {{type}} 文件格式导出阈值规则",
|
||||
"alert.group-converge.delete": "删除分组策略",
|
||||
"alert.group-converge.edit": "编辑分组策略",
|
||||
"alert.group-converge.enable": "启用分组策略",
|
||||
"alert.group-converge.group-interval": "间隔时间",
|
||||
"alert.group-converge.group-interval.tip": "发送分组告警通知的最小时间间隔,避免告警通知过于频繁,默认5分钟",
|
||||
"alert.group-converge.group-labels": "分组标签",
|
||||
@@ -145,9 +144,19 @@
|
||||
"alert.notice.sender.sms.tencent.secretKey": "腾讯短信SecretKey",
|
||||
"alert.notice.sender.sms.tencent.signName": "腾讯短信SignName",
|
||||
"alert.notice.sender.sms.tencent.templateId": "腾讯短信TemplateId",
|
||||
"alert.notice.sender.sms.alibaba.accessKeyId": "阿里短信AccessKeyId",
|
||||
"alert.notice.sender.sms.alibaba.accessKeySecret": "阿里短信AccessKeySecret",
|
||||
"alert.notice.sender.sms.alibaba.signName": "阿里短信SignName",
|
||||
"alert.notice.sender.sms.alibaba.templateCode": "阿里短信TemplateCode",
|
||||
"alert.notice.sender.sms.unisms.accessKeyId": "合一短信AccessKeyId",
|
||||
"alert.notice.sender.sms.unisms.accessKeySecret": "合一短信AccessKeySecret",
|
||||
"alert.notice.sender.sms.unisms.signature": "合一短信Signature",
|
||||
"alert.notice.sender.sms.unisms.templateId": "合一短信TemplateId",
|
||||
"alert.notice.sender.sms.unisms.authMode": "合一短信鉴权方式",
|
||||
"alert.notice.sender.sms.type": "短信类型",
|
||||
"alert.notice.sender.sms.type.alibaba": "阿里短信",
|
||||
"alert.notice.sender.sms.type.tencent": "腾讯短信",
|
||||
"alert.notice.sender.sms.type.unisms": "合一短信(UniSMS)",
|
||||
"alert.notice.template": "通知模板",
|
||||
"alert.notice.template.content": "模板内容",
|
||||
"alert.notice.template.delete": "删除通知模板",
|
||||
@@ -301,7 +310,6 @@
|
||||
"alert.severity.all": "全部级别",
|
||||
"alert.silence.delete": "删除静默策略",
|
||||
"alert.silence.edit": "编辑静默策略",
|
||||
"alert.silence.enable": "启用静默策略",
|
||||
"alert.silence.labels": "匹配标签",
|
||||
"alert.silence.match-all": "应用所有",
|
||||
"alert.silence.name": "策略名称",
|
||||
@@ -424,11 +432,12 @@
|
||||
"common.copy.button": "复制",
|
||||
"common.disable": "关闭",
|
||||
"common.edit": "操作",
|
||||
"common.edit-time": "更新时间",
|
||||
"common.enable": "是否启用",
|
||||
"common.edit-time": "编辑时间",
|
||||
"common.enable": "启用状态",
|
||||
"common.file.select": "选择文件",
|
||||
"common.ignore": "忽略",
|
||||
"common.mute": "静音",
|
||||
"common.unmute": "取消静音",
|
||||
"common.name": "指标名",
|
||||
"common.new-time": "创建时间",
|
||||
"common.no": "否",
|
||||
@@ -764,6 +773,7 @@
|
||||
"settings.system-config.locale.zh_CN": "简体中文(zh_CN)",
|
||||
"settings.system-config.locale.zh_TW": "繁体中文(zh_TW)",
|
||||
"settings.system-config.locale.ja-JP": "日语(ja_JP)",
|
||||
"settings.system-config.locale.pt_BR": "Português(pt_BR)",
|
||||
"settings.system-config.ok": "确认更新",
|
||||
"settings.system-config.theme": "系统主题",
|
||||
"settings.system-config.theme.compact": "紧凑主题",
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
"alert.export.use-type": "以 {{type}} 文件格式導出阈值規則",
|
||||
"alert.group-converge.delete": "刪除收斂策略",
|
||||
"alert.group-converge.edit": "編輯收斂策略",
|
||||
"alert.group-converge.enable": "啟用收斂策略",
|
||||
"alert.group-converge.group-interval": "間隔時間",
|
||||
"alert.group-converge.group-interval.tip": "發送分組告警通知的最小時間間隔,避免告警通知過於頻繁,默認5分鐘",
|
||||
"alert.group-converge.group-labels": "分組標籤",
|
||||
@@ -145,9 +144,19 @@
|
||||
"alert.notice.sender.sms.tencent.secretKey": "騰訊短訊SecretKey",
|
||||
"alert.notice.sender.sms.tencent.signName": "騰訊短訊SignName",
|
||||
"alert.notice.sender.sms.tencent.templateId": "騰訊短訊TemplateId",
|
||||
"alert.notice.sender.sms.alibaba.accessKeyId": "阿里短訊AccessKeyId",
|
||||
"alert.notice.sender.sms.alibaba.accessKeySecret": "阿里短訊AccessKeySecret",
|
||||
"alert.notice.sender.sms.alibaba.signName": "阿里短訊SignName",
|
||||
"alert.notice.sender.sms.alibaba.templateCode": "阿里短訊TemplateCode",
|
||||
"alert.notice.sender.sms.unisms.accessKeyId": "合一簡訊AccessKeyId",
|
||||
"alert.notice.sender.sms.unisms.accessKeySecret": "合一簡訊AccessKeySecret",
|
||||
"alert.notice.sender.sms.unisms.signature": "合一簡訊Signature",
|
||||
"alert.notice.sender.sms.unisms.templateId": "合一簡訊TemplateId",
|
||||
"alert.notice.sender.sms.unisms.authMode": "合一簡訊驗證方式",
|
||||
"alert.notice.sender.sms.type": "騰訊類型",
|
||||
"alert.notice.sender.sms.type.alibaba": "阿裏短訊",
|
||||
"alert.notice.sender.sms.type.tencent": "騰訊短訊",
|
||||
"alert.notice.sender.sms.type.unisms": "合一簡訊(UniSMS)",
|
||||
"alert.notice.template": "通知模板",
|
||||
"alert.notice.template.content": "模板内容",
|
||||
"alert.notice.template.delete": "刪除通知模板",
|
||||
@@ -301,7 +310,6 @@
|
||||
"alert.severity.all": "全部級別",
|
||||
"alert.silence.delete": "刪除靜默策略",
|
||||
"alert.silence.edit": "編輯靜默策略",
|
||||
"alert.silence.enable": "啟用靜默策略",
|
||||
"alert.silence.labels": "匹配標籤",
|
||||
"alert.silence.match-all": "應用所有",
|
||||
"alert.silence.name": "策略名稱",
|
||||
@@ -424,11 +432,12 @@
|
||||
"common.copy.button": "複製",
|
||||
"common.disable": "關閉",
|
||||
"common.edit": "操作",
|
||||
"common.edit-time": "更新時間",
|
||||
"common.edit-time": "編輯時間",
|
||||
"common.enable": "開啓",
|
||||
"common.file.select": "選擇文件",
|
||||
"common.ignore": "忽略",
|
||||
"common.mute": "靜音",
|
||||
"common.unmute": "取消靜音",
|
||||
"common.name": "指標名",
|
||||
"common.new-time": "創建時間",
|
||||
"common.no": "否",
|
||||
@@ -764,6 +773,7 @@
|
||||
"settings.system-config.locale.zh_CN": "簡體中文(zh_CN)",
|
||||
"settings.system-config.locale.zh_TW": "繁體中文(zh_TW)",
|
||||
"settings.system-config.locale.ja-JP": "日語(ja_JP)",
|
||||
"settings.system-config.locale.pt_BR": "Português(pt_BR)",
|
||||
"settings.system-config.ok": "確認更新",
|
||||
"settings.system-config.theme": "系統主題",
|
||||
"settings.system-config.theme.compact": "緊湊主題",
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
@primary-color: #3f51b5;
|
||||
@secondary-color: #8c8c8c;
|
||||
@highlight-blue: #d6e4ff;
|
||||
@border-color: #d9d9d9;
|
||||
@success-green: #52c41a;
|
||||
@error-red: #f5222d;
|
||||
@font-size-base: 14px;
|
||||
|
||||
Reference in New Issue
Block a user