Compare commits

..
57 changed files with 1584 additions and 464 deletions
@@ -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
@@ -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";
}
@@ -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:
+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:
+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:
@@ -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
```
+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:
+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
@@ -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>
@@ -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;
@@ -19,7 +19,7 @@
import { Component, Inject, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { I18NService, StartupService } from '@core';
import { I18NService } from '@core';
import { ALAIN_I18N_TOKEN, TitleService } from '@delon/theme';
import { NzNotificationService } from 'ng-zorro-antd/notification';
import { switchMap } from 'rxjs/operators';
@@ -32,7 +32,6 @@ import { Param } from '../../../pojo/Param';
import { ParamDefine } from '../../../pojo/ParamDefine';
import { AppDefineService } from '../../../service/app-define.service';
import { CollectorService } from '../../../service/collector.service';
import { GeneralConfigService } from '../../../service/general-config.service';
import { MonitorService } from '../../../service/monitor.service';
import { generateReadableRandomString } from '../../../shared/utils/common-util';
@@ -60,8 +59,6 @@ export class MonitorNewComponent implements OnInit {
private route: ActivatedRoute,
private router: Router,
private notifySvc: NzNotificationService,
private configService: GeneralConfigService,
private startUpSvc: StartupService,
@Inject(ALAIN_I18N_TOKEN) private i18nSvc: I18NService,
private titleSvc: TitleService,
private collectorSvc: CollectorService
@@ -171,19 +168,13 @@ export class MonitorNewComponent implements OnInit {
};
this.spinningTip = 'Loading...';
this.isSpinning = true;
this.monitorSvc.newMonitor(addMonitor).subscribe(
message => {
this.isSpinning = false;
if (message.code === 0) {
this.configService.updateAppTemplateConfig({ hide: false }, info.monitor.app).subscribe(() => {
this.startUpSvc.loadConfigResourceViaHttp().subscribe(() => {
this.isSpinning = false;
this.notifySvc.success(this.i18nSvc.fanyi('monitor.new.success'), '');
this.router.navigateByUrl(`/monitors?app=${info.monitor.app}`);
});
});
this.notifySvc.success(this.i18nSvc.fanyi('monitor.new.success'), '');
this.router.navigateByUrl(`/monitors?app=${info.monitor.app}`);
} else {
this.isSpinning = false;
this.notifySvc.error(this.i18nSvc.fanyi('monitor.new.failed'), message.msg);
}
},
@@ -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>
@@ -266,7 +266,7 @@ export class DefineComponent implements OnInit {
.subscribe(
message => {
if (message.code === 0) {
this.updateLocalAppState(app, hide);
this.loadMenus();
this.startUpSvc.loadConfigResourceViaHttp().subscribe(() => {});
this.notifySvc.success(this.i18nSvc.fanyi('common.notify.apply-success'), '');
} else {
@@ -279,18 +279,6 @@ export class DefineComponent implements OnInit {
);
}
private updateLocalAppState(app: string, hide: boolean): void {
this.appMenusArr.forEach(([category, menuData]) => {
if (menuData.child) {
menuData.child.forEach((item: any) => {
if (item.value === app) {
item.hide = hide;
}
});
}
});
}
renderCategoryName(category: string): string {
let label = this.i18nSvc.fanyi(`menu.monitor.${category}`);
if (label == `menu.monitor.${category}`) {
@@ -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;
}
@@ -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>
+11 -3
View File
@@ -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",
@@ -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,7 +310,6 @@
"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.match-all": "Match All",
"alert.silence.name": "Silence Strategy Name",
@@ -424,7 +432,7 @@
"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",
+11 -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,7 +432,7 @@
"common.copy.button": "コピー",
"common.disable": "無効化",
"common.edit": "操作",
"common.edit-time": "更新時間",
"common.edit-time": "編集時間",
"common.enable": "有効化",
"common.file.select": "ファイルを選択",
"common.ignore": "無視",
+12 -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,8 +432,8 @@
"common.copy.button": "复制",
"common.disable": "关闭",
"common.edit": "操作",
"common.edit-time": "更新时间",
"common.enable": "是否启用",
"common.edit-time": "编辑时间",
"common.enable": "启用状态",
"common.file.select": "选择文件",
"common.ignore": "忽略",
"common.mute": "静音",
+11 -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,7 +432,7 @@
"common.copy.button": "複製",
"common.disable": "關閉",
"common.edit": "操作",
"common.edit-time": "更新時間",
"common.edit-time": "編輯時間",
"common.enable": "開啓",
"common.file.select": "選擇文件",
"common.ignore": "忽略",
-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;