mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-09-17 18:19:02 +00:00
Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcb8beed50 | ||
|
|
39582f34e1 | ||
|
|
7cd302ca19 | ||
|
|
d14bfa227d | ||
|
|
265ca270b1 | ||
|
|
56a4d41fd4 | ||
|
|
f3050662f6 | ||
|
|
6c2710bf66 | ||
|
|
56ef35c4f9 | ||
|
|
811a07553b | ||
|
|
9fd58ced4a | ||
|
|
a331c045ae | ||
|
|
63d3d18428 | ||
|
|
cec8d8b396 | ||
|
|
9d68d16a78 | ||
|
|
290474a715 | ||
|
|
0dbc7347b4 | ||
|
|
e2a2d5eaec | ||
|
|
356cca295d | ||
|
|
5e09c6d9ae | ||
|
|
54caa84fc1 |
@@ -2325,6 +2325,42 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "JuJinPark",
|
||||
"name": "jujin",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/44892459?v=4",
|
||||
"profile": "https://github.com/JuJinPark",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "LL-LIN",
|
||||
"name": "LL-LIN",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/43002118?v=4",
|
||||
"profile": "https://github.com/LL-LIN",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "bigcyy",
|
||||
"name": "Yang Chen",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/73413979?v=4",
|
||||
"profile": "https://bigcyy.github.io/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sarthakeash",
|
||||
"name": "Sarthak Arora",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/74091160?v=4",
|
||||
"profile": "https://github.com/sarthakeash",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
@@ -31,6 +31,8 @@ name: "CodeQL"
|
||||
on:
|
||||
schedule:
|
||||
- cron: '21 13 * * 4'
|
||||
push:
|
||||
branches: [ action* ]
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
|
||||
@@ -19,7 +19,7 @@ name: DOC CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, dev ]
|
||||
branches: [ master, dev, action* ]
|
||||
paths:
|
||||
- 'home/**'
|
||||
pull_request:
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
|
||||
name: 'HertzBeat Doc Pdf Builder'
|
||||
on:
|
||||
page_build
|
||||
push:
|
||||
branches: [ page_build* ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -22,7 +22,7 @@ name: Frontend CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, dev ]
|
||||
branches: [ master, dev, action* ]
|
||||
paths:
|
||||
- 'web-app/**'
|
||||
pull_request:
|
||||
|
||||
@@ -22,7 +22,7 @@ name: License Checker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, dev ]
|
||||
branches: [ master, dev, action* ]
|
||||
pull_request:
|
||||
branches: [ master, dev ]
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
run: |
|
||||
mvn clean install
|
||||
mvn clean package -Prelease -DskipTests
|
||||
cd collector
|
||||
cd hertzbeat-collector
|
||||
mvn clean package -Pcluster -DskipTests
|
||||
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
|
||||
@@ -510,6 +510,12 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yasminvo"><img src="https://avatars.githubusercontent.com/u/107528848?v=4?s=100" width="100px;" alt="yasminvo"/><br /><sub><b>yasminvo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yasminvo" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yyahang"><img src="https://avatars.githubusercontent.com/u/90464876?v=4?s=100" width="100px;" alt="yyahang"/><br /><sub><b>yyahang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yyahang" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JuJinPark"><img src="https://avatars.githubusercontent.com/u/44892459?v=4?s=100" width="100px;" alt="jujin"/><br /><sub><b>jujin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LL-LIN"><img src="https://avatars.githubusercontent.com/u/43002118?v=4?s=100" width="100px;" alt="LL-LIN"/><br /><sub><b>LL-LIN</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LL-LIN" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://bigcyy.github.io/"><img src="https://avatars.githubusercontent.com/u/73413979?v=4?s=100" width="100px;" alt="Yang Chen"/><br /><sub><b>Yang Chen</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sarthakeash"><img src="https://avatars.githubusercontent.com/u/74091160?v=4?s=100" width="100px;" alt="Sarthak Arora"/><br /><sub><b>Sarthak Arora</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -509,6 +509,12 @@ Thanks these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yasminvo"><img src="https://avatars.githubusercontent.com/u/107528848?v=4?s=100" width="100px;" alt="yasminvo"/><br /><sub><b>yasminvo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yasminvo" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yyahang"><img src="https://avatars.githubusercontent.com/u/90464876?v=4?s=100" width="100px;" alt="yyahang"/><br /><sub><b>yyahang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yyahang" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JuJinPark"><img src="https://avatars.githubusercontent.com/u/44892459?v=4?s=100" width="100px;" alt="jujin"/><br /><sub><b>jujin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LL-LIN"><img src="https://avatars.githubusercontent.com/u/43002118?v=4?s=100" width="100px;" alt="LL-LIN"/><br /><sub><b>LL-LIN</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LL-LIN" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://bigcyy.github.io/"><img src="https://avatars.githubusercontent.com/u/73413979?v=4?s=100" width="100px;" alt="Yang Chen"/><br /><sub><b>Yang Chen</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sarthakeash"><img src="https://avatars.githubusercontent.com/u/74091160?v=4?s=100" width="100px;" alt="Sarthak Arora"/><br /><sub><b>Sarthak Arora</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -73,6 +73,11 @@
|
||||
"field": "successCode",
|
||||
"type": 4,
|
||||
"paramValue": "200, 201"
|
||||
},
|
||||
{
|
||||
"field": "enableUrlEncoding",
|
||||
"type": 1,
|
||||
"paramValue": true
|
||||
}
|
||||
]
|
||||
}
|
||||
+9
@@ -17,30 +17,39 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Alibaba Cloud SMS configuration properties
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AlibabaSmsProperties {
|
||||
/**
|
||||
* Alibaba Cloud access key id
|
||||
*/
|
||||
@NotBlank(message = "AccessKeyId cannot be empty")
|
||||
private String accessKeyId;
|
||||
|
||||
/**
|
||||
* Alibaba Cloud access key secret
|
||||
*/
|
||||
@NotBlank(message = "AccessKeySecret cannot be empty")
|
||||
private String accessKeySecret;
|
||||
|
||||
/**
|
||||
* SMS signature
|
||||
*/
|
||||
@NotBlank(message = "SignName cannot be empty")
|
||||
private String signName;
|
||||
|
||||
/**
|
||||
* SMS template Code
|
||||
*/
|
||||
@NotBlank(message = "TemplateCode cannot be null")
|
||||
private String templateCode;
|
||||
}
|
||||
|
||||
+4
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
@@ -27,16 +28,19 @@ public class AwsSmsProperties {
|
||||
/**
|
||||
* AWS Cloud access key id
|
||||
*/
|
||||
@NotBlank(message = "AccessKeyId cannot be empty")
|
||||
private String accessKeyId;
|
||||
|
||||
/**
|
||||
* AWS Cloud access key secret
|
||||
*/
|
||||
@NotBlank(message = "AccessKeySecret cannot be empty")
|
||||
private String accessKeySecret;
|
||||
|
||||
/**
|
||||
* AWS Cloud region
|
||||
*/
|
||||
@NotBlank(message = "region cannot be empty")
|
||||
private String region;
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -25,6 +28,8 @@ import org.springframework.stereotype.Component;
|
||||
* SMS configuration
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "alerter.sms")
|
||||
public class SmsConfig {
|
||||
@@ -37,6 +42,7 @@ public class SmsConfig {
|
||||
/**
|
||||
* sms service provider
|
||||
*/
|
||||
@NotBlank(message = "Type cannot be empty")
|
||||
private String type;
|
||||
|
||||
/**
|
||||
@@ -58,6 +64,12 @@ public class SmsConfig {
|
||||
* Aws configuration
|
||||
*/
|
||||
private AwsSmsProperties aws;
|
||||
|
||||
/**
|
||||
* Twilio SMS configuration
|
||||
*/
|
||||
private TwilioSmsProperties twilio;
|
||||
|
||||
/**
|
||||
* Smslocal SMS configuration
|
||||
*/
|
||||
|
||||
+4
@@ -18,12 +18,16 @@
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Smslocal SMS Properties
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SmslocalSmsProperties {
|
||||
/**
|
||||
* SmsLocal account api key
|
||||
|
||||
+10
@@ -17,35 +17,45 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Tencent Cloud SMS properties
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TencentSmsProperties {
|
||||
/**
|
||||
* Tencent cloud account secret id
|
||||
*/
|
||||
@NotBlank(message = "SecretId cannot be empty")
|
||||
private String secretId;
|
||||
|
||||
/**
|
||||
* Tencent cloud account secret key
|
||||
*/
|
||||
@NotBlank(message = "SecretKey cannot be empty")
|
||||
private String secretKey;
|
||||
|
||||
/**
|
||||
* SMS app id
|
||||
*/
|
||||
@NotBlank(message = "AppId cannot be null")
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* SMS signature
|
||||
*/
|
||||
@NotBlank(message = "SignName cannot be empty")
|
||||
private String signName;
|
||||
|
||||
/**
|
||||
* SMS template ID
|
||||
*/
|
||||
@NotBlank(message = "templateId cannot be null")
|
||||
private String templateId;
|
||||
}
|
||||
+20
-18
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.pojo.dto;
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -23,25 +23,27 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Sms Sender configuration dto
|
||||
* Twilio SMS configuration properties
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SmsNoticeSender {
|
||||
|
||||
@NotBlank(message = "Type cannot be empty")
|
||||
private String type;
|
||||
|
||||
private SmsTencentConfig tencent;
|
||||
|
||||
private SmsAlibabaConfig alibaba;
|
||||
@NoArgsConstructor
|
||||
public class TwilioSmsProperties {
|
||||
/**
|
||||
* Twilio Account SID
|
||||
*/
|
||||
@NotBlank(message = "Account SID cannot be empty")
|
||||
private String accountSid;
|
||||
|
||||
private SmsUniSmsConfig unisms;
|
||||
/**
|
||||
* Twilio Auth Token
|
||||
*/
|
||||
@NotBlank(message = "Auth Token cannot be empty")
|
||||
private String authToken;
|
||||
|
||||
private SmslocalConfig smslocal;
|
||||
|
||||
private SmsAwsConfig aws;
|
||||
|
||||
private boolean enable = true;
|
||||
}
|
||||
/**
|
||||
* Twilio Issued Phone Number
|
||||
*/
|
||||
@NotBlank(message = "Twilio Phone Number cannot be empty")
|
||||
private String twilioPhoneNumber;
|
||||
}
|
||||
+9
@@ -17,16 +17,22 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* UniSMS properties
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class UniSmsProperties {
|
||||
/**
|
||||
* UniSMS access key id
|
||||
*/
|
||||
@NotBlank(message = "accessKeyId cannot be empty")
|
||||
private String accessKeyId;
|
||||
|
||||
/**
|
||||
@@ -37,15 +43,18 @@ public class UniSmsProperties {
|
||||
/**
|
||||
* 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";
|
||||
}
|
||||
+2
-2
@@ -118,11 +118,11 @@ public class TencentCloudExternAlert {
|
||||
/**
|
||||
* Transaction alarm
|
||||
*/
|
||||
private static final String EVENT = "event";
|
||||
public static final String EVENT = "event";
|
||||
|
||||
/**
|
||||
* Indicator alarm
|
||||
*/
|
||||
private static final String METRIC = "metric";
|
||||
public static final String METRIC = "metric";
|
||||
|
||||
}
|
||||
|
||||
+3
@@ -48,6 +48,7 @@ final class TelegramBotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandler
|
||||
TelegramBotNotifyDTO notifyBody = TelegramBotNotifyDTO.builder()
|
||||
.chatId(receiver.getTgUserId())
|
||||
.text(renderContent(noticeTemplate, alert))
|
||||
.messageThreadId(receiver.getTgMessageThreadId())
|
||||
.disableWebPagePreview(true)
|
||||
.build();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
@@ -84,6 +85,8 @@ final class TelegramBotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandler
|
||||
private String text;
|
||||
@JsonProperty("disable_web_page_preview")
|
||||
private Boolean disableWebPagePreview;
|
||||
@JsonProperty("message_thread_id")
|
||||
private String messageThreadId;
|
||||
}
|
||||
|
||||
@NoArgsConstructor
|
||||
|
||||
+6
-1
@@ -23,6 +23,7 @@ import org.apache.hertzbeat.alert.config.SmsConfig;
|
||||
import org.apache.hertzbeat.alert.service.impl.SmsLocalSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.AwsSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.TencentSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.TwilioSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.UniSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.AlibabaSmsClientImpl;
|
||||
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
|
||||
@@ -35,6 +36,7 @@ import org.springframework.stereotype.Component;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.ALIBABA;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.AWS;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.TENCENT;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.TWILIO;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.UNISMS;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.SMSLOCAL;
|
||||
|
||||
@@ -141,9 +143,12 @@ public class SmsClientFactory {
|
||||
case AWS:
|
||||
currentSmsClient = new AwsSmsClientImpl(smsConfig.getAws());
|
||||
break;
|
||||
case TWILIO:
|
||||
currentSmsClient = new TwilioSmsClientImpl(smsConfig.getTwilio());
|
||||
break;
|
||||
default:
|
||||
log.warn("[SmsClientFactory] Unsupported SMS provider type: {}", smsConfig.getType());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+174
-2
@@ -18,14 +18,22 @@
|
||||
package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.alert.dto.TencentCloudExternAlert;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.alert.service.ExternAlertService;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Default external alarm service impl
|
||||
*/
|
||||
@@ -43,8 +51,7 @@ public class TencentExternAlertService implements ExternAlertService {
|
||||
log.warn("parse extern alert content failed! content: {}", content);
|
||||
return;
|
||||
}
|
||||
// todo convert TenCloudAlertReport to SingleAlert
|
||||
SingleAlert alert = new SingleAlert();
|
||||
SingleAlert alert = new TencentCloudAlertConverter().convert(report);
|
||||
alarmCommonReduce.reduceAndSendAlarm(alert);
|
||||
}
|
||||
|
||||
@@ -52,4 +59,169 @@ public class TencentExternAlertService implements ExternAlertService {
|
||||
public String supportSource() {
|
||||
return "tencent";
|
||||
}
|
||||
|
||||
/**
|
||||
* Converter: TencentCloud alert to SingleAlert
|
||||
*/
|
||||
public static class TencentCloudAlertConverter {
|
||||
|
||||
/**
|
||||
* Metric alert content template
|
||||
*/
|
||||
private final String metricTemplate = "Tencent Cloud Metric Alert: {metricShowName} {calcType} {calcValue}{calcUnit}, Current: {currentValue}{unit}";
|
||||
|
||||
/**
|
||||
* Event alert content template
|
||||
*/
|
||||
private final String eventTemplate = "Tencent Cloud Event Alert: {productShowName} - {eventShowName}";
|
||||
|
||||
|
||||
/**
|
||||
* Convert TencentCloud alert to SingleAlert
|
||||
*/
|
||||
public SingleAlert convert(TencentCloudExternAlert tencentAlert) {
|
||||
// Building basic information
|
||||
SingleAlert.SingleAlertBuilder builder = SingleAlert.builder()
|
||||
.status(convertStatus(tencentAlert.getAlarmStatus()))
|
||||
.startAt(parseTime(tencentAlert.getFirstOccurTime()))
|
||||
.activeAt(parseTime(tencentAlert.getFirstOccurTime()))
|
||||
.triggerTimes(1);
|
||||
// If it is a recovery state, set the end time.
|
||||
if ("0".equals(tencentAlert.getAlarmStatus())) {
|
||||
builder.endAt(parseTime(tencentAlert.getRecoverTime()));
|
||||
}
|
||||
|
||||
// Build labels
|
||||
Map<String, String> labels = new HashMap<>();
|
||||
buildLabels(labels, tencentAlert);
|
||||
|
||||
// Build annotations
|
||||
Map<String, String> annotations = new HashMap<>();
|
||||
buildAnnotations(annotations, tencentAlert);
|
||||
|
||||
// Build content
|
||||
String content = generateContent(tencentAlert);
|
||||
|
||||
return builder
|
||||
.labels(labels)
|
||||
.annotations(annotations)
|
||||
.content(content)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void buildLabels(Map<String, String> labels, TencentCloudExternAlert alert) {
|
||||
labels.put("__source__", "tencent_cloud");
|
||||
labels.put("alert_type", alert.getAlarmType());
|
||||
if (TencentCloudExternAlert.METRIC.equals(alert.getAlarmType())) {
|
||||
labels.put("metric_name", alert.getAlarmPolicyInfo().getConditions().getMetricName());
|
||||
labels.put("namespace", alert.getAlarmObjInfo().getNamespace());
|
||||
} else {
|
||||
labels.put("event_name", alert.getAlarmPolicyInfo().getConditions().getEventName());
|
||||
labels.put("product_name", alert.getAlarmPolicyInfo().getConditions().getProductName());
|
||||
}
|
||||
}
|
||||
|
||||
private void buildAnnotations(Map<String, String> annotations, TencentCloudExternAlert alert) {
|
||||
TencentCloudExternAlert.AlarmPolicyInfo alarmPolicyInfo = alert.getAlarmPolicyInfo();
|
||||
TencentCloudExternAlert.AlarmObjInfo alarmObjInfo = alert.getAlarmObjInfo();
|
||||
TencentCloudExternAlert.Dimensions dimensions = alert.getAlarmObjInfo().getDimensions();
|
||||
|
||||
putIfNotNull(annotations, "policy_id", alarmPolicyInfo.getPolicyId());
|
||||
putIfNotNull(annotations, "policy_type", alarmPolicyInfo.getPolicyType());
|
||||
putIfNotNull(annotations, "policy_name", alarmPolicyInfo.getPolicyName());
|
||||
putIfNotNull(annotations, "policy_type_cname", alarmPolicyInfo.getPolicyTypeCname());
|
||||
|
||||
putIfNotNull(annotations, "namespace", alarmObjInfo.getNamespace());
|
||||
putIfNotNull(annotations, "region", alarmObjInfo.getRegion());
|
||||
putIfNotNull(annotations, "app_id", alarmObjInfo.getAppId());
|
||||
putIfNotNull(annotations, "uin", alarmObjInfo.getUin());
|
||||
|
||||
putIfNotNull(annotations, "instance_id", dimensions.getUnInstanceId());
|
||||
putIfNotNull(annotations, "obj_id", dimensions.getObjId());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate alert content with template
|
||||
*/
|
||||
private String generateContent(TencentCloudExternAlert alert) {
|
||||
if (TencentCloudExternAlert.METRIC.equals(alert.getAlarmType())) {
|
||||
return generateMetricContent(alert);
|
||||
} else {
|
||||
return generateEventContent(alert);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate metric alert content
|
||||
*/
|
||||
private String generateMetricContent(TencentCloudExternAlert alert) {
|
||||
TencentCloudExternAlert.Conditions conditions = alert.getAlarmPolicyInfo().getConditions();
|
||||
Map<String, String> params = new HashMap<>();
|
||||
|
||||
params.put("metricShowName", conditions.getMetricShowName());
|
||||
|
||||
if (StringUtils.isNotEmpty(conditions.getCalcType())
|
||||
&& StringUtils.isNotEmpty(conditions.getCalcValue())) {
|
||||
params.put("calcType", conditions.getCalcType());
|
||||
params.put("calcValue", conditions.getCalcValue());
|
||||
params.put("calcUnit", Objects.toString(conditions.getCalcUnit(), ""));
|
||||
} else {
|
||||
params.put("calcType", "");
|
||||
params.put("calcValue", "N/A");
|
||||
params.put("calcUnit", "");
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(conditions.getCurrentValue())) {
|
||||
params.put("currentValue", conditions.getCurrentValue());
|
||||
params.put("unit", Objects.toString(conditions.getUnit(), ""));
|
||||
} else {
|
||||
params.put("currentValue", "N/A");
|
||||
params.put("unit", "");
|
||||
}
|
||||
return replacePlaceholders(metricTemplate, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate event alert content
|
||||
*/
|
||||
private String generateEventContent(TencentCloudExternAlert alert) {
|
||||
TencentCloudExternAlert.Conditions conditions = alert.getAlarmPolicyInfo().getConditions();
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("productShowName", conditions.getProductShowName());
|
||||
params.put("eventShowName", conditions.getEventShowName());
|
||||
return replacePlaceholders(eventTemplate, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace placeholders in template with actual values
|
||||
*/
|
||||
private String replacePlaceholders(String template, Map<String, String> params) {
|
||||
String result = template;
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
result = result.replace("{" + entry.getKey() + "}", Objects.toString(entry.getValue(), "NULL"));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String convertStatus(String alarmStatus) {
|
||||
return "1".equals(alarmStatus) ? CommonConstants.ALERT_STATUS_FIRING : CommonConstants.ALERT_STATUS_RESOLVED;
|
||||
}
|
||||
|
||||
private Long parseTime(String timeStr) {
|
||||
try {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
return sdf.parse(timeStr).getTime();
|
||||
} catch (ParseException e) {
|
||||
log.error("Failed to parse time: {}", timeStr);
|
||||
throw new IllegalArgumentException("Failed to parse time: " + timeStr, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void putIfNotNull(Map<String, String> map, String key, String value){
|
||||
if (StringUtils.isNotEmpty(value)){
|
||||
map.put(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* 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.TwilioSmsProperties;
|
||||
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.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.TWILIO;
|
||||
|
||||
/**
|
||||
* Twilio SMS Client Implementation<br>
|
||||
* API doc: <a href=
|
||||
* "https://www.twilio.com/docs/sms/api">https://www.twilio.com/docs/sms/api</a>
|
||||
*/
|
||||
@Slf4j
|
||||
public class TwilioSmsClientImpl implements SmsClient {
|
||||
|
||||
private static final String API_URL_FORMAT = "https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json";
|
||||
private static final String MESSAGE_TEMPLATE = "Instance: %s, Priority: %s, Content: %s";
|
||||
|
||||
private final String accountSid;
|
||||
private final String authToken;
|
||||
private final String twilioPhoneNumber;
|
||||
|
||||
public TwilioSmsClientImpl(TwilioSmsProperties config) {
|
||||
if (config != null) {
|
||||
this.accountSid = config.getAccountSid();
|
||||
this.authToken = config.getAuthToken();
|
||||
this.twilioPhoneNumber = config.getTwilioPhoneNumber();
|
||||
} else {
|
||||
this.accountSid = "";
|
||||
this.authToken = "";
|
||||
this.twilioPhoneNumber = "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
|
||||
String instance = null;
|
||||
String priority = null;
|
||||
String content = null;
|
||||
if (alert.getCommonLabels() != null) {
|
||||
instance = alert.getCommonLabels().get("instance") == null ? alert.getGroupKey()
|
||||
: alert.getCommonLabels().get("instance");
|
||||
priority = alert.getCommonLabels().get("priority") == null ? "unknown"
|
||||
: 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);
|
||||
}
|
||||
}
|
||||
this.send(receiver.getPhone(), createMessage(instance, priority, content));
|
||||
}
|
||||
|
||||
private String createMessage(String instance, String priority, String content) {
|
||||
return String.format(MESSAGE_TEMPLATE, instance, priority, content);
|
||||
}
|
||||
|
||||
private void send(String phoneNumber, String message) {
|
||||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||
String endpoint = String.format(API_URL_FORMAT, accountSid);
|
||||
URI requestUri = new URI(endpoint);
|
||||
|
||||
HttpPost httpPost = createHttpPost(requestUri, phoneNumber, message);
|
||||
log.info("Sending Twilio SMS request to {}", requestUri);
|
||||
executeRequest(httpClient, httpPost, phoneNumber);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to send SMS: {}", e.getMessage());
|
||||
throw new SendMessageException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private HttpPost createHttpPost(URI requestUri, String toNumber, String message) {
|
||||
HttpPost httpPost = new HttpPost(requestUri);
|
||||
|
||||
String auth = accountSid + ":" + authToken;
|
||||
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
|
||||
httpPost.setHeader("Authorization", "Basic " + encodedAuth);
|
||||
|
||||
List<BasicNameValuePair> parameters = new ArrayList<>();
|
||||
parameters.add(new BasicNameValuePair("To", toNumber));
|
||||
parameters.add(new BasicNameValuePair("From", twilioPhoneNumber));
|
||||
parameters.add(new BasicNameValuePair("Body", message));
|
||||
|
||||
try {
|
||||
httpPost.setEntity(new UrlEncodedFormEntity(parameters));
|
||||
return httpPost;
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to create HTTP request: {}", e.getMessage());
|
||||
throw new SendMessageException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void executeRequest(CloseableHttpClient httpClient, HttpPost httpPost, String phoneNumber)
|
||||
throws Exception {
|
||||
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 || statusCode >= 300) {
|
||||
|
||||
if (responseBody.contains("21608")) {
|
||||
throw new SendMessageException(
|
||||
"The Twilio trial account can only send SMS to verified phone numbers");
|
||||
} else {
|
||||
throw new SendMessageException(
|
||||
"HTTP request failed with status code: " + statusCode + ", response: " + responseBody);
|
||||
}
|
||||
}
|
||||
|
||||
JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
|
||||
if (jsonResponse == null) {
|
||||
throw new SendMessageException(statusCode + ":" + responseBody);
|
||||
}
|
||||
|
||||
JsonNode sidNode = jsonResponse.get("sid");
|
||||
if (sidNode == null) {
|
||||
throw new SendMessageException(statusCode + ":" + responseBody);
|
||||
}
|
||||
|
||||
String sid = sidNode.asText();
|
||||
log.info("Successfully sent SMS to phone: {}, sid: {}", phoneNumber, sid);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return TWILIO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkConfig() {
|
||||
return !(accountSid.isBlank() || authToken.isBlank() || twilioPhoneNumber.isBlank());
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.service;
|
||||
|
||||
import org.apache.hertzbeat.alert.config.TwilioSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.impl.TwilioSmsClientImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Test case for {@link TwilioSmsClientImpl}
|
||||
*/
|
||||
class TwilioSmsClientImplTest {
|
||||
|
||||
@Test
|
||||
void getType() {
|
||||
TwilioSmsProperties twilioSmsProperties = new TwilioSmsProperties();
|
||||
twilioSmsProperties.setAccountSid("accountSid");
|
||||
twilioSmsProperties.setAuthToken("authToken");
|
||||
twilioSmsProperties.setTwilioPhoneNumber("twilioPhoneNumber");
|
||||
TwilioSmsClientImpl twilioSmsClient = new TwilioSmsClientImpl(twilioSmsProperties);
|
||||
|
||||
assertEquals("twilio", twilioSmsClient.getType());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"accountSid, authToken, twilioPhoneNumber, true",
|
||||
"accountSid, authToken, '', false",
|
||||
"accountSid, '', twilioPhoneNumber, false",
|
||||
"accountSid, '', '', false",
|
||||
"'', authToken, twilioPhoneNumber, false",
|
||||
"'', authToken, '', false",
|
||||
"'', '', twilioPhoneNumber, false",
|
||||
"'', '', '', false",
|
||||
})
|
||||
void checkConfig(String accountSid, String authToken, String twilioPhoneNumber, boolean expected) {
|
||||
TwilioSmsProperties twilioSmsProperties = new TwilioSmsProperties();
|
||||
twilioSmsProperties.setAccountSid(accountSid);
|
||||
twilioSmsProperties.setAuthToken(authToken);
|
||||
twilioSmsProperties.setTwilioPhoneNumber(twilioPhoneNumber);
|
||||
TwilioSmsClientImpl twilioSmsClient = new TwilioSmsClientImpl(twilioSmsProperties);
|
||||
|
||||
assertEquals(expected, twilioSmsClient.checkConfig());
|
||||
}
|
||||
}
|
||||
+59
-10
@@ -86,6 +86,7 @@ import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
@@ -509,13 +510,37 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
}
|
||||
// params
|
||||
Map<String, String> params = httpProtocol.getParams();
|
||||
boolean enableUrlEncoding = Boolean.parseBoolean(httpProtocol.getEnableUrlEncoding());
|
||||
StringBuilder queryParams = new StringBuilder();
|
||||
|
||||
if (params != null && !params.isEmpty()) {
|
||||
for (Map.Entry<String, String> param : params.entrySet()) {
|
||||
if (StringUtils.hasText(param.getValue())) {
|
||||
requestBuilder.addParameter(param.getKey(), TimeExpressionUtil.calculate(param.getValue()));
|
||||
String key = param.getKey();
|
||||
String value = param.getValue();
|
||||
|
||||
if (!StringUtils.hasText(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (queryParams.length() > 0) {
|
||||
queryParams.append("&");
|
||||
}
|
||||
|
||||
if (enableUrlEncoding) {
|
||||
key = UriUtils.encodeQueryParam(key, "UTF-8");
|
||||
}
|
||||
queryParams.append(key);
|
||||
|
||||
if (StringUtils.hasText(value)) {
|
||||
String calculatedValue = TimeExpressionUtil.calculate(value);
|
||||
if (enableUrlEncoding) {
|
||||
calculatedValue = UriUtils.encodeQueryParam(calculatedValue, "UTF-8");
|
||||
}
|
||||
queryParams.append("=").append(calculatedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The default request header can be overridden if customized
|
||||
// keep-alive
|
||||
requestBuilder.addHeader(HttpHeaders.CONNECTION, NetworkConstants.KEEP_ALIVE);
|
||||
@@ -525,8 +550,7 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
if (headers != null && !headers.isEmpty()) {
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
if (StringUtils.hasText(header.getValue())) {
|
||||
requestBuilder.addHeader(CollectUtil.replaceUriSpecialChar(header.getKey()),
|
||||
CollectUtil.replaceUriSpecialChar(header.getValue()));
|
||||
requestBuilder.addHeader(header.getKey(), header.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -560,11 +584,29 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
requestBuilder.setEntity(new StringEntity(httpProtocol.getPayload(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
// uri
|
||||
String uri = CollectUtil.replaceUriSpecialChar(httpProtocol.getUrl());
|
||||
if (IpDomainUtil.isHasSchema(httpProtocol.getHost())) {
|
||||
// uri encode
|
||||
String uri;
|
||||
if (enableUrlEncoding) {
|
||||
// if the url contains parameters directly
|
||||
if (httpProtocol.getUrl().contains("?")) {
|
||||
String path = httpProtocol.getUrl().substring(0, httpProtocol.getUrl().indexOf("?"));
|
||||
String query = httpProtocol.getUrl().substring(httpProtocol.getUrl().indexOf("?") + 1);
|
||||
uri = UriUtils.encodePath(path, "UTF-8") + "?" + UriUtils.encodeQuery(query, "UTF-8");
|
||||
} else {
|
||||
uri = UriUtils.encodePath(httpProtocol.getUrl(), "UTF-8");
|
||||
}
|
||||
} else {
|
||||
uri = httpProtocol.getUrl();
|
||||
}
|
||||
|
||||
requestBuilder.setUri(httpProtocol.getHost() + ":" + httpProtocol.getPort() + uri);
|
||||
// append query params
|
||||
if (queryParams.length() > 0) {
|
||||
uri += (uri.contains("?") ? "&" : "?") + queryParams.toString();
|
||||
}
|
||||
|
||||
String finalUri;
|
||||
if (IpDomainUtil.isHasSchema(httpProtocol.getHost())) {
|
||||
finalUri = httpProtocol.getHost() + ":" + httpProtocol.getPort() + uri;
|
||||
} else {
|
||||
String ipAddressType = IpDomainUtil.checkIpAddressType(httpProtocol.getHost());
|
||||
String baseUri = NetworkConstants.IPV6.equals(ipAddressType)
|
||||
@@ -572,11 +614,18 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
: String.format("%s:%s%s", httpProtocol.getHost(), httpProtocol.getPort(), uri);
|
||||
boolean ssl = Boolean.parseBoolean(httpProtocol.getSsl());
|
||||
if (ssl) {
|
||||
requestBuilder.setUri(NetworkConstants.HTTPS_HEADER + baseUri);
|
||||
finalUri = NetworkConstants.HTTPS_HEADER + baseUri;
|
||||
} else {
|
||||
requestBuilder.setUri(NetworkConstants.HTTP_HEADER + baseUri);
|
||||
finalUri = NetworkConstants.HTTP_HEADER + baseUri;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
requestBuilder.setUri(finalUri);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Invalid URI with illegal characters: {}. User has disabled URL encoding, not applying any encoding.", finalUri);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// custom timeout
|
||||
int timeout = CollectUtil.getTimeout(httpProtocol.getTimeout(), 0);
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ spring:
|
||||
|
||||
collector:
|
||||
info:
|
||||
version: ${COLLECTOR_VERSION:1.6.1}
|
||||
version: ${COLLECTOR_VERSION:1.7.0}
|
||||
ip: ${COLLECTOR_IP:}
|
||||
dispatch:
|
||||
entrance:
|
||||
|
||||
@@ -35,4 +35,7 @@ public interface SmsConstants {
|
||||
|
||||
// Aws cloud SMS
|
||||
String AWS = "aws";
|
||||
|
||||
// Twilio SMS
|
||||
String TWILIO = "twilio";
|
||||
}
|
||||
|
||||
+5
@@ -122,6 +122,11 @@ public class NoticeReceiver {
|
||||
example = "779294123", accessMode = READ_WRITE)
|
||||
private String tgUserId;
|
||||
|
||||
@Schema(title = "Telegram message thread id: The notification method is valid for Telegram Bot",
|
||||
description = "TTelegram message thread id: The notification method is valid for Telegram Bot",
|
||||
example = "779294123", accessMode = READ_WRITE)
|
||||
private String tgMessageThreadId;
|
||||
|
||||
@Schema(title = "DingTalk,FeiShu,WeWork user id: The notification method is valid for DingTalk,FeiShu,WeWork Bot",
|
||||
description = "DingTalk,FeiShu,WeWork user id: The notification method is valid for DingTalk,FeiShu,WeWork Bot",
|
||||
example = "779294123", accessMode = READ_WRITE)
|
||||
|
||||
+6
@@ -95,6 +95,12 @@ public class HttpProtocol implements CommonRequestProtocol, Protocol {
|
||||
*/
|
||||
private List<String> successCodes;
|
||||
|
||||
/**
|
||||
* Whether to enable URL encoding for the path. Default is true.
|
||||
* When true, the URL path will be encoded. When false, the URL path will not be encoded.
|
||||
*/
|
||||
private String enableUrlEncoding = "true";
|
||||
|
||||
/**
|
||||
* authentication information
|
||||
*/
|
||||
|
||||
-40
@@ -1,40 +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.manager.pojo.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Alibaba Sms Sender configuration dto
|
||||
*/
|
||||
@Data
|
||||
public class SmsAlibabaConfig {
|
||||
|
||||
@NotBlank(message = "AccessKeyId cannot be empty")
|
||||
private String accessKeyId;
|
||||
|
||||
@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;
|
||||
}
|
||||
-38
@@ -1,38 +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.manager.pojo.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* AWS Sms Sender configuration dto
|
||||
*/
|
||||
@Data
|
||||
public class SmsAwsConfig {
|
||||
|
||||
@NotBlank(message = "AccessKeyId cannot be empty")
|
||||
private String accessKeyId;
|
||||
|
||||
@NotBlank(message = "AccessKeySecret cannot be empty")
|
||||
private String accessKeySecret;
|
||||
|
||||
@NotBlank(message = "region cannot be empty")
|
||||
private String region;
|
||||
|
||||
}
|
||||
-48
@@ -1,48 +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.manager.pojo.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Tencent Sms Sender configuration dto
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SmsTencentConfig {
|
||||
|
||||
@NotBlank(message = "SecretId cannot be empty")
|
||||
private String secretId;
|
||||
|
||||
@NotBlank(message = "SecretKey cannot be empty")
|
||||
private String secretKey;
|
||||
|
||||
@NotBlank(message = "SignName cannot be empty")
|
||||
private String signName;
|
||||
|
||||
@NotBlank(message = "AppId cannot be null")
|
||||
private String appId;
|
||||
|
||||
@NotBlank(message = "templateId cannot be null")
|
||||
private String templateId;
|
||||
|
||||
}
|
||||
-62
@@ -1,62 +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.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";
|
||||
}
|
||||
+2
-1
@@ -75,7 +75,8 @@ import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
/**
|
||||
* Monitoring Type Management Implementation
|
||||
* temporarily stores the monitoring configuration and parameter configuration in memory and then stores it in the
|
||||
* Temporarily stores the monitoring configuration and parameter configuration in memory,
|
||||
* and then persists it to a storage system.
|
||||
*/
|
||||
@Service
|
||||
@Order(value = Ordered.HIGHEST_PRECEDENCE)
|
||||
|
||||
+5
-5
@@ -22,10 +22,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.hertzbeat.alert.config.SmsConfig;
|
||||
import org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum;
|
||||
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
|
||||
import org.apache.hertzbeat.common.support.event.SmsConfigChangeEvent;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.SmsNoticeSender;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -35,7 +35,7 @@ import org.springframework.stereotype.Service;
|
||||
*/
|
||||
|
||||
@Service
|
||||
public class SmsGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImpl<SmsNoticeSender> {
|
||||
public class SmsGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImpl<SmsConfig> {
|
||||
@Resource
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@@ -55,7 +55,7 @@ public class SmsGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImp
|
||||
* This method is used to handle the sms configuration change event.
|
||||
*/
|
||||
@Override
|
||||
public void handler(SmsNoticeSender smsNoticeSender) {
|
||||
public void handler(SmsConfig smsConfig) {
|
||||
applicationContext.publishEvent(new SmsConfigChangeEvent(applicationContext));
|
||||
}
|
||||
|
||||
@@ -69,11 +69,11 @@ public class SmsGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImp
|
||||
* a TypeReference of NoticeSender type
|
||||
*/
|
||||
@Override
|
||||
public TypeReference<SmsNoticeSender> getTypeReference() {
|
||||
public TypeReference<SmsConfig> getTypeReference() {
|
||||
return new TypeReference<>() {
|
||||
@Override
|
||||
public Type getType() {
|
||||
return SmsNoticeSender.class;
|
||||
return SmsConfig.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -72,14 +72,22 @@ warehouse:
|
||||
rpc-port: 6667
|
||||
username: root
|
||||
password: root
|
||||
redis:
|
||||
enabled: false
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
password: 123456
|
||||
# store real-time metrics data, enable only one below
|
||||
real-time:
|
||||
memory:
|
||||
enabled: true
|
||||
init-size: 16
|
||||
redis:
|
||||
enabled: false
|
||||
# redis mode: single, sentinel, cluster. Default is single
|
||||
mode: single
|
||||
# separate each address with comma when using cluster mode, eg: 127.0.0.1:6379,127.0.0.1:6380
|
||||
address: 127.0.0.1:6379
|
||||
# enter master name when using sentinel mode
|
||||
masterName: mymaster
|
||||
password: 123456
|
||||
# redis db index, default: DB0
|
||||
db: 0
|
||||
|
||||
scheduler:
|
||||
server:
|
||||
@@ -102,4 +110,4 @@ ai:
|
||||
# api key
|
||||
api-key:
|
||||
#At present, only IFLYTEK large model needs to be filled in
|
||||
api-secret:
|
||||
api-secret:
|
||||
|
||||
@@ -231,6 +231,10 @@ alerter:
|
||||
access-key-id: YOUR_ACCESS_KEY_ID
|
||||
access-key-secret: YOUR_ACCESS_KEY_SECRET
|
||||
region: AWS_REGION_FOR_END_USER_MESSAGING
|
||||
twilio:
|
||||
account-sid: YOUR_ACCOUNT_SID
|
||||
auth-token: YOUR_AUTH_TOKEN
|
||||
twilio-phone-number: YOUR_TWILIO_PHONE_NUMBER
|
||||
scheduler:
|
||||
server:
|
||||
enabled: true
|
||||
|
||||
@@ -246,6 +246,20 @@ params:
|
||||
defaultValue: '200, 201'
|
||||
# hide param-true or false
|
||||
hide: true
|
||||
# field-param field key
|
||||
- field: enableUrlEncoding
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 启用URL编码(UTF-8)
|
||||
en-US: Enable URL Encoding(UTF-8)
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: boolean
|
||||
# required-true or false
|
||||
required: true
|
||||
# default value
|
||||
defaultValue: true
|
||||
# hide param-true or false
|
||||
hide: true
|
||||
# collect metrics config list
|
||||
metrics:
|
||||
# metrics - summary
|
||||
@@ -286,6 +300,8 @@ metrics:
|
||||
method: ^_^httpMethod^_^
|
||||
# if enabled https
|
||||
ssl: ^_^ssl^_^
|
||||
# enable url encoding
|
||||
enableUrlEncoding: ^_^enableUrlEncoding^_^
|
||||
# http request payload
|
||||
payload: ^_^payload^_^
|
||||
# http request header content
|
||||
@@ -344,6 +360,8 @@ metrics:
|
||||
method: GET
|
||||
# if enabled https
|
||||
ssl: ^_^ssl^_^
|
||||
# enable url encoding
|
||||
enableUrlEncoding: ^_^enableUrlEncoding^_^
|
||||
authorization:
|
||||
# http auth type: Basic Auth, Digest Auth, Bearer Token
|
||||
type: ^_^authType^_^
|
||||
|
||||
+3
-3
@@ -21,9 +21,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.hertzbeat.alert.config.SmsConfig;
|
||||
import org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum;
|
||||
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.SmsNoticeSender;
|
||||
import org.apache.hertzbeat.manager.service.impl.SmsGeneralConfigServiceImpl;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -66,9 +66,9 @@ class SmsGeneralConfigServiceTest {
|
||||
@Test
|
||||
void testGetTypeReference() {
|
||||
|
||||
TypeReference<SmsNoticeSender> typeReference = service.getTypeReference();
|
||||
TypeReference<SmsConfig> typeReference = service.getTypeReference();
|
||||
assertNotNull(typeReference);
|
||||
assertEquals(SmsNoticeSender.class, typeReference.getType());
|
||||
assertEquals(SmsConfig.class, typeReference.getType());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -225,6 +225,52 @@ alerter:
|
||||
|
||||
Now you can configure this information in your hertzbeat application.
|
||||
|
||||
### Twilio SMS Configuration
|
||||
|
||||
To activate and use the Twilio SMS service, refer to the official Twilio documentation: [SMS Getting Started Guide](https://www.twilio.com/docs/sms/quickstart)
|
||||
|
||||
You can configure the Twilio SMS service either through the graphical interface or in the `application.yml` file.
|
||||
To use `application.yml`, add/fill in the following Twilio SMS configuration (replace parameters with your own SMS server configuration):
|
||||
|
||||
```yaml
|
||||
alerter:
|
||||
sms:
|
||||
enable: true # Whether to enable
|
||||
type: twilio # SMS provider type, supports "twilio"
|
||||
twilio: # Twilio SMS configuration
|
||||
account-sid: # Your Twilio Account SID
|
||||
auth-token: # Your Twilio Auth Token
|
||||
twilio-phone-number: # Your Twilio Phone Number
|
||||
```
|
||||
|
||||
1. Create a Twilio account
|
||||
|
||||
- If you don't have a Twilio account, sign up at [Twilio Console](https://www.twilio.com/console)
|
||||
- You'll get a free trial account that allows you to test the SMS service
|
||||
|
||||
2. Obtain Twilio credentials
|
||||
|
||||
- After signing in, locate your Account SID and Auth Token on the dashboard
|
||||
- These credentials will be used to authenticate API requests
|
||||
|
||||
3. Get a Twilio Phone Number
|
||||
|
||||
- In the Twilio Console, navigate to "Phone Numbers" > "Manage" > "Active Numbers"
|
||||
- Click "Buy a Number" or use the trial number provided by Twilio
|
||||
- Make sure the number has SMS capabilities enabled
|
||||
- This number will be used as the sender for your SMS alerts
|
||||
- A phone number is required for the Twilio SMS service to work
|
||||
|
||||
4. Testing Your Configuration on Twilio
|
||||
|
||||
- Twilio provides a sandbox environment for testing
|
||||
- If you are in the trial period, you will only be able to send SMS to verified phone numbers
|
||||
- To verify a phone number, add it to your verified phone numbers list in the Twilio Console
|
||||
|
||||
> The message follows the format: "Instance: {}, Priority: {}, Content: {}"
|
||||
|
||||
This information can be configured in the Hertzbeat application.
|
||||
|
||||
## Operation steps
|
||||
|
||||
1. **【Alarm notification】->【Add new recipient】 ->【Select SMS notification method】**
|
||||
|
||||
@@ -157,6 +157,13 @@
|
||||
"help/windows_script"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "server",
|
||||
"items": [
|
||||
"help/ipmi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "mid",
|
||||
|
||||
@@ -448,6 +448,12 @@ export default function () {
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yasminvo"><img src="https://avatars.githubusercontent.com/u/107528848?v=4?s=100" width="100px;" alt="yasminvo"/><br /><sub><b>yasminvo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yasminvo" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yyahang"><img src="https://avatars.githubusercontent.com/u/90464876?v=4?s=100" width="100px;" alt="yyahang"/><br /><sub><b>yyahang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yyahang" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JuJinPark"><img src="https://avatars.githubusercontent.com/u/44892459?v=4?s=100" width="100px;" alt="jujin"/><br /><sub><b>jujin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LL-LIN"><img src="https://avatars.githubusercontent.com/u/43002118?v=4?s=100" width="100px;" alt="LL-LIN"/><br /><sub><b>LL-LIN</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LL-LIN" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://bigcyy.github.io/"><img src="https://avatars.githubusercontent.com/u/73413979?v=4?s=100" width="100px;" alt="Yang Chen"/><br /><sub><b>Yang Chen</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sarthakeash"><img src="https://avatars.githubusercontent.com/u/74091160?v=4?s=100" width="100px;" alt="Sarthak Arora"/><br /><sub><b>Sarthak Arora</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<hzb.version>1.6.1</hzb.version>
|
||||
<hzb.version>1.7.0</hzb.version>
|
||||
|
||||
<java.version>17</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
|
||||
@@ -230,6 +230,10 @@ alerter:
|
||||
access-key-id: YOUR_ACCESS_KEY_ID
|
||||
access-key-secret: YOUR_ACCESS_KEY_SECRET
|
||||
region: AWS_REGION_FOR_END_USER_MESSAGING
|
||||
twilio:
|
||||
account-sid: YOUR_ACCOUNT_SID
|
||||
auth-token: YOUR_AUTH_TOKEN
|
||||
twilio-phone-number: YOUR_TWILIO_PHONE_NUMBER
|
||||
scheduler:
|
||||
server:
|
||||
enabled: true
|
||||
|
||||
@@ -34,6 +34,10 @@ spring:
|
||||
exclude: org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration, org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration
|
||||
freemarker:
|
||||
enabled: false
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 100MB
|
||||
max-request-size: 100MB
|
||||
|
||||
management:
|
||||
health:
|
||||
@@ -197,6 +201,10 @@ alerter:
|
||||
access-key-id: YOUR_ACCESS_KEY_ID
|
||||
access-key-secret: YOUR_ACCESS_KEY_SECRET
|
||||
region: AWS_REGION_FOR_END_USER_MESSAGING
|
||||
twilio:
|
||||
account-sid: YOUR_ACCOUNT_SID
|
||||
auth-token: YOUR_AUTH_TOKEN
|
||||
twilio-phone-number: YOUR_TWILIO_PHONE_NUMBER
|
||||
scheduler:
|
||||
server:
|
||||
enabled: true
|
||||
|
||||
@@ -63,7 +63,7 @@ services:
|
||||
- hertzbeat
|
||||
|
||||
hertzbeat:
|
||||
image: apache/hertzbeat
|
||||
image: apache/hertzbeat:1.7.0
|
||||
container_name: compose-hertzbeat
|
||||
hostname: hertzbeat
|
||||
restart: always
|
||||
|
||||
@@ -34,6 +34,10 @@ spring:
|
||||
exclude: org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration, org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration
|
||||
freemarker:
|
||||
enabled: false
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 100MB
|
||||
max-request-size: 100MB
|
||||
|
||||
management:
|
||||
health:
|
||||
@@ -193,6 +197,10 @@ alerter:
|
||||
access-key-id: YOUR_ACCESS_KEY_ID
|
||||
access-key-secret: YOUR_ACCESS_KEY_SECRET
|
||||
region: AWS_REGION_FOR_END_USER_MESSAGING
|
||||
twilio:
|
||||
account-sid: YOUR_ACCOUNT_SID
|
||||
auth-token: YOUR_AUTH_TOKEN
|
||||
twilio-phone-number: YOUR_TWILIO_PHONE_NUMBER
|
||||
scheduler:
|
||||
server:
|
||||
enabled: true
|
||||
|
||||
@@ -62,7 +62,7 @@ services:
|
||||
- hertzbeat
|
||||
|
||||
hertzbeat:
|
||||
image: apache/hertzbeat
|
||||
image: apache/hertzbeat:1.7.0
|
||||
container_name: compose-hertzbeat
|
||||
hostname: hertzbeat
|
||||
restart: always
|
||||
|
||||
@@ -34,6 +34,10 @@ spring:
|
||||
exclude: org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration, org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration
|
||||
freemarker:
|
||||
enabled: false
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 100MB
|
||||
max-request-size: 100MB
|
||||
|
||||
management:
|
||||
health:
|
||||
@@ -191,6 +195,10 @@ alerter:
|
||||
access-key-id: YOUR_ACCESS_KEY_ID
|
||||
access-key-secret: YOUR_ACCESS_KEY_SECRET
|
||||
region: AWS_REGION_FOR_END_USER_MESSAGING
|
||||
twilio:
|
||||
account-sid: YOUR_ACCOUNT_SID
|
||||
auth-token: YOUR_AUTH_TOKEN
|
||||
twilio-phone-number: YOUR_TWILIO_PHONE_NUMBER
|
||||
scheduler:
|
||||
server:
|
||||
enabled: true
|
||||
|
||||
@@ -62,7 +62,7 @@ services:
|
||||
- hertzbeat
|
||||
|
||||
hertzbeat:
|
||||
image: apache/hertzbeat
|
||||
image: apache/hertzbeat:1.7.0
|
||||
container_name: compose-hertzbeat
|
||||
hostname: hertzbeat
|
||||
restart: always
|
||||
|
||||
@@ -34,6 +34,10 @@ spring:
|
||||
exclude: org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration, org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration
|
||||
freemarker:
|
||||
enabled: false
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 100MB
|
||||
max-request-size: 100MB
|
||||
|
||||
management:
|
||||
health:
|
||||
@@ -190,6 +194,10 @@ alerter:
|
||||
access-key-id: YOUR_ACCESS_KEY_ID
|
||||
access-key-secret: YOUR_ACCESS_KEY_SECRET
|
||||
region: AWS_REGION_FOR_END_USER_MESSAGING
|
||||
twilio:
|
||||
account-sid: YOUR_ACCOUNT_SID
|
||||
auth-token: YOUR_AUTH_TOKEN
|
||||
twilio-phone-number: YOUR_TWILIO_PHONE_NUMBER
|
||||
scheduler:
|
||||
server:
|
||||
enabled: true
|
||||
|
||||
@@ -64,7 +64,7 @@ services:
|
||||
- hertzbeat
|
||||
|
||||
hertzbeat:
|
||||
image: apache/hertzbeat
|
||||
image: apache/hertzbeat:1.7.0
|
||||
container_name: compose-hertzbeat
|
||||
hostname: hertzbeat
|
||||
restart: always
|
||||
|
||||
@@ -19,11 +19,11 @@ $ docker buildx use mybuilder
|
||||
|
||||
```shell
|
||||
# Use the --platform flag to specify the target platforms for the build --push flag to push the image to a registry
|
||||
$ docker buildx build --platform linux/arm64,linux/amd64 -t apache/hertzbeat-collector:v1.0.0 . --push
|
||||
$ docker buildx build --platform linux/arm64,linux/amd64 -t apache/hertzbeat-collector:1.0.0 . --push
|
||||
|
||||
$ docker buildx build --platform linux/arm64,linux/amd64 -t quay.io/tancloud/hertzbeat-collector:latest . --push
|
||||
|
||||
# Inspect the image info
|
||||
$ docker buildx imagetools inspect apache/hertzbeat-collector:v1.0.0
|
||||
$ docker buildx imagetools inspect apache/hertzbeat-collector:1.0.0
|
||||
|
||||
```
|
||||
|
||||
@@ -31,11 +31,11 @@ fi
|
||||
# docker compile context
|
||||
CONTEXT_DIR=`pwd`
|
||||
|
||||
COMMAND="docker buildx build --platform linux/arm64,linux/amd64 -t apache/hertzbeat-collector:v$VERSION -f $CURRENT_DIR/Dockerfile $CONTEXT_DIR --build-arg VERSION="$VERSION" --push"
|
||||
COMMAND="docker buildx build --platform linux/arm64,linux/amd64 -t apache/hertzbeat-collector:$VERSION -f $CURRENT_DIR/Dockerfile $CONTEXT_DIR --build-arg VERSION="$VERSION" --push"
|
||||
|
||||
#COMMAND="docker buildx build --platform linux/arm64,linux/amd64 -t apache/hertzbeat-collector:latest -f $CURRENT_DIR/Dockerfile $CONTEXT_DIR --build-arg VERSION="$VERSION" --push"
|
||||
|
||||
#COMMAND="docker buildx build --platform linux/arm64,linux/amd64 -t quay.io/tancloud/hertzbeat-collector:v$VERSION -f $CURRENT_DIR/Dockerfile $CONTEXT_DIR --build-arg VERSION="$VERSION" --push"
|
||||
#COMMAND="docker buildx build --platform linux/arm64,linux/amd64 -t quay.io/tancloud/hertzbeat-collector:$VERSION -f $CURRENT_DIR/Dockerfile $CONTEXT_DIR --build-arg VERSION="$VERSION" --push"
|
||||
|
||||
#COMMAND="docker buildx build --platform linux/arm64,linux/amd64 -t quay.io/tancloud/hertzbeat-collector:latest -f $CURRENT_DIR/Dockerfile $CONTEXT_DIR --build-arg VERSION="$VERSION" --push"
|
||||
|
||||
|
||||
@@ -19,10 +19,10 @@ $ docker buildx use mybuilder
|
||||
|
||||
```shell
|
||||
# Use the --platform flag to specify the target platforms for the build --push flag to push the image to a registry
|
||||
$ docker buildx build --platform linux/arm64,linux/amd64 -t apache/hertzbeat:v1.0.0 . --push
|
||||
$ docker buildx build --platform linux/arm64,linux/amd64 -t apache/hertzbeat:1.0.0 . --push
|
||||
|
||||
$ docker buildx build --platform linux/arm64,linux/amd64 -t quay.io/tancloud/hertzbeat:latest . --push
|
||||
|
||||
# Inspect the image info
|
||||
$ docker buildx imagetools inspect apache/hertzbeat:v1.0.0
|
||||
$ docker buildx imagetools inspect apache/hertzbeat:1.0.0
|
||||
```
|
||||
|
||||
@@ -27,11 +27,11 @@ VERSION=`ls apache-hertzbeat-*-incubating-docker-compose.tar.gz| awk -F"-" '{pri
|
||||
# compile context dir
|
||||
CONTEXT_DIR=`pwd`
|
||||
|
||||
COMMAND="docker buildx build --platform ${IMAGE_PLATFORM:-linux/arm64,linux/amd64} -t apache/hertzbeat:v$VERSION -f $CURRENT_DIR/Dockerfile $CONTEXT_DIR --push=${IMAGE_PUSH:-true} --load=${IMAGE_LOAD:-false}"
|
||||
COMMAND="docker buildx build --platform ${IMAGE_PLATFORM:-linux/arm64,linux/amd64} -t apache/hertzbeat:$VERSION -f $CURRENT_DIR/Dockerfile $CONTEXT_DIR --push=${IMAGE_PUSH:-true} --load=${IMAGE_LOAD:-false}"
|
||||
|
||||
#COMMAND="docker buildx build --platform linux/arm64,linux/amd64 -t apache/hertzbeat:latest -f $CURRENT_DIR/Dockerfile $CONTEXT_DIR --push"
|
||||
|
||||
#COMMAND="docker buildx build --platform linux/arm64,linux/amd64 -t quay.io/tancloud/hertzbeat:v$VERSION -f $CURRENT_DIR/Dockerfile $CONTEXT_DIR --push"
|
||||
#COMMAND="docker buildx build --platform linux/arm64,linux/amd64 -t quay.io/tancloud/hertzbeat:$VERSION -f $CURRENT_DIR/Dockerfile $CONTEXT_DIR --push"
|
||||
|
||||
#COMMAND="docker buildx build --platform linux/arm64,linux/amd64 -t quay.io/tancloud/hertzbeat:latest -f $CURRENT_DIR/Dockerfile $CONTEXT_DIR --push"
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ $gpgKeyId = "********"
|
||||
|
||||
# ********************************************************
|
||||
# Set version and release candidate number
|
||||
$version = "1.6.1"
|
||||
$version = "1.7.0"
|
||||
$rcNumber = "rc1"
|
||||
# ********************************************************
|
||||
|
||||
|
||||
@@ -86,8 +86,8 @@
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb",
|
||||
"maximumError": "10kb"
|
||||
"maximumWarning": "50kb",
|
||||
"maximumError": "100kb"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -27,6 +27,7 @@ export class NoticeReceiver {
|
||||
email!: string;
|
||||
tgBotToken!: string;
|
||||
tgUserId!: string;
|
||||
tgMessageThreadId!: string;
|
||||
userId!: string;
|
||||
slackWebHookUrl!: string;
|
||||
discordChannelId!: string;
|
||||
|
||||
@@ -21,6 +21,7 @@ import { AlibabaSmsConfig } from './AlibabaSmsConfig';
|
||||
import { AwsSmsConfig } from './AwsSmsConfig';
|
||||
import { SmslocalSmsConfig } from './SmslocalSmsConfig';
|
||||
import { TencentSmsConfig } from './TencentSmsConfig';
|
||||
import { TwilioSmsConfig } from './TwilioSmsConfig';
|
||||
import { UniSmsConfig } from './UniSmsConfig';
|
||||
import { SmsType } from './enums/sms-type.enum';
|
||||
|
||||
@@ -32,6 +33,7 @@ export class SmsNoticeSender {
|
||||
unisms: UniSmsConfig = new UniSmsConfig();
|
||||
smslocal: SmslocalSmsConfig = new SmslocalSmsConfig();
|
||||
aws: AwsSmsConfig = new AwsSmsConfig();
|
||||
twilio: TwilioSmsConfig = new TwilioSmsConfig();
|
||||
enable: boolean = false;
|
||||
creator!: string;
|
||||
modifier!: string;
|
||||
|
||||
+4
-18
@@ -17,22 +17,8 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.pojo.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
|
||||
/**
|
||||
* Smslocal SMS configuration
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SmslocalConfig {
|
||||
/**
|
||||
* Smslocal api key
|
||||
*/
|
||||
private String apiKey;
|
||||
export class TwilioSmsConfig {
|
||||
accountSid!: string;
|
||||
authToken!: string;
|
||||
twilioPhoneNumber!: string;
|
||||
}
|
||||
@@ -22,7 +22,8 @@ export enum SmsType {
|
||||
ALIBABA = 'alibaba',
|
||||
UNISMS = 'unisms',
|
||||
SMSLOCAL = 'smslocal',
|
||||
AWS = 'aws'
|
||||
AWS = 'aws',
|
||||
TWILIO = 'twilio'
|
||||
}
|
||||
|
||||
export enum UniSmsAuthMode {
|
||||
|
||||
@@ -163,7 +163,15 @@
|
||||
{{ 'alert.inhibit.source_labels' | i18n }}
|
||||
</nz-form-label>
|
||||
<nz-form-control [nzSpan]="12">
|
||||
<app-labels-input [(ngModel)]="alertInhibit.sourceLabels" name="sourceLabels" required></app-labels-input>
|
||||
<app-form-field
|
||||
[item]="{
|
||||
field: 'labels',
|
||||
type: 'labels',
|
||||
required: true
|
||||
}"
|
||||
[name]="'sourceLabels'"
|
||||
[(ngModel)]="alertInhibit.sourceLabels"
|
||||
/>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
@@ -171,7 +179,15 @@
|
||||
{{ 'alert.inhibit.target_labels' | i18n }}
|
||||
</nz-form-label>
|
||||
<nz-form-control [nzSpan]="12">
|
||||
<app-labels-input [(ngModel)]="alertInhibit.targetLabels" name="targetLabels" required></app-labels-input>
|
||||
<app-form-field
|
||||
[item]="{
|
||||
field: 'labels',
|
||||
type: 'labels',
|
||||
required: true
|
||||
}"
|
||||
[name]="'targetLabels'"
|
||||
[(ngModel)]="alertInhibit.targetLabels"
|
||||
/>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
|
||||
@@ -67,12 +67,12 @@ export class AlertIntegrationComponent implements OnInit {
|
||||
id: 'skywalking',
|
||||
name: this.i18nSvc.fanyi('alert.integration.source.skywalking'),
|
||||
icon: 'assets/img/integration/skywalking.svg'
|
||||
},
|
||||
{
|
||||
id: 'tencent',
|
||||
name: this.i18nSvc.fanyi('alert.integration.source.tencent'),
|
||||
icon: 'assets/img/integration/tencent.svg'
|
||||
}
|
||||
// {
|
||||
// id: 'tencent',
|
||||
// name: this.i18nSvc.fanyi('alert.integration.source.tencent'),
|
||||
// icon: 'assets/img/integration/tencent.svg'
|
||||
// }
|
||||
];
|
||||
|
||||
selectedSource: DataSource | null = null;
|
||||
|
||||
+6
@@ -344,6 +344,12 @@
|
||||
<input [(ngModel)]="receiver.tgUserId" [required]="receiver.type === 7" name="tgUserId" nz-input type="text" />
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item *ngIf="receiver.type === 7">
|
||||
<nz-form-label [nzSpan]="7" nzFor="tgMessageThreadId">{{ 'alert.notice.type.telegram-message-thread-id' | i18n }}</nz-form-label>
|
||||
<nz-form-control [nzErrorTip]="'validation.required' | i18n" [nzSpan]="12">
|
||||
<input [(ngModel)]="receiver.tgMessageThreadId" name="tgMessageThreadId" nz-input type="text" />
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item *ngIf="receiver.type === 8">
|
||||
<nz-form-label [nzRequired]="receiver.type === 8" [nzSpan]="7" nzFor="slackWebHookUrl"
|
||||
>{{ 'alert.notice.type.slack-webHook-url' | i18n }}
|
||||
|
||||
+9
-1
@@ -186,7 +186,15 @@
|
||||
<nz-form-item *ngIf="!rule.filterAll">
|
||||
<nz-form-label nzFor="labels" nzSpan="7" nzRequired="true">{{ 'alert.notice.rule.tag' | i18n }} </nz-form-label>
|
||||
<nz-form-control nzSpan="12" [nzErrorTip]="'validation.required' | i18n">
|
||||
<app-labels-input [(ngModel)]="rule.labels" name="labels" required></app-labels-input>
|
||||
<app-form-field
|
||||
[item]="{
|
||||
field: 'labels',
|
||||
type: 'labels',
|
||||
required: true
|
||||
}"
|
||||
[name]="'labels'"
|
||||
[(ngModel)]="rule.labels"
|
||||
/>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
|
||||
@@ -538,7 +538,14 @@
|
||||
<nz-form-item>
|
||||
<nz-form-label nzSpan="7" [nzFor]="'labels'" [nzTooltipTitle]="'label.bind.tip' | i18n">{{ 'label.bind' | i18n }} </nz-form-label>
|
||||
<nz-form-control nzSpan="12">
|
||||
<app-labels-input [(ngModel)]="define.labels" name="labels"></app-labels-input>
|
||||
<app-form-field
|
||||
[item]="{
|
||||
field: 'labels',
|
||||
type: 'labels'
|
||||
}"
|
||||
[name]="'labels'"
|
||||
[(ngModel)]="define.labels"
|
||||
/>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
@@ -546,7 +553,14 @@
|
||||
>{{ 'common.annotation.bind' | i18n }}
|
||||
</nz-form-label>
|
||||
<nz-form-control nzSpan="12">
|
||||
<app-labels-input [(ngModel)]="define.annotations" name="annotations"></app-labels-input>
|
||||
<app-form-field
|
||||
[item]="{
|
||||
field: 'labels',
|
||||
type: 'labels'
|
||||
}"
|
||||
[name]="'annotations'"
|
||||
[(ngModel)]="define.annotations"
|
||||
/>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
|
||||
@@ -179,7 +179,15 @@
|
||||
<nz-form-item *ngIf="!silence.matchAll">
|
||||
<nz-form-label nzSpan="7" nzFor="labels" nzRequired="true">{{ 'alert.silence.labels' | i18n }}</nz-form-label>
|
||||
<nz-form-control nzSpan="12" [nzErrorTip]="'validation.required' | i18n">
|
||||
<app-labels-input [(ngModel)]="silence.labels" name="labels" required></app-labels-input>
|
||||
<app-form-field
|
||||
[item]="{
|
||||
field: 'labels',
|
||||
type: 'labels',
|
||||
required: true
|
||||
}"
|
||||
[name]="'labels'"
|
||||
[(ngModel)]="silence.labels"
|
||||
/>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
|
||||
@@ -131,12 +131,14 @@
|
||||
<nz-form-item>
|
||||
<nz-form-label nzSpan="7" [nzFor]="'labels'" [nzTooltipTitle]="'label.bind.tip' | i18n">{{ 'label.bind' | i18n }} </nz-form-label>
|
||||
<nz-form-control nzSpan="8" [nzErrorTip]="'validation.required' | i18n">
|
||||
<app-labels-input
|
||||
<app-form-field
|
||||
[item]="{
|
||||
field: 'labels',
|
||||
type: 'labels'
|
||||
}"
|
||||
[name]="'labels'"
|
||||
[(ngModel)]="monitor.labels"
|
||||
name="labels"
|
||||
[keyAlias]="'placeholder.key' | i18n"
|
||||
[valueAlias]="'placeholder.value' | i18n"
|
||||
></app-labels-input>
|
||||
/>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
@@ -145,12 +147,14 @@
|
||||
>{{ 'common.annotation.bind' | i18n }}
|
||||
</nz-form-label>
|
||||
<nz-form-control nzSpan="8" [nzErrorTip]="'validation.required' | i18n">
|
||||
<app-labels-input
|
||||
<app-form-field
|
||||
[item]="{
|
||||
field: 'annotations',
|
||||
type: 'labels'
|
||||
}"
|
||||
[name]="'annotations'"
|
||||
[(ngModel)]="monitor.annotations"
|
||||
name="annotation"
|
||||
[keyAlias]="'placeholder.key' | i18n"
|
||||
[valueAlias]="'placeholder.value' | i18n"
|
||||
></app-labels-input>
|
||||
/>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
|
||||
@@ -72,6 +72,9 @@ export class MonitorListComponent implements OnInit, OnDestroy {
|
||||
appSearchOrigin: any[] = [];
|
||||
appSearchLoading = false;
|
||||
intervalId: any;
|
||||
// save the current sorting status
|
||||
currentSortField: string | null = null;
|
||||
currentSortOrder: string | null = null;
|
||||
|
||||
switchExportTypeModalFooter: ModalButtonOptions[] = [
|
||||
{ label: this.i18nSvc.fanyi('common.button.cancel'), type: 'default', onClick: () => (this.isSwitchExportTypeModalVisible = false) }
|
||||
@@ -149,7 +152,7 @@ export class MonitorListComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
sync() {
|
||||
this.loadMonitorTable();
|
||||
this.loadMonitorTable(this.currentSortField, this.currentSortOrder);
|
||||
}
|
||||
|
||||
getAppIconName(app: string | undefined): string {
|
||||
@@ -499,9 +502,9 @@ export class MonitorListComponent implements OnInit, OnDestroy {
|
||||
this.pageIndex = pageIndex;
|
||||
this.pageSize = pageSize;
|
||||
const currentSort = sort.find(item => item.value !== null);
|
||||
const sortField = (currentSort && currentSort.key) || null;
|
||||
const sortOrder = (currentSort && currentSort.value) || null;
|
||||
this.changeMonitorTable(sortField, sortOrder);
|
||||
this.currentSortField = (currentSort && currentSort.key) || null;
|
||||
this.currentSortOrder = (currentSort && currentSort.value) || null;
|
||||
this.changeMonitorTable(this.currentSortField, this.currentSortOrder);
|
||||
}
|
||||
|
||||
// begin: app type search filter
|
||||
|
||||
@@ -72,6 +72,11 @@
|
||||
<br />
|
||||
{{ 'alert.notice.sender.sms.unisms.authMode' | i18n }}: {{ smsNoticeSender.unisms.authMode }}
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="SmsType.TWILIO">
|
||||
{{ 'alert.notice.sender.sms.twilio.accountSid' | i18n }}: {{ smsNoticeSender.twilio.accountSid }}
|
||||
<br />
|
||||
{{ 'alert.notice.sender.sms.twilio.twilioPhoneNumber' | i18n }}: {{ smsNoticeSender.twilio.twilioPhoneNumber }}
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<br />
|
||||
{{ 'common.enable' | i18n }}: {{ smsNoticeSender.enable ? ('common.yes' | i18n) : ('common.no' | i18n) }}
|
||||
@@ -160,6 +165,7 @@
|
||||
<nz-option [nzValue]="SmsType.UNISMS" nzLabel="{{ 'alert.notice.sender.sms.type.unisms' | i18n }}"></nz-option>
|
||||
<nz-option [nzValue]="SmsType.SMSLOCAL" nzLabel="{{ 'alert.notice.sender.sms.type.smslocal' | i18n }}"></nz-option>
|
||||
<nz-option [nzValue]="SmsType.AWS" nzLabel="{{ 'alert.notice.sender.sms.type.aws' | i18n }}"></nz-option>
|
||||
<nz-option [nzValue]="SmsType.TWILIO" nzLabel="{{ 'alert.notice.sender.sms.type.twilio' | i18n }}"></nz-option>
|
||||
</nz-select>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
@@ -373,6 +379,48 @@
|
||||
</nz-form-item>
|
||||
</ng-container>
|
||||
|
||||
<!-- Twilio SMS -->
|
||||
<ng-container *ngIf="smsType === SmsType.TWILIO">
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSpan]="7" nzFor="accountSid" nzRequired="true">
|
||||
{{ 'alert.notice.sender.sms.twilio.accountSid' | i18n }}
|
||||
</nz-form-label>
|
||||
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
|
||||
<input
|
||||
[(ngModel)]="smsNoticeSender.twilio.accountSid"
|
||||
nz-input
|
||||
required
|
||||
name="accountSid"
|
||||
type="password"
|
||||
id="twilioAccountSid"
|
||||
/>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSpan]="7" nzFor="authToken" nzRequired="true">
|
||||
{{ 'alert.notice.sender.sms.twilio.authToken' | i18n }}
|
||||
</nz-form-label>
|
||||
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
|
||||
<input [(ngModel)]="smsNoticeSender.twilio.authToken" nz-input required name="authToken" type="password" id="twilioAuthToken" />
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSpan]="7" nzFor="twilioPhoneNumber" nzRequired="true">
|
||||
{{ 'alert.notice.sender.sms.twilio.twilioPhoneNumber' | i18n }}
|
||||
</nz-form-label>
|
||||
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
|
||||
<input
|
||||
[(ngModel)]="smsNoticeSender.twilio.twilioPhoneNumber"
|
||||
nz-input
|
||||
required
|
||||
name="twilioPhoneNumber"
|
||||
type="text"
|
||||
id="twilioPhoneNumber"
|
||||
/>
|
||||
</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">
|
||||
|
||||
@@ -33,6 +33,7 @@ import { SmsType, UniSmsAuthMode } from 'src/app/pojo/enums/sms-type.enum';
|
||||
import { AwsSmsConfig } from '../../../../pojo/AwsSmsConfig';
|
||||
import { EmailNoticeSender } from '../../../../pojo/EmailNoticeSender';
|
||||
import { SmslocalSmsConfig } from '../../../../pojo/SmslocalSmsConfig';
|
||||
import { TwilioSmsConfig } from '../../../../pojo/TwilioSmsConfig';
|
||||
import { GeneralConfigService } from '../../../../service/general-config.service';
|
||||
|
||||
@Component({
|
||||
@@ -146,6 +147,7 @@ export class MessageServerComponent implements OnInit {
|
||||
this.smsNoticeSender.unisms = { ...new UniSmsConfig(), ...message.data.unisms };
|
||||
this.smsNoticeSender.smslocal = { ...new SmslocalSmsConfig(), ...message.data.smslocal };
|
||||
this.smsNoticeSender.aws = { ...new AwsSmsConfig(), ...message.data.aws };
|
||||
this.smsNoticeSender.twilio = { ...new TwilioSmsConfig(), ...message.data.twilio };
|
||||
this.smsType = message.data.type || 'tencent';
|
||||
} else {
|
||||
this.smsNoticeSender = new SmsNoticeSender();
|
||||
@@ -171,7 +173,8 @@ export class MessageServerComponent implements OnInit {
|
||||
alibaba: { ...this.smsNoticeSender.alibaba },
|
||||
unisms: { ...this.smsNoticeSender.unisms },
|
||||
smslocal: { ...this.smsNoticeSender.smslocal },
|
||||
aws: { ...this.smsNoticeSender.aws }
|
||||
aws: { ...this.smsNoticeSender.aws },
|
||||
twilio: { ...this.smsNoticeSender.twilio }
|
||||
};
|
||||
this.isSmsServerModalVisible = true;
|
||||
}
|
||||
@@ -184,7 +187,8 @@ export class MessageServerComponent implements OnInit {
|
||||
alibaba: { ...this.tempSmsNoticeSender.alibaba },
|
||||
unisms: { ...this.tempSmsNoticeSender.unisms },
|
||||
smslocal: { ...this.tempSmsNoticeSender.smslocal },
|
||||
aws: { ...this.tempSmsNoticeSender.aws }
|
||||
aws: { ...this.tempSmsNoticeSender.aws },
|
||||
twilio: { ...this.tempSmsNoticeSender.twilio }
|
||||
};
|
||||
this.isSmsServerModalVisible = false;
|
||||
}
|
||||
|
||||
@@ -391,7 +391,15 @@
|
||||
'status.component.tag' | i18n
|
||||
}}</nz-form-label>
|
||||
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
|
||||
<app-labels-input [(ngModel)]="currentStatusComponent.labels" name="sourceLabels" required></app-labels-input>
|
||||
<app-form-field
|
||||
[item]="{
|
||||
field: 'labels',
|
||||
type: 'labels',
|
||||
required: true
|
||||
}"
|
||||
[name]="'sourceLabels'"
|
||||
[(ngModel)]="currentStatusComponent.labels"
|
||||
/>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item *ngIf="currentStatusComponent.method == 1">
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<div class="container">
|
||||
<div *ngFor="let obj of value; let i = index" class="row">
|
||||
<ng-container *ngFor="let cfg of configs">
|
||||
<app-multi-func-input
|
||||
[style]="cfg.style"
|
||||
[name]="(name || '') + '-' + i + '-' + cfg.key"
|
||||
[id]="(id || '') + '-' + i + '-' + cfg.key"
|
||||
[required]="cfg.required ?? false"
|
||||
[placeholder]="cfg.placeholder ?? ''"
|
||||
[(value)]="value[i][cfg.key]"
|
||||
(valueChange)="onChange(i, cfg.key, $event)"
|
||||
/>
|
||||
<span *ngIf="cfg.separatorAfter" class="separator">{{ cfg.separatorAfter }}</span>
|
||||
</ng-container>
|
||||
<div class="btns">
|
||||
<button nz-button nzSize="small" *ngIf="value.length > 1" (click)="del(i, $event)">
|
||||
<i nz-icon nzType="minus" nzTheme="outline"></i>
|
||||
</button>
|
||||
<button nz-button nzSize="small" (click)="add($event)">
|
||||
<i nz-icon nzType="plus" nzTheme="outline"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.row {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
|
||||
app-multi-func-input {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
flex-basis: min-content;
|
||||
text-align: center;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.btns {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
|
||||
button {
|
||||
margin-left: 0px!important;
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -19,20 +19,20 @@
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MetricsFieldInputComponent } from './metrics-field-input.component';
|
||||
import { ConfigurableFieldComponent } from './configurable-field.component';
|
||||
|
||||
describe('MetricsFieldInputComponent', () => {
|
||||
let component: MetricsFieldInputComponent;
|
||||
let fixture: ComponentFixture<MetricsFieldInputComponent>;
|
||||
describe('ConfigurableFieldComponent', () => {
|
||||
let component: ConfigurableFieldComponent;
|
||||
let fixture: ComponentFixture<ConfigurableFieldComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [MetricsFieldInputComponent]
|
||||
declarations: [ConfigurableFieldComponent]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MetricsFieldInputComponent);
|
||||
fixture = TestBed.createComponent(ConfigurableFieldComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { AbstractControl, ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator } from '@angular/forms';
|
||||
|
||||
export interface FieldConfig {
|
||||
key: string;
|
||||
style?: string;
|
||||
required?: boolean;
|
||||
placeholder?: string;
|
||||
separatorAfter?: string;
|
||||
validators?: any[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-configurable-field',
|
||||
templateUrl: './configurable-field.component.html',
|
||||
styleUrls: ['./configurable-field.component.less'],
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
multi: true,
|
||||
useExisting: ConfigurableFieldComponent
|
||||
},
|
||||
{
|
||||
provide: NG_VALIDATORS,
|
||||
multi: true,
|
||||
useExisting: ConfigurableFieldComponent
|
||||
}
|
||||
]
|
||||
})
|
||||
export class ConfigurableFieldComponent implements ControlValueAccessor, Validator {
|
||||
constructor() {}
|
||||
@Input() configs!: FieldConfig[];
|
||||
@Input() name!: string;
|
||||
@Input() id!: string;
|
||||
|
||||
value: any = [{}];
|
||||
validateStatus!: string;
|
||||
|
||||
_onChange: Function = () => {};
|
||||
_onTouched: Function = () => {};
|
||||
|
||||
writeValue(value: any): void {
|
||||
this.value = value && value.length > 0 ? value : [{}];
|
||||
}
|
||||
|
||||
registerOnChange(fn: Function): void {
|
||||
this._onChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: Function): void {
|
||||
this._onTouched = fn;
|
||||
}
|
||||
|
||||
validate(control: AbstractControl): ValidationErrors | null {
|
||||
let value = control?.value ?? [];
|
||||
for (let o of value) {
|
||||
for (let c of this.configs) {
|
||||
let v = o[c.key];
|
||||
if (c.required && (v === null || v === undefined || (typeof v === 'string' && v.trim() === ''))) {
|
||||
this.validateStatus = 'error';
|
||||
return {
|
||||
required: {
|
||||
valid: false
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
this.validateStatus = '';
|
||||
return null;
|
||||
}
|
||||
|
||||
onChange(index: number, key: string, value: any) {
|
||||
this.value[index][key] = value;
|
||||
this._onChange(this.value);
|
||||
this._onTouched();
|
||||
}
|
||||
|
||||
add(e?: MouseEvent) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
this.value.push({});
|
||||
this._onChange(this.value);
|
||||
}
|
||||
|
||||
del(index: number, e?: MouseEvent) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
if (this.value.length > 1) {
|
||||
this.value.splice(index, 1);
|
||||
}
|
||||
this._onChange(this.value);
|
||||
}
|
||||
}
|
||||
@@ -89,23 +89,70 @@
|
||||
</label>
|
||||
</nz-radio-group>
|
||||
|
||||
<app-key-value-input
|
||||
*ngIf="item.type === 'key-value'"
|
||||
<app-configurable-field
|
||||
*ngIf="item.type === 'labels'"
|
||||
[(ngModel)]="value"
|
||||
(ngModelChange)="onChange($event)"
|
||||
[keyAlias]="'monitor.headerName.tip' | i18n"
|
||||
[valueAlias]="'monitor.headerValue.tip' | i18n"
|
||||
[configs]="[
|
||||
{
|
||||
key: 'key',
|
||||
required: true,
|
||||
separatorAfter: ':',
|
||||
placeholder: 'placeholder.key' | i18n
|
||||
},
|
||||
{
|
||||
key: 'value',
|
||||
required: true,
|
||||
placeholder: 'placeholder.value' | i18n
|
||||
}
|
||||
]"
|
||||
[name]="item.field"
|
||||
[id]="item.field"
|
||||
/>
|
||||
|
||||
<app-metrics-field-input
|
||||
<app-configurable-field
|
||||
*ngIf="item.type === 'key-value'"
|
||||
[(ngModel)]="value"
|
||||
(ngModelChange)="onChange($event)"
|
||||
[configs]="[
|
||||
{
|
||||
key: 'key',
|
||||
required: true,
|
||||
placeholder: 'monitor.headerName.tip' | i18n
|
||||
},
|
||||
{
|
||||
key: 'value',
|
||||
required: true,
|
||||
placeholder: 'monitor.headerValue.tip' | i18n
|
||||
}
|
||||
]"
|
||||
[name]="item.field"
|
||||
[id]="item.field"
|
||||
/>
|
||||
|
||||
<app-configurable-field
|
||||
*ngIf="item.type === 'metrics-field'"
|
||||
[(value)]="value"
|
||||
(valueChange)="onChange($event)"
|
||||
[FieldAlias]="'Field'"
|
||||
[UnitAlias]="'Unit'"
|
||||
[TypeAlias]="'Type'"
|
||||
[(ngModel)]="value"
|
||||
(ngModelChange)="onChange($event)"
|
||||
[configs]="[
|
||||
{
|
||||
key: 'field',
|
||||
required: true,
|
||||
placeholder: 'Field'
|
||||
},
|
||||
{
|
||||
key: 'unit',
|
||||
required: true,
|
||||
placeholder: 'Unit',
|
||||
style: 'flex: 0; flex-basis: min-content; min-width: 50px'
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
required: true,
|
||||
placeholder: 'Type'
|
||||
}
|
||||
]"
|
||||
[name]="item.field"
|
||||
[id]="item.field"
|
||||
/>
|
||||
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { Component, Input, ViewChild } from '@angular/core';
|
||||
import { AbstractControl, ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator } from '@angular/forms';
|
||||
|
||||
import { ConfigurableFieldComponent } from '../configurable-field/configurable-field.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-form-field',
|
||||
templateUrl: './form-field.component.html',
|
||||
@@ -41,15 +43,39 @@ export class FormFieldComponent implements ControlValueAccessor, Validator {
|
||||
constructor() {}
|
||||
@Input() item!: any;
|
||||
@Input() extra: any = {};
|
||||
@ViewChild(ConfigurableFieldComponent) configurableField: ConfigurableFieldComponent | undefined;
|
||||
|
||||
value: any;
|
||||
validateStatus!: string;
|
||||
|
||||
_onChange: Function = () => {};
|
||||
_onTouched: Function = () => {};
|
||||
|
||||
writeValue(value: any): void {
|
||||
this.value = value;
|
||||
if (this.item.type === 'key-value' && value && typeof value === 'string') {
|
||||
// It is compatible with the key-value type data structure of existing data. For the conversion from the old to the new:
|
||||
// Old structure: "{"1":"2","3":"4"}"
|
||||
// New structure: [{key: "1", value: "2"}, {key: "3", value: "4"}]
|
||||
let tmpValue = JSON.parse(value);
|
||||
let newValue = [];
|
||||
for (let k in tmpValue) {
|
||||
newValue[newValue.length] = {
|
||||
key: k,
|
||||
value: tmpValue[k]
|
||||
};
|
||||
}
|
||||
this.value = newValue;
|
||||
} else if (this.item.type === 'labels' && value && !(value instanceof Array)) {
|
||||
// It is compatible with the lables type data structure of existing data. For the conversion from the old to the new:
|
||||
// Old structure: {1:"2", 3:"4"}
|
||||
// New structure: [{key: "1", value: "2"}, {key: "3", value: "4"}]
|
||||
let newValue = Object.entries(value).map(([key, value]) => ({
|
||||
key: String(key),
|
||||
value: String(value)
|
||||
}));
|
||||
this.value = newValue;
|
||||
} else {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
registerOnChange(fn: Function): void {
|
||||
@@ -63,19 +89,44 @@ export class FormFieldComponent implements ControlValueAccessor, Validator {
|
||||
validate(control: AbstractControl): ValidationErrors | null {
|
||||
// if (!(control.dirty) && !(control.touched)) return null;
|
||||
let { value } = control;
|
||||
if (this.item.required && (value === null || value === undefined || value === '')) {
|
||||
this.validateStatus = 'error';
|
||||
if (
|
||||
this.item.required &&
|
||||
(value === null ||
|
||||
value === undefined ||
|
||||
(typeof value === 'string' && value.trim() === '') ||
|
||||
this.configurableField?.validateStatus === 'error')
|
||||
) {
|
||||
return {
|
||||
required: {
|
||||
valid: false
|
||||
}
|
||||
};
|
||||
}
|
||||
this.validateStatus = '';
|
||||
return null;
|
||||
}
|
||||
|
||||
onChange(value: any) {
|
||||
this._onChange(value);
|
||||
if (this.item.type === 'key-value' && value) {
|
||||
// It is compatible with the key-value type data structure of existing data. For the conversion from the new to the old:
|
||||
// Old structure: "{"1":"2","3":"4"}"
|
||||
// New structure: [{key: "1", value: "2"}, {key: "3", value: "4"}]
|
||||
const newValue: any = {};
|
||||
value.forEach((item: any) => {
|
||||
newValue[item.key] = item.value;
|
||||
});
|
||||
this._onChange(JSON.stringify(newValue));
|
||||
} else if (this.item.type === 'labels' && value) {
|
||||
// It is compatible with the lables type data structure of existing data. For the conversion from the new to the old:
|
||||
// Old structure: {1:"2", 3:"4"}
|
||||
// New structure: [{key: "1", value: "2"}, {key: "3", value: "4"}]
|
||||
const newValue: any = {};
|
||||
value.forEach((item: any) => {
|
||||
newValue[item.key] = item.value;
|
||||
});
|
||||
this._onChange(newValue);
|
||||
} else {
|
||||
this._onChange(value);
|
||||
}
|
||||
this._onTouched();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +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.
|
||||
-->
|
||||
|
||||
<div style="margin-bottom: 2px; margin-top: 2px" *ngFor="let item of keyValues; let i = index" nz-row>
|
||||
<div nz-col nzSpan="10">
|
||||
<input nz-input [placeholder]="keyAlias" [(ngModel)]="item.key" (ngModelChange)="emitChange()" />
|
||||
</div>
|
||||
<div nz-col nzSpan="10">
|
||||
<input nz-input [placeholder]="valueAlias" [(ngModel)]="item.value" (ngModelChange)="emitChange()" />
|
||||
</div>
|
||||
<div nz-col nzSpan="4">
|
||||
<i nz-icon nzType="plus-circle" class="dynamic-button" *ngIf="i === 0" (click)="addNew($event)"></i>
|
||||
<i nz-icon nzType="minus-circle" class="dynamic-button" (click)="removeCurrent(i, $event)"></i>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,12 +0,0 @@
|
||||
.dynamic-button {
|
||||
position: relative;
|
||||
top: 20%;
|
||||
margin-left: 12%;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.dynamic-button:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
@@ -1,43 +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.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { KeyValueInputComponent } from './key-value-input.component';
|
||||
|
||||
describe('KeyValueInputComponent', () => {
|
||||
let component: KeyValueInputComponent;
|
||||
let fixture: ComponentFixture<KeyValueInputComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [KeyValueInputComponent]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(KeyValueInputComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,119 +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.
|
||||
*/
|
||||
|
||||
import { Component, forwardRef, Input, OnInit } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-key-value-input',
|
||||
templateUrl: './key-value-input.component.html',
|
||||
styleUrls: ['./key-value-input.component.less'],
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => KeyValueInputComponent),
|
||||
multi: true
|
||||
}
|
||||
]
|
||||
})
|
||||
export class KeyValueInputComponent implements OnInit, ControlValueAccessor {
|
||||
constructor() {}
|
||||
|
||||
@Input()
|
||||
keyAlias: string = 'Key';
|
||||
@Input()
|
||||
valueAlias: string = 'Value';
|
||||
|
||||
keyValues: any[] = [];
|
||||
|
||||
// ControlValueAccessor
|
||||
private onChange: any = () => {};
|
||||
private onTouched: any = () => {};
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.keyValues.length === 0) {
|
||||
this.addNew();
|
||||
}
|
||||
}
|
||||
|
||||
writeValue(obj: string): void {
|
||||
if (obj != undefined) {
|
||||
let value = JSON.parse(obj);
|
||||
if (value != null) {
|
||||
this.keyValues = [];
|
||||
Object.keys(value).map(item => {
|
||||
this.keyValues.push({
|
||||
key: item,
|
||||
value: value[item]
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
if (this.keyValues.length === 0) {
|
||||
this.addNew();
|
||||
}
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.onChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
this.onTouched = fn;
|
||||
}
|
||||
|
||||
setDisabledState(isDisabled: boolean): void {
|
||||
// set disabled state here
|
||||
}
|
||||
|
||||
addNew(e?: MouseEvent) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
this.keyValues.push({
|
||||
key: '',
|
||||
value: ''
|
||||
});
|
||||
}
|
||||
|
||||
removeCurrent(index: number, e?: MouseEvent) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
if (this.keyValues.length > 1) {
|
||||
this.keyValues.splice(index, 1);
|
||||
this.emitChange();
|
||||
}
|
||||
}
|
||||
|
||||
emitChange() {
|
||||
let value: Record<string, string> = {};
|
||||
this.keyValues.forEach(item => {
|
||||
if (item != null && item.key != null) {
|
||||
value[item.key] = item.value;
|
||||
}
|
||||
});
|
||||
if (Object.keys(value).length === 0 || Object.values(value).every(v => v === '')) {
|
||||
this.onChange(null);
|
||||
} else {
|
||||
this.onChange(JSON.stringify(value));
|
||||
}
|
||||
this.onTouched();
|
||||
}
|
||||
}
|
||||
@@ -1,34 +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.
|
||||
-->
|
||||
|
||||
<div style="margin-bottom: 2px; margin-top: 2px" *ngFor="let item of keyValues; let i = index" nz-row>
|
||||
<div nz-col nzSpan="10">
|
||||
<input nz-input [placeholder]="keyAlias" [(ngModel)]="item.key" (ngModelChange)="emitChange()" />
|
||||
</div>
|
||||
<div style="text-align: center; font-weight: bolder" nz-col nzSpan="1">
|
||||
<span>:</span>
|
||||
</div>
|
||||
<div nz-col nzSpan="10">
|
||||
<input nz-input [placeholder]="valueAlias" [(ngModel)]="item.value" (ngModelChange)="emitChange()" />
|
||||
</div>
|
||||
<div nz-col nzSpan="3">
|
||||
<i nz-icon nzType="plus-circle" class="dynamic-button" *ngIf="i === 0" (click)="addNew($event)"></i>
|
||||
<i nz-icon nzType="minus-circle" class="dynamic-button" (click)="removeCurrent(i, $event)"></i>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,12 +0,0 @@
|
||||
.dynamic-button {
|
||||
position: relative;
|
||||
top: 20%;
|
||||
margin-left: 12%;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.dynamic-button:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
@@ -1,41 +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.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LabelsInputComponent } from './labels-input.component';
|
||||
|
||||
describe('LabelsInputComponent', () => {
|
||||
let component: LabelsInputComponent;
|
||||
let fixture: ComponentFixture<LabelsInputComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [LabelsInputComponent]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(LabelsInputComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,115 +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.
|
||||
*/
|
||||
|
||||
import { Component, Input, OnInit, forwardRef } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
|
||||
interface KeyValuePair {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-labels-input',
|
||||
templateUrl: './labels-input.component.html',
|
||||
styleUrl: './labels-input.component.less',
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => LabelsInputComponent),
|
||||
multi: true
|
||||
}
|
||||
]
|
||||
})
|
||||
export class LabelsInputComponent implements OnInit, ControlValueAccessor {
|
||||
constructor() {}
|
||||
|
||||
@Input() keyAlias: string = 'Key';
|
||||
@Input() valueAlias: string = 'Value';
|
||||
|
||||
keyValues: KeyValuePair[] = [];
|
||||
|
||||
// ControlValueAccessor
|
||||
private onChange: any = () => {};
|
||||
private onTouched: any = () => {};
|
||||
|
||||
writeValue(value: Record<string, string>): void {
|
||||
this.keyValues = Object.entries(value || {}).map(([key, value]) => ({
|
||||
key,
|
||||
value
|
||||
}));
|
||||
|
||||
if (this.keyValues.length === 0) {
|
||||
this.addNew();
|
||||
}
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.onChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
this.onTouched = fn;
|
||||
}
|
||||
|
||||
setDisabledState(isDisabled: boolean): void {
|
||||
// set disabled state here
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.keyValues.length === 0) {
|
||||
this.addNew();
|
||||
}
|
||||
}
|
||||
|
||||
addNew(e?: MouseEvent) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
this.keyValues.push({
|
||||
key: '',
|
||||
value: ''
|
||||
});
|
||||
}
|
||||
|
||||
removeCurrent(index: number, e?: MouseEvent) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
if (this.keyValues.length > 1) {
|
||||
this.keyValues.splice(index, 1);
|
||||
this.emitChange();
|
||||
}
|
||||
}
|
||||
|
||||
emitChange() {
|
||||
const result: Record<string, string> = {};
|
||||
this.keyValues.forEach(item => {
|
||||
if (item.key?.trim()) {
|
||||
result[item.key] = item.value || '';
|
||||
}
|
||||
});
|
||||
if (Object.keys(result).length === 0 || Object.values(result).every(v => v === '')) {
|
||||
this.onChange(null);
|
||||
} else {
|
||||
this.onChange(result);
|
||||
}
|
||||
this.onTouched();
|
||||
}
|
||||
}
|
||||
-34
@@ -1,34 +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.
|
||||
-->
|
||||
|
||||
<div *ngFor="let item of fields; let i = index" nz-row>
|
||||
<div nz-col nzSpan="7">
|
||||
<input nz-input [placeholder]="FieldAlias" [(ngModel)]="item.field" (ngModelChange)="onChange()" />
|
||||
</div>
|
||||
<div nz-col nzSpan="7">
|
||||
<input nz-input [placeholder]="UnitAlias" [(ngModel)]="item.unit" (ngModelChange)="onChange()" />
|
||||
</div>
|
||||
<div nz-col nzSpan="7">
|
||||
<input nz-input [placeholder]="TypeAlias" [(ngModel)]="item.type" (ngModelChange)="onChange()" />
|
||||
</div>
|
||||
<div nz-col nzSpan="3">
|
||||
<i nz-icon nzType="plus-circle" class="dynamic-button" *ngIf="i === 0" (click)="addNew($event)"></i>
|
||||
<i nz-icon nzType="minus-circle" class="dynamic-button" (click)="removeCurrent(i, $event)"></i>
|
||||
</div>
|
||||
</div>
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
.dynamic-button {
|
||||
position: relative;
|
||||
top: 20%;
|
||||
margin-left: 12%;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.dynamic-button:hover {
|
||||
transform: scale(1.7);
|
||||
}
|
||||
-87
@@ -1,87 +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.
|
||||
*/
|
||||
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-metrics-field-input',
|
||||
templateUrl: './metrics-field-input.component.html',
|
||||
styleUrls: ['./metrics-field-input.component.less']
|
||||
})
|
||||
export class MetricsFieldInputComponent implements OnInit {
|
||||
constructor() {}
|
||||
|
||||
@Input() value!: any;
|
||||
@Output() readonly valueChange = new EventEmitter<string>();
|
||||
|
||||
@Input()
|
||||
FieldAlias: string = 'field';
|
||||
@Input()
|
||||
UnitAlias: string = 'unit';
|
||||
@Input()
|
||||
TypeAlias: string = 'type';
|
||||
|
||||
fields: any[] = [];
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.value == undefined) {
|
||||
this.fields.push({
|
||||
field: '',
|
||||
unit: '',
|
||||
type: ''
|
||||
});
|
||||
} else {
|
||||
this.value = JSON.parse(this.value);
|
||||
}
|
||||
if (this.value) {
|
||||
for (let item of this.value) {
|
||||
this.fields.push({
|
||||
field: item.field,
|
||||
unit: item.unit,
|
||||
type: item.type
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addNew(e?: MouseEvent) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
this.fields.push({
|
||||
field: '',
|
||||
unit: '',
|
||||
type: ''
|
||||
});
|
||||
}
|
||||
|
||||
removeCurrent(index: number, e?: MouseEvent) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
if (this.fields.length > 1) {
|
||||
this.fields.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
onChange() {
|
||||
this.value = this.fields;
|
||||
this.valueChange.emit(JSON.stringify(this.value));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
:host {
|
||||
::ng-deep {
|
||||
.ant-input-affix-wrapper {
|
||||
width: 100%!important;
|
||||
}
|
||||
.ant-input-prefix, .ant-input-suffix {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
|
||||
@@ -18,5 +18,5 @@
|
||||
*/
|
||||
|
||||
export const CONSTANTS = {
|
||||
VERSION: 'v1.6.1'
|
||||
VERSION: 'v1.7.0'
|
||||
};
|
||||
|
||||
@@ -11,11 +11,9 @@ import { NzRadioComponent, NzRadioGroupComponent } from 'ng-zorro-antd/radio';
|
||||
import { NzSwitchComponent } from 'ng-zorro-antd/switch';
|
||||
import { NzTagModule } from 'ng-zorro-antd/tag';
|
||||
|
||||
import { ConfigurableFieldComponent } from './components/configurable-field/configurable-field.component';
|
||||
import { FormFieldComponent } from './components/form-field/form-field.component';
|
||||
import { HelpMessageShowComponent } from './components/help-message-show/help-message-show.component';
|
||||
import { KeyValueInputComponent } from './components/key-value-input/key-value-input.component';
|
||||
import { LabelsInputComponent } from './components/labels-input/labels-input.component';
|
||||
import { MetricsFieldInputComponent } from './components/metrics-field-input/metrics-field-input.component';
|
||||
import { MonitorSelectMenuComponent } from './components/monitor-select-menu/monitor-select-menu.component';
|
||||
import { MultiFuncInputComponent } from './components/multi-func-input/multi-func-input.component';
|
||||
import { ToolbarComponent } from './components/toolbar/toolbar.component';
|
||||
@@ -27,14 +25,12 @@ import { SHARED_ZORRO_MODULES } from './shared-zorro.module';
|
||||
|
||||
const ThirdModules: Array<Type<void>> = [];
|
||||
const COMPONENTS: Array<Type<void>> = [
|
||||
KeyValueInputComponent,
|
||||
MultiFuncInputComponent,
|
||||
HelpMessageShowComponent,
|
||||
MetricsFieldInputComponent,
|
||||
ToolbarComponent,
|
||||
ConfigurableFieldComponent,
|
||||
FormFieldComponent,
|
||||
MonitorSelectMenuComponent,
|
||||
LabelsInputComponent
|
||||
MonitorSelectMenuComponent
|
||||
];
|
||||
const DIRECTIVES: Array<Type<void>> = [TimezonePipe, I18nElsePipe, ElapsedTimePipe];
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
> Prometheus AlertManager のアラートを HertzBeat アラートプラットフォームに送信します。
|
||||
|
||||
### Alertmanager の Webhook 設定
|
||||
|
||||
1. Alertmanager の設定ファイルで webhook を設定する。
|
||||
|
||||
```yaml
|
||||
receivers:
|
||||
- name: 'webhook'
|
||||
webhook_configs:
|
||||
- url: 'http://{hertzbeat_host}:1157/api/alerts/report/alertmanager'
|
||||
send_resolved: true
|
||||
http_config:
|
||||
authorization:
|
||||
type: 'Bearer'
|
||||
credentials: '{token}'
|
||||
```
|
||||
|
||||
- `http://{hertzbeat_host}:1157/api/alerts/report/alertmanager` は、HertzBeat の webhook URLです。
|
||||
- `send_resolved: true` は、アラート解決情報を送信することを意味します。
|
||||
- `credentials` の `{token}` はHertzBeatが提供するtokenです。
|
||||
|
||||
2. Alertmanager のサービスを再起動する。
|
||||
|
||||
### 設定の検証
|
||||
|
||||
- Prometheus AlertManager のアラートをトリガーする。
|
||||
- HertzBeat アラートプラットフォームでアラートデータを閲覧し、正しいか検証する。
|
||||
|
||||
### データフロー
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Prometheus Alertmanager] --> B[Webhook]
|
||||
B --> C[HertzBeatアラートプラットフォーム]
|
||||
C --> D[グループ収束]
|
||||
D --> E[アラート抑制]
|
||||
E --> F[アラームサイレンス]
|
||||
F --> G[アラームセンター]
|
||||
F --> H[メッセージ配信]
|
||||
```
|
||||
|
||||
### FAQ
|
||||
|
||||
- Alertmanager の設定ファイルで webhook アドレスが正しく、ネットワークが接続されていることを確認してください。
|
||||
- Alertmanagerのアラートがトリガーされたかどうか、またHertzBeatアラートプラットフォームに送信されたかどうかを確認してください。
|
||||
@@ -0,0 +1,40 @@
|
||||
> Prometheus ServerのAlertmanager設定でHertzBeatのサービスアドレスを設定し、HertzBeatを使用してAlertmanagerを置き換え、Prometheus Serverのアラート情報を受信および処理します。
|
||||
|
||||
### Prometheus サービスの設定
|
||||
|
||||
- Prometheusの設定ファイル prometheus.yml を編集し、HertzBeatをアラートの受信先として追加します。
|
||||
```yaml
|
||||
# Alertmanager configuration
|
||||
alerting:
|
||||
alertmanagers:
|
||||
- static_configs:
|
||||
- targets:
|
||||
- {hertzbeat_host}:1157
|
||||
authorization:
|
||||
type: 'Bearer'
|
||||
credentials: '{token}'
|
||||
|
||||
```
|
||||
- `{hertzbeat_host}:1157` は、HertzBeat Server のアドレスとポートです。実際の環境に合わせて設定を修正し、ネットワーク接続が確立されていることを確認してください。
|
||||
- `{token}` は、HertzBeat Server の認証トークンです。新しいトークンを申請した後、置き換えてください。
|
||||
- Prometheus Server を再起動してください。
|
||||
|
||||
## 設定の検証
|
||||
|
||||
1. Prometheus の設定が正しいことを確認し、設定を再読み込みしてください。
|
||||
```bash
|
||||
curl -X POST http://localhost:9090/-/reload
|
||||
```
|
||||
2. Prometheusアラートルールの状態を確認する。
|
||||
```bash
|
||||
curl http://localhost:9090/api/v1/rules
|
||||
```
|
||||
3. テストアラートをトリガーし、HertzBeatアラートセンターで表示を確認する 。
|
||||
|
||||
## FAQ
|
||||
|
||||
- PrometheusサーバーからHertzBeatのURLへアクセス可能であること。
|
||||
- Prometheusログ内にアラート送信失敗のエラーメッセージがないか確認する。
|
||||
- アラートルールの式が正しいことを検証する。
|
||||
|
||||
詳細情報は [Prometheusアラート設定ドキュメント](https://prometheus.io/docs/alerting/latest/configuration/) を参照してください
|
||||
@@ -0,0 +1,30 @@
|
||||
>SkyWalking のアラートを Webhook 方式で HertzBeat アラートプラットフォームに送信します。
|
||||
|
||||
### SkyWalking の設定
|
||||
|
||||
- SkyWalking 設定ファイル `alarm-settings.yml` を編集、HertzBeat をアラート受信先として設定します。
|
||||
```yaml
|
||||
hooks:
|
||||
webhook:
|
||||
default:
|
||||
is-default: true
|
||||
urls:
|
||||
- http://{hertzbeat_host}:1157/api/alerts/report/skywalking
|
||||
```
|
||||
- `http://{hertzbeat_host}:1157/api/alerts/report/skywalking` は、 HertzBeat が提供する Webhook インターフェースです。
|
||||
- SkyWalking OAP Server を再起動してください。
|
||||
|
||||
### 設定の検証
|
||||
|
||||
1. SkyWalking の設定が正しいことを確認し、設定を再読み込みしてください。
|
||||
2. SkyWalkingアラートルールの状態を確認する。
|
||||
3. テストアラートをトリガーし、HertzBeatアラートセンターで表示を確認する。
|
||||
|
||||
|
||||
### FAQ
|
||||
|
||||
- SkyWalkingサーバーからHertzBeatのURLへアクセス可能であること。
|
||||
- SkyWalkingログ内にアラート送信失敗のエラーメッセージがないか確認する。
|
||||
- アラートルールの式が正しいことを検証する。
|
||||
|
||||
詳細情報は [SkyWalkingアラート設定ドキュメント](https://skywalking.apache.org/docs/main/latest/en/setup/backend/backend-alarm/)
|
||||
@@ -18,7 +18,7 @@ hooks:
|
||||
|
||||
1. 确保 SkyWalking 配置正确并重新加载配置
|
||||
2. 检查 SkyWalking 告警规则状态
|
||||
3. 触发测试告警并在 SkyWalking 告警中心查看
|
||||
3. 触发测试告警并在 HertzBeat 告警中心查看
|
||||
|
||||
### 常见问题
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
>Send Tencent Cloud alerts to the HertzBeat alert platform via Webhook.
|
||||
|
||||
### Configure Tencent Cloud Alert Callback
|
||||
|
||||
#### Configure Notification Template
|
||||
1. Log in to [Tencent Cloud Observability Platform](https://console.cloud.tencent.com/monitorv2)
|
||||
2. Navigate to **Alarm Management** > **Alarm Configuration** > **Notification Templates**
|
||||
3. Click **Create Notification Template**
|
||||
4. On the new notification template page, fill in the basic information
|
||||
5. In the **API Callback** module, enter the HertzBeat Webhook API URL:
|
||||
```
|
||||
http://{your_system_host}/api/alerts/tencent
|
||||
```
|
||||
6. Save the notification template
|
||||
|
||||
#### Bind Alert Policy
|
||||
1. Go to **Alert Policy List**
|
||||
2. Select the alert policy that needs to be bound with the Webhook callback, click the policy name to enter the management page
|
||||
3. In the notification template configuration, bind the notification template created in the previous step
|
||||
4. Save the policy configuration
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Not Receiving Alerts
|
||||
- Ensure the Webhook URL is accessible from the public network
|
||||
- Check if there are request records in the server logs
|
||||
- Test if the Webhook is available on the Tencent Cloud notification template page
|
||||
|
||||
#### Alerts Not Triggered
|
||||
- Ensure the alert policy conditions are correct and the notification template is bound
|
||||
- Check the alert history on the Tencent Cloud monitoring page to confirm if the policy was triggered
|
||||
|
||||
For more information, please refer to [Tencent Cloud Alert Configuration Documentation](https://cloud.tencent.com/document/product/248/50409)
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
>TencentCloudのアラートをWebhookでHertzBeatアラートプラットフォームに送信します。
|
||||
|
||||
### テンセントクラウドアラートコールバックの設定
|
||||
|
||||
#### 通知テンプレートの設定
|
||||
1. [TencentCloud Observability Platform](https://console.cloud.tencent.com/monitorv2)にログインします。
|
||||
2. **アラート管理** > **アラート設定** > **通知テンプレート** に移動します。
|
||||
4. **新しい通知テンプレート** をクリックします。
|
||||
4. 新しい通知テンプレートページで、基本情報を入力します。
|
||||
5. **インターフェースコールバック** モジュールで、HertzBeat が提供する Webhook インターフェースアドレス URL を入力します。
|
||||
```
|
||||
http://{your_system_host}/api/alerts/tencent
|
||||
```
|
||||
6. 通知テンプレートを保存します。
|
||||
|
||||
#### アラートポリシーを関連付ける
|
||||
1. **アラートポリシー一覧画面** に移動します。
|
||||
2. Webhookコールバックを関連付けるアラートポリシーを選択し、ポリシー名をクリックして管理ページにアクセスします。
|
||||
3. 通知テンプレート設定項目で、前の手順で作成した通知テンプレートを関連付けます。
|
||||
4. ポリシー設定を保存します。
|
||||
|
||||
### FAQ
|
||||
|
||||
#### アラートが受信されない場合
|
||||
- Webhook URLがパブリックネットワークからアクセス可能か確認してください。
|
||||
- サーバーログにリクエスト記録があるかチェックしてください。
|
||||
- Tencent Cloudの通知テンプレートページでWebhookが使用可能かテストしてください。
|
||||
|
||||
#### アラートがトリガーされない場合
|
||||
- アラートポリシーの条件が正しく設定されていること、および通知テンプレートが関連付けられていることを確認してください 。
|
||||
- Tencent Cloud監視ページのアラート履歴を確認し、ポリシーがトリガーされていることを確認してください。
|
||||
|
||||
詳細情報は [TencentCloud アラート設定ドキュメント](https://cloud.tencent.com/document/product/248/50409)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
>将腾讯云的告警通过 Webhook 方式发送到 HertzBeat 告警平台。
|
||||
|
||||
### 配置腾讯云告警回调
|
||||
|
||||
#### 进入通知模板配置
|
||||
1. 登录 [腾讯云可观测平台](https://console.cloud.tencent.com/monitorv2)
|
||||
2. 进入 **告警管理** > **告警配置** > **通知模板**
|
||||
3. 单击 **新建通知模板**
|
||||
4. 在新建通知模板页面,填写基础信息
|
||||
5. 在 **接口回调** 模块中,填写 HertzBeat 提供的 Webhook 接口地址 URL:
|
||||
```
|
||||
http://{your_system_host}/api/alerts/tencent
|
||||
```
|
||||
6. 保存通知模板
|
||||
|
||||
#### 绑定告警策略
|
||||
1. 进入 **告警策略列表**
|
||||
2. 选择需要绑定 Webhook 回调的告警策略,点击策略名称进入管理页面
|
||||
3. 在通知模板配置项中,绑定上一步创建的通知模板
|
||||
4. 保存策略配置
|
||||
|
||||
### 常见问题
|
||||
|
||||
#### 未收到告警
|
||||
- 确保 Webhook URL 可以被公网访问
|
||||
- 检查服务器日志是否有请求记录
|
||||
- 在腾讯云通知模板页面测试 Webhook 是否可用
|
||||
|
||||
#### 告警未触发
|
||||
- 确保告警策略的条件正确,并已绑定通知模板
|
||||
- 在腾讯云监控页面查看告警历史,确保策略被触发
|
||||
|
||||
更多信息请参考 [腾讯云告警配置文档](https://cloud.tencent.com/document/product/248/50409)
|
||||
|
||||
|
||||
@@ -1,9 +1,34 @@
|
||||
## 验证配置
|
||||
>將騰訊雲的告警通過 Webhook 方式發送到 HertzBeat 告警平台。
|
||||
|
||||
1. 确保 Prometheus 配置正确并重新加载配置
|
||||
2. 触发一个测试告警
|
||||
3. 在 HertzBeat 告警中心查看是否收到告警
|
||||
### 配置騰訊雲告警回調
|
||||
|
||||
更多信息请参考 [Prometheus 官方文档](https://prometheus.io/docs/alerting/latest/configuration/)
|
||||
#### 進入通知模板配置
|
||||
1. 登錄 [騰訊雲可觀測平台](https://console.cloud.tencent.com/monitorv2)
|
||||
2. 進入 **告警管理** > **告警配置** > **通知模板**
|
||||
3. 單擊 **新建通知模板**
|
||||
4. 在新建通知模板頁面,填寫基礎信息
|
||||
5. 在 **接口回調** 模塊中,填寫 HertzBeat 提供的 Webhook 接口地址 URL:
|
||||
```
|
||||
http://{your_system_host}/api/alerts/tencent
|
||||
```
|
||||
6. 保存通知模板
|
||||
|
||||
#### 綁定告警策略
|
||||
1. 進入 **告警策略列表**
|
||||
2. 選擇需要綁定 Webhook 回調的告警策略,點擊策略名稱進入管理頁面
|
||||
3. 在通知模板配置項中,綁定上一步創建的通知模板
|
||||
4. 保存策略配置
|
||||
|
||||
### 常見問題
|
||||
|
||||
#### 未收到告警
|
||||
- 確保 Webhook URL 可以被公網訪問
|
||||
- 檢查服務器日誌是否有請求記錄
|
||||
- 在騰訊雲通知模板頁面測試 Webhook 是否可用
|
||||
|
||||
#### 告警未觸發
|
||||
- 確保告警策略的條件正確,並已綁定通知模板
|
||||
- 在騰訊雲監控頁面查看告警歷史,確保策略被觸發
|
||||
|
||||
更多信息請參考 [騰訊雲告警配置文檔](https://cloud.tencent.com/document/product/248/50409)
|
||||
|
||||
## Alert Rule Example
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user