Compare commits

...
Author SHA1 Message Date
tomsun28 bf21538062 [improve] common metric data query design
Signed-off-by: tomsun28 <tomsun28@outlook.com>
2025-02-28 16:36:19 +08:00
tomsun28 3688974610 [bugfix] fix alert sse illegal state exception (#3106) 2025-02-25 15:27:53 +08:00
af35043663 [feature] adding-ptBR-translation (#3098)
Co-authored-by: shown <yuluo08290126@gmail.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-02-25 09:37:21 +08:00
Jastandshown 84c28229b2 [Feature] Add deepseek Api Monitor (#3096)
Co-authored-by: shown <yuluo08290126@gmail.com>
2025-02-24 15:39:53 +08:00
tomsun28 1a3e614209 [webapp] fix web oom crash when backend api can not access (#3100) 2025-02-23 21:22:37 +08:00
Jast 3df0e56da9 [Improve] Message notification prompt optimization (#3095) 2025-02-23 21:19:10 +08:00
Jast b5b4771e1a [Doc]Improve openai doc (#3097) 2025-02-22 10:30:25 +08:00
b8c5ae4d40 [bugfix] kafka client detect error (#3088)
Co-authored-by: Jast <shenghang@apache.org>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-02-21 22:05:12 +08:00
淞筱 207f2d958c [doc] correct home's new_committer_process (#3094) 2025-02-21 20:59:51 +08:00
Nick Guo 398a40bba4 [improve] optimize kafka collect test (#3093) 2025-02-21 17:47:46 +08:00
yunfan24andtomsun28 bb14681621 [feature] supports alibabacloud sms. (#3084)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-02-21 13:55:30 +08:00
yunfan24 d3a080fbaf [improve] Improve and unify the search. (#3085) 2025-02-20 14:58:56 +08:00
tomsun28 ce6f9feac6 [webapp] update and fix alert ui when theme dark (#3082) 2025-02-16 23:25:04 +08:00
yunfan24 7d18118bbd [feature] SMS notification supports unisms. (#3077) 2025-02-15 22:40:03 +08:00
yunfan24andaias00 d5d2b6bc71 [doc] Update the SMS configuration document. (#3073)
Co-authored-by: aias00 <rokkki@163.com>
2025-02-13 20:32:48 +08:00
Logicandtomsun28 e11a4da2ac [improve](web-app): update monitor chart configuration and springboot GreptimeDB version (#3071)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-02-13 10:19:49 +08:00
71 changed files with 3071 additions and 493 deletions
@@ -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);
}
}
}
@@ -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;
}
@@ -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";
}
@@ -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());
@@ -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());
}
}
@@ -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;
}
}
@@ -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&timestamp=%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();
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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";
}
@@ -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;
}
}
@@ -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;
}
@@ -37,5 +37,7 @@ public class SmsNoticeSender {
private SmsAlibabaConfig alibaba;
private SmsUniSmsConfig unisms;
private boolean enable = true;
}
@@ -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 categoryservice-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.*'
@@ -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)));
}
}
@@ -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);
}
@@ -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();
}
}
+1 -1
View File
@@ -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:
+35
View File
@@ -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. |
+62 -38
View File
@@ -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
![image](https://github.com/apache/hertzbeat/assets/40455946/3a4c287d-b23d-4398-8562-4894296af485)
> 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
```
![image](https://github.com/apache/hertzbeat/assets/40455946/face71a6-46d5-452c-bed3-59d2a975afeb)
3. Create an application (app-id) in Tencent Cloud SMS
![image](https://github.com/apache/hertzbeat/assets/40455946/2732d710-37fa-4455-af64-48bba273c2f8)
4. Obtain Tencent Cloud Access Management credentials (secret-id, secret-key)
![image](https://github.com/apache/hertzbeat/assets/40455946/36f056f0-94e7-43db-8f07-82893c98024e)
## 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
```
@@ -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)
![image](https://github.com/apache/hertzbeat/assets/40455946/3a4c287d-b23d-4398-8562-4894296af485)
```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
![image](https://github.com/apache/hertzbeat/assets/40455946/3a4c287d-b23d-4398-8562-4894296af485)
```text
监控:{1},告警级别:{2}。内容:{3}
```
2. 腾讯云短信创建正文模板(template-id
![image](https://github.com/apache/hertzbeat/assets/40455946/face71a6-46d5-452c-bed3-59d2a975afeb)
```text
监控:{1},告警级别:{2}。内容:{3}
```
1.3 腾讯云短信创建应用(app-id)
![image](https://github.com/apache/hertzbeat/assets/40455946/2732d710-37fa-4455-af64-48bba273c2f8)
![image](https://github.com/apache/hertzbeat/assets/40455946/face71a6-46d5-452c-bed3-59d2a975afeb)
1.4 腾讯云访问管理(secret-id、secret-key
![image](https://github.com/apache/hertzbeat/assets/40455946/36f056f0-94e7-43db-8f07-82893c98024e)
3. 腾讯云短信创建应用(app-id
![image](https://github.com/apache/hertzbeat/assets/40455946/2732d710-37fa-4455-af64-48bba273c2f8)
2. 配置告警自定义参数
4. 腾讯云访问管理(secret-id、secret-key
![image](https://github.com/apache/hertzbeat/assets/40455946/36f056f0-94e7-43db-8f07-82893c98024e)
```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
```
+2 -2
View File
@@ -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>
+22
View File
@@ -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');
});
});
});
+18 -2
View File
@@ -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();
};
}
}
+3 -4
View File
@@ -18,9 +18,8 @@
*/
export class AlibabaSmsConfig {
secretId!: string;
secretKey!: string;
accessKeyId!: string;
accessKeySecret!: string;
signName!: string;
appId!: string;
templateId!: string;
templateCode!: string;
}
+7 -4
View File
@@ -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;
+26
View File
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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">
@@ -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>
+19 -9
View File
@@ -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",
+13 -3
View File
@@ -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": "コンパクトテーマ",
+770
View File
@@ -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"
}
+14 -4
View File
@@ -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": "紧凑主题",
+13 -3
View File
@@ -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": "緊湊主題",
-1
View File
@@ -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;