mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-09-17 18:19:02 +00:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
047d59381c | ||
|
|
cdb4219065 | ||
|
|
cabe8d8ff9 | ||
|
|
4ac8170d21 | ||
|
|
5641a5ca89 | ||
|
|
de8bd79ef9 | ||
|
|
30332969ee |
@@ -29,11 +29,6 @@
|
||||
"type": 0,
|
||||
"paramValue": 1000
|
||||
},
|
||||
{
|
||||
"field": "ssl",
|
||||
"type": 1,
|
||||
"paramValue": false
|
||||
},
|
||||
{
|
||||
"field": "username",
|
||||
"type": 1
|
||||
|
||||
@@ -98,6 +98,25 @@
|
||||
<version>${easy-poi.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- sms -->
|
||||
<dependency>
|
||||
<groupId>com.tencentcloudapi</groupId>
|
||||
<artifactId>tencentcloud-sdk-java-sms</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.squareup.okhttp</groupId>
|
||||
<artifactId>logging-interceptor</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.squareup.okhttp</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.squareup.okio</groupId>
|
||||
<artifactId>okio</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.huaweicloud.sdk</groupId>
|
||||
<artifactId>huaweicloud-sdk-smn</artifactId>
|
||||
|
||||
+2
-9
@@ -19,7 +19,6 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
@@ -31,7 +30,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
/**
|
||||
* SSE manager for alert
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AlertSseManager {
|
||||
private final Map<Long, SseEmitter> emitters = new ConcurrentHashMap<>();
|
||||
@@ -40,7 +38,6 @@ public class AlertSseManager {
|
||||
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
|
||||
emitter.onCompletion(() -> removeEmitter(clientId));
|
||||
emitter.onTimeout(() -> removeEmitter(clientId));
|
||||
emitter.onError((ex) -> removeEmitter(clientId));
|
||||
emitters.put(clientId, emitter);
|
||||
return emitter;
|
||||
}
|
||||
@@ -53,11 +50,7 @@ public class AlertSseManager {
|
||||
.id(String.valueOf(System.currentTimeMillis()))
|
||||
.name("ALERT_EVENT")
|
||||
.data(data));
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
emitter.complete();
|
||||
removeEmitter(clientId);
|
||||
} catch (Exception exception) {
|
||||
log.error("Failed to broadcast alert data to client: {}", exception.getMessage());
|
||||
} catch (IOException e) {
|
||||
emitter.complete();
|
||||
removeEmitter(clientId);
|
||||
}
|
||||
@@ -67,4 +60,4 @@ public class AlertSseManager {
|
||||
private void removeEmitter(Long clientId) {
|
||||
emitters.remove(clientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Alibaba Cloud SMS configuration properties
|
||||
*/
|
||||
@Data
|
||||
public class AlibabaSmsProperties {
|
||||
/**
|
||||
* Alibaba Cloud access key id
|
||||
*/
|
||||
private String accessKeyId;
|
||||
|
||||
/**
|
||||
* Alibaba Cloud access key secret
|
||||
*/
|
||||
private String accessKeySecret;
|
||||
|
||||
/**
|
||||
* SMS signature
|
||||
*/
|
||||
private String signName;
|
||||
|
||||
/**
|
||||
* SMS template Code
|
||||
*/
|
||||
private String templateCode;
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* SMS configuration
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "alerter.sms")
|
||||
public class SmsConfig {
|
||||
|
||||
/**
|
||||
* whether to enable SMS, default is false
|
||||
*/
|
||||
private boolean enable = false;
|
||||
|
||||
/**
|
||||
* sms service provider
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* Tencent cloud SMS configuration
|
||||
*/
|
||||
private TencentSmsProperties tencent;
|
||||
|
||||
/**
|
||||
* Aliyun SMS configuration
|
||||
*/
|
||||
private AlibabaSmsProperties alibaba;
|
||||
|
||||
/**
|
||||
* UniSMS configuration
|
||||
*/
|
||||
private UniSmsProperties unisms;
|
||||
|
||||
}
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Tencent Cloud SMS properties
|
||||
*/
|
||||
@Data
|
||||
public class TencentSmsProperties {
|
||||
/**
|
||||
* Tencent cloud account secret id
|
||||
*/
|
||||
private String secretId;
|
||||
|
||||
/**
|
||||
* Tencent cloud account secret key
|
||||
*/
|
||||
private String secretKey;
|
||||
|
||||
/**
|
||||
* SMS app id
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* SMS signature
|
||||
*/
|
||||
private String signName;
|
||||
|
||||
/**
|
||||
* SMS template ID
|
||||
*/
|
||||
private String templateId;
|
||||
}
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* UniSMS properties
|
||||
*/
|
||||
@Data
|
||||
public class UniSmsProperties {
|
||||
/**
|
||||
* UniSMS access key id
|
||||
*/
|
||||
private String accessKeyId;
|
||||
|
||||
/**
|
||||
* UniSMS access key secret, required for HMAC mode
|
||||
*/
|
||||
private String accessKeySecret;
|
||||
|
||||
/**
|
||||
* SMS signature
|
||||
*/
|
||||
private String signature;
|
||||
|
||||
/**
|
||||
* SMS template ID
|
||||
*/
|
||||
private String templateId;
|
||||
|
||||
/**
|
||||
* Authentication mode: simple or hmac, default is simple
|
||||
*/
|
||||
private String authMode = "simple";
|
||||
}
|
||||
+7
-7
@@ -38,9 +38,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Alarm Inhibit management API
|
||||
* Alarm Silence management API
|
||||
*/
|
||||
@Tag(name = "Alert Inhibit API")
|
||||
@Tag(name = "Alert Silence API")
|
||||
@RestController
|
||||
@RequestMapping(path = "/api/alert/inhibit", produces = {APPLICATION_JSON_VALUE})
|
||||
public class AlertInhibitController {
|
||||
@@ -49,7 +49,7 @@ public class AlertInhibitController {
|
||||
private AlertInhibitService alertInhibitService;
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "New Alarm Inhibit", description = "Added an alarm Inhibit")
|
||||
@Operation(summary = "New Alarm Silence", description = "Added an alarm Silence")
|
||||
public ResponseEntity<Message<Void>> addNewAlertInhibit(@Valid @RequestBody AlertInhibit alertInhibit) {
|
||||
alertInhibitService.validate(alertInhibit, false);
|
||||
alertInhibitService.addAlertInhibit(alertInhibit);
|
||||
@@ -57,7 +57,7 @@ public class AlertInhibitController {
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@Operation(summary = "Modifying an Alarm Inhibit", description = "Modify an existing alarm Inhibit")
|
||||
@Operation(summary = "Modifying an Alarm Silence", description = "Modify an existing alarm Silence")
|
||||
public ResponseEntity<Message<Void>> modifyAlertInhibit(@Valid @RequestBody AlertInhibit alertInhibit) {
|
||||
alertInhibitService.validate(alertInhibit, true);
|
||||
alertInhibitService.modifyAlertInhibit(alertInhibit);
|
||||
@@ -65,10 +65,10 @@ public class AlertInhibitController {
|
||||
}
|
||||
|
||||
@GetMapping(path = "/{id}")
|
||||
@Operation(summary = "Querying Alarm Inhibit",
|
||||
description = "You can obtain alarm Inhibit information based on the alarm Inhibit ID")
|
||||
@Operation(summary = "Querying Alarm Silence",
|
||||
description = "You can obtain alarm Silence information based on the alarm Silence ID")
|
||||
public ResponseEntity<Message<AlertInhibit>> getAlertInhibit(
|
||||
@Parameter(description = "Alarm Inhibit ID", example = "6565463543") @PathVariable("id") long id) {
|
||||
@Parameter(description = "Alarm Silence ID", example = "6565463543") @PathVariable("id") long id) {
|
||||
AlertInhibit alertInhibit = alertInhibitService.getAlertInhibit(id);
|
||||
|
||||
return Objects.isNull(alertInhibit)
|
||||
|
||||
+4
-4
@@ -36,9 +36,9 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Inhibit the batch API for alarms
|
||||
* Silence the batch API for alarms
|
||||
*/
|
||||
@Tag(name = "Alert Inhibit Batch API")
|
||||
@Tag(name = "Alert Silence Batch API")
|
||||
@RestController
|
||||
@RequestMapping(path = "/api/alert/inhibits", produces = {APPLICATION_JSON_VALUE})
|
||||
public class AlertInhibitsController {
|
||||
@@ -50,7 +50,7 @@ public class AlertInhibitsController {
|
||||
@Operation(summary = "Query the alarm inhibit list",
|
||||
description = "You can obtain the list of alarm inhibit by querying filter items")
|
||||
public ResponseEntity<Message<Page<AlertInhibit>>> getAlertInhibits(
|
||||
@Parameter(description = "Alarm Inhibit ID", example = "6565463543") @RequestParam(required = false) List<Long> ids,
|
||||
@Parameter(description = "Alarm Silence ID", example = "6565463543") @RequestParam(required = false) List<Long> ids,
|
||||
@Parameter(description = "Search Name", example = "x") @RequestParam(required = false) String search,
|
||||
@Parameter(description = "Sort field, default id", example = "id") @RequestParam(defaultValue = "id") String sort,
|
||||
@Parameter(description = "Sort mode: asc: ascending, desc: descending", example = "desc") @RequestParam(defaultValue = "desc") String order,
|
||||
@@ -64,7 +64,7 @@ public class AlertInhibitsController {
|
||||
@Operation(summary = "Delete alarm inhibit in batches",
|
||||
description = "Delete alarm inhibit in batches based on the alarm inhibit ID list")
|
||||
public ResponseEntity<Message<Void>> deleteAlertDefines(
|
||||
@Parameter(description = "Alarm Inhibit IDs", example = "6565463543") @RequestParam(required = false) List<Long> ids
|
||||
@Parameter(description = "Alarm Silence IDs", example = "6565463543") @RequestParam(required = false) List<Long> ids
|
||||
) {
|
||||
if (ids != null && !ids.isEmpty()) {
|
||||
alertInhibitService.deleteAlertInhibits(new HashSet<>(ids));
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ public class AlertNoticeDispatch {
|
||||
if (noticeTemplate == null) {
|
||||
noticeTemplate = noticeConfigService.getDefaultNoticeTemplateByType(alertNotifyHandler.type());
|
||||
}
|
||||
if (noticeTemplate == null && alertNotifyHandler.type() != 0) {
|
||||
if (noticeTemplate == null) {
|
||||
log.error("alert does not have mapping default notice template. type: {}.", alertNotifyHandler.type());
|
||||
throw new NullPointerException(alertNotifyHandler.type() + " does not have mapping default notice template");
|
||||
}
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.notice.impl;
|
||||
|
||||
import java.util.ResourceBundle;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
|
||||
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.util.ResourceBundleUtil;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Send alarm information through Alibaba Cloud SMS
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@ConditionalOnProperty("common.sms.aliyun.app-id")
|
||||
final class AliYunAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
|
||||
|
||||
private final ResourceBundle bundle = ResourceBundleUtil.getBundle("alerter");
|
||||
|
||||
@Override
|
||||
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
|
||||
// SMS notification
|
||||
try {
|
||||
// todo send aliyun sms
|
||||
} catch (Exception e) {
|
||||
throw new AlertNoticeException("[Sms Notify Error] " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte type() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+23
-10
@@ -21,12 +21,12 @@ import java.util.ResourceBundle;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.alert.service.SmsClientFactory;
|
||||
import org.apache.hertzbeat.alert.service.TencentSmsClient;
|
||||
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.util.ResourceBundleUtil;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -35,22 +35,35 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@ConditionalOnProperty("common.sms.tencent.app-id")
|
||||
@Deprecated
|
||||
final class SmsAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
|
||||
|
||||
private final SmsClientFactory smsFactory;
|
||||
private final TencentSmsClient tencentSmsClient;
|
||||
|
||||
private final ResourceBundle bundle = ResourceBundleUtil.getBundle("alerter");
|
||||
|
||||
@Override
|
||||
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
|
||||
// SMS notification todo use the rest api not sdk
|
||||
try {
|
||||
SmsClient smsClient = smsFactory.getSmsClient();
|
||||
if (smsClient == null) {
|
||||
throw new AlertNoticeException("No SMS Service available, please check the configuration");
|
||||
String instance = null;
|
||||
String priority = null;
|
||||
String content = null;
|
||||
if (alert.getCommonLabels() != null) {
|
||||
instance = alert.getCommonLabels().get("instance");
|
||||
priority = alert.getCommonLabels().get("priority");
|
||||
content = alert.getCommonAnnotations().get("summary");
|
||||
content = content == null ? alert.getCommonAnnotations().get("description") : content;
|
||||
if (content == null) {
|
||||
content = alert.getCommonAnnotations().values().stream().findFirst().orElse(null);
|
||||
}
|
||||
}
|
||||
if (!smsClient.checkConfig()) {
|
||||
throw new AlertNoticeException(smsClient.getType() + " SMS Service configuration is invalid, please check the configuration");
|
||||
}
|
||||
smsClient.sendMessage(receiver, noticeTemplate, alert);
|
||||
String[] params = new String[3];
|
||||
params[0] = instance == null ? alert.getGroupKey() : instance;
|
||||
params[1] = priority == null ? "unknown" : priority;
|
||||
params[2] = content;
|
||||
tencentSmsClient.sendMessage(params, new String[]{receiver.getPhone()});
|
||||
} catch (Exception e) {
|
||||
throw new AlertNoticeException("[Sms Notify Error] " + e.getMessage());
|
||||
}
|
||||
|
||||
+1
@@ -80,6 +80,7 @@ final class WeChatAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl
|
||||
JsonObject textContent = new JsonObject();
|
||||
|
||||
// Here you can construct the message content based on the NoticeTemplate and Alert information
|
||||
// String alertMessage = String.format("警告:%s\n详情:%s", alert.getAlertDefineId(), alert.getContent());
|
||||
String alertMessage = "Alert message content";
|
||||
textContent.addProperty("content", alertMessage);
|
||||
messageContent.add("text", textContent);
|
||||
|
||||
+2
-2
@@ -21,8 +21,8 @@ package org.apache.hertzbeat.alert.reduce;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -267,7 +267,7 @@ public class AlarmGroupReduce {
|
||||
.groupLabels(alert.getLabels())
|
||||
.commonLabels(alert.getLabels())
|
||||
.commonAnnotations(alert.getAnnotations())
|
||||
.alerts(new LinkedList<>(List.of(alert)))
|
||||
.alerts(Collections.singletonList(alert))
|
||||
.status(alert.getStatus())
|
||||
.build();
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.service;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
|
||||
/**
|
||||
* SMS client interface
|
||||
*/
|
||||
public interface SmsClient {
|
||||
/**
|
||||
* send SMS
|
||||
*/
|
||||
void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert);
|
||||
|
||||
/**
|
||||
* get SMS provider type
|
||||
*/
|
||||
String getType();
|
||||
|
||||
/**
|
||||
* check SMS configuration, return true if the configuration is correct
|
||||
*/
|
||||
boolean checkConfig();
|
||||
}
|
||||
-141
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.SmsConfig;
|
||||
import org.apache.hertzbeat.alert.service.impl.TencentSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.UniSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.AlibabaSmsClientImpl;
|
||||
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
|
||||
import org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum;
|
||||
import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
|
||||
import org.apache.hertzbeat.common.support.event.SmsConfigChangeEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.ALIBABA;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.TENCENT;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.UNISMS;
|
||||
|
||||
/**
|
||||
* SMS client factory
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SmsClientFactory {
|
||||
|
||||
private static final String TYPE = GeneralConfigTypeEnum.sms.name();
|
||||
|
||||
private final GeneralConfigDao generalConfigDao;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final SmsConfig yamlSmsConfig;
|
||||
|
||||
private volatile SmsClient currentSmsClient;
|
||||
|
||||
public SmsClientFactory(GeneralConfigDao generalConfigDao,
|
||||
ObjectMapper objectMapper,
|
||||
SmsConfig yamlSmsConfig) {
|
||||
this.generalConfigDao = generalConfigDao;
|
||||
this.objectMapper = objectMapper;
|
||||
this.yamlSmsConfig = yamlSmsConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* SMS configuration change event listener
|
||||
*/
|
||||
@EventListener(SmsConfigChangeEvent.class)
|
||||
public void onSmsConfigChange(SmsConfigChangeEvent event) {
|
||||
log.info("[SmsClientFactory] SMS configuration change event received");
|
||||
synchronized (this) {
|
||||
currentSmsClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
public SmsClient getSmsClient() {
|
||||
if (currentSmsClient != null) {
|
||||
return currentSmsClient;
|
||||
}
|
||||
synchronized (this) {
|
||||
if (currentSmsClient != null) {
|
||||
return currentSmsClient;
|
||||
}
|
||||
loadConfig();
|
||||
return currentSmsClient;
|
||||
}
|
||||
}
|
||||
|
||||
private void loadConfig() {
|
||||
try {
|
||||
// 1. try to load database configuration
|
||||
SmsConfig dbConfig = loadDatabaseConfig();
|
||||
if (dbConfig != null && !dbConfig.getType().isBlank() && dbConfig.isEnable()) {
|
||||
createSmsClient(dbConfig);
|
||||
if (currentSmsClient != null) {
|
||||
log.info("[SmsClientFactory] Using database SMS configuration, provider: {}", dbConfig.getType());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. try to load YAML configuration
|
||||
if (yamlSmsConfig != null && !yamlSmsConfig.getType().isBlank() && yamlSmsConfig.isEnable()) {
|
||||
createSmsClient(yamlSmsConfig);
|
||||
if (currentSmsClient != null) {
|
||||
log.info("[SmsClientFactory] Using YAML SMS configuration, provider: {}", yamlSmsConfig.getType());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log.warn("[SmsClientFactory] No valid SMS configuration found");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[SmsClientFactory] Failed to load SMS configuration", e);
|
||||
currentSmsClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
private SmsConfig loadDatabaseConfig() {
|
||||
try {
|
||||
GeneralConfig config = generalConfigDao.findByType(TYPE);
|
||||
if (config != null && config.getContent() != null) {
|
||||
return objectMapper.readValue(config.getContent(), SmsConfig.class);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[SmsClientFactory] Failed to load database configuration", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void createSmsClient(SmsConfig smsConfig) {
|
||||
switch (smsConfig.getType()) {
|
||||
case TENCENT:
|
||||
currentSmsClient = new TencentSmsClientImpl(smsConfig.getTencent());
|
||||
break;
|
||||
case UNISMS:
|
||||
currentSmsClient = new UniSmsClientImpl(smsConfig.getUnisms());
|
||||
break;
|
||||
case ALIBABA:
|
||||
currentSmsClient = new AlibabaSmsClientImpl(smsConfig.getAlibaba());
|
||||
break;
|
||||
default:
|
||||
log.warn("[SmsClientFactory] Unsupported SMS provider type: {}", smsConfig.getType());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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 com.tencentcloudapi.common.Credential;
|
||||
import com.tencentcloudapi.sms.v20210111.SmsClient;
|
||||
import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest;
|
||||
import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
|
||||
import com.tencentcloudapi.sms.v20210111.models.SendStatus;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.config.CommonProperties;
|
||||
import org.apache.hertzbeat.common.support.exception.SendMessageException;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* sms service client for tencent cloud
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty("common.sms.tencent.app-id")
|
||||
@Slf4j
|
||||
public class TencentSmsClient {
|
||||
|
||||
private static final String RESPONSE_OK = "Ok";
|
||||
private static final String REGION = "ap-guangzhou";
|
||||
|
||||
private SmsClient smsClient;
|
||||
private String appId;
|
||||
private String signName;
|
||||
private String templateId;
|
||||
|
||||
public TencentSmsClient(CommonProperties properties) {
|
||||
if (properties == null || properties.getSms() == null || properties.getSms().getTencent() == null) {
|
||||
log.error("init error, please config TencentSmsClient props in application.yml");
|
||||
throw new IllegalArgumentException("please config TencentSmsClient props");
|
||||
}
|
||||
initSmsClient(properties.getSms().getTencent());
|
||||
}
|
||||
|
||||
private void initSmsClient(CommonProperties.TencentSmsProperties tencent) {
|
||||
this.appId = tencent.getAppId();
|
||||
this.signName = tencent.getSignName();
|
||||
this.templateId = tencent.getTemplateId();
|
||||
Credential cred = new Credential(tencent.getSecretId(), tencent.getSecretKey());
|
||||
smsClient = new SmsClient(cred, REGION);
|
||||
}
|
||||
|
||||
/**
|
||||
* send text message
|
||||
* @param appId appId
|
||||
* @param signName sign name
|
||||
* @param templateId template id
|
||||
* @param templateValues template values
|
||||
* @param phones phones num
|
||||
*/
|
||||
public void sendMessage(String appId, String signName, String templateId,
|
||||
String[] templateValues, String[] phones) {
|
||||
SendSmsRequest req = new SendSmsRequest();
|
||||
req.setSmsSdkAppId(appId);
|
||||
req.setSignName(signName);
|
||||
req.setTemplateId(templateId);
|
||||
req.setTemplateParamSet(templateValues);
|
||||
req.setPhoneNumberSet(phones);
|
||||
try {
|
||||
SendSmsResponse smsResponse = this.smsClient.SendSms(req);
|
||||
SendStatus sendStatus = smsResponse.getSendStatusSet()[0];
|
||||
if (!RESPONSE_OK.equals(sendStatus.getCode())) {
|
||||
throw new SendMessageException(sendStatus.getCode() + ":" + sendStatus.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn(e.getMessage());
|
||||
throw new SendMessageException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* send text message
|
||||
* @param templateValues template values
|
||||
* @param phones phones num
|
||||
*/
|
||||
public void sendMessage(String[] templateValues, String[] phones) {
|
||||
sendMessage(this.appId, this.signName, this.templateId, templateValues, phones);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
-234
@@ -1,234 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.AlibabaSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.alert.util.CryptoUtils;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.apache.hertzbeat.common.support.exception.SendMessageException;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.SimpleTimeZone;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.ALIBABA;
|
||||
|
||||
/**
|
||||
* Alibaba Cloud SMS Client Implementation<br>
|
||||
* API doc: <a href="https://next.api.aliyun.com/document/Dysmsapi/2017-05-25/SendSms">https://next.api.aliyun.com/document/Dysmsapi/2017-05-25/SendSms</a><br>
|
||||
* Singnature doc: <a href="https://help.aliyun.com/zh/sdk/product-overview/v3-request-structure-and-signature">https://help.aliyun.com/zh/sdk/product-overview/v3-request-structure-and-signature</a>
|
||||
*/
|
||||
@Slf4j
|
||||
public class AlibabaSmsClientImpl implements SmsClient {
|
||||
|
||||
private static final String API_VERSION = "2017-05-25";
|
||||
private static final String ACTION = "SendSms";
|
||||
private static final String HOST = "dysmsapi.aliyuncs.com";
|
||||
private static final String ALGORITHM = "ACS3-HMAC-SHA256";
|
||||
|
||||
private final String accessKeyId;
|
||||
private final String accessKeySecret;
|
||||
private final String signName;
|
||||
private final String templateCode;
|
||||
|
||||
public AlibabaSmsClientImpl(AlibabaSmsProperties config) {
|
||||
if (config != null) {
|
||||
this.accessKeyId = config.getAccessKeyId();
|
||||
this.accessKeySecret = config.getAccessKeySecret();
|
||||
this.signName = config.getSignName();
|
||||
this.templateCode = config.getTemplateCode();
|
||||
} else {
|
||||
this.accessKeyId = "";
|
||||
this.accessKeySecret = "";
|
||||
this.signName = "";
|
||||
this.templateCode = "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
|
||||
// Extract alert info
|
||||
String instance = null;
|
||||
String priority = null;
|
||||
String content = null;
|
||||
if (alert.getCommonLabels() != null) {
|
||||
instance = alert.getCommonLabels().get("instance");
|
||||
priority = alert.getCommonLabels().get("priority");
|
||||
content = alert.getCommonAnnotations().get("summary");
|
||||
content = content == null ? alert.getCommonAnnotations().get("description") : content;
|
||||
if (content == null) {
|
||||
content = alert.getCommonAnnotations().values().stream().findFirst().orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Build template parameters
|
||||
Map<String, String> templateParam = new HashMap<>();
|
||||
templateParam.put("instance", instance == null ? alert.getGroupKey() : instance);
|
||||
templateParam.put("priority", priority == null ? "unknown" : priority);
|
||||
templateParam.put("content", content);
|
||||
|
||||
sendSms(receiver.getPhone(), JsonUtil.toJson(templateParam));
|
||||
}
|
||||
|
||||
private void sendSms(String phoneNumber, String templateParam) {
|
||||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||
// Build query parameters
|
||||
Map<String, String> queryParams = new TreeMap<>();
|
||||
queryParams.put("PhoneNumbers", phoneNumber);
|
||||
queryParams.put("SignName", signName);
|
||||
queryParams.put("TemplateCode", templateCode);
|
||||
queryParams.put("TemplateParam", templateParam);
|
||||
|
||||
// Build canonical query string
|
||||
StringBuilder canonicalQueryString = new StringBuilder();
|
||||
queryParams.forEach((key, value) -> {
|
||||
if (canonicalQueryString.length() > 0) {
|
||||
canonicalQueryString.append("&");
|
||||
}
|
||||
canonicalQueryString.append(percentEncode(key))
|
||||
.append("=")
|
||||
.append(percentEncode(value));
|
||||
});
|
||||
|
||||
// Generate timestamp and nonce
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||
sdf.setTimeZone(new SimpleTimeZone(0, "GMT"));
|
||||
String timestamp = sdf.format(new Date());
|
||||
String nonce = UUID.randomUUID().toString();
|
||||
|
||||
// Calculate signature
|
||||
String authorization = calculateAuthorization(
|
||||
canonicalQueryString.toString(),
|
||||
timestamp,
|
||||
nonce
|
||||
);
|
||||
|
||||
// Build URL
|
||||
String url = "https://" + HOST + "/?" + canonicalQueryString;
|
||||
|
||||
// Build HTTP request
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
httpPost.setHeader("Content-Type", "application/json");
|
||||
httpPost.setHeader("Host", HOST);
|
||||
httpPost.setHeader("Authorization", authorization);
|
||||
httpPost.setHeader("x-acs-action", ACTION);
|
||||
httpPost.setHeader("x-acs-version", API_VERSION);
|
||||
httpPost.setHeader("x-acs-date", timestamp);
|
||||
httpPost.setHeader("x-acs-signature-nonce", nonce);
|
||||
httpPost.setHeader("x-acs-content-sha256",
|
||||
CryptoUtils.sha256Hex(""));
|
||||
|
||||
log.info("Sending Alibaba SMS request to {}", url + ", params: " + templateParam + "headers: " + Arrays.toString(httpPost.getAllHeaders()));
|
||||
|
||||
// Send request and handle response
|
||||
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
String responseBody = EntityUtils.toString(response.getEntity());
|
||||
|
||||
log.info("SMS response status: {}, body: {}", statusCode, responseBody);
|
||||
|
||||
if (statusCode != 200) {
|
||||
throw new SendMessageException("HTTP request failed with status code: " + statusCode + ", response: " + responseBody);
|
||||
}
|
||||
|
||||
JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
|
||||
String code = jsonResponse.get("Code").asText();
|
||||
if (!"OK".equals(code)) {
|
||||
String message = jsonResponse.get("Message").asText();
|
||||
throw new SendMessageException(code + ":" + message);
|
||||
}
|
||||
|
||||
log.info("Successfully sent SMS to phone: {}", phoneNumber);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to send SMS: {}", e.getMessage());
|
||||
throw new SendMessageException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String calculateAuthorization(String canonicalQueryString, String timestamp, String nonce) {
|
||||
try {
|
||||
// Step 1: Build canonical request
|
||||
String canonicalRequest = buildCanonicalRequest(canonicalQueryString, timestamp, nonce);
|
||||
|
||||
// Step 2: Build string to sign
|
||||
String stringToSign = ALGORITHM + "\n" + CryptoUtils.sha256Hex(canonicalRequest);
|
||||
|
||||
// Step 3: Calculate signature
|
||||
String signature = CryptoUtils.hmacSha256Hex(accessKeySecret, stringToSign);
|
||||
|
||||
// Step 4: Build authorization header
|
||||
return ALGORITHM + " Credential=" + accessKeyId + ",SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;" + "x-acs-signature-nonce;x-acs-version,Signature=" + signature;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to calculate authorization", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildCanonicalRequest(String canonicalQueryString, String timestamp, String nonce) {
|
||||
return "POST\n"
|
||||
+ "/\n"
|
||||
+ canonicalQueryString + "\n"
|
||||
+ "host:" + HOST + "\n"
|
||||
+ "x-acs-action:" + ACTION + "\n"
|
||||
+ "x-acs-content-sha256:" + CryptoUtils.sha256Hex("") + "\n"
|
||||
+ "x-acs-date:" + timestamp + "\n"
|
||||
+ "x-acs-signature-nonce:" + nonce + "\n"
|
||||
+ "x-acs-version:" + API_VERSION + "\n\n"
|
||||
+ "host;x-acs-action;x-acs-content-sha256;x-acs-date;"
|
||||
+ "x-acs-signature-nonce;x-acs-version\n"
|
||||
+ CryptoUtils.sha256Hex("");
|
||||
}
|
||||
|
||||
private String percentEncode(String value) {
|
||||
try {
|
||||
return java.net.URLEncoder.encode(value, StandardCharsets.UTF_8)
|
||||
.replace("+", "%20")
|
||||
.replace("*", "%2A")
|
||||
.replace("%7E", "~");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to encode value", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return ALIBABA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkConfig() {
|
||||
return !(accessKeyId.isBlank() || accessKeySecret.isBlank() || signName.isBlank() || templateCode.isBlank());
|
||||
}
|
||||
}
|
||||
-223
@@ -1,223 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.TencentSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.alert.util.CryptoUtils;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.apache.hertzbeat.common.support.exception.SendMessageException;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.TENCENT;
|
||||
|
||||
/**
|
||||
* sms service client for tencent cloud <br>
|
||||
* doc: <a href="https://cloud.tencent.com/document/api/382/55981">https://cloud.tencent.com/document/api/382/55981</a>
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
public class TencentSmsClientImpl implements SmsClient {
|
||||
|
||||
private static final String RESPONSE_OK = "Ok";
|
||||
private static final String REGION = "ap-guangzhou";
|
||||
private static final String API_VERSION = "2021-01-11";
|
||||
private static final String ACTION = "SendSms";
|
||||
private static final String HOST = "sms.tencentcloudapi.com";
|
||||
private static final Charset UTF8 = StandardCharsets.UTF_8;
|
||||
|
||||
private String appId;
|
||||
private String signName;
|
||||
private String templateId;
|
||||
private String secretId;
|
||||
private String secretKey;
|
||||
|
||||
public TencentSmsClientImpl(TencentSmsProperties config) {
|
||||
if (config != null) {
|
||||
this.appId = config.getAppId();
|
||||
this.signName = config.getSignName();
|
||||
this.templateId = config.getTemplateId();
|
||||
this.secretId = config.getSecretId();
|
||||
this.secretKey = config.getSecretKey();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
|
||||
// todo limit the number of words
|
||||
String instance = null;
|
||||
String priority = null;
|
||||
String content = null;
|
||||
if (alert.getCommonLabels() != null) {
|
||||
instance = alert.getCommonLabels().get("instance");
|
||||
priority = alert.getCommonLabels().get("priority");
|
||||
content = alert.getCommonAnnotations().get("summary");
|
||||
content = content == null ? alert.getCommonAnnotations().get("description") : content;
|
||||
if (content == null) {
|
||||
content = alert.getCommonAnnotations().values().stream().findFirst().orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
String[] templateValues = new String[3];
|
||||
templateValues[0] = instance == null ? alert.getGroupKey() : instance;
|
||||
templateValues[1] = priority == null ? "unknown" : priority;
|
||||
templateValues[2] = content;
|
||||
|
||||
String[] phones = new String[1];
|
||||
phones[0] = receiver.getPhone();
|
||||
|
||||
sendSms(this.appId, this.signName, this.templateId, templateValues, phones);
|
||||
}
|
||||
|
||||
public void sendSms(String appId, String signName, String templateId,
|
||||
String[] templateValues, String[] phones) {
|
||||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
|
||||
// build request payload
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("SmsSdkAppId", appId);
|
||||
params.put("SignName", signName);
|
||||
params.put("TemplateId", templateId);
|
||||
params.put("TemplateParamSet", templateValues);
|
||||
params.put("PhoneNumberSet", phones);
|
||||
|
||||
String payload = JsonUtil.toJson(params);
|
||||
|
||||
// calculate request signature
|
||||
String authorization = calculateAuthorization(
|
||||
secretId, secretKey, "sms", HOST, REGION,
|
||||
ACTION, API_VERSION, payload);
|
||||
|
||||
// build http request
|
||||
HttpPost httpPost = new HttpPost("https://" + HOST);
|
||||
httpPost.setHeader("Content-Type", "application/json; charset=utf-8");
|
||||
httpPost.setHeader("Host", HOST);
|
||||
httpPost.setHeader("X-TC-Action", ACTION);
|
||||
httpPost.setHeader("X-TC-Timestamp", timestamp);
|
||||
httpPost.setHeader("X-TC-Version", API_VERSION);
|
||||
httpPost.setHeader("X-TC-Region", REGION);
|
||||
httpPost.setHeader("Authorization", authorization);
|
||||
httpPost.setEntity(new StringEntity(payload, StandardCharsets.UTF_8));
|
||||
|
||||
log.debug("Sending SMS request to {}, payload: {}", httpPost.getURI(), payload);
|
||||
|
||||
// send http request and handle response
|
||||
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
String responseBody = EntityUtils.toString(response.getEntity());
|
||||
|
||||
log.debug("SMS response status: {}, body: {}", statusCode, responseBody);
|
||||
|
||||
if (statusCode != 200) {
|
||||
throw new SendMessageException("HTTP request failed with status code: " + statusCode);
|
||||
}
|
||||
|
||||
JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
|
||||
JsonNode responseNode = jsonResponse.get("Response");
|
||||
JsonNode error = responseNode.get("Error");
|
||||
if (error != null) {
|
||||
String code = error.get("Code").asText();
|
||||
String message = error.get("Message").asText();
|
||||
throw new SendMessageException(code + ":" + message);
|
||||
}
|
||||
JsonNode sendStatusSet = responseNode.get("SendStatusSet");
|
||||
if (sendStatusSet != null && sendStatusSet.isArray() && sendStatusSet.size() > 0) {
|
||||
JsonNode firstStatus = sendStatusSet.get(0);
|
||||
String code = firstStatus.get("Code").asText();
|
||||
String message = firstStatus.get("Message").asText();
|
||||
if (!RESPONSE_OK.equals(code)) {
|
||||
throw new SendMessageException(code + ":" + message);
|
||||
}
|
||||
}
|
||||
log.info("Successfully sent SMS to phones: {}", String.join(",", phones));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to send SMS: {}", e.getMessage());
|
||||
throw new SendMessageException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return TENCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkConfig() {
|
||||
if (appId.isBlank() || templateId.isBlank() || secretId.isBlank() || secretKey.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static String calculateAuthorization(String secretId, String secretKey,
|
||||
String service, String host, String region,
|
||||
String action, String version, String payload) throws Exception {
|
||||
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));
|
||||
|
||||
// Step 1: Construct the canonical request string
|
||||
String httpRequestMethod = "POST";
|
||||
String canonicalUri = "/";
|
||||
String canonicalQueryString = "";
|
||||
String canonicalHeaders = "content-type:application/json; charset=utf-8\n" + "host:" + host + "\n";
|
||||
String signedHeaders = "content-type;host";
|
||||
String hashedRequestPayload = CryptoUtils.sha256Hex(payload);
|
||||
String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n"
|
||||
+ canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload;
|
||||
|
||||
// Step 2: Construct the string to sign
|
||||
String algorithm = "TC3-HMAC-SHA256";
|
||||
String credentialScope = date + "/" + service + "/" + "tc3_request";
|
||||
String hashedCanonicalRequest = CryptoUtils.sha256Hex(canonicalRequest);
|
||||
String stringToSign = algorithm + "\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
|
||||
|
||||
// Step 3: Calculate the signature
|
||||
byte[] secretDate = CryptoUtils.hmac256(("TC3" + secretKey).getBytes(UTF8), date);
|
||||
byte[] secretService = CryptoUtils.hmac256(secretDate, service);
|
||||
byte[] secretSigning = CryptoUtils.hmac256(secretService, "tc3_request");
|
||||
String signature = DatatypeConverter.printHexBinary(
|
||||
CryptoUtils.hmac256(secretSigning, stringToSign)).toLowerCase();
|
||||
|
||||
// Step 4: Construct the Authorization header
|
||||
return algorithm + " " + "Credential=" + secretId + "/" + credentialScope + ", "
|
||||
+ "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature;
|
||||
}
|
||||
}
|
||||
-193
@@ -1,193 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.UniSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.apache.hertzbeat.common.support.exception.SendMessageException;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.apache.hertzbeat.alert.util.CryptoUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.UNISMS;
|
||||
|
||||
/**
|
||||
* UniSMS client implementation <br/>
|
||||
* doc:<a href="https://unisms.apistd.com/docs/api/send">https://unisms.apistd.com/docs/api/send</a>
|
||||
*/
|
||||
@Slf4j
|
||||
public class UniSmsClientImpl implements SmsClient {
|
||||
|
||||
private static final String API_URL = "https://uni.apistd.com";
|
||||
private static final String ACTION = "sms.message.send";
|
||||
private static final String SUCCESS_CODE = "0";
|
||||
private static final String HMAC_ALGORITHM = "hmac-sha256";
|
||||
|
||||
private final UniSmsProperties config;
|
||||
|
||||
public UniSmsClientImpl(UniSmsProperties config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
|
||||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||
// build request parameters
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("to", receiver.getPhone());
|
||||
params.put("signature", config.getSignature());
|
||||
params.put("templateId", config.getTemplateId());
|
||||
|
||||
// build template data
|
||||
Map<String, String> templateData = new HashMap<>();
|
||||
String instance = alert.getCommonLabels().getOrDefault("instance", alert.getGroupKey());
|
||||
String priority = alert.getCommonLabels().getOrDefault("priority", "unknown");
|
||||
String content = alert.getCommonAnnotations().get("summary");
|
||||
content = content == null ? alert.getCommonAnnotations().get("description") : content;
|
||||
if (content == null) {
|
||||
content = alert.getCommonAnnotations().values().stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
templateData.put("instance", instance);
|
||||
templateData.put("priority", priority);
|
||||
templateData.put("content", content);
|
||||
params.put("templateData", templateData);
|
||||
|
||||
// build URL and request headers
|
||||
String url;
|
||||
if ("hmac".equalsIgnoreCase(config.getAuthMode())) {
|
||||
url = buildHmacUrl();
|
||||
} else {
|
||||
url = buildSimpleUrl();
|
||||
}
|
||||
|
||||
// send HTTP request
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
httpPost.setHeader("Content-Type", "application/json");
|
||||
httpPost.setHeader("Accept", "application/json");
|
||||
|
||||
String payload = JsonUtil.toJson(params);
|
||||
httpPost.setEntity(new StringEntity(payload, StandardCharsets.UTF_8));
|
||||
|
||||
log.info("Sending SMS request to UniSMS, payload: {}, url: {}", payload, url);
|
||||
|
||||
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
|
||||
handleResponse(response, receiver.getPhone());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send SMS via UniSMS: {}", e.getMessage());
|
||||
throw new SendMessageException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String buildSimpleUrl() {
|
||||
return String.format("%s/?action=%s&accessKeyId=%s",
|
||||
API_URL, ACTION, config.getAccessKeyId());
|
||||
}
|
||||
|
||||
private String buildHmacUrl() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
String nonce = generateNonce();
|
||||
|
||||
// build query parameters
|
||||
Map<String, String> params = new TreeMap<>();
|
||||
params.put("accessKeyId", config.getAccessKeyId());
|
||||
params.put("action", ACTION);
|
||||
params.put("algorithm", HMAC_ALGORITHM);
|
||||
params.put("nonce", nonce);
|
||||
params.put("timestamp", String.valueOf(timestamp));
|
||||
|
||||
// build sign text
|
||||
String signText = params.entrySet().stream()
|
||||
.map(entry -> entry.getKey() + "=" + entry.getValue())
|
||||
.collect(Collectors.joining("&"));
|
||||
|
||||
// calculate signature
|
||||
String signature = CryptoUtils.hmacSha256Base64(config.getAccessKeySecret(), signText);
|
||||
return String.format("%s/?action=%s&accessKeyId=%s&algorithm=%s×tamp=%d&nonce=%s&signature=%s",
|
||||
API_URL, ACTION, config.getAccessKeyId(), HMAC_ALGORITHM, timestamp, nonce, signature);
|
||||
}
|
||||
|
||||
private String generateNonce() {
|
||||
return UUID.randomUUID().toString().replace("-", "").substring(0, 16);
|
||||
}
|
||||
|
||||
private void handleResponse(CloseableHttpResponse response, String phone) throws IOException {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
String responseBody = EntityUtils.toString(response.getEntity());
|
||||
|
||||
log.info("UniSMS response status: {}, body: {}", statusCode, responseBody);
|
||||
|
||||
if (statusCode != 200) {
|
||||
throw new SendMessageException("HTTP request failed with status code: " + statusCode + ", response: " + responseBody);
|
||||
}
|
||||
|
||||
JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
|
||||
String code = jsonResponse.get("code").asText();
|
||||
if (!SUCCESS_CODE.equals(code)) {
|
||||
String message = jsonResponse.get("message").asText();
|
||||
throw new SendMessageException(code + ":" + message);
|
||||
}
|
||||
|
||||
log.info("Successfully sent SMS to phone: {}", phone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return UNISMS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkConfig() {
|
||||
if (config == null
|
||||
|| config.getAccessKeyId() == null
|
||||
|| config.getAccessKeyId().isBlank()
|
||||
|| config.getSignature() == null
|
||||
|| config.getSignature().isBlank()
|
||||
|| config.getTemplateId() == null
|
||||
|| config.getTemplateId().isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// HMAC mode requires additional check for accessKeySecret
|
||||
if ("hmac".equalsIgnoreCase(config.getAuthMode())
|
||||
&& (config.getAccessKeySecret() == null || config.getAccessKeySecret().isBlank())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.util;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* General encryption utility class
|
||||
*/
|
||||
@Slf4j
|
||||
public class CryptoUtils {
|
||||
|
||||
private static final Charset UTF8 = StandardCharsets.UTF_8;
|
||||
|
||||
private CryptoUtils() {}
|
||||
|
||||
/**
|
||||
* Calculate HMAC-SHA256 signature
|
||||
* @param key secret key
|
||||
* @param msg message to be signed
|
||||
* @return signed byte array
|
||||
*/
|
||||
public static byte[] hmac256(byte[] key, String msg) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
|
||||
mac.init(secretKeySpec);
|
||||
return mac.doFinal(msg.getBytes(UTF8));
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to calculate HMAC-SHA256: {}", e.getMessage());
|
||||
throw new RuntimeException("Failed to calculate HMAC-SHA256", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate SHA256 hash and convert to lowercase hexadecimal string
|
||||
* @param data data to be hashed
|
||||
* @return lowercase hexadecimal string
|
||||
*/
|
||||
public static String sha256Hex(String data) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
byte[] digest = md.digest(data.getBytes(UTF8));
|
||||
return DatatypeConverter.printHexBinary(digest).toLowerCase();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to calculate SHA256: {}", e.getMessage());
|
||||
throw new RuntimeException("Failed to calculate SHA256", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate HMAC-SHA256 signature and convert to Base64 string
|
||||
* @param key secret key
|
||||
* @param data data to be signed
|
||||
* @return Base64 encoded signature string
|
||||
*/
|
||||
public static String hmacSha256Base64(String key, String data) {
|
||||
byte[] hmacResult = hmac256(key.getBytes(UTF8), data);
|
||||
return Base64.getEncoder().encodeToString(hmacResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate HMAC-SHA256 signature and convert to lowercase hexadecimal string
|
||||
* @param key secret key
|
||||
* @param data data to be signed
|
||||
* @return lowercase hexadecimal string
|
||||
*/
|
||||
public static String hmacSha256Hex(String key, String data) {
|
||||
byte[] hmacResult = hmac256(key.getBytes(UTF8), data);
|
||||
return DatatypeConverter.printHexBinary(hmacResult).toLowerCase();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -99,7 +99,7 @@ class EmailAlertNotifyHandlerImplTest {
|
||||
template.setName("test-template");
|
||||
template.setContent("test content");
|
||||
|
||||
// Set up email server configuration
|
||||
// 设置邮件服务器配置
|
||||
MailServerConfig mailServerConfig = new MailServerConfig();
|
||||
mailServerConfig.setEmailHost("smtp.example.com");
|
||||
mailServerConfig.setEmailPort(587);
|
||||
|
||||
+2
-2
@@ -83,8 +83,8 @@ class AlarmInhibitReduceTest {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
when(alertInhibitDao.findAlertInhibitsByEnableIsTrue())
|
||||
.thenReturn(Collections.emptyList());
|
||||
|
||||
// Correctly set up AlerterProperties mock
|
||||
|
||||
// 正确设置 AlerterProperties mock
|
||||
AlerterProperties.InhibitProperties inhibitProperties = new AlerterProperties.InhibitProperties();
|
||||
inhibitProperties.setTtl(60000);
|
||||
when(alerterProperties.getInhibit()).thenReturn(inhibitProperties);
|
||||
|
||||
@@ -155,9 +155,5 @@
|
||||
<artifactId>plc4j-driver-modbus</artifactId>
|
||||
<version>0.12.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.sshd</groupId>
|
||||
<artifactId>sshd-sftp</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+24
-46
@@ -26,25 +26,20 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollect;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.AbstractConnection;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.CacheIdentifier;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.GlobalConnectionCache;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.JdbcConnect;
|
||||
import org.apache.hertzbeat.collector.collect.common.ssh.SshTunnelHelper;
|
||||
import org.apache.hertzbeat.collector.constants.CollectorConstants;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.collector.util.CollectUtil;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.SshTunnel;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.JdbcProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.sshd.common.SshException;
|
||||
import org.apache.sshd.common.channel.exception.SshChannelOpenException;
|
||||
import org.postgresql.util.PSQLException;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.jdbc.datasource.init.ScriptUtils;
|
||||
@@ -60,7 +55,7 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
private static final String QUERY_TYPE_MULTI_ROW = "multiRow";
|
||||
private static final String QUERY_TYPE_COLUMNS = "columns";
|
||||
private static final String RUN_SCRIPT = "runScript";
|
||||
|
||||
|
||||
private static final String[] VULNERABLE_KEYWORDS = {"allowLoadLocalInfile", "allowLoadLocalInfileInPath", "useLocalInfile"};
|
||||
|
||||
private final GlobalConnectionCache connectionCommonCache = GlobalConnectionCache.getInstance();
|
||||
@@ -78,26 +73,16 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
}
|
||||
}
|
||||
}
|
||||
SshTunnelHelper.checkTunnelParam(metrics.getJdbc().getSshTunnel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
JdbcProtocol jdbcProtocol = metrics.getJdbc();
|
||||
SshTunnel sshTunnel = jdbcProtocol.getSshTunnel();
|
||||
|
||||
String databaseUrl = constructDatabaseUrl(jdbcProtocol);
|
||||
int timeout = CollectUtil.getTimeout(jdbcProtocol.getTimeout());
|
||||
Statement statement = null;
|
||||
String databaseUrl;
|
||||
try {
|
||||
if (sshTunnel != null && Boolean.parseBoolean(sshTunnel.getEnable())) {
|
||||
int localPort = SshTunnelHelper.localPortForward(sshTunnel, jdbcProtocol.getHost(), jdbcProtocol.getPort());
|
||||
databaseUrl = constructDatabaseUrl(jdbcProtocol, "localhost", String.valueOf(localPort));
|
||||
} else {
|
||||
databaseUrl = constructDatabaseUrl(jdbcProtocol, jdbcProtocol.getHost(), jdbcProtocol.getPort());
|
||||
}
|
||||
|
||||
statement = getConnection(jdbcProtocol.getUsername(),
|
||||
jdbcProtocol.getPassword(), databaseUrl, timeout);
|
||||
switch (jdbcProtocol.getQueryType()) {
|
||||
@@ -127,14 +112,6 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
log.warn("Jdbc sql error: {}, code: {}.", sqlException.getMessage(), sqlException.getErrorCode());
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("Query Error: " + sqlException.getMessage() + " Code: " + sqlException.getErrorCode());
|
||||
} catch (SshException sshException) {
|
||||
Throwable throwable = sshException.getCause();
|
||||
if (throwable instanceof SshChannelOpenException) {
|
||||
log.warn("[Jdbc collect] Remote ssh server no more session channel, please increase sshd_config MaxSessions.");
|
||||
}
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(sshException);
|
||||
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
|
||||
builder.setMsg("Peer ssh connection failed: " + errorMsg);
|
||||
} catch (Exception e) {
|
||||
String errorMessage = CommonUtil.getMessageFromThrowable(e);
|
||||
log.error("Jdbc error: {}.", errorMessage, e);
|
||||
@@ -208,14 +185,13 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
* eg:
|
||||
* query metrics:one tow three four
|
||||
* query sql:select one, tow, three, four from book limit 1;
|
||||
*
|
||||
* @param statement statement
|
||||
* @param sql sql
|
||||
* @param columns query metrics field list
|
||||
* @param sql sql
|
||||
* @param columns query metrics field list
|
||||
* @throws Exception when error happen
|
||||
*/
|
||||
private void queryOneRow(Statement statement, String sql, List<String> columns,
|
||||
CollectRep.MetricsData.Builder builder, long startTime) throws Exception {
|
||||
CollectRep.MetricsData.Builder builder, long startTime) throws Exception {
|
||||
statement.setMaxRows(1);
|
||||
try (ResultSet resultSet = statement.executeQuery(sql)) {
|
||||
if (resultSet.next()) {
|
||||
@@ -240,15 +216,14 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
* eg:
|
||||
* query metrics:one two three four
|
||||
* query sql:select key, value from book; the key is the query metrics fields
|
||||
* select key, value from book;
|
||||
* select key, value from book;
|
||||
* one - value1
|
||||
* two - value2
|
||||
* three - value3
|
||||
* four - value4
|
||||
*
|
||||
* @param statement statement
|
||||
* @param sql sql
|
||||
* @param columns query metrics field list
|
||||
* @param sql sql
|
||||
* @param columns query metrics field list
|
||||
* @throws Exception when error happen
|
||||
*/
|
||||
private void queryOneRowByMatchTwoColumns(Statement statement, String sql, List<String> columns,
|
||||
@@ -281,10 +256,9 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
* query metrics:one tow three four
|
||||
* query sql:select one, tow, three, four from book;
|
||||
* and return multi row record mapping with the metrics
|
||||
*
|
||||
* @param statement statement
|
||||
* @param sql sql
|
||||
* @param columns query metrics field list
|
||||
* @param sql sql
|
||||
* @param columns query metrics field list
|
||||
* @throws Exception when error happen
|
||||
*/
|
||||
private void queryMultiRow(Statement statement, String sql, List<String> columns,
|
||||
@@ -309,16 +283,14 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
|
||||
/**
|
||||
* construct jdbc url due the jdbc protocol
|
||||
*
|
||||
* @param jdbcProtocol jdbc
|
||||
* @return URL
|
||||
*/
|
||||
private String constructDatabaseUrl(JdbcProtocol jdbcProtocol, String host, String port) {
|
||||
private String constructDatabaseUrl(JdbcProtocol jdbcProtocol) {
|
||||
if (Objects.nonNull(jdbcProtocol.getUrl())
|
||||
&& !Objects.equals("", jdbcProtocol.getUrl())
|
||||
&& jdbcProtocol.getUrl().startsWith("jdbc")) {
|
||||
// convert the URL to lowercase for case-insensitive checking
|
||||
String url = jdbcProtocol.getUrl().toLowerCase();
|
||||
String url = jdbcProtocol.getUrl().toLowerCase(); // convert the URL to lowercase for case-insensitive checking
|
||||
// check whether the parameter is valid
|
||||
if (url.contains("create trigger") || url.contains("create alias") || url.contains("runscript from")
|
||||
|| url.contains("allowloadlocalinfile") || url.contains("allowloadlocalinfileinpath")
|
||||
@@ -330,19 +302,25 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
return jdbcProtocol.getUrl();
|
||||
}
|
||||
return switch (jdbcProtocol.getPlatform()) {
|
||||
case "mysql", "mariadb" -> "jdbc:mysql://" + host + ":" + port
|
||||
case "mysql", "mariadb" ->
|
||||
"jdbc:mysql://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
|
||||
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase())
|
||||
+ "?useUnicode=true&characterEncoding=utf-8&useSSL=false";
|
||||
case "postgresql" -> "jdbc:postgresql://" + host + ":" + port
|
||||
case "postgresql" ->
|
||||
"jdbc:postgresql://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
|
||||
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase());
|
||||
case "clickhouse" -> "jdbc:clickhouse://" + host + ":" + port
|
||||
case "clickhouse" ->
|
||||
"jdbc:clickhouse://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
|
||||
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase());
|
||||
case "sqlserver" -> "jdbc:sqlserver://" + host + ":" + port
|
||||
case "sqlserver" ->
|
||||
"jdbc:sqlserver://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
|
||||
+ ";" + (jdbcProtocol.getDatabase() == null ? "" : "DatabaseName=" + jdbcProtocol.getDatabase())
|
||||
+ ";trustServerCertificate=true;";
|
||||
case "oracle" -> "jdbc:oracle:thin:@" + host + ":" + port
|
||||
case "oracle" ->
|
||||
"jdbc:oracle:thin:@" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
|
||||
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase());
|
||||
case "dm" -> "jdbc:dm://" + host + ":" + port;
|
||||
case "dm" ->
|
||||
"jdbc:dm://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort();
|
||||
default -> throw new IllegalArgumentException("Not support database platform: " + jdbcProtocol.getPlatform());
|
||||
};
|
||||
}
|
||||
|
||||
+26
-104
@@ -17,11 +17,9 @@
|
||||
|
||||
package org.apache.hertzbeat.collector.collect.ftp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.net.ftp.FTPClient;
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollect;
|
||||
@@ -31,10 +29,6 @@ import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.FtpProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.sshd.client.SshClient;
|
||||
import org.apache.sshd.client.session.ClientSession;
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.apache.sshd.sftp.client.SftpClientFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -62,14 +56,33 @@ public class FtpCollectImpl extends AbstractCollect {
|
||||
Assert.hasText(ftpProtocol.getTimeout(), "Ftp Protocol timeout is required.");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
boolean ssl = Boolean.parseBoolean(metrics.getFtp().getSsl());
|
||||
if (ssl){
|
||||
handleSftpCollect(builder, metrics);
|
||||
} else {
|
||||
handleFtpCollect(builder, metrics);
|
||||
FTPClient ftpClient = new FTPClient();
|
||||
FtpProtocol ftpProtocol = metrics.getFtp();
|
||||
// Set timeout
|
||||
ftpClient.setControlKeepAliveReplyTimeout(Integer.parseInt(ftpProtocol.getTimeout()));
|
||||
|
||||
// Collect data to load in CollectRep.ValueRow.Builder's object
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
Map<String, String> valueMap;
|
||||
try {
|
||||
valueMap = collectValue(ftpClient, ftpProtocol);
|
||||
metrics.getAliasFields().forEach(it -> {
|
||||
if (valueMap.containsKey(it)) {
|
||||
String fieldValue = valueMap.get(it);
|
||||
valueRowBuilder.addColumn(Objects.requireNonNullElse(fieldValue, CommonConstants.NULL_VALUE));
|
||||
} else {
|
||||
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
|
||||
builder.setMsg(e.getMessage());
|
||||
return;
|
||||
}
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,23 +113,8 @@ public class FtpCollectImpl extends AbstractCollect {
|
||||
};
|
||||
}
|
||||
|
||||
private Map<String, String> collectValue(SftpClient sftpClient, FtpProtocol ftpProtocol) {
|
||||
boolean isActive;
|
||||
String responseTime;
|
||||
try {
|
||||
long startTime = System.currentTimeMillis();
|
||||
sftpClient.stat(ftpProtocol.getDirection());
|
||||
isActive = true;
|
||||
long endTime = System.currentTimeMillis();
|
||||
responseTime = String.valueOf(endTime - startTime);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("[SFTPClient] error: {}" + CommonUtil.getMessageFromThrowable(e), e);
|
||||
}
|
||||
return Map.of("isActive", Boolean.toString(isActive), "responseTime", responseTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* ftp login
|
||||
* login
|
||||
*/
|
||||
private void login(FTPClient ftpClient, FtpProtocol ftpProtocol) {
|
||||
try {
|
||||
@@ -149,84 +147,8 @@ public class FtpCollectImpl extends AbstractCollect {
|
||||
}
|
||||
}
|
||||
|
||||
private ClientSession connect(SshClient client, FtpProtocol ftpProtocol) {
|
||||
client.start();
|
||||
try {
|
||||
ClientSession session = client.connect(ftpProtocol.getUsername(), ftpProtocol.getHost(), Integer.parseInt(ftpProtocol.getPort()))
|
||||
.verify(Integer.parseInt(ftpProtocol.getTimeout()))
|
||||
.getSession();
|
||||
session.addPasswordIdentity(ftpProtocol.getPassword());
|
||||
session.auth().verify(Integer.parseInt(ftpProtocol.getTimeout()));
|
||||
return session;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("[sftp connection] error: {}" + CommonUtil.getMessageFromThrowable(e), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String supportProtocol() {
|
||||
return DispatchConstants.PROTOCOL_FTP;
|
||||
}
|
||||
|
||||
private void handleFtpCollect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
FTPClient ftpClient = new FTPClient();
|
||||
FtpProtocol ftpProtocol = metrics.getFtp();
|
||||
// Set timeout
|
||||
ftpClient.setControlKeepAliveReplyTimeout(Integer.parseInt(ftpProtocol.getTimeout()));
|
||||
|
||||
// Collect data to load in CollectRep.ValueRow.Builder's object
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
Map<String, String> valueMap;
|
||||
try {
|
||||
valueMap = collectValue(ftpClient, ftpProtocol);
|
||||
metrics.getAliasFields().forEach(it -> {
|
||||
if (valueMap.containsKey(it)) {
|
||||
String fieldValue = valueMap.get(it);
|
||||
valueRowBuilder.addColumn(Objects.requireNonNullElse(fieldValue, CommonConstants.NULL_VALUE));
|
||||
} else {
|
||||
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
|
||||
builder.setMsg(e.getMessage());
|
||||
return;
|
||||
}
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
}
|
||||
|
||||
private void handleSftpCollect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
FtpProtocol ftpProtocol = metrics.getFtp();
|
||||
ClientSession session = null;
|
||||
SftpClient sftpClient = null;
|
||||
SshClient client = null;
|
||||
try {
|
||||
client = SshClient.setUpDefaultClient();
|
||||
session = connect(client, ftpProtocol);
|
||||
sftpClient = SftpClientFactory.instance().createSftpClient(session);
|
||||
Map<String, String> valueMap = collectValue(sftpClient, ftpProtocol);
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
metrics.getAliasFields().forEach(it ->
|
||||
valueRowBuilder.addColumn(valueMap.getOrDefault(it, CommonConstants.NULL_VALUE))
|
||||
);
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
} catch (Exception e) {
|
||||
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
|
||||
builder.setMsg(e.getMessage());
|
||||
} finally {
|
||||
try {
|
||||
if (sftpClient != null && sftpClient.isOpen()){
|
||||
sftpClient.close();
|
||||
}
|
||||
if (session != null && session.isOpen()){
|
||||
session.close();
|
||||
}
|
||||
if (client != null && client.isOpen()){
|
||||
client.close();
|
||||
}
|
||||
} catch (Exception e){
|
||||
log.error("[SFTPClient] error while closing: {}", CommonUtil.getMessageFromThrowable(e), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-56
@@ -28,8 +28,6 @@ import io.lettuce.core.cluster.models.partitions.Partitions;
|
||||
import io.lettuce.core.cluster.models.partitions.RedisClusterNode;
|
||||
import io.lettuce.core.resource.ClientResources;
|
||||
import io.lettuce.core.resource.DefaultClientResources;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -44,7 +42,6 @@ import org.apache.hertzbeat.collector.collect.common.cache.AbstractConnection;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.CacheIdentifier;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.GlobalConnectionCache;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.RedisConnect;
|
||||
import org.apache.hertzbeat.collector.collect.common.ssh.SshTunnelHelper;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.collector.util.CollectUtil;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
@@ -54,8 +51,6 @@ import org.apache.hertzbeat.common.entity.job.protocol.RedisProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.hertzbeat.common.util.MapCapUtil;
|
||||
import org.apache.sshd.common.SshException;
|
||||
import org.apache.sshd.common.channel.exception.SshChannelOpenException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -84,7 +79,6 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
RedisProtocol redisProtocol = metrics.getRedis();
|
||||
Assert.hasText(redisProtocol.getHost(), "Redis Protocol host is required.");
|
||||
Assert.hasText(redisProtocol.getPort(), "Redis Protocol port is required.");
|
||||
SshTunnelHelper.checkTunnelParam(metrics.getRedis().getSshTunnel());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,14 +96,6 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
log.info("[redis connection] error: {}", errorMsg);
|
||||
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
|
||||
builder.setMsg(errorMsg);
|
||||
} catch (SshException sshException) {
|
||||
Throwable throwable = sshException.getCause();
|
||||
if (throwable instanceof SshChannelOpenException) {
|
||||
log.warn("[redis collect] Remote ssh server no more session channel, please increase sshd_config MaxSessions.");
|
||||
}
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(sshException);
|
||||
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
|
||||
builder.setMsg("Peer ssh connection failed: " + errorMsg);
|
||||
} catch (Exception e) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e);
|
||||
log.warn("[redis collect] error: {}", e.getMessage(), e);
|
||||
@@ -123,7 +109,7 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
* @param metrics metrics config
|
||||
* @return data
|
||||
*/
|
||||
private Map<String, String> getSingleRedisInfo(Metrics metrics) throws GeneralSecurityException, IOException {
|
||||
private Map<String, String> getSingleRedisInfo(Metrics metrics) {
|
||||
StatefulRedisConnection<String, String> connection = getSingleConnection(metrics.getRedis());
|
||||
String info = connection.sync().info(metrics.getName());
|
||||
Map<String, String> valueMap = parseInfo(info, metrics);
|
||||
@@ -139,7 +125,7 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
* @param metrics metrics config
|
||||
* @return data
|
||||
*/
|
||||
private List<Map<String, String>> getClusterRedisInfo(Metrics metrics) throws GeneralSecurityException, IOException {
|
||||
private List<Map<String, String>> getClusterRedisInfo(Metrics metrics) {
|
||||
Map<String, StatefulRedisClusterConnection<String, String>> connectionMap = getConnectionList(metrics.getRedis());
|
||||
List<Map<String, String>> list = new ArrayList<>(connectionMap.size());
|
||||
connectionMap.forEach((identity, connection) ->{
|
||||
@@ -193,16 +179,12 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
* @param redisProtocol protocol
|
||||
* @return connection
|
||||
*/
|
||||
private StatefulRedisConnection<String, String> getSingleConnection(RedisProtocol redisProtocol) throws GeneralSecurityException, IOException {
|
||||
String[] resolvedArr = resolveHostAndPort(redisProtocol);
|
||||
String host = resolvedArr[0];
|
||||
String port = resolvedArr[1];
|
||||
|
||||
CacheIdentifier identifier = doIdentifier(redisProtocol, host, port);
|
||||
private StatefulRedisConnection<String, String> getSingleConnection(RedisProtocol redisProtocol) {
|
||||
CacheIdentifier identifier = doIdentifier(redisProtocol);
|
||||
StatefulRedisConnection<String, String> connection = (StatefulRedisConnection<String, String>) getStatefulConnection(identifier);
|
||||
if (Objects.isNull(connection)) {
|
||||
// reuse connection failed, new one
|
||||
RedisClient redisClient = buildSingleClient(redisProtocol, host, port);
|
||||
RedisClient redisClient = buildSingleClient(redisProtocol);
|
||||
connection = redisClient.connect();
|
||||
connectionCache.addCache(identifier, new RedisConnect(connection));
|
||||
}
|
||||
@@ -214,7 +196,7 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
* @param redisProtocol protocol
|
||||
* @return connection map
|
||||
*/
|
||||
private Map<String, StatefulRedisClusterConnection<String, String>> getConnectionList(RedisProtocol redisProtocol) throws GeneralSecurityException, IOException {
|
||||
private Map<String, StatefulRedisClusterConnection<String, String>> getConnectionList(RedisProtocol redisProtocol) {
|
||||
// first connection
|
||||
StatefulRedisClusterConnection<String, String> connection = getClusterConnection(redisProtocol);
|
||||
Partitions partitions = connection.getPartitions();
|
||||
@@ -235,16 +217,12 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
* @param redisProtocol redis protocol
|
||||
* @return cluster connection
|
||||
*/
|
||||
private StatefulRedisClusterConnection<String, String> getClusterConnection(RedisProtocol redisProtocol) throws GeneralSecurityException, IOException {
|
||||
String[] resolvedArr = resolveHostAndPort(redisProtocol);
|
||||
String host = resolvedArr[0];
|
||||
String port = resolvedArr[1];
|
||||
|
||||
CacheIdentifier identifier = doIdentifier(redisProtocol, host, port);
|
||||
private StatefulRedisClusterConnection<String, String> getClusterConnection(RedisProtocol redisProtocol) {
|
||||
CacheIdentifier identifier = doIdentifier(redisProtocol);
|
||||
StatefulRedisClusterConnection<String, String> connection = (StatefulRedisClusterConnection<String, String>) getStatefulConnection(identifier);
|
||||
if (connection == null) {
|
||||
// reuse connection failed, new one
|
||||
RedisClusterClient redisClusterClient = buildClusterClient(redisProtocol, host, port);
|
||||
RedisClusterClient redisClusterClient = buildClusterClient(redisProtocol);
|
||||
connection = redisClusterClient.connect();
|
||||
connectionCache.addCache(identifier, new RedisConnect(connection));
|
||||
}
|
||||
@@ -282,8 +260,8 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
* @param redisProtocol redis protocol config
|
||||
* @return redis cluster client
|
||||
*/
|
||||
private RedisClusterClient buildClusterClient(RedisProtocol redisProtocol, String host, String port) {
|
||||
return RedisClusterClient.create(defaultClientResources, redisUri(redisProtocol, host, port));
|
||||
private RedisClusterClient buildClusterClient(RedisProtocol redisProtocol) {
|
||||
return RedisClusterClient.create(defaultClientResources, redisUri(redisProtocol));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,12 +270,12 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
* @param redisProtocol redis protocol config
|
||||
* @return redis single client
|
||||
*/
|
||||
private RedisClient buildSingleClient(RedisProtocol redisProtocol, String host, String port) {
|
||||
return RedisClient.create(defaultClientResources, redisUri(redisProtocol, host, port));
|
||||
private RedisClient buildSingleClient(RedisProtocol redisProtocol) {
|
||||
return RedisClient.create(defaultClientResources, redisUri(redisProtocol));
|
||||
}
|
||||
|
||||
private RedisURI redisUri(RedisProtocol redisProtocol, String host, String port) {
|
||||
RedisURI.Builder redisUriBuilder = RedisURI.builder().withHost(host).withPort(Integer.parseInt(port));
|
||||
private RedisURI redisUri(RedisProtocol redisProtocol) {
|
||||
RedisURI.Builder redisUriBuilder = RedisURI.builder().withHost(redisProtocol.getHost()).withPort(Integer.parseInt(redisProtocol.getPort()));
|
||||
if (StringUtils.hasText(redisProtocol.getUsername())) {
|
||||
redisUriBuilder.withClientName(redisProtocol.getUsername());
|
||||
}
|
||||
@@ -317,10 +295,10 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
return ip + SignConstants.DOUBLE_MARK + port;
|
||||
}
|
||||
|
||||
private CacheIdentifier doIdentifier(RedisProtocol redisProtocol, String host, String port) {
|
||||
private CacheIdentifier doIdentifier(RedisProtocol redisProtocol) {
|
||||
return CacheIdentifier.builder()
|
||||
.ip(host)
|
||||
.port(port)
|
||||
.ip(redisProtocol.getHost())
|
||||
.port(redisProtocol.getPort())
|
||||
.username(redisProtocol.getUsername())
|
||||
.password(redisProtocol.getPassword())
|
||||
.customArg(redisProtocol.getPattern())
|
||||
@@ -350,22 +328,6 @@ public class RedisCommonCollectImpl extends AbstractCollect {
|
||||
return result;
|
||||
}
|
||||
|
||||
private String[] resolveHostAndPort(RedisProtocol redisProtocol) throws GeneralSecurityException, IOException {
|
||||
boolean enableSshTunnel = Optional.ofNullable(redisProtocol.getSshTunnel())
|
||||
.map(ssh -> Boolean.parseBoolean(ssh.getEnable()))
|
||||
.orElse(false);
|
||||
String host;
|
||||
String port;
|
||||
if (enableSshTunnel){
|
||||
host = "localhost";
|
||||
port = String.valueOf(SshTunnelHelper.localPortForward(redisProtocol.getSshTunnel(), redisProtocol.getHost(), redisProtocol.getPort()));
|
||||
} else {
|
||||
host = redisProtocol.getHost();
|
||||
port = redisProtocol.getPort();
|
||||
}
|
||||
return new String[]{host, port};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String supportProtocol() {
|
||||
return DispatchConstants.PROTOCOL_REDIS;
|
||||
|
||||
+62
-5
@@ -18,6 +18,7 @@
|
||||
package org.apache.hertzbeat.collector.collect.ssh;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.SocketTimeoutException;
|
||||
@@ -29,27 +30,35 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollect;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.AbstractConnection;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.CacheIdentifier;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.GlobalConnectionCache;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.SshConnect;
|
||||
import org.apache.hertzbeat.collector.collect.common.ssh.CommonSshBlacklist;
|
||||
import org.apache.hertzbeat.collector.collect.common.ssh.SshHelper;
|
||||
import org.apache.hertzbeat.collector.collect.common.ssh.CommonSshClient;
|
||||
import org.apache.hertzbeat.collector.constants.CollectorConstants;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.collector.util.CollectUtil;
|
||||
import org.apache.hertzbeat.collector.util.PrivateKeyUtils;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.SshProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.sshd.client.SshClient;
|
||||
import org.apache.sshd.client.channel.ClientChannel;
|
||||
import org.apache.sshd.client.channel.ClientChannelEvent;
|
||||
import org.apache.sshd.client.session.ClientSession;
|
||||
import org.apache.sshd.common.SshException;
|
||||
import org.apache.sshd.common.channel.exception.SshChannelOpenException;
|
||||
import org.apache.sshd.common.config.keys.FilePasswordProvider;
|
||||
import org.apache.sshd.common.util.io.output.NoCloseOutputStream;
|
||||
import org.apache.sshd.common.util.security.SecurityUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -282,9 +291,57 @@ public class SshCollectImpl extends AbstractCollect {
|
||||
|
||||
private ClientSession getConnectSession(SshProtocol sshProtocol, int timeout, boolean reuseConnection)
|
||||
throws IOException, GeneralSecurityException {
|
||||
return SshHelper.getConnectSession(
|
||||
sshProtocol.getHost(), sshProtocol.getPort(), sshProtocol.getUsername(), sshProtocol.getPassword(),
|
||||
sshProtocol.getPrivateKey(), sshProtocol.getPrivateKeyPassphrase(), timeout, reuseConnection
|
||||
);
|
||||
CacheIdentifier identifier = CacheIdentifier.builder()
|
||||
.ip(sshProtocol.getHost()).port(sshProtocol.getPort())
|
||||
.username(sshProtocol.getUsername()).password(sshProtocol.getPassword())
|
||||
.build();
|
||||
ClientSession clientSession = null;
|
||||
if (reuseConnection) {
|
||||
Optional<AbstractConnection<?>> cacheOption = connectionCommonCache.getCache(identifier, true);
|
||||
if (cacheOption.isPresent()) {
|
||||
SshConnect sshConnect = (SshConnect) cacheOption.get();
|
||||
clientSession = sshConnect.getConnection();
|
||||
try {
|
||||
if (clientSession == null || clientSession.isClosed() || clientSession.isClosing()) {
|
||||
clientSession = null;
|
||||
connectionCommonCache.removeCache(identifier);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn(e.getMessage());
|
||||
clientSession = null;
|
||||
connectionCommonCache.removeCache(identifier);
|
||||
}
|
||||
}
|
||||
if (clientSession != null) {
|
||||
return clientSession;
|
||||
}
|
||||
}
|
||||
SshClient sshClient = CommonSshClient.getSshClient();
|
||||
clientSession = sshClient.connect(sshProtocol.getUsername(), sshProtocol.getHost(), Integer.parseInt(sshProtocol.getPort()))
|
||||
.verify(timeout, TimeUnit.MILLISECONDS).getSession();
|
||||
if (StringUtils.hasText(sshProtocol.getPassword())) {
|
||||
clientSession.addPasswordIdentity(sshProtocol.getPassword());
|
||||
} else if (StringUtils.hasText(sshProtocol.getPrivateKey())) {
|
||||
var resourceKey = PrivateKeyUtils.writePrivateKey(sshProtocol.getHost(), sshProtocol.getPrivateKey());
|
||||
FilePasswordProvider passwordProvider = (session, resource, index) -> {
|
||||
if (StringUtils.hasText(sshProtocol.getPrivateKeyPassphrase())) {
|
||||
return sshProtocol.getPrivateKeyPassphrase();
|
||||
}
|
||||
return null;
|
||||
};
|
||||
SecurityUtils.loadKeyPairIdentities(null, () -> resourceKey, new FileInputStream(resourceKey), passwordProvider)
|
||||
.forEach(clientSession::addPublicKeyIdentity);
|
||||
} // else auth with localhost private public key certificates
|
||||
|
||||
// auth
|
||||
if (!clientSession.auth().verify(timeout, TimeUnit.MILLISECONDS).isSuccess()) {
|
||||
clientSession.close();
|
||||
throw new IllegalArgumentException("ssh auth failed.");
|
||||
}
|
||||
if (reuseConnection) {
|
||||
SshConnect sshConnect = new SshConnect(clientSession);
|
||||
connectionCommonCache.addCache(identifier, sshConnect);
|
||||
}
|
||||
return clientSession;
|
||||
}
|
||||
}
|
||||
|
||||
+15
-15
@@ -20,7 +20,6 @@ package org.apache.hertzbeat.collector.collect.database;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
@@ -100,27 +99,28 @@ class JdbcCommonCollectTest {
|
||||
"dm"
|
||||
};
|
||||
for (String platform : platforms) {
|
||||
assertDoesNotThrow(() -> {
|
||||
JdbcProtocol jdbc = new JdbcProtocol();
|
||||
jdbc.setPlatform(platform);
|
||||
|
||||
Metrics metrics = new Metrics();
|
||||
metrics.setJdbc(jdbc);
|
||||
|
||||
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
|
||||
jdbcCommonCollect.collect(builder, metrics);
|
||||
});
|
||||
}
|
||||
// invalid platform
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
JdbcProtocol jdbc = new JdbcProtocol();
|
||||
jdbc.setPlatform(platform);
|
||||
jdbc.setPlatform("invalid");
|
||||
|
||||
Metrics metrics = new Metrics();
|
||||
metrics.setJdbc(jdbc);
|
||||
|
||||
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
|
||||
jdbcCommonCollect.collect(builder, metrics);
|
||||
assertNotEquals(builder.getMsg(), "Query Error: Not support database platform: " + platform);
|
||||
}
|
||||
// invalid platform
|
||||
JdbcProtocol jdbc = new JdbcProtocol();
|
||||
jdbc.setPlatform("invalid");
|
||||
|
||||
Metrics metrics = new Metrics();
|
||||
metrics.setJdbc(jdbc);
|
||||
|
||||
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
|
||||
jdbcCommonCollect.collect(builder, metrics);
|
||||
assertEquals(builder.getCode(), CollectRep.Code.FAIL);
|
||||
assertEquals(builder.getMsg(), "Query Error: Not support database platform: invalid");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
-2
@@ -25,7 +25,6 @@ import org.apache.sshd.common.NamedFactory;
|
||||
import org.apache.sshd.common.PropertyResolverUtils;
|
||||
import org.apache.sshd.common.kex.BuiltinDHFactories;
|
||||
import org.apache.sshd.core.CoreModuleProperties;
|
||||
import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
|
||||
|
||||
/**
|
||||
* common ssh pool client
|
||||
@@ -53,7 +52,6 @@ public class CommonSshClient {
|
||||
BuiltinDHFactories.VALUES,
|
||||
ClientBuilder.DH2KEX
|
||||
));
|
||||
SSH_CLIENT.setForwardingFilter(new AcceptAllForwardingFilter());
|
||||
// todo when connect AlibabaCloud ubuntu server, custom signature factories will cause error, why?
|
||||
// SSH_CLIENT.setSignatureFactories(new ArrayList<>(BuiltinSignatures.VALUES));
|
||||
SSH_CLIENT.start();
|
||||
|
||||
-104
@@ -1,104 +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.collector.collect.common.ssh;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.AbstractConnection;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.CacheIdentifier;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.GlobalConnectionCache;
|
||||
import org.apache.hertzbeat.collector.collect.common.cache.SshConnect;
|
||||
import org.apache.hertzbeat.collector.util.PrivateKeyUtils;
|
||||
import org.apache.sshd.client.SshClient;
|
||||
import org.apache.sshd.client.session.ClientSession;
|
||||
import org.apache.sshd.common.config.keys.FilePasswordProvider;
|
||||
import org.apache.sshd.common.util.security.SecurityUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* ssh helper
|
||||
*/
|
||||
@Slf4j
|
||||
public class SshHelper {
|
||||
|
||||
private static final GlobalConnectionCache CONNECTION_COMMON_CACHE = GlobalConnectionCache.getInstance();
|
||||
|
||||
public static ClientSession getConnectSession(String host, String port, String username, String password, String privateKey,
|
||||
String privateKeyPassphrase, int timeout, boolean reuseConnection)
|
||||
throws IOException, GeneralSecurityException {
|
||||
CacheIdentifier identifier = CacheIdentifier.builder()
|
||||
.ip(host).port(port)
|
||||
.username(username).password(password)
|
||||
.build();
|
||||
ClientSession clientSession = null;
|
||||
if (reuseConnection) {
|
||||
Optional<AbstractConnection<?>> cacheOption = CONNECTION_COMMON_CACHE.getCache(identifier, true);
|
||||
if (cacheOption.isPresent()) {
|
||||
SshConnect sshConnect = (SshConnect) cacheOption.get();
|
||||
clientSession = sshConnect.getConnection();
|
||||
try {
|
||||
if (clientSession == null || clientSession.isClosed() || clientSession.isClosing()) {
|
||||
clientSession = null;
|
||||
CONNECTION_COMMON_CACHE.removeCache(identifier);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn(e.getMessage());
|
||||
clientSession = null;
|
||||
CONNECTION_COMMON_CACHE.removeCache(identifier);
|
||||
}
|
||||
}
|
||||
if (clientSession != null) {
|
||||
return clientSession;
|
||||
}
|
||||
}
|
||||
SshClient sshClient = CommonSshClient.getSshClient();
|
||||
|
||||
clientSession = sshClient.connect(username, host, Integer.parseInt(port))
|
||||
.verify(timeout, TimeUnit.MILLISECONDS).getSession();
|
||||
if (StringUtils.hasText(password)) {
|
||||
clientSession.addPasswordIdentity(password);
|
||||
} else if (StringUtils.hasText(privateKey)) {
|
||||
var resourceKey = PrivateKeyUtils.writePrivateKey(host, privateKey);
|
||||
FilePasswordProvider passwordProvider = (session, resource, index) -> {
|
||||
if (StringUtils.hasText(privateKeyPassphrase)) {
|
||||
return privateKeyPassphrase;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
SecurityUtils.loadKeyPairIdentities(null, () -> resourceKey, new FileInputStream(resourceKey), passwordProvider)
|
||||
.forEach(clientSession::addPublicKeyIdentity);
|
||||
} // else auth with localhost private public key certificates
|
||||
|
||||
// auth
|
||||
if (!clientSession.auth().verify(timeout, TimeUnit.MILLISECONDS).isSuccess()) {
|
||||
clientSession.close();
|
||||
throw new IllegalArgumentException("ssh auth failed.");
|
||||
}
|
||||
if (reuseConnection) {
|
||||
SshConnect sshConnect = new SshConnect(clientSession);
|
||||
CONNECTION_COMMON_CACHE.addCache(identifier, sshConnect);
|
||||
}
|
||||
return clientSession;
|
||||
}
|
||||
|
||||
}
|
||||
-313
@@ -1,313 +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.collector.collect.common.ssh;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.RemovalCause;
|
||||
import com.github.benmanes.caffeine.cache.Scheduler;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.entity.job.SshTunnel;
|
||||
import org.apache.sshd.client.session.ClientSession;
|
||||
import org.apache.sshd.client.session.forward.ExplicitPortForwardingTracker;
|
||||
import org.apache.sshd.common.util.net.SshdSocketAddress;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.time.Duration;
|
||||
import java.util.Comparator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Ssh Tunnel Helper
|
||||
*/
|
||||
@Slf4j
|
||||
public class SshTunnelHelper {
|
||||
|
||||
private static final long DEFAULT_CACHE_TIMEOUT = 500 * 1000;
|
||||
|
||||
private static final Cache<SshClientSessionWrapper, LocalPortForwardingWrapper> TRACKER_CACHE =
|
||||
Caffeine.newBuilder()
|
||||
.initialCapacity(1)
|
||||
.maximumSize(1000)
|
||||
.expireAfterAccess(Duration.ofMillis(DEFAULT_CACHE_TIMEOUT))
|
||||
.scheduler(Scheduler.systemScheduler())
|
||||
.removalListener((key, value, cause) -> {
|
||||
if (cause == RemovalCause.REPLACED) {
|
||||
return;
|
||||
}
|
||||
if (key != null && value != null) {
|
||||
// 1. try close tunnel
|
||||
SshClientSessionWrapper clientSessionWrapper = (SshClientSessionWrapper) key;
|
||||
LocalPortForwardingWrapper wrapper = (LocalPortForwardingWrapper) value;
|
||||
wrapper.remove(clientSessionWrapper.getClientSession());
|
||||
|
||||
// 2. try close session
|
||||
if (!clientSessionWrapper.isShareConnection()) {
|
||||
try {
|
||||
clientSessionWrapper.close();
|
||||
log.info("[SSH Tunnel] close unshared ssh connection, {}", clientSessionWrapper);
|
||||
} catch (IOException e) {
|
||||
log.error("[SSH Tunnel] close unshared ssh connection error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
|
||||
/**
|
||||
* check ssh tunnel param
|
||||
*
|
||||
* @param sshTunnel ssh tunnel param
|
||||
*/
|
||||
public static void checkTunnelParam(SshTunnel sshTunnel) {
|
||||
if (sshTunnel == null || !Boolean.parseBoolean(sshTunnel.getEnable())) {
|
||||
return;
|
||||
}
|
||||
if (!StringUtils.hasText(sshTunnel.getHost())) {
|
||||
throw new IllegalArgumentException("ssh tunnel must has ssh host param");
|
||||
}
|
||||
if (!StringUtils.hasText(sshTunnel.getPort())) {
|
||||
throw new IllegalArgumentException("ssh tunnel must has ssh port param");
|
||||
}
|
||||
if (!StringUtils.hasText(sshTunnel.getUsername())) {
|
||||
throw new IllegalArgumentException("ssh tunnel must has ssh username param");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* create ssh tunnel
|
||||
*
|
||||
* @param sshTunnel ssh tunnel param
|
||||
* @param remoteHost remote host
|
||||
* @param remotePort remote port
|
||||
* @return local port
|
||||
*/
|
||||
public static int localPortForward(SshTunnel sshTunnel, String remoteHost, String remotePort) throws GeneralSecurityException, IOException {
|
||||
boolean shareConnection = Boolean.parseBoolean(sshTunnel.getShareConnection());
|
||||
// 1. get ssh session
|
||||
ClientSession session = SshHelper.getConnectSession(sshTunnel.getHost(), sshTunnel.getPort(),
|
||||
sshTunnel.getUsername(), sshTunnel.getPassword(), sshTunnel.getPrivateKey(), sshTunnel.getPrivateKeyPassphrase(),
|
||||
Integer.parseInt(sshTunnel.getTimeout()), shareConnection);
|
||||
SshClientSessionWrapper sessionWrapper = new SshClientSessionWrapper(session, shareConnection);
|
||||
|
||||
// 2. get tunnel
|
||||
LocalPortForwardingWrapper forwardingWrapper = selectWrapper(
|
||||
TRACKER_CACHE.getIfPresent(sessionWrapper), sessionWrapper, remoteHost, remotePort);
|
||||
int localPort;
|
||||
if (forwardingWrapper == null) {
|
||||
localPort = getRandomPort();
|
||||
LocalPortForwardingWrapper newForwardingWrapper = sessionWrapper
|
||||
.createLocalPortForwardingTracker(localPort, remoteHost, Integer.parseInt(remotePort));
|
||||
if (TRACKER_CACHE.getIfPresent(sessionWrapper) == null) {
|
||||
TRACKER_CACHE.put(sessionWrapper, newForwardingWrapper);
|
||||
}
|
||||
log.info("[SSH Tunnel] created ssh forwarding tracker ssh:{}, remote:{}, localPort:{}",
|
||||
sshTunnel.getHost() + ":" + sshTunnel.getPort(), remoteHost + ":" + remotePort, localPort);
|
||||
} else {
|
||||
localPort = forwardingWrapper.getTracker().getLocalAddress().getPort();
|
||||
}
|
||||
|
||||
return localPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* get tunnel
|
||||
*
|
||||
* @param wrapper LocalPortForwardingWrapper
|
||||
* @param sessionWrapper SshClientSessionWrapper
|
||||
* @param remoteHost remote host
|
||||
* @param remotePort remote port
|
||||
* @return LocalPortForwardingWrapper
|
||||
*/
|
||||
private static LocalPortForwardingWrapper selectWrapper(LocalPortForwardingWrapper wrapper, SshClientSessionWrapper sessionWrapper,
|
||||
String remoteHost, String remotePort) {
|
||||
if (wrapper == null) {
|
||||
return null;
|
||||
}
|
||||
List<LocalPortForwardingWrapper> selectList = wrapper.select(sessionWrapper.getClientSession(), localPortForwardWrapper -> {
|
||||
if (!localPortForwardWrapper.isOpen()) {
|
||||
return false;
|
||||
}
|
||||
ExplicitPortForwardingTracker tracker = localPortForwardWrapper.getTracker();
|
||||
SshdSocketAddress remoteAddress = tracker.getRemoteAddress();
|
||||
return Objects.equals(remoteAddress.getHostName(), remoteHost)
|
||||
&& Objects.equals(remoteAddress.getPort(), Integer.parseInt(remotePort));
|
||||
});
|
||||
|
||||
if (selectList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
LocalPortForwardingWrapper selected;
|
||||
if (selectList.size() == 1) {
|
||||
selected = selectList.get(0);
|
||||
} else {
|
||||
selected = selectList.stream().min(Comparator.comparing(LocalPortForwardingWrapper::getLastAccessTime)).get();
|
||||
}
|
||||
selected.setLastAccessTime(System.currentTimeMillis());
|
||||
return selected;
|
||||
}
|
||||
|
||||
|
||||
private static int getRandomPort() throws IOException {
|
||||
try (ServerSocket serverSocket = new ServerSocket(0)) {
|
||||
return serverSocket.getLocalPort();
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@EqualsAndHashCode
|
||||
private static class SshClientSessionWrapper {
|
||||
private ClientSession clientSession;
|
||||
private boolean shareConnection;
|
||||
|
||||
public SshClientSessionWrapper(ClientSession clientSession, boolean shareConnection) {
|
||||
this.clientSession = clientSession;
|
||||
this.shareConnection = shareConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a local port forwarding
|
||||
* @param localPort local port
|
||||
* @param remoteHost remove host
|
||||
* @param remotePort remote port
|
||||
* @return LocalPortForwardingWrapper
|
||||
*/
|
||||
public LocalPortForwardingWrapper createLocalPortForwardingTracker(Integer localPort, String remoteHost, Integer remotePort) throws IOException {
|
||||
SshdSocketAddress remoteAddress = new SshdSocketAddress(remoteHost, remotePort);
|
||||
SshdSocketAddress localAddress = new SshdSocketAddress("localhost", localPort);
|
||||
ExplicitPortForwardingTracker tracker = clientSession.createLocalPortForwardingTracker(localAddress, remoteAddress);
|
||||
return new LocalPortForwardingWrapper(tracker);
|
||||
}
|
||||
|
||||
/**
|
||||
* close client session
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
clientSession.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ ssh:%s, shareConnection:%b }".formatted(clientSession, shareConnection);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@EqualsAndHashCode
|
||||
private static class LocalPortForwardingWrapper {
|
||||
private static Map<ClientSession, List<LocalPortForwardingWrapper>> map = new ConcurrentHashMap<>();
|
||||
|
||||
private ExplicitPortForwardingTracker tracker;
|
||||
private Long lastAccessTime;
|
||||
|
||||
public LocalPortForwardingWrapper(ExplicitPortForwardingTracker tracker) {
|
||||
this.tracker = tracker;
|
||||
this.lastAccessTime = System.currentTimeMillis();
|
||||
map.computeIfAbsent(tracker.getClientSession(), (key) -> new ArrayList<>()).add(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* select ClientSession LocalPortForwardingWrapper List
|
||||
* @param session ssh client session
|
||||
* @param predicate condition
|
||||
* @return LocalPortForwardWrapper
|
||||
*/
|
||||
public List<LocalPortForwardingWrapper> select(ClientSession session, Predicate<LocalPortForwardingWrapper> predicate) {
|
||||
List<LocalPortForwardingWrapper> trackerList = map.get(session);
|
||||
if (CollectionUtils.isEmpty(trackerList)) {
|
||||
return trackerList;
|
||||
}
|
||||
List<LocalPortForwardingWrapper> list = new ArrayList<>();
|
||||
long currentTimeMillis = System.currentTimeMillis();
|
||||
Iterator<LocalPortForwardingWrapper> iterator = trackerList.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
LocalPortForwardingWrapper wrapper = iterator.next();
|
||||
// lazy remove
|
||||
if (currentTimeMillis - wrapper.getLastAccessTime() > DEFAULT_CACHE_TIMEOUT) {
|
||||
try {
|
||||
wrapper.getTracker().close();
|
||||
iterator.remove();
|
||||
log.info("[SSH Tunnel] Lazy Remove ssh local port forwarding {}", wrapper);
|
||||
} catch (IOException e) {
|
||||
log.warn("[SSH Tunnel] Lazy Remove ssh local port forwarding Error", e);
|
||||
}
|
||||
} else if (predicate == null || predicate.test(wrapper)) {
|
||||
list.add(wrapper);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* remove session local port forwarding
|
||||
* @param session ssh client session
|
||||
*/
|
||||
public void remove(ClientSession session) {
|
||||
List<LocalPortForwardingWrapper> trackerList = map.get(session);
|
||||
if (CollectionUtils.isEmpty(trackerList)) {
|
||||
return;
|
||||
}
|
||||
Iterator<LocalPortForwardingWrapper> iterator = trackerList.iterator();
|
||||
while (iterator.hasNext()){
|
||||
try {
|
||||
LocalPortForwardingWrapper next = iterator.next();
|
||||
next.close();
|
||||
iterator.remove();
|
||||
log.info("[SSH Tunnel] Remove ssh local port forwarding, {}", next);
|
||||
} catch (IOException e) {
|
||||
log.error("[SSH Tunnel] Remove ssh session local port forwarding error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
tracker.close();
|
||||
}
|
||||
|
||||
public boolean isOpen() {
|
||||
return tracker.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ ssh:%s, remote:%s, localPort:%d }".formatted(
|
||||
tracker.getSession().getConnectAddress(),
|
||||
tracker.getRemoteAddress(),
|
||||
tracker.getLocalAddress().getPort()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+5
@@ -154,11 +154,13 @@ public class DispatchProperties {
|
||||
|
||||
/**
|
||||
* Schedule Data Export Configuration Properties
|
||||
* 调度数据出口配置属性
|
||||
*/
|
||||
public static class ExportProperties {
|
||||
|
||||
/**
|
||||
* kafka configuration information
|
||||
* kafka配置信息
|
||||
*/
|
||||
private KafkaProperties kafka;
|
||||
|
||||
@@ -176,15 +178,18 @@ public class DispatchProperties {
|
||||
public static class KafkaProperties {
|
||||
/**
|
||||
* Whether the kafka data export is started
|
||||
* kafka数据出口是否启动
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* kafka's connection server url
|
||||
* kafka的连接服务器url
|
||||
*/
|
||||
private String servers = "http://127.0.0.1:2379";
|
||||
/**
|
||||
* Topic name to send data to
|
||||
* 发送数据的topic名称
|
||||
*/
|
||||
private String topic;
|
||||
|
||||
|
||||
-4
@@ -223,9 +223,7 @@ public class KafkaCollectImpl extends AbstractCollect {
|
||||
|
||||
@Override
|
||||
public void preCheck(Metrics metrics) throws IllegalArgumentException {
|
||||
Assert.isTrue(metrics != null, "Metrics cannot be null");
|
||||
KafkaProtocol kafkaProtocol = metrics.getKclient();
|
||||
|
||||
// Ensure that metrics and kafkaProtocol are not null
|
||||
Assert.isTrue(metrics != null && kafkaProtocol != null, "Kafka collect must have kafkaProtocol params");
|
||||
// Ensure that host and port are not empty
|
||||
@@ -267,8 +265,6 @@ public class KafkaCollectImpl extends AbstractCollect {
|
||||
break;
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("Kafka collect error: " + e.getMessage());
|
||||
log.error("Kafka collect error", e);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-8
@@ -42,7 +42,7 @@ public class KafkaCollectTest {
|
||||
@Test
|
||||
void preCheck() {
|
||||
// metrics is null
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
assertThrows(NullPointerException.class, () -> {
|
||||
collect.preCheck(null);
|
||||
});
|
||||
|
||||
@@ -51,21 +51,22 @@ public class KafkaCollectTest {
|
||||
collect.preCheck(Metrics.builder().build());
|
||||
});
|
||||
|
||||
KafkaProtocol kafka = new KafkaProtocol();
|
||||
Metrics metric = Metrics.builder().kclient(kafka).build();
|
||||
// kafka srv host is null
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
collect.preCheck(metric);
|
||||
KafkaProtocol kafka = new KafkaProtocol();
|
||||
collect.preCheck(Metrics.builder().kclient(kafka).build());
|
||||
});
|
||||
|
||||
// kafka port is null
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
kafka.setHost("127.0.0.1");
|
||||
collect.preCheck(metric);
|
||||
KafkaProtocol kafka = KafkaProtocol.builder().host("127.0.0.1").build();
|
||||
collect.preCheck(Metrics.builder().kclient(kafka).build());
|
||||
});
|
||||
|
||||
// no exception throw
|
||||
assertDoesNotThrow(() -> {
|
||||
kafka.setPort("9092");
|
||||
collect.preCheck(metric);
|
||||
KafkaProtocol kafka = KafkaProtocol.builder().host("127.0.0.1").port("9092").build();
|
||||
collect.preCheck(Metrics.builder().kclient(kafka).build());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -173,12 +173,6 @@
|
||||
<artifactId>snappy-java</artifactId>
|
||||
<version>${snappy-java.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.javaparser</groupId>
|
||||
<artifactId>javaparser-core</artifactId>
|
||||
<version>${javaparser.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
+84
@@ -42,6 +42,11 @@ public class CommonProperties {
|
||||
*/
|
||||
private DataQueueProperties queue;
|
||||
|
||||
/**
|
||||
* sms impl properties
|
||||
*/
|
||||
private SmsProperties sms;
|
||||
|
||||
/**
|
||||
* data queue properties
|
||||
*/
|
||||
@@ -141,4 +146,83 @@ public class CommonProperties {
|
||||
*/
|
||||
private String alertsDataTopic;
|
||||
}
|
||||
|
||||
/**
|
||||
* sms properties
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public static class SmsProperties {
|
||||
//Tencent cloud SMS configuration
|
||||
private TencentSmsProperties tencent;
|
||||
//Ali cloud SMS configuration
|
||||
private AliYunSmsProperties aliYun;
|
||||
}
|
||||
|
||||
/**
|
||||
* tencent sms properties
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public static class TencentSmsProperties {
|
||||
|
||||
/**
|
||||
* Tencent cloud account secret id
|
||||
*/
|
||||
private String secretId;
|
||||
|
||||
/**
|
||||
* Tencent cloud account secret key
|
||||
*/
|
||||
private String secretKey;
|
||||
|
||||
/**
|
||||
* SMS app id
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* SMS signature
|
||||
*/
|
||||
private String signName;
|
||||
|
||||
/**
|
||||
* SMS template ID
|
||||
*/
|
||||
private String templateId;
|
||||
}
|
||||
|
||||
/**
|
||||
* aliYun sms properties
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public static class AliYunSmsProperties {
|
||||
|
||||
/**
|
||||
* Aliyun account access key id
|
||||
*/
|
||||
private String secretId;
|
||||
|
||||
/**
|
||||
* Ali Cloud account access key
|
||||
*/
|
||||
private String secretKey;
|
||||
|
||||
/**
|
||||
* SMS app id
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* SMS signature
|
||||
*/
|
||||
private String signName;
|
||||
|
||||
/**
|
||||
* ID of the SMS template
|
||||
*/
|
||||
private String templateId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-32
@@ -1,32 +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.common.constants;
|
||||
|
||||
/**
|
||||
* SMS provider constants
|
||||
*/
|
||||
public interface SmsConstants {
|
||||
// Tencent cloud SMS
|
||||
String TENCENT = "tencent";
|
||||
|
||||
// Alibaba Cloud SMS
|
||||
String ALIBABA = "alibaba";
|
||||
|
||||
// UniSMS
|
||||
String UNISMS = "unisms";
|
||||
}
|
||||
-81
@@ -1,81 +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.common.entity.dto.query;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Metric History Range Query Data
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Metric Query Data")
|
||||
public class MetricQueryData {
|
||||
|
||||
@Schema(title = "Metric Schema")
|
||||
private MetricSchema schema;
|
||||
|
||||
@Schema(title = "metrics row values, first is the timestamp-ts", example = "[[29,32,44],[32,34,true]]")
|
||||
private List<List<Object>> values;
|
||||
|
||||
/**
|
||||
* Metric Schema
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public static final class MetricSchema {
|
||||
|
||||
@Schema(title = "Metrics Field")
|
||||
private List<MetricField> fields;
|
||||
|
||||
@Schema(title = "Meta Information")
|
||||
private Map<String, String> meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metric Field
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public static final class MetricField {
|
||||
|
||||
@Schema(title = "Metric Field Name")
|
||||
private String name;
|
||||
|
||||
@Schema(title = "Field Type: number, string, time, bool")
|
||||
private String type;
|
||||
|
||||
@Schema(title = "Field Unit: %, Mb, Kbps etc.")
|
||||
private String unit;
|
||||
|
||||
@Schema(title = "Whether is a label")
|
||||
private Boolean label;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -41,7 +41,7 @@ public class Configmap implements Serializable {
|
||||
private String key;
|
||||
|
||||
/**
|
||||
* parameter value
|
||||
* parameter value 参数value
|
||||
*/
|
||||
private Object value;
|
||||
|
||||
|
||||
@@ -1,80 +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.common.entity.job;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.CommonRequestProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.Protocol;
|
||||
|
||||
/**
|
||||
* ssh tunnel
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class SshTunnel implements CommonRequestProtocol, Protocol {
|
||||
|
||||
/**
|
||||
* enable ssh tunnel
|
||||
*/
|
||||
private String enable = "false";
|
||||
|
||||
/**
|
||||
* IP ADDRESS OR DOMAIN NAME OF THE PEER HOST
|
||||
*/
|
||||
private String host;
|
||||
|
||||
/**
|
||||
* Peer host port
|
||||
*/
|
||||
private String port = "22";
|
||||
|
||||
/**
|
||||
* TIME OUT PERIOD
|
||||
*/
|
||||
private String timeout = "6000";
|
||||
|
||||
/**
|
||||
* UserName
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* Password (optional)
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* Private key (optional)
|
||||
*/
|
||||
private String privateKey;
|
||||
|
||||
/**
|
||||
* private key passphrase (optional)
|
||||
*/
|
||||
private String privateKeyPassphrase;
|
||||
|
||||
/**
|
||||
* share connection session
|
||||
*/
|
||||
private String shareConnection = "true";
|
||||
}
|
||||
-6
@@ -59,10 +59,4 @@ public class FtpProtocol implements CommonRequestProtocol, Protocol {
|
||||
* Timeout
|
||||
*/
|
||||
private String timeout;
|
||||
|
||||
/**
|
||||
* Whether ftp uses link encryption ssl/tls, i.e. ftp or sftp
|
||||
*
|
||||
*/
|
||||
private String ssl = "false";
|
||||
}
|
||||
|
||||
-6
@@ -21,7 +21,6 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.hertzbeat.common.entity.job.SshTunnel;
|
||||
|
||||
/**
|
||||
* Database configuration information implemented by the common jdbc specification
|
||||
@@ -71,9 +70,4 @@ public class JdbcProtocol implements CommonRequestProtocol, Protocol {
|
||||
* DATABASE LINK URL eg: jdbc:mysql://localhost:3306/usthe
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* ssh tunnel
|
||||
*/
|
||||
private SshTunnel sshTunnel;
|
||||
}
|
||||
|
||||
-6
@@ -21,7 +21,6 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.hertzbeat.common.entity.job.SshTunnel;
|
||||
|
||||
/**
|
||||
* Redis Protocol
|
||||
@@ -62,9 +61,4 @@ public class RedisProtocol implements CommonRequestProtocol, Protocol {
|
||||
*/
|
||||
private String timeout;
|
||||
|
||||
/**
|
||||
* SSH TUNNEL
|
||||
*/
|
||||
private SshTunnel sshTunnel;
|
||||
|
||||
}
|
||||
|
||||
-30
@@ -1,30 +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.common.support.event;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* the event for sms config change
|
||||
*/
|
||||
public class SmsConfigChangeEvent extends ApplicationEvent {
|
||||
|
||||
public SmsConfigChangeEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
@@ -142,7 +142,7 @@ public final class AesUtil {
|
||||
/**
|
||||
* Determine whether it is encrypted
|
||||
* @param text text
|
||||
* @return true false
|
||||
* @return true-是 false-否
|
||||
*/
|
||||
public static boolean isCiphertext(String text, String decryptKey) {
|
||||
// First use whether it is base64 to determine whether it has been encrypted
|
||||
|
||||
+6
-6
@@ -68,19 +68,19 @@ class KafkaCommonDataQueueTest {
|
||||
when(commonProperties.getQueue()).thenReturn(dataQueueProperties);
|
||||
when(dataQueueProperties.getKafka()).thenReturn(kafkaProperties);
|
||||
|
||||
// Set all required topics
|
||||
// 设置所有必需的 topic
|
||||
when(kafkaProperties.getMetricsDataTopic()).thenReturn("metricsDataTopic");
|
||||
when(kafkaProperties.getAlertsDataTopic()).thenReturn("alertsDataTopic");
|
||||
when(kafkaProperties.getMetricsDataToStorageTopic()).thenReturn("metricsDataToStorageTopic");
|
||||
when(kafkaProperties.getServiceDiscoveryDataTopic()).thenReturn("serviceDiscoveryDataTopic");
|
||||
when(kafkaProperties.getServers()).thenReturn("localhost:9092");
|
||||
|
||||
// Simulate the subscribe method for consumers
|
||||
// 模拟 consumer 的 subscribe 方法
|
||||
doNothing().when(metricsDataToAlertConsumer).subscribe(anyCollection());
|
||||
|
||||
kafkaCommonDataQueue = new KafkaCommonDataQueue(commonProperties);
|
||||
|
||||
// Use reflection to set private fields
|
||||
// 使用反射设置私有字段
|
||||
setPrivateField(kafkaCommonDataQueue, "metricsDataProducer", metricsDataProducer);
|
||||
setPrivateField(kafkaCommonDataQueue, "metricsDataToAlertConsumer", metricsDataToAlertConsumer);
|
||||
}
|
||||
@@ -98,16 +98,16 @@ class KafkaCommonDataQueueTest {
|
||||
|
||||
@Test
|
||||
void testPollMetricsDataToAlerter() throws InterruptedException {
|
||||
// Create a test data
|
||||
// 创建一个测试数据
|
||||
CollectRep.MetricsData expectedData = CollectRep.MetricsData.newBuilder()
|
||||
.setMetrics("test metrics")
|
||||
.build();
|
||||
|
||||
// Create a ConsumerRecord containing test data
|
||||
// 创建一个包含测试数据的 ConsumerRecord
|
||||
ConsumerRecord<Long, CollectRep.MetricsData> record =
|
||||
new ConsumerRecord<>("metricsDataTopic", 0, 0L, 1L, expectedData);
|
||||
|
||||
// Create a ConsumerRecords containing a single record.
|
||||
// 创建一个包含单个记录的 ConsumerRecords
|
||||
Map<TopicPartition, List<ConsumerRecord<Long, CollectRep.MetricsData>>> recordsMap =
|
||||
Collections.singletonMap(
|
||||
new TopicPartition("metricsDataTopic", 0),
|
||||
|
||||
-146
@@ -1,146 +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.common.util;
|
||||
|
||||
import com.github.javaparser.JavaParser;
|
||||
import com.github.javaparser.ParseResult;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitOption;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Test case for checking Chinese characters in Java files
|
||||
*/
|
||||
@Slf4j
|
||||
public class ChineseCharacterCheckTest {
|
||||
|
||||
private static final Pattern CHINESE_CHAR_PATTERN = Pattern.compile("[\u4e00-\u9fa5]");
|
||||
private static final Set<String> EXCLUDED_FILES = new HashSet<>(Collections.singletonList("Metrics"));
|
||||
private static final String MAIN_SOURCE_DIR = "src/main/java";
|
||||
private static final String TEST_SOURCE_DIR = "src/test/java";
|
||||
|
||||
private final JavaParser javaParser = new JavaParser();
|
||||
private final String sourceDir;
|
||||
private final String testDir;
|
||||
|
||||
public ChineseCharacterCheckTest() {
|
||||
boolean isWindowsOs = System.getProperty("os.name").toLowerCase().startsWith("win");
|
||||
String separator = isWindowsOs ? "\\" : "/";
|
||||
this.sourceDir = MAIN_SOURCE_DIR.replace("/", separator);
|
||||
this.testDir = TEST_SOURCE_DIR.replace("/", separator);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotContainChineseInComments() {
|
||||
List<String> violations = scanForChineseCharacters(ScanTarget.COMMENTS);
|
||||
assertNoChineseCharacters(violations);
|
||||
}
|
||||
|
||||
private List<String> scanForChineseCharacters(ScanTarget target) {
|
||||
List<String> violations = new ArrayList<>();
|
||||
try (Stream<Path> paths = Files.walk(Paths.get(".."), FileVisitOption.FOLLOW_LINKS)) {
|
||||
paths.filter(this::isValidJavaFile)
|
||||
.forEach(path -> processFile(path, target, violations));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to scan Java files", e);
|
||||
}
|
||||
return violations;
|
||||
}
|
||||
|
||||
private boolean isValidJavaFile(Path path) {
|
||||
String pathStr = path.toString();
|
||||
return pathStr.endsWith(".java")
|
||||
&& (pathStr.contains(sourceDir) || pathStr.contains(testDir))
|
||||
&& EXCLUDED_FILES.stream().noneMatch(pathStr::contains);
|
||||
}
|
||||
|
||||
private void processFile(Path path, ScanTarget target, List<String> violations) {
|
||||
try {
|
||||
ParseResult<CompilationUnit> parseResult = javaParser.parse(Files.newInputStream(path));
|
||||
parseResult.getResult().ifPresent(cu -> {
|
||||
if (target.includeComments()) {
|
||||
checkComments(cu, path, violations);
|
||||
}
|
||||
if (target.includeCode()) {
|
||||
checkCode(cu, path, violations);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("Error processing file: {}", path, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkComments(CompilationUnit cu, Path path, List<String> violations) {
|
||||
cu.getAllContainedComments().stream()
|
||||
.filter(comment -> CHINESE_CHAR_PATTERN.matcher(comment.getContent()).find())
|
||||
.forEach(comment -> violations.add(formatViolation(path, "comment", comment.getContent().trim())));
|
||||
}
|
||||
|
||||
private void checkCode(CompilationUnit cu, Path path, List<String> violations) {
|
||||
cu.findAll(com.github.javaparser.ast.expr.StringLiteralExpr.class).stream()
|
||||
.filter(str -> CHINESE_CHAR_PATTERN.matcher(str.getValue()).find())
|
||||
.forEach(str -> violations.add(formatViolation(path, "code", str.getValue())));
|
||||
}
|
||||
|
||||
private String formatViolation(Path path, String location, String content) {
|
||||
return String.format("Chinese characters found in %s at %s: %s",
|
||||
location, path.toAbsolutePath(), content);
|
||||
}
|
||||
|
||||
private void assertNoChineseCharacters(List<String> violations) {
|
||||
Assertions.assertEquals(0, violations.size(),
|
||||
() -> String.format("Found Chinese characters in files:%n%s",
|
||||
String.join(System.lineSeparator(), violations)));
|
||||
}
|
||||
|
||||
private enum ScanTarget {
|
||||
COMMENTS(true, false),
|
||||
CODE(false, true),
|
||||
ALL(true, true);
|
||||
|
||||
private final boolean checkComments;
|
||||
private final boolean checkCode;
|
||||
|
||||
ScanTarget(boolean checkComments, boolean checkCode) {
|
||||
this.checkComments = checkComments;
|
||||
this.checkCode = checkCode;
|
||||
}
|
||||
|
||||
public boolean includeComments() {
|
||||
return checkComments;
|
||||
}
|
||||
|
||||
public boolean includeCode() {
|
||||
return checkCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,10 +53,5 @@
|
||||
<version>${hertzbeat.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+1
-7
@@ -56,7 +56,6 @@ public class HttpMonitorE2eTest extends AbstractCollectE2eTest {
|
||||
private static final int MOCK_SERVER_PORT = 52376;
|
||||
private static final String LOCALHOST = "127.0.0.1";
|
||||
private static final String RELATIVE_PATH = "/";
|
||||
private static final List<String> ALLOW_EMPTY_WHITE_LIST = List.of("header");
|
||||
private static HttpServer mockServer;
|
||||
|
||||
@AfterAll
|
||||
@@ -97,12 +96,7 @@ public class HttpMonitorE2eTest extends AbstractCollectE2eTest {
|
||||
List<Map<String, Configmap>> configmapFromPreCollectData = new LinkedList<>();
|
||||
for (Metrics metricsDef : dockerJob.getMetrics()) {
|
||||
metricsDef = CollectUtil.replaceCryPlaceholderToMetrics(metricsDef, configmapFromPreCollectData.size() > 0 ? configmapFromPreCollectData.get(0) : new HashMap<>());
|
||||
CollectRep.MetricsData metricsData;
|
||||
if (ALLOW_EMPTY_WHITE_LIST.contains(metricsDef.getName())) {
|
||||
metricsData = validateMetricsCollection(metricsDef, metricsDef.getName(), true);
|
||||
} else {
|
||||
metricsData = validateMetricsCollection(metricsDef, metricsDef.getName());
|
||||
}
|
||||
CollectRep.MetricsData metricsData = validateMetricsCollection(metricsDef, metricsDef.getName());
|
||||
configmapFromPreCollectData = CollectUtil.getConfigmapFromPreCollectData(metricsData);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-10
@@ -38,8 +38,6 @@ import org.testcontainers.lifecycle.Startables;
|
||||
import org.testcontainers.utility.DockerImageName;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
@@ -56,7 +54,6 @@ public class SshCollectE2eTest extends AbstractCollectE2eTest {
|
||||
private static final String ROOT_USER = "root";
|
||||
private static final int SSH_PORT = 22;
|
||||
private static final int PASSWORD_LENGTH = 12;
|
||||
private static final List<String> ALLOW_EMPTY_WHITE_LIST = Arrays.asList("top_mem_process", "top_cpu_process");
|
||||
|
||||
private static GenericContainer<?> linuxContainer;
|
||||
|
||||
@@ -91,13 +88,8 @@ public class SshCollectE2eTest extends AbstractCollectE2eTest {
|
||||
Assertions.assertTrue(linuxContainer.isRunning(), "Ubuntu container should be running");
|
||||
|
||||
Job ubuntuJob = appService.getAppDefine("ubuntu");
|
||||
ubuntuJob.getMetrics().forEach(metricsDef -> {
|
||||
if (ALLOW_EMPTY_WHITE_LIST.contains(metricsDef.getName())) {
|
||||
validateMetricsCollection(metricsDef, metricsDef.getName(), true);
|
||||
} else {
|
||||
validateMetricsCollection(metricsDef, metricsDef.getName());
|
||||
}
|
||||
});
|
||||
ubuntuJob.getMetrics().forEach(metricsDef ->
|
||||
validateMetricsCollection(metricsDef, metricsDef.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
-128
@@ -1,128 +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.collector.collect.basic.telnet;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollectE2eTest;
|
||||
import org.apache.hertzbeat.collector.collect.telnet.TelnetCollectImpl;
|
||||
import org.apache.hertzbeat.collector.util.CollectUtil;
|
||||
import org.apache.hertzbeat.common.entity.job.Configmap;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.Protocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.TelnetProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.utility.DockerImageName;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Integration test for Zookeeper monitoring functionality
|
||||
*/
|
||||
@Slf4j
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class ZookeeperMonitorE2eTest extends AbstractCollectE2eTest {
|
||||
|
||||
private static final String ZOOKEEPER_IMAGE_NAME = "zookeeper:3.8.4";
|
||||
private static final String ZOOKEEPER_NAME = "zookeeper";
|
||||
private static final Integer ZOOKEEPER_PORT = 2181;
|
||||
private static GenericContainer<?> zookeeperContainer;
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
if (zookeeperContainer != null) {
|
||||
zookeeperContainer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
collect = new TelnetCollectImpl();
|
||||
metrics = new Metrics();
|
||||
|
||||
try {
|
||||
// Start Zookeeper container with custom configuration
|
||||
zookeeperContainer = new GenericContainer<>(DockerImageName.parse(ZOOKEEPER_IMAGE_NAME))
|
||||
.withExposedPorts(ZOOKEEPER_PORT)
|
||||
.withEnv("ZOO_4LW_COMMANDS_WHITELIST", "*")
|
||||
.withNetworkAliases(ZOOKEEPER_NAME)
|
||||
.waitingFor(
|
||||
Wait.forLogMessage(".*Started AdminServer on address.*\\n", 1)
|
||||
.withStartupTimeout(Duration.ofSeconds(60))
|
||||
)
|
||||
.withLogConsumer(outputFrame -> {
|
||||
log.info(outputFrame.getUtf8String());
|
||||
});
|
||||
|
||||
zookeeperContainer.start();
|
||||
log.info("Zookeeper container started at {}:{}",
|
||||
zookeeperContainer.getHost(),
|
||||
zookeeperContainer.getMappedPort(ZOOKEEPER_PORT));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error("Failed to start Zookeeper container", e);
|
||||
throw e;
|
||||
}
|
||||
Thread.sleep(30000);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CollectRep.MetricsData.Builder collectMetrics(Metrics metricsDef) {
|
||||
TelnetProtocol telnetProtocol = (TelnetProtocol) buildProtocol(metricsDef);
|
||||
metrics.setTelnet(telnetProtocol);
|
||||
CollectRep.MetricsData.Builder metricsData = CollectRep.MetricsData.newBuilder();
|
||||
metricsData.setApp(ZOOKEEPER_NAME);
|
||||
metrics.setAliasFields(metricsDef.getAliasFields());
|
||||
return collectMetricsData(metrics, metricsDef, metricsData);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Protocol buildProtocol(Metrics metricsDef) {
|
||||
TelnetProtocol protocol = new TelnetProtocol();
|
||||
protocol.setHost(zookeeperContainer.getHost());
|
||||
protocol.setPort(String.valueOf(zookeeperContainer.getMappedPort(ZOOKEEPER_PORT)));
|
||||
protocol.setCmd(metricsDef.getTelnet().getCmd());
|
||||
return protocol;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testZookeeperMonitor() {
|
||||
Assertions.assertTrue(zookeeperContainer.isRunning(), "Zookeeper container should be running");
|
||||
|
||||
Job dockerJob = appService.getAppDefine("zookeeper");
|
||||
List<Map<String, Configmap>> configmapFromPreCollectData = new LinkedList<>();
|
||||
for (Metrics metricsDef : dockerJob.getMetrics()) {
|
||||
metricsDef = CollectUtil.replaceCryPlaceholderToMetrics(metricsDef, configmapFromPreCollectData.size() > 0 ? configmapFromPreCollectData.get(0) : new HashMap<>());
|
||||
CollectRep.MetricsData metricsData = validateMetricsCollection(metricsDef, metricsDef.getName());
|
||||
configmapFromPreCollectData = CollectUtil.getConfigmapFromPreCollectData(metricsData);
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-38
@@ -22,7 +22,6 @@ import org.apache.hertzbeat.collector.dispatch.CollectDataDispatch;
|
||||
import org.apache.hertzbeat.collector.dispatch.MetricsCollect;
|
||||
import org.apache.hertzbeat.collector.dispatch.timer.Timeout;
|
||||
import org.apache.hertzbeat.collector.dispatch.timer.WheelTimerTask;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.Protocol;
|
||||
@@ -78,39 +77,20 @@ public abstract class AbstractCollectE2eTest {
|
||||
|
||||
/**
|
||||
* Validate metrics collection, check if the metrics values are not empty <br/>
|
||||
* @param metricsDef metrics definition
|
||||
* @param metricName metric name
|
||||
* @return metrics data
|
||||
* We believe that all monitoring metrics should have data
|
||||
*/
|
||||
protected CollectRep.MetricsData validateMetricsCollection(Metrics metricsDef, String metricName) {
|
||||
// By default, we do not allow empty values
|
||||
return validateMetricsCollection(metricsDef, metricName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate metrics collection, check if the metrics values are not empty <br/>
|
||||
* We believe that all monitoring metrics should have data
|
||||
*
|
||||
* @param metricsDef metrics definition
|
||||
* @param metricName metric name
|
||||
* @param allowEmpty In some special scenarios, it is not necessary to check if the value is ` `
|
||||
*/
|
||||
protected CollectRep.MetricsData validateMetricsCollection(Metrics metricsDef, String metricName, boolean allowEmpty) {
|
||||
CollectRep.MetricsData.Builder metricsData = collectMetrics(metricsDef);
|
||||
|
||||
metricsCollect.calculateFields(metricsDef, metricsData);
|
||||
|
||||
Assertions.assertTrue(metricsData.getValuesList().size() > 0,
|
||||
String.format("%s metrics values should not be empty, detail: %s", metricName, metricsData.getMsg()));
|
||||
String.format("%s metrics values should not be empty", metricName));
|
||||
|
||||
for (CollectRep.ValueRow valueRow : metricsData.getValuesList()) {
|
||||
for (int i = 0; i < valueRow.getColumnsCount(); i++) {
|
||||
Assertions.assertFalse(valueRow.getColumns(i).isEmpty(),
|
||||
String.format("%s metric column %d should not be empty", metricName, i));
|
||||
if (!allowEmpty) {
|
||||
// Check if the value is not null
|
||||
Assertions.assertNotEquals(CommonConstants.NULL_VALUE, valueRow.getColumns(i), String.format("%s metric column %d should not be null", metricName, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,31 +98,21 @@ public abstract class AbstractCollectE2eTest {
|
||||
return metricsData.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set alias fields for metrics
|
||||
*
|
||||
* @param metrics metrics
|
||||
* @param metricsDef metrics definition
|
||||
*/
|
||||
protected void setMetricsAliasFields(Metrics metrics, Metrics metricsDef) {
|
||||
List<String> aliasFields = metricsDef.getAliasFields() == null
|
||||
? metricsDef.getFields().stream().map(Metrics.Field::getField).collect(Collectors.toList())
|
||||
: metricsDef.getAliasFields();
|
||||
metrics.setAliasFields(aliasFields);
|
||||
metricsDef.setAliasFields(aliasFields);
|
||||
metrics.setAliasFields(metricsDef.getAliasFields() == null
|
||||
? metricsDef.getFields().stream()
|
||||
.map(Metrics.Field::getField)
|
||||
.collect(Collectors.toList()) :
|
||||
metricsDef.getAliasFields());
|
||||
}
|
||||
|
||||
protected abstract CollectRep.MetricsData.Builder collectMetrics(Metrics metricsDef);
|
||||
|
||||
protected CollectRep.MetricsData.Builder collectMetricsData(Metrics metrics, Metrics metricsDef) {
|
||||
CollectRep.MetricsData.Builder metricsData = CollectRep.MetricsData.newBuilder();
|
||||
return this.collectMetricsData(metrics, metricsDef, metricsData);
|
||||
}
|
||||
|
||||
protected CollectRep.MetricsData.Builder collectMetricsData(Metrics metrics, Metrics metricsDef, CollectRep.MetricsData.Builder metricsData) {
|
||||
setMetricsAliasFields(metrics, metricsDef);
|
||||
|
||||
// Collect metrics
|
||||
CollectRep.MetricsData.Builder metricsData = CollectRep.MetricsData.newBuilder();
|
||||
collect.collect(metricsData, metrics);
|
||||
return metricsData;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ public class AppCount {
|
||||
private String app;
|
||||
|
||||
/**
|
||||
* 任务状态
|
||||
* task status
|
||||
*/
|
||||
private transient byte status;
|
||||
|
||||
+4
-9
@@ -26,15 +26,10 @@ import lombok.Data;
|
||||
@Data
|
||||
public class SmsAlibabaConfig {
|
||||
|
||||
@NotBlank(message = "AccessKeyId cannot be empty")
|
||||
private String accessKeyId;
|
||||
@NotBlank(message = "SecretId cannot be empty")
|
||||
private String secretId;
|
||||
|
||||
@NotBlank(message = "AccessKeySecret cannot be empty")
|
||||
private String accessKeySecret;
|
||||
@NotBlank(message = "SecretKey cannot be empty")
|
||||
private String secretKey;
|
||||
|
||||
@NotBlank(message = "SignName cannot be empty")
|
||||
private String signName;
|
||||
|
||||
@NotBlank(message = "TemplateCode cannot be null")
|
||||
private String templateCode;
|
||||
}
|
||||
|
||||
-2
@@ -37,7 +37,5 @@ public class SmsNoticeSender {
|
||||
|
||||
private SmsAlibabaConfig alibaba;
|
||||
|
||||
private SmsUniSmsConfig unisms;
|
||||
|
||||
private boolean enable = true;
|
||||
}
|
||||
|
||||
-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";
|
||||
}
|
||||
+6
-9
@@ -436,12 +436,11 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
newJobId = collectJobScheduling.updateAsyncCollectJob(appDefine, collector);
|
||||
}
|
||||
monitor.setJobId(newJobId);
|
||||
}
|
||||
|
||||
// execute only in non paused status
|
||||
try {
|
||||
detectMonitor(monitor, params, collector);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
try {
|
||||
detectMonitor(monitor, params, collector);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// After the update is successfully released, refresh the database
|
||||
@@ -568,12 +567,10 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
if (StringUtils.isNotBlank(search)) {
|
||||
Predicate predicateHost = criteriaBuilder.like(root.get("host"), "%" + search + "%");
|
||||
Predicate predicateName = criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), "%" + search.toLowerCase() + "%");
|
||||
if (StringUtils.isNumeric(search)){
|
||||
Predicate predicateId = criteriaBuilder.equal(root.get("id"), Long.parseLong(search));
|
||||
orList.add(predicateId);
|
||||
}
|
||||
Predicate predicateId = criteriaBuilder.like(root.get("id"), "%" + search + "%");
|
||||
orList.add(predicateHost);
|
||||
orList.add(predicateName);
|
||||
orList.add(predicateId);
|
||||
}
|
||||
if (StringUtils.isNotBlank(labels)) {
|
||||
String[] labelAres = labels.split(",");
|
||||
|
||||
-14
@@ -20,13 +20,9 @@ package org.apache.hertzbeat.manager.service.impl;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -36,8 +32,6 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class SmsGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImpl<SmsNoticeSender> {
|
||||
@Resource
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
/**
|
||||
* SmsGeneralConfigServiceImpl's constructor creates an instance of this class
|
||||
@@ -50,14 +44,6 @@ public class SmsGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImp
|
||||
public SmsGeneralConfigServiceImpl(GeneralConfigDao generalConfigDao, ObjectMapper objectMapper) {
|
||||
super(generalConfigDao, objectMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to handle the sms configuration change event.
|
||||
*/
|
||||
@Override
|
||||
public void handler(SmsNoticeSender smsNoticeSender) {
|
||||
applicationContext.publishEvent(new SmsConfigChangeEvent(applicationContext));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
|
||||
@@ -203,28 +203,6 @@ alerter:
|
||||
# alert inhibit ttl unit ms, default 14400000(4 hours)
|
||||
inhibit:
|
||||
ttl: 14400000
|
||||
sms:
|
||||
enable: false
|
||||
type: tencent
|
||||
tencent:
|
||||
secret-id:
|
||||
secret-key:
|
||||
app-id:
|
||||
sign-name:
|
||||
template-id:
|
||||
alibaba:
|
||||
access-key-id:
|
||||
access-key-secret:
|
||||
sign-name:
|
||||
template-code:
|
||||
unisms:
|
||||
# auth-mode: simple or hmac
|
||||
auth-mode: simple
|
||||
access-key-id: YOUR_ACCESS_KEY_ID
|
||||
# hmac mode need to fill in access-key-secret
|
||||
access-key-secret: YOUR_ACCESS_KEY_SECRET
|
||||
signature: YOUR_SMS_SIGNATURE
|
||||
template-id: YOUR_TEMPLATE_ID
|
||||
|
||||
scheduler:
|
||||
server:
|
||||
|
||||
@@ -1,100 +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.
|
||||
|
||||
|
||||
# The monitoring type category:service-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
|
||||
category: llm
|
||||
# The monitoring type eg: linux windows tomcat mysql aws...
|
||||
app: deepseek
|
||||
# The monitoring i18n name
|
||||
name:
|
||||
zh-CN: Deepseek
|
||||
en-US: Deepseek
|
||||
# The description and help of this monitoring type
|
||||
help:
|
||||
zh-CN: Hertzbeat 对 Deepseek Api进行测量监控。<br>您可以点击 “<i>新建 Deepseek</i>” 并进行配置,或者选择“<i>更多操作</i>”,导入已有配置。
|
||||
en-US: Hertzbeat monitors Deepseek Api. You could click the "<i>New Deepseek</i>" button and proceed with the configuration or import an existing setup through the "<i>More Actions</i>" menu.
|
||||
zh-TW: Hertzbeat對Deepseek Api進行量測監控。<br>您可以點擊“<i>Deepseek</i>”並進行配寘,或者選擇“<i>更多操作</i>”,導入已有配寘。
|
||||
helpLink:
|
||||
zh-CN: https://hertzbeat.apache.org/zh-cn/docs/help/deepseek
|
||||
en-US: https://hertzbeat.apache.org/docs/help/deepseek
|
||||
# Input params define for monitoring(render web ui by the definition)
|
||||
params:
|
||||
# field-param field key
|
||||
- field: token
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 会话密钥
|
||||
en-US: Session Key
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: text
|
||||
# required-true or false
|
||||
required: true
|
||||
|
||||
# collect metrics config list
|
||||
metrics:
|
||||
# metrics - auth
|
||||
- name: billing
|
||||
i18n:
|
||||
zh-CN: 计费
|
||||
en-US: Billing
|
||||
# metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel
|
||||
# priority 0's metrics is availability metrics, it will be scheduled first, only availability metrics collect success will the scheduling continue
|
||||
priority: 0
|
||||
# collect metrics content
|
||||
fields:
|
||||
# field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field
|
||||
- field: currency
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 货币
|
||||
en-US: Currency
|
||||
- field: total_balance
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 可用余额
|
||||
en-US: Available Balance
|
||||
- field: granted_balance
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 未过期的赠金余额
|
||||
en-US: Unexpired Gift Balance
|
||||
- field: topped_up_balance
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 充值的余额
|
||||
en-US: Topped Up Balance
|
||||
# the protocol used for monitoring, eg: sql, ssh, http, telnet, wmi, snmp, sdk
|
||||
protocol: http
|
||||
# the config content when protocol is http
|
||||
http:
|
||||
# http host: ipv4 ipv6 domain
|
||||
host: api.deepseek.com
|
||||
# http port
|
||||
port: 443
|
||||
# http url
|
||||
url: /user/balance
|
||||
# http method: GET POST PUT DELETE PATCH
|
||||
method: GET
|
||||
# if enabled https
|
||||
ssl: true
|
||||
# http auth
|
||||
authorization:
|
||||
# http auth type: Basic Auth, Digest Auth, Bearer Token
|
||||
type: Bearer Token
|
||||
bearerTokenToken: ^_^token^_^
|
||||
# http response data parse type: default-hertzbeat rule, jsonpath-jsonpath script, website-for website monitoring, prometheus-prometheus exporter rule
|
||||
parseType: jsonPath
|
||||
parseScript: '$.balance_infos.*'
|
||||
@@ -88,15 +88,6 @@ params:
|
||||
range: '[0,100000]'
|
||||
required: true
|
||||
defaultValue: 1000
|
||||
- field: ssl
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 启用SFTP
|
||||
en-US: SFTP
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: boolean
|
||||
# required-true or false
|
||||
required: true
|
||||
# collect metrics config list
|
||||
metrics:
|
||||
# metrics - basic
|
||||
@@ -131,4 +122,3 @@ metrics:
|
||||
password: ^_^password^_^
|
||||
direction: ^_^direction^_^
|
||||
timeout: ^_^timeout^_^
|
||||
ssl: ^_^ssl^_^
|
||||
|
||||
@@ -115,78 +115,6 @@ params:
|
||||
required: false
|
||||
# hide param-true or false
|
||||
hide: true
|
||||
- field: enableSshTunnel
|
||||
name:
|
||||
zh-CN: 是否启用SSH隧道
|
||||
en-US: Enable SSH Tunnel
|
||||
type: boolean
|
||||
required: true
|
||||
hide: true
|
||||
- field: sshHost
|
||||
name:
|
||||
zh-CN: SSH Host
|
||||
en-US: SSH Host
|
||||
type: text
|
||||
required: false
|
||||
placeholder: 'When Enable SSH Tunnel'
|
||||
hide: true
|
||||
- field: sshPort
|
||||
name:
|
||||
zh-CN: SSH端口
|
||||
en-US: SSH Port
|
||||
type: number
|
||||
range: '[0,65535]'
|
||||
required: false
|
||||
defaultValue: 22
|
||||
placeholder: 'When Enable SSH tunnel'
|
||||
hide: true
|
||||
- field: sshTimeout
|
||||
name:
|
||||
zh-CN: SSH超时时间(ms)
|
||||
en-US: SSH Timeout(ms)
|
||||
type: number
|
||||
required: false
|
||||
range: '[400,200000]'
|
||||
defaultValue: 6000
|
||||
hide: true
|
||||
- field: sshUsername
|
||||
name:
|
||||
zh-CN: SSH用户名
|
||||
en-US: SSH Username
|
||||
type: text
|
||||
required: false
|
||||
placeholder: 'When Enable SSH tunnel'
|
||||
hide: true
|
||||
- field: sshPassword
|
||||
name:
|
||||
zh-CN: SSH密码
|
||||
en-US: SSH Password
|
||||
type: password
|
||||
required: false
|
||||
hide: true
|
||||
- field: sshShareConnection
|
||||
name:
|
||||
zh-CN: 是否共享SSH连接
|
||||
en-US: Share SSH Connection
|
||||
type: boolean
|
||||
required: true
|
||||
defaultValue: true
|
||||
hide: true
|
||||
- field: sshPrivateKey
|
||||
name:
|
||||
zh-CN: SSH私钥
|
||||
en-US: SSH PrivateKey
|
||||
type: textarea
|
||||
placeholder: -----BEGIN RSA PRIVATE KEY-----
|
||||
required: false
|
||||
hide: true
|
||||
- field: sshPrivateKeyPassphrase
|
||||
name:
|
||||
zh-CN: SSH密钥短语
|
||||
en-US: SSH PrivateKey PassPhrase
|
||||
type: password
|
||||
required: false
|
||||
hide: true
|
||||
|
||||
# collect metrics config list
|
||||
metrics:
|
||||
@@ -277,16 +205,6 @@ metrics:
|
||||
sql: show global variables where Variable_name like 'version%' or Variable_name = 'max_connections' or Variable_name = 'datadir' or Variable_name = 'port' or Variable_name = 'thread_cache_size' or Variable_name = 'table_open_cache' or Variable_name = 'innodb_buffer_pool_size';
|
||||
# JDBC url
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: cache
|
||||
# metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel
|
||||
@@ -366,16 +284,6 @@ metrics:
|
||||
# sql
|
||||
sql: show global status like 'QCache%';
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: performance
|
||||
priority: 2
|
||||
@@ -410,16 +318,6 @@ metrics:
|
||||
queryType: columns
|
||||
sql: show global status where Variable_name = 'questions' or Variable_name = 'uptime';
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: innodb
|
||||
priority: 3
|
||||
@@ -486,16 +384,6 @@ metrics:
|
||||
queryType: columns
|
||||
sql: show global status where Variable_name like 'innodb%';
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: status
|
||||
priority: 4
|
||||
@@ -591,16 +479,6 @@ metrics:
|
||||
queryType: columns
|
||||
sql: show global status where Variable_name like 'thread%' or Variable_name = 'com_select' or Variable_name = 'com_insert' or Variable_name = 'com_update' or Variable_name = 'com_delete' or Variable_name = 'com_commit' or Variable_name = 'com_rollback' or Variable_name = 'questions' or Variable_name = 'uptime';
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: handler
|
||||
priority: 5
|
||||
@@ -680,16 +558,6 @@ metrics:
|
||||
queryType: columns
|
||||
sql: show global status like 'Handler%';
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: connection
|
||||
priority: 6
|
||||
@@ -729,16 +597,6 @@ metrics:
|
||||
queryType: columns
|
||||
sql: show global status;
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: thread
|
||||
priority: 7
|
||||
@@ -778,16 +636,6 @@ metrics:
|
||||
queryType: columns
|
||||
sql: show global status like 'thread%';
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: tmp
|
||||
priority: 8
|
||||
@@ -822,16 +670,6 @@ metrics:
|
||||
queryType: columns
|
||||
sql: show global status where Variable_name like '%tmp%';
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: select_type
|
||||
priority: 9
|
||||
@@ -876,16 +714,6 @@ metrics:
|
||||
queryType: columns
|
||||
sql: show global status where Variable_name like 'select%';
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: sort
|
||||
priority: 10
|
||||
@@ -925,16 +753,6 @@ metrics:
|
||||
queryType: columns
|
||||
sql: show global status where Variable_name like 'sort%';
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: table_lock
|
||||
priority: 11
|
||||
@@ -964,16 +782,6 @@ metrics:
|
||||
queryType: columns
|
||||
sql: show global status where Variable_name like 'table_lock%';
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: process_state
|
||||
priority: 12
|
||||
@@ -1004,16 +812,6 @@ metrics:
|
||||
queryType: multiRow
|
||||
sql: select state, count(*) as num from information_schema.PROCESSLIST where state != '' group by state;
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: slow_sql
|
||||
priority: 13
|
||||
@@ -1066,13 +864,3 @@ metrics:
|
||||
queryType: multiRow
|
||||
sql: select sql_text, start_time, db, query_time from mysql.slow_log;
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
@@ -91,78 +91,6 @@ params:
|
||||
type: text
|
||||
required: false
|
||||
hide: true
|
||||
- field: enableSshTunnel
|
||||
name:
|
||||
zh-CN: 是否启用SSH隧道
|
||||
en-US: Enable SSH Tunnel
|
||||
type: boolean
|
||||
required: true
|
||||
hide: true
|
||||
- field: sshHost
|
||||
name:
|
||||
zh-CN: SSH Host
|
||||
en-US: SSH Host
|
||||
type: text
|
||||
required: false
|
||||
placeholder: 'When Enable SSH Tunnel'
|
||||
hide: true
|
||||
- field: sshPort
|
||||
name:
|
||||
zh-CN: SSH端口
|
||||
en-US: SSH Port
|
||||
type: number
|
||||
range: '[0,65535]'
|
||||
required: false
|
||||
defaultValue: 22
|
||||
placeholder: 'When Enable SSH tunnel'
|
||||
hide: true
|
||||
- field: sshTimeout
|
||||
name:
|
||||
zh-CN: SSH超时时间(ms)
|
||||
en-US: SSH Timeout(ms)
|
||||
type: number
|
||||
required: false
|
||||
range: '[400,200000]'
|
||||
defaultValue: 6000
|
||||
hide: true
|
||||
- field: sshUsername
|
||||
name:
|
||||
zh-CN: SSH用户名
|
||||
en-US: SSH Username
|
||||
type: text
|
||||
required: false
|
||||
placeholder: 'When Enable SSH tunnel'
|
||||
hide: true
|
||||
- field: sshPassword
|
||||
name:
|
||||
zh-CN: SSH密码
|
||||
en-US: SSH Password
|
||||
type: password
|
||||
required: false
|
||||
hide: true
|
||||
- field: sshShareConnection
|
||||
name:
|
||||
zh-CN: 是否共享SSH连接
|
||||
en-US: Share SSH Connection
|
||||
type: boolean
|
||||
required: true
|
||||
defaultValue: true
|
||||
hide: true
|
||||
- field: sshPrivateKey
|
||||
name:
|
||||
zh-CN: SSH私钥
|
||||
en-US: SSH PrivateKey
|
||||
type: textarea
|
||||
placeholder: -----BEGIN RSA PRIVATE KEY-----
|
||||
required: false
|
||||
hide: true
|
||||
- field: sshPrivateKeyPassphrase
|
||||
name:
|
||||
zh-CN: SSH密钥短语
|
||||
en-US: SSH PrivateKey PassPhrase
|
||||
type: password
|
||||
required: false
|
||||
hide: true
|
||||
|
||||
# collect metrics config list
|
||||
metrics:
|
||||
@@ -222,16 +150,6 @@ metrics:
|
||||
sql: select name, setting as value from pg_settings where name = 'max_connections' or name = 'server_version' or name = 'server_encoding' or name = 'port' or name = 'data_directory';
|
||||
# JDBC url
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: state
|
||||
i18n:
|
||||
@@ -298,16 +216,6 @@ metrics:
|
||||
queryType: multiRow
|
||||
sql: SELECT COALESCE(datname,'shared-object') as db_name, conflicts, deadlocks, blks_read, blks_hit, blk_read_time, blk_write_time, stats_reset from pg_stat_database where (datname != 'template1' and datname != 'template0') or datname is null;
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: activity
|
||||
i18n:
|
||||
@@ -333,16 +241,6 @@ metrics:
|
||||
queryType: oneRow
|
||||
sql: SELECT count(*) as running FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: resource_config
|
||||
i18n:
|
||||
@@ -396,16 +294,6 @@ metrics:
|
||||
queryType: columns
|
||||
sql: show all;
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: connection
|
||||
i18n:
|
||||
@@ -430,16 +318,6 @@ metrics:
|
||||
queryType: oneRow
|
||||
sql: select count(1) as active from pg_stat_activity;
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: connection_state
|
||||
i18n:
|
||||
@@ -470,16 +348,6 @@ metrics:
|
||||
queryType: multiRow
|
||||
sql: select COALESCE(state, 'other') as state, count(*) as num from pg_stat_activity group by state;
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: connection_db
|
||||
i18n:
|
||||
@@ -510,16 +378,6 @@ metrics:
|
||||
queryType: multiRow
|
||||
sql: select count(*) as active, COALESCE(datname, 'other') as db_name from pg_stat_activity group by datname;
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: tuple
|
||||
i18n:
|
||||
@@ -564,16 +422,6 @@ metrics:
|
||||
queryType: multiRow
|
||||
sql: select sum(tup_fetched) as fetched, sum(tup_updated) as updated, sum(tup_deleted) as deleted, sum(tup_inserted) as inserted, sum(tup_returned) as returned from pg_stat_database;
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: temp_file
|
||||
i18n:
|
||||
@@ -610,16 +458,6 @@ metrics:
|
||||
queryType: multiRow
|
||||
sql: select COALESCE(datname, 'other') as db_name, sum(temp_files) as num, sum(temp_bytes) as size from pg_stat_database group by datname;
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: lock
|
||||
i18n:
|
||||
@@ -657,16 +495,6 @@ metrics:
|
||||
queryType: multiRow
|
||||
sql: SELECT COALESCE(datname,'shared-object') as db_name, conflicts, deadlocks from pg_stat_database where (datname != 'template1' and datname != 'template0') or datname is null;
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: slow_sql
|
||||
i18n:
|
||||
@@ -724,16 +552,6 @@ metrics:
|
||||
queryType: multiRow
|
||||
sql: select * from pg_stat_statements;
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: transaction
|
||||
i18n:
|
||||
@@ -771,16 +589,6 @@ metrics:
|
||||
queryType: multiRow
|
||||
sql: select COALESCE(datname, 'other') as db_name, sum(xact_commit) as commits, sum(xact_rollback) as rollbacks from pg_stat_database group by datname;
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: conflicts
|
||||
i18n:
|
||||
@@ -831,16 +639,6 @@ metrics:
|
||||
queryType: multiRow
|
||||
sql: select datname as db_name, confl_tablespace as tablespace, confl_lock as lock, confl_snapshot as snapshot, confl_bufferpin as bufferpin, confl_deadlock as deadlock from pg_stat_database_conflicts;
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: cache_hit_ratio
|
||||
i18n:
|
||||
@@ -878,16 +676,6 @@ metrics:
|
||||
queryType: multiRow
|
||||
sql: select datname as db_name, blks_hit, blks_read from pg_stat_database;
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: checkpoint
|
||||
i18n:
|
||||
@@ -919,16 +707,6 @@ metrics:
|
||||
queryType: oneRow
|
||||
sql: select checkpoint_sync_time, checkpoint_write_time from pg_stat_bgwriter;
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: buffer
|
||||
i18n:
|
||||
@@ -973,13 +751,3 @@ metrics:
|
||||
queryType: oneRow
|
||||
sql: select buffers_alloc as allocated, buffers_backend_fsync as fsync_calls_by_backend, buffers_backend as written_directly_by_backend, buffers_clean as written_by_background_writer, buffers_checkpoint as written_during_checkpoints from pg_stat_bgwriter;
|
||||
url: ^_^url^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
@@ -84,78 +84,7 @@ params:
|
||||
en-US: Password
|
||||
type: password
|
||||
required: false
|
||||
- field: enableSshTunnel
|
||||
name:
|
||||
zh-CN: 是否启用SSH隧道
|
||||
en-US: Enable SSH Tunnel
|
||||
type: boolean
|
||||
required: true
|
||||
hide: true
|
||||
- field: sshHost
|
||||
name:
|
||||
zh-CN: SSH Host
|
||||
en-US: SSH Host
|
||||
type: text
|
||||
required: false
|
||||
placeholder: 'When Enable SSH Tunnel'
|
||||
hide: true
|
||||
- field: sshPort
|
||||
name:
|
||||
zh-CN: SSH端口
|
||||
en-US: SSH Port
|
||||
type: number
|
||||
range: '[0,65535]'
|
||||
required: false
|
||||
defaultValue: 22
|
||||
placeholder: 'When Enable SSH tunnel'
|
||||
hide: true
|
||||
- field: sshTimeout
|
||||
name:
|
||||
zh-CN: SSH超时时间(ms)
|
||||
en-US: SSH Timeout(ms)
|
||||
type: number
|
||||
required: false
|
||||
range: '[400,200000]'
|
||||
defaultValue: 6000
|
||||
hide: true
|
||||
- field: sshUsername
|
||||
name:
|
||||
zh-CN: SSH用户名
|
||||
en-US: SSH Username
|
||||
type: text
|
||||
required: false
|
||||
placeholder: 'When Enable SSH tunnel'
|
||||
hide: true
|
||||
- field: sshPassword
|
||||
name:
|
||||
zh-CN: SSH密码
|
||||
en-US: SSH Password
|
||||
type: password
|
||||
required: false
|
||||
hide: true
|
||||
- field: sshShareConnection
|
||||
name:
|
||||
zh-CN: 是否共享SSH连接
|
||||
en-US: Share SSH Connection
|
||||
type: boolean
|
||||
required: true
|
||||
defaultValue: true
|
||||
hide: true
|
||||
- field: sshPrivateKey
|
||||
name:
|
||||
zh-CN: SSH私钥
|
||||
en-US: SSH PrivateKey
|
||||
type: textarea
|
||||
placeholder: -----BEGIN RSA PRIVATE KEY-----
|
||||
required: false
|
||||
hide: true
|
||||
- field: sshPrivateKeyPassphrase
|
||||
name:
|
||||
zh-CN: SSH密钥短语
|
||||
en-US: SSH PrivateKey PassPhrase
|
||||
type: password
|
||||
required: false
|
||||
hide: true
|
||||
|
||||
# collect metrics config list
|
||||
metrics:
|
||||
# metrics - server
|
||||
@@ -302,16 +231,6 @@ metrics:
|
||||
password: ^_^password^_^
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
# metrics - clients
|
||||
- name: clients
|
||||
# metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel
|
||||
@@ -369,16 +288,6 @@ metrics:
|
||||
username: ^_^username^_^
|
||||
password: ^_^password^_^
|
||||
timeout: ^_^timeout^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
# metrics - memory
|
||||
- name: memory
|
||||
# metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel
|
||||
@@ -618,16 +527,6 @@ metrics:
|
||||
password: ^_^password^_^
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
# metrics - persistence
|
||||
- name: persistence
|
||||
@@ -769,16 +668,6 @@ metrics:
|
||||
password: ^_^password^_^
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
# metrics - stats
|
||||
- name: stats
|
||||
@@ -995,16 +884,6 @@ metrics:
|
||||
password: ^_^password^_^
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
# metrics - replication
|
||||
- name: replication
|
||||
@@ -1086,16 +965,6 @@ metrics:
|
||||
password: ^_^password^_^
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
# metrics - cpu
|
||||
- name: cpu
|
||||
@@ -1152,16 +1021,6 @@ metrics:
|
||||
password: ^_^password^_^
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
# metrics - errorstats
|
||||
- name: errorstats
|
||||
@@ -1198,16 +1057,6 @@ metrics:
|
||||
password: ^_^password^_^
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
# metrics - cluster
|
||||
- name: cluster
|
||||
@@ -1237,16 +1086,6 @@ metrics:
|
||||
password: ^_^password^_^
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
# metrics - commandstats
|
||||
- name: commandstats
|
||||
@@ -1323,16 +1162,6 @@ metrics:
|
||||
password: ^_^password^_^
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
# metrics - keyspace
|
||||
- name: keyspace
|
||||
@@ -1439,13 +1268,3 @@ metrics:
|
||||
password: ^_^password^_^
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
@@ -105,78 +105,6 @@ params:
|
||||
defaultValue: 3
|
||||
# hide-is hide this field and put it in advanced layout
|
||||
hide: true
|
||||
- field: enableSshTunnel
|
||||
name:
|
||||
zh-CN: 是否启用SSH隧道
|
||||
en-US: Enable SSH Tunnel
|
||||
type: boolean
|
||||
required: true
|
||||
hide: true
|
||||
- field: sshHost
|
||||
name:
|
||||
zh-CN: SSH Host
|
||||
en-US: SSH Host
|
||||
type: text
|
||||
required: false
|
||||
placeholder: 'When Enable SSH Tunnel'
|
||||
hide: true
|
||||
- field: sshPort
|
||||
name:
|
||||
zh-CN: SSH端口
|
||||
en-US: SSH Port
|
||||
type: number
|
||||
range: '[0,65535]'
|
||||
required: false
|
||||
defaultValue: 22
|
||||
placeholder: 'When Enable SSH tunnel'
|
||||
hide: true
|
||||
- field: sshTimeout
|
||||
name:
|
||||
zh-CN: SSH超时时间(ms)
|
||||
en-US: SSH Timeout(ms)
|
||||
type: number
|
||||
required: false
|
||||
range: '[400,200000]'
|
||||
defaultValue: 6000
|
||||
hide: true
|
||||
- field: sshUsername
|
||||
name:
|
||||
zh-CN: SSH用户名
|
||||
en-US: SSH Username
|
||||
type: text
|
||||
required: false
|
||||
placeholder: 'When Enable SSH tunnel'
|
||||
hide: true
|
||||
- field: sshPassword
|
||||
name:
|
||||
zh-CN: SSH密码
|
||||
en-US: SSH Password
|
||||
type: password
|
||||
required: false
|
||||
hide: true
|
||||
- field: sshShareConnection
|
||||
name:
|
||||
zh-CN: 是否共享SSH连接
|
||||
en-US: Share SSH Connection
|
||||
type: boolean
|
||||
required: true
|
||||
defaultValue: true
|
||||
hide: true
|
||||
- field: sshPrivateKey
|
||||
name:
|
||||
zh-CN: SSH私钥
|
||||
en-US: SSH PrivateKey
|
||||
type: textarea
|
||||
placeholder: -----BEGIN RSA PRIVATE KEY-----
|
||||
required: false
|
||||
hide: true
|
||||
- field: sshPrivateKeyPassphrase
|
||||
name:
|
||||
zh-CN: SSH密钥短语
|
||||
en-US: SSH PrivateKey PassPhrase
|
||||
type: password
|
||||
required: false
|
||||
hide: true
|
||||
metrics:
|
||||
- name: server
|
||||
# metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel
|
||||
@@ -319,16 +247,6 @@ metrics:
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
pattern: ^_^pattern^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
|
||||
- name: clients
|
||||
@@ -400,16 +318,6 @@ metrics:
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
pattern: ^_^pattern^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: memory
|
||||
i18n:
|
||||
@@ -654,16 +562,6 @@ metrics:
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
pattern: ^_^pattern^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: persistence
|
||||
i18n:
|
||||
@@ -809,16 +707,6 @@ metrics:
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
pattern: ^_^pattern^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: stats
|
||||
i18n:
|
||||
@@ -1039,16 +927,6 @@ metrics:
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
pattern: ^_^pattern^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: replication
|
||||
i18n:
|
||||
@@ -1134,16 +1012,6 @@ metrics:
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
pattern: ^_^pattern^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: cpu
|
||||
i18n:
|
||||
@@ -1204,16 +1072,6 @@ metrics:
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
pattern: ^_^pattern^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: errorstats
|
||||
i18n:
|
||||
@@ -1254,16 +1112,6 @@ metrics:
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
pattern: ^_^pattern^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: cluster
|
||||
i18n:
|
||||
@@ -1379,16 +1227,6 @@ metrics:
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
pattern: ^_^pattern^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: commandstats
|
||||
i18n:
|
||||
@@ -1469,16 +1307,6 @@ metrics:
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
pattern: ^_^pattern^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
|
||||
- name: keyspace
|
||||
@@ -1542,13 +1370,3 @@ metrics:
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
pattern: ^_^pattern^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
@@ -104,78 +104,6 @@ params:
|
||||
defaultValue: 2
|
||||
# hide-is hide this field and put it in advanced layout
|
||||
hide: true
|
||||
- field: enableSshTunnel
|
||||
name:
|
||||
zh-CN: 是否启用SSH隧道
|
||||
en-US: Enable SSH Tunnel
|
||||
type: boolean
|
||||
required: true
|
||||
hide: true
|
||||
- field: sshHost
|
||||
name:
|
||||
zh-CN: SSH Host
|
||||
en-US: SSH Host
|
||||
type: text
|
||||
required: false
|
||||
placeholder: 'When Enable SSH Tunnel'
|
||||
hide: true
|
||||
- field: sshPort
|
||||
name:
|
||||
zh-CN: SSH端口
|
||||
en-US: SSH Port
|
||||
type: number
|
||||
range: '[0,65535]'
|
||||
required: false
|
||||
defaultValue: 22
|
||||
placeholder: 'When Enable SSH tunnel'
|
||||
hide: true
|
||||
- field: sshTimeout
|
||||
name:
|
||||
zh-CN: SSH超时时间(ms)
|
||||
en-US: SSH Timeout(ms)
|
||||
type: number
|
||||
required: false
|
||||
range: '[400,200000]'
|
||||
defaultValue: 6000
|
||||
hide: true
|
||||
- field: sshUsername
|
||||
name:
|
||||
zh-CN: SSH用户名
|
||||
en-US: SSH Username
|
||||
type: text
|
||||
required: false
|
||||
placeholder: 'When Enable SSH tunnel'
|
||||
hide: true
|
||||
- field: sshPassword
|
||||
name:
|
||||
zh-CN: SSH密码
|
||||
en-US: SSH Password
|
||||
type: password
|
||||
required: false
|
||||
hide: true
|
||||
- field: sshShareConnection
|
||||
name:
|
||||
zh-CN: 是否共享SSH连接
|
||||
en-US: Share SSH Connection
|
||||
type: boolean
|
||||
required: true
|
||||
defaultValue: true
|
||||
hide: true
|
||||
- field: sshPrivateKey
|
||||
name:
|
||||
zh-CN: SSH私钥
|
||||
en-US: SSH PrivateKey
|
||||
type: textarea
|
||||
placeholder: -----BEGIN RSA PRIVATE KEY-----
|
||||
required: false
|
||||
hide: true
|
||||
- field: sshPrivateKeyPassphrase
|
||||
name:
|
||||
zh-CN: SSH密钥短语
|
||||
en-US: SSH PrivateKey PassPhrase
|
||||
type: password
|
||||
required: false
|
||||
hide: true
|
||||
metrics:
|
||||
- name: server
|
||||
i18n:
|
||||
@@ -321,16 +249,6 @@ metrics:
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
pattern: ^_^pattern^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: clients
|
||||
i18n:
|
||||
@@ -396,16 +314,6 @@ metrics:
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
pattern: ^_^pattern^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: stats
|
||||
i18n:
|
||||
@@ -621,16 +529,6 @@ metrics:
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
pattern: ^_^pattern^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: cpu
|
||||
i18n:
|
||||
@@ -686,16 +584,6 @@ metrics:
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
pattern: ^_^pattern^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
- name: sentinel
|
||||
i18n:
|
||||
@@ -746,13 +634,3 @@ metrics:
|
||||
# timeout unit:ms
|
||||
timeout: ^_^timeout^_^
|
||||
pattern: ^_^pattern^_^
|
||||
sshTunnel:
|
||||
enable: ^_^enableSshTunnel^_^
|
||||
host: ^_^sshHost^_^
|
||||
port: ^_^sshPort^_^
|
||||
timeout: ^_^sshTimeout^_^
|
||||
username: ^_^sshUsername^_^
|
||||
password: ^_^sshPassword^_^
|
||||
privateKey: ^_^sshPrivateKey^_^
|
||||
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
|
||||
shareConnection: ^_^sshShareConnection^_^
|
||||
|
||||
@@ -80,7 +80,6 @@ excludedResource:
|
||||
- /api/status/page/public/**===*
|
||||
# web ui resource
|
||||
- /===get
|
||||
- /assets/**===get
|
||||
- /dashboard/**===get
|
||||
- /monitors/**===get
|
||||
- /alert/**===get
|
||||
|
||||
@@ -43,7 +43,7 @@ import org.apache.hertzbeat.common.config.CommonConfig;
|
||||
import org.apache.hertzbeat.common.config.CommonProperties;
|
||||
import org.apache.hertzbeat.common.queue.impl.InMemoryCommonDataQueue;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.apache.hertzbeat.alert.service.impl.TencentSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.TencentSmsClient;
|
||||
import org.apache.hertzbeat.warehouse.WarehouseWorkerPool;
|
||||
import org.apache.hertzbeat.warehouse.controller.MetricsDataController;
|
||||
import org.apache.hertzbeat.warehouse.store.history.iotdb.IotDbDataStorage;
|
||||
@@ -93,7 +93,7 @@ class ManagerTest extends AbstractSpringIntegrationTest {
|
||||
assertNotNull(ctx.getBean(CommonConfig.class));
|
||||
assertNotNull(ctx.getBean(InMemoryCommonDataQueue.class));
|
||||
// condition on common.sms.tencent.app-id
|
||||
assertThrows(NoSuchBeanDefinitionException.class, () -> ctx.getBean(TencentSmsClientImpl.class));
|
||||
assertThrows(NoSuchBeanDefinitionException.class, () -> ctx.getBean(TencentSmsClient.class));
|
||||
assertNotNull(ctx.getBean(SpringContextHolder.class));
|
||||
|
||||
// test warehouse module
|
||||
|
||||
-72
@@ -1,72 +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.warehouse.controller;
|
||||
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.util.List;
|
||||
import org.apache.hertzbeat.common.entity.dto.Message;
|
||||
import org.apache.hertzbeat.common.entity.dto.query.MetricQueryData;
|
||||
import org.apache.hertzbeat.warehouse.service.MetricsDataQueryService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Metrics Data Query API
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(produces = {APPLICATION_JSON_VALUE})
|
||||
@Tag(name = "Metrics Data Query API")
|
||||
public class MetricsDataQueryController {
|
||||
|
||||
|
||||
@Autowired
|
||||
private MetricsDataQueryService queryService;
|
||||
|
||||
|
||||
@GetMapping("/api/warehouse/query")
|
||||
@Operation(summary = "Query Real Time Metrics Data")
|
||||
public ResponseEntity<Message<List<MetricQueryData>>> queryMetricsData(
|
||||
@Parameter(description = "Query PromQL expr list", example = "cpu")
|
||||
@RequestParam List<String> queries,
|
||||
@Parameter(description = "Query timestamp", example = "1725854804451")
|
||||
@RequestParam long time) {
|
||||
return ResponseEntity.ok(Message.success(queryService.query(queries, time)));
|
||||
}
|
||||
|
||||
@GetMapping("/api/warehouse/query/range")
|
||||
@Operation(summary = "Query Range Metrics Data")
|
||||
public ResponseEntity<Message<List<MetricQueryData>>> queryMetricsDataRange(
|
||||
@Parameter(description = "Query PromQL expr list", example = "cpu")
|
||||
@RequestParam List<String> queries,
|
||||
@Parameter(description = "Query start timestamp", example = "1725854804451")
|
||||
@RequestParam long start,
|
||||
@Parameter(description = "Query end timestamp", example = "1733630804452")
|
||||
@RequestParam long end,
|
||||
@Parameter(description = "Query step", example = "4m")
|
||||
@RequestParam String step
|
||||
) {
|
||||
return ResponseEntity.ok(Message.success(queryService.queryRange(queries, start, end, step)));
|
||||
}
|
||||
}
|
||||
-45
@@ -1,45 +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.warehouse.service;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.hertzbeat.common.entity.dto.query.MetricQueryData;
|
||||
|
||||
/**
|
||||
* metrics data query service
|
||||
*/
|
||||
public interface MetricsDataQueryService {
|
||||
|
||||
/**
|
||||
* Query metrics data
|
||||
* @param queries query expr
|
||||
* @param time time
|
||||
* @return data
|
||||
*/
|
||||
List<MetricQueryData> query(List<String> queries, long time);
|
||||
|
||||
/**
|
||||
* Query metrics data range
|
||||
* @param queries query expr
|
||||
* @param start start
|
||||
* @param end end
|
||||
* @param step step
|
||||
* @return data
|
||||
*/
|
||||
List<MetricQueryData> queryRange(List<String> queries, long start, long end, String step);
|
||||
}
|
||||
-282
@@ -1,282 +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.warehouse.service.impl.greptime;
|
||||
|
||||
import io.greptime.GreptimeDB;
|
||||
import io.greptime.models.AuthInfo;
|
||||
import io.greptime.models.DataType;
|
||||
import io.greptime.models.Err;
|
||||
import io.greptime.models.Result;
|
||||
import io.greptime.models.Table;
|
||||
import io.greptime.models.TableSchema;
|
||||
import io.greptime.models.WriteOk;
|
||||
import io.greptime.options.GreptimeOptions;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.time.temporal.TemporalAmount;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.dto.Value;
|
||||
import org.apache.hertzbeat.common.entity.dto.query.MetricQueryData;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.hertzbeat.common.util.TimePeriodUtil;
|
||||
import org.apache.hertzbeat.warehouse.service.MetricsDataQueryService;
|
||||
import org.apache.hertzbeat.warehouse.store.history.greptime.GreptimeProperties;
|
||||
import org.apache.hertzbeat.warehouse.store.history.vm.PromQlQueryContent;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* GreptimeDB data storage, only supports GreptimeDB version >= v0.5
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(prefix = "warehouse.store.greptime", name = "enabled", havingValue = "true")
|
||||
@Slf4j
|
||||
public class GreptimeDbStorage implements MetricsDataQueryService, DisposableBean {
|
||||
|
||||
private static final String BASIC = "Basic";
|
||||
private static final String QUERY_RANGE_PATH = "/v1/prometheus/api/v1/query_range";
|
||||
private static final String LABEL_KEY_NAME = "__name__";
|
||||
private static final String LABEL_KEY_FIELD = "__field__";
|
||||
private static final String LABEL_KEY_INSTANCE = "instance";
|
||||
private static final String SPILT = "_";
|
||||
|
||||
private GreptimeDB greptimeDb;
|
||||
|
||||
private final GreptimeProperties greptimeProperties;
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
public GreptimeDbStorage(GreptimeProperties greptimeProperties, RestTemplate restTemplate) {
|
||||
if (greptimeProperties == null) {
|
||||
log.error("init error, please config Warehouse GreptimeDB props in application.yml");
|
||||
throw new IllegalArgumentException("please config Warehouse GreptimeDB props");
|
||||
}
|
||||
this.restTemplate = restTemplate;
|
||||
this.greptimeProperties = greptimeProperties;
|
||||
initGreptimeDbClient(greptimeProperties);
|
||||
}
|
||||
|
||||
private void initGreptimeDbClient(GreptimeProperties greptimeProperties) {
|
||||
String endpoints = greptimeProperties.grpcEndpoints();
|
||||
GreptimeOptions opts = GreptimeOptions.newBuilder(endpoints.split(","), greptimeProperties.database())
|
||||
.writeMaxRetries(3)
|
||||
.authInfo(new AuthInfo(greptimeProperties.username(), greptimeProperties.password()))
|
||||
.routeTableRefreshPeriodSeconds(30)
|
||||
.build();
|
||||
this.greptimeDb = GreptimeDB.create(opts);
|
||||
}
|
||||
|
||||
public void saveData(CollectRep.MetricsData metricsData) {
|
||||
if (metricsData.getCode() != CollectRep.Code.SUCCESS) {
|
||||
return;
|
||||
}
|
||||
if (metricsData.getValuesList().isEmpty()) {
|
||||
log.info("[warehouse greptime] flush metrics data {} {} is null, ignore.", metricsData.getId(), metricsData.getMetrics());
|
||||
return;
|
||||
}
|
||||
String monitorId = String.valueOf(metricsData.getId());
|
||||
String tableName = getTableName(metricsData.getApp(), metricsData.getMetrics());
|
||||
TableSchema.Builder tableSchemaBuilder = TableSchema.newBuilder(tableName);
|
||||
|
||||
tableSchemaBuilder.addTag("instance", DataType.String)
|
||||
.addTimestamp("ts", DataType.TimestampMillisecond);
|
||||
|
||||
List<CollectRep.Field> fieldsList = metricsData.getFieldsList();
|
||||
for (CollectRep.Field field : fieldsList) {
|
||||
// handle field type
|
||||
if (field.getLabel()) {
|
||||
tableSchemaBuilder.addTag(field.getName(), DataType.String);
|
||||
} else {
|
||||
if (field.getType() == CommonConstants.TYPE_NUMBER) {
|
||||
tableSchemaBuilder.addField(field.getName(), DataType.Float64);
|
||||
} else if (field.getType() == CommonConstants.TYPE_STRING) {
|
||||
tableSchemaBuilder.addField(field.getName(), DataType.String);
|
||||
}
|
||||
}
|
||||
}
|
||||
Table table = Table.from(tableSchemaBuilder.build());
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
Object[] values = new Object[2 + fieldsList.size()];
|
||||
values[0] = monitorId;
|
||||
values[1] = now;
|
||||
for (CollectRep.ValueRow valueRow : metricsData.getValuesList()) {
|
||||
for (int i = 0; i < fieldsList.size(); i++) {
|
||||
if (!CommonConstants.NULL_VALUE.equals(valueRow.getColumns(i))) {
|
||||
CollectRep.Field field = fieldsList.get(i);
|
||||
if (field.getLabel()) {
|
||||
values[2 + i] = valueRow.getColumns(i);
|
||||
} else {
|
||||
if (field.getType() == CommonConstants.TYPE_NUMBER) {
|
||||
values[2 + i] = Double.parseDouble(valueRow.getColumns(i));
|
||||
} else if (field.getType() == CommonConstants.TYPE_STRING) {
|
||||
values[2 + i] = valueRow.getColumns(i);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
values[2 + i] = null;
|
||||
}
|
||||
}
|
||||
table.addRow(values);
|
||||
}
|
||||
CompletableFuture<Result<WriteOk, Err>> writeFuture = greptimeDb.write(table);
|
||||
try {
|
||||
Result<WriteOk, Err> result = writeFuture.get(10, TimeUnit.SECONDS);
|
||||
if (result.isOk()) {
|
||||
log.debug("[warehouse greptime]-Write successful");
|
||||
} else {
|
||||
log.warn("[warehouse greptime]--Write failed: {}", result.getErr());
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
log.error("[warehouse greptime]--Error occurred: {}", throwable.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[warehouse greptime]--Error: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, List<Value>> getHistoryMetricData(Long monitorId, String app, String metrics, String metric,
|
||||
String label, String history) {
|
||||
String name = getTableName(app, metrics);
|
||||
String timeSeriesSelector = LABEL_KEY_NAME + "=\"" + name + "\""
|
||||
+ "," + LABEL_KEY_INSTANCE + "=\"" + monitorId + "\"";
|
||||
if (!CommonConstants.PROMETHEUS.equals(app)) {
|
||||
timeSeriesSelector = timeSeriesSelector + "," + LABEL_KEY_FIELD + "=\"" + metric + "\"";
|
||||
}
|
||||
Map<String, List<Value>> instanceValuesMap = new HashMap<>(8);
|
||||
try {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
|
||||
if (StringUtils.hasText(greptimeProperties.username())
|
||||
&& StringUtils.hasText(greptimeProperties.password())) {
|
||||
String authStr = greptimeProperties.username() + ":" + greptimeProperties.password();
|
||||
String encodedAuth = new String(Base64.encodeBase64(authStr.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
|
||||
headers.add(HttpHeaders.AUTHORIZATION, BASIC + " " + encodedAuth);
|
||||
}
|
||||
Instant now = Instant.now();
|
||||
long start;
|
||||
try {
|
||||
if (NumberUtils.isParsable(history)) {
|
||||
start = NumberUtils.toLong(history);
|
||||
start = (ZonedDateTime.now().toEpochSecond() - start);
|
||||
} else {
|
||||
TemporalAmount temporalAmount = TimePeriodUtil.parseTokenTime(history);
|
||||
assert temporalAmount != null;
|
||||
Instant dateTime = now.minus(temporalAmount);
|
||||
start = dateTime.getEpochSecond();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("history time error: {}. use default: 6h", e.getMessage());
|
||||
start = now.minus(6, ChronoUnit.HOURS).getEpochSecond();
|
||||
}
|
||||
long end = now.getEpochSecond();
|
||||
String step = "60s";
|
||||
if (end - start < Duration.ofDays(7).getSeconds() && end - start > Duration.ofDays(1).getSeconds()) {
|
||||
step = "1h";
|
||||
} else if (end - start >= Duration.ofDays(7).getSeconds()) {
|
||||
step = "4h";
|
||||
}
|
||||
HttpEntity<Void> httpEntity = new HttpEntity<>(headers);
|
||||
URI uri = UriComponentsBuilder.fromHttpUrl(greptimeProperties.httpEndpoint() + QUERY_RANGE_PATH)
|
||||
.queryParam(URLEncoder.encode("query", StandardCharsets.UTF_8), URLEncoder.encode("{" + timeSeriesSelector + "}", StandardCharsets.UTF_8))
|
||||
.queryParam("start", start)
|
||||
.queryParam("end", end)
|
||||
.queryParam("step", step)
|
||||
.build(true).toUri();
|
||||
ResponseEntity<PromQlQueryContent> responseEntity = restTemplate.exchange(uri,
|
||||
HttpMethod.GET, httpEntity, PromQlQueryContent.class);
|
||||
if (responseEntity.getStatusCode().is2xxSuccessful()) {
|
||||
log.debug("query metrics data from victoria-metrics success. {}", uri);
|
||||
if (responseEntity.getBody() != null && responseEntity.getBody().getData() != null
|
||||
&& responseEntity.getBody().getData().getResult() != null) {
|
||||
List<PromQlQueryContent.ContentData.Content> contents = responseEntity.getBody().getData().getResult();
|
||||
for (PromQlQueryContent.ContentData.Content content : contents) {
|
||||
Map<String, String> labels = content.getMetric();
|
||||
labels.remove(LABEL_KEY_NAME);
|
||||
labels.remove(LABEL_KEY_INSTANCE);
|
||||
String labelStr = JsonUtil.toJson(labels);
|
||||
if (content.getValues() != null && !content.getValues().isEmpty()) {
|
||||
List<Value> valueList = instanceValuesMap.computeIfAbsent(labelStr, k -> new LinkedList<>());
|
||||
for (Object[] valueArr : content.getValues()) {
|
||||
long timestamp = ((Double) valueArr[0]).longValue();
|
||||
String value = new BigDecimal(String.valueOf(valueArr[1])).setScale(4, RoundingMode.HALF_UP).stripTrailingZeros().toPlainString();
|
||||
// read timestamp here is s unit
|
||||
valueList.add(new Value(value, timestamp * 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.error("query metrics data from greptime failed. {}", responseEntity);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
return instanceValuesMap;
|
||||
}
|
||||
|
||||
private String getTableName(String app, String metrics) {
|
||||
return app + SPILT + metrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (this.greptimeDb != null) {
|
||||
this.greptimeDb.shutdownGracefully();
|
||||
this.greptimeDb = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetricQueryData> query(List<String> queries, long time) {
|
||||
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetricQueryData> queryRange(List<String> queries, long start, long end, String step) {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -92,10 +92,9 @@ public class DataStorageDispatch {
|
||||
long id = metricsData.getId();
|
||||
CollectRep.Code code = metricsData.getCode();
|
||||
try {
|
||||
String sql = "UPDATE hzb_monitor SET status = ? WHERE id = ? AND status = ?";
|
||||
String sql = "UPDATE hzb_monitor SET status = ? WHERE id = ? AND status != ?";
|
||||
int status = code == CollectRep.Code.SUCCESS ? CommonConstants.MONITOR_UP_CODE : CommonConstants.MONITOR_DOWN_CODE;
|
||||
int preStatus = code == CollectRep.Code.SUCCESS ? CommonConstants.MONITOR_DOWN_CODE : CommonConstants.MONITOR_UP_CODE;
|
||||
int matchedRows = jdbcTemplate.update(sql, status, id, preStatus);
|
||||
int matchedRows = jdbcTemplate.update(sql, status, id, status);
|
||||
if (matchedRows > 0) {
|
||||
entityManager.getEntityManagerFactory().getCache().evict(Monitor.class, id);
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ Hi HertzBeat PPMC,
|
||||
|
||||
This is a formal vote about inviting ${NEW_COMMITTER_NAME} as our community new committer.
|
||||
|
||||
Work list: https://github.com/apache/hertzbeat/commits?author=xxx
|
||||
Work list: https://github.com/apache/hertzbeat/commits?author=${NEW_COMMITTER_NAME}
|
||||
|
||||
Here is the list of ${NEW_COMMITTER_NAME}'s contributions in HertzBeat Community:
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
id: deepseek
|
||||
title: Monitoring Deepseek Account Status
|
||||
sidebar_label: Deepseek Account Status
|
||||
keywords: [Open Source Monitoring System, Open Source Network Monitoring, Deepseek Account Monitoring]
|
||||
---
|
||||
|
||||
### Preparation
|
||||
|
||||
#### Obtain Session Key
|
||||
|
||||
Log in to the Deepseek backend and visit the `https://platform.deepseek.com/api_keys` page to obtain the session key.
|
||||
|
||||
### Configuration Parameters
|
||||
|
||||
| Parameter Name | Parameter Description |
|
||||
| ------------- | --------------------- |
|
||||
| Monitoring Host | Enter `api.deepseek.com` here. |
|
||||
| Task Name | The name that identifies this monitoring task, which must be unique. |
|
||||
| Session Key | The session key obtained in the preparation step. |
|
||||
| Collector | Configure which collector is used to schedule data collection for this monitoring. |
|
||||
| Monitoring Interval | The interval for periodically collecting data, in seconds. The minimum interval that can be set is 30 seconds. |
|
||||
| Bound Tags | Tags for categorizing and managing monitoring resources. |
|
||||
| Description/Remarks | Additional remarks to identify and describe this monitoring. Users can add notes here. |
|
||||
|
||||
### Collection Metrics
|
||||
|
||||
#### Metric Set: Billing
|
||||
|
||||
| Metric Name | Metric Unit | Metric Description |
|
||||
| ---------- | ---------- | ----------------- |
|
||||
| Currency | None | Currency, either RMB or USD. |
|
||||
| Available Balance | RMB/USD | Total available balance, including bonus and recharge balance. |
|
||||
| Unexpired Bonus Balance | RMB/USD | Unexpired bonus balance. |
|
||||
| Recharge Balance | RMB/USD | Recharge balance. |
|
||||
@@ -8,76 +8,52 @@ This describes how to configure the SMS server, the number of built-in availabil
|
||||
|
||||
**Configuration file `application.yml` of `hertzbeat`**
|
||||
|
||||
Configuring the HertzBeat configuration file:
|
||||
### Configure the configuration file of HertzBeat
|
||||
|
||||
- Modify the configuration file located at `hertzbeat/config/application.yml`
|
||||
- **Docker Deployment:** ⚠️ When using a Docker container, the `application.yml` file must be mounted to the host machine
|
||||
- **Installation Package Deployment:** Extract the package and modify the configuration file located at `hertzbeat/config/application.yml`
|
||||
Modify the configuration file located at `hertzbeat/config/application.yml`
|
||||
Note ⚠️The docker container method needs to mount the application.yml file to the local host
|
||||
The installation package can be decompressed and modified in `hertzbeat/config/application.yml`
|
||||
|
||||
## 1. Configuring the SMS Sending Service
|
||||
1. Configure the SMS sending server
|
||||
|
||||
Only when you successfully configure your own SMS service will the alert SMS triggered within the monitoring system be sent correctly.
|
||||
HertzBeat provides two ways to configure the SMS service: modifying the `application.yml` configuration file directly or configuring it through the HertzBeat frontend interface (Settings > Message Server Setting).
|
||||
> Only when your own SMS server is successfully configured, the alarm SMS triggered in the monitoring tool will be sent normally.
|
||||
|
||||
> ⚠️ Note: Only one method can be effective at a time. If both methods are configured and enabled, HertzBeat will prioritize the SMS service configured in the frontend interface.
|
||||
Add the following Tencent platform SMS server configuration in `application.yml` (parameters need to be replaced with your SMS server configuration)
|
||||
|
||||
### 1.1 Tencent Cloud SMS Configuration
|
||||
```yaml
|
||||
common:
|
||||
sms:
|
||||
tencent:
|
||||
secret-id: AKIDbQ4VhdMr89wDedFrIcgU2PaaMvOuBCzY
|
||||
secret-key: PaXGl0ziY9UcWFjUyiFlCPMr77rLkJYlyA
|
||||
app-id: 1435441637
|
||||
sign-name: XX Technology
|
||||
template-id: 1343434
|
||||
```
|
||||
|
||||
Add the following Tencent Cloud SMS server configuration to `application.yml` (replace parameters with your own SMS server configuration):
|
||||
2. Configure alarm custom parameters
|
||||
|
||||
```yaml
|
||||
alerter:
|
||||
sms:
|
||||
enable: true # Whether to enable
|
||||
type: tencent # SMS provider type, supports "tencent"
|
||||
tencent: # Tencent Cloud SMS configuration
|
||||
secret-id: AKIDbQ4VhdMr89wDedFrIcgU2PaaMvOuBCzY
|
||||
secret-key: PaXGl0ziY9UcWFjUyiFlCPMr77rLkJYlyA
|
||||
app-id: 1435441637
|
||||
sign-name: HertzBeat
|
||||
template-id: 1343434
|
||||
```
|
||||
```yaml
|
||||
alerter:
|
||||
# Custom console address
|
||||
console-url: https://console.tancloud.io
|
||||
```
|
||||
|
||||
1. Create a signature (sign-name) in Tencent Cloud SMS
|
||||

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

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

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

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

|
||||
|
||||
1. 腾讯云短信创建签名(sign-name)
|
||||

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

|
||||
|
||||

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

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

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

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

|
||||
2. 配置告警自定义参数
|
||||
|
||||
## 2. 配置告警自定义参数
|
||||
```yaml
|
||||
alerter:
|
||||
# 自定义控制台地址
|
||||
console-url: https://console.tancloud.io
|
||||
```
|
||||
|
||||
```yaml
|
||||
alerter:
|
||||
# 自定义控制台地址
|
||||
console-url: https://console.tancloud.io
|
||||
```
|
||||
3. 使用外置redis代替内存存储实时指标数据
|
||||
|
||||
## 3. 使用外置redis代替内存存储实时指标数据
|
||||
> 默认我们的指标实时数据存储在内存中,可以配置如下来使用redis代替内存存储。
|
||||
|
||||
> 默认我们的指标实时数据存储在内存中,可以配置如下来使用redis代替内存存储。
|
||||
注意⚠️ `memory.enabled: false, redis.enabled: true`
|
||||
|
||||
注意⚠️ `memory.enabled: false, redis.enabled: true`
|
||||
|
||||
```yaml
|
||||
warehouse:
|
||||
store:
|
||||
memory:
|
||||
enabled: false
|
||||
init-size: 1024
|
||||
redis:
|
||||
enabled: true
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
password: 123456
|
||||
```
|
||||
```yaml
|
||||
warehouse:
|
||||
store:
|
||||
memory:
|
||||
enabled: false
|
||||
init-size: 1024
|
||||
redis:
|
||||
enabled: true
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
password: 123456
|
||||
```
|
||||
|
||||
@@ -235,6 +235,8 @@ The text of each license is the standard Apache 2.0 license.
|
||||
https://mvnrepository.com/artifact/com.squareup.okio/okio-jvm/3.6.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/com.squareup.retrofit2/converter-moshi/2.9.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/com.squareup.retrofit2/retrofit/2.9.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/com.tencentcloudapi/tencentcloud-sdk-java-common/3.1.648 Apache-2.0
|
||||
https://mvnrepository.com/artifact/com.tencentcloudapi/tencentcloud-sdk-java-sms/3.1.648 Apache-2.0
|
||||
https://mvnrepository.com/artifact/com.usthe.sureness/spring-boot3-starter-sureness/1.1.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/com.usthe.sureness/sureness-core/1.1.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/com.zaxxer/HikariCP/5.0.1 Apache-2.0
|
||||
@@ -350,9 +352,8 @@ The text of each license is the standard Apache 2.0 license.
|
||||
https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-remoting/4.9.4 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-srvutil/4.9.4 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-tools/4.9.4 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.sshd/sshd-common/2.13.1 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.sshd/sshd-core/2.13.1 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.sshd/sshd-sftp/2.13.1 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.sshd/sshd-common/2.8.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.sshd/sshd-core/2.8.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-el/10.1.19 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.19 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.xmlbeans/xmlbeans/3.1.0 Apache-2.0
|
||||
|
||||
@@ -235,6 +235,8 @@ The text of each license is the standard Apache 2.0 license.
|
||||
https://mvnrepository.com/artifact/com.squareup.okio/okio-jvm/3.6.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/com.squareup.retrofit2/converter-moshi/2.9.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/com.squareup.retrofit2/retrofit/2.9.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/com.tencentcloudapi/tencentcloud-sdk-java-common/3.1.648 Apache-2.0
|
||||
https://mvnrepository.com/artifact/com.tencentcloudapi/tencentcloud-sdk-java-sms/3.1.648 Apache-2.0
|
||||
https://mvnrepository.com/artifact/com.usthe.sureness/spring-boot3-starter-sureness/1.1.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/com.usthe.sureness/sureness-core/1.1.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/com.zaxxer/HikariCP/5.0.1 Apache-2.0
|
||||
@@ -350,9 +352,8 @@ The text of each license is the standard Apache 2.0 license.
|
||||
https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-remoting/4.9.4 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-srvutil/4.9.4 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-tools/4.9.4 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.sshd/sshd-common/2.13.1 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.sshd/sshd-core/2.13.1 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.sshd/sshd-sftp/2.13.1 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.sshd/sshd-common/2.8.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.sshd/sshd-core/2.8.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-el/10.1.19 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.19 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.xmlbeans/xmlbeans/3.1.0 Apache-2.0
|
||||
|
||||
@@ -284,9 +284,8 @@ The text of each license is the standard Apache 2.0 license.
|
||||
https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-remoting/4.9.4 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-srvutil/4.9.4 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-tools/4.9.4 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.sshd/sshd-common/2.13.1 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.sshd/sshd-core/2.13.1 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.sshd/sshd-sftp/2.13.1 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.sshd/sshd-common/2.8.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.sshd/sshd-core/2.8.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-el/10.1.19 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.19 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.plc4x/plc4j-api/0.12.0 Apache-2.0
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.4.2</version>
|
||||
<version>3.2.3</version>
|
||||
</parent>
|
||||
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
@@ -112,12 +112,14 @@
|
||||
|
||||
<springdoc.version>2.3.0</springdoc.version>
|
||||
<spring-boot-starter-sureness.version>1.1.0</spring-boot-starter-sureness.version>
|
||||
<javaparser.version>3.26.1</javaparser.version>
|
||||
|
||||
<nekohtml.version>1.9.22</nekohtml.version>
|
||||
<json-path.version>2.9.0</json-path.version>
|
||||
<gson.version>2.10.1</gson.version>
|
||||
<guava.version>32.1.2-jre</guava.version>
|
||||
<protobuf.version>3.25.5</protobuf.version>
|
||||
<tencentcloud-sdk-java-sms.version>3.1.648</tencentcloud-sdk-java-sms.version>
|
||||
<aliYun-sdk-java-sms.version>2.0.24</aliYun-sdk-java-sms.version>
|
||||
<caffeine.version>2.9.3</caffeine.version>
|
||||
<httpclient.version>4.5.14</httpclient.version>
|
||||
|
||||
@@ -168,11 +170,10 @@
|
||||
<influxdb.version>2.23</influxdb.version>
|
||||
<spring-cloud-starter-openfeign.version>3.0.5</spring-cloud-starter-openfeign.version>
|
||||
<taos-jdbcdriver.version>3.0.0</taos-jdbcdriver.version>
|
||||
<greptimedb.version>0.11.0</greptimedb.version>
|
||||
<greptimedb.version>0.9.1</greptimedb.version>
|
||||
<mysql-jdbcdriver.version>8.0.33</mysql-jdbcdriver.version>
|
||||
<arrow.version>18.1.0</arrow.version>
|
||||
<snappy-java.version>1.1.10.7</snappy-java.version>
|
||||
<sshd-sftp.version>2.13.1</sshd-sftp.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -420,6 +421,17 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- sms -->
|
||||
<dependency>
|
||||
<groupId>com.tencentcloudapi</groupId>
|
||||
<artifactId>tencentcloud-sdk-java-sms</artifactId>
|
||||
<version>${tencentcloud-sdk-java-sms.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>dysmsapi20170525</artifactId>
|
||||
<version>${aliYun-sdk-java-sms.version}</version>
|
||||
</dependency>
|
||||
<!-- okhttp -->
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
@@ -462,11 +474,6 @@
|
||||
<artifactId>arrow-memory-netty</artifactId>
|
||||
<version>${arrow.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.sshd</groupId>
|
||||
<artifactId>sshd-sftp</artifactId>
|
||||
<version>${sshd-sftp.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
@@ -202,28 +202,6 @@ alerter:
|
||||
# alert inhibit ttl unit ms, default 14400000(4 hours)
|
||||
inhibit:
|
||||
ttl: 14400000
|
||||
sms:
|
||||
enable: false
|
||||
type: tencent
|
||||
tencent:
|
||||
secret-id:
|
||||
secret-key:
|
||||
app-id:
|
||||
sign-name:
|
||||
template-id:
|
||||
alibaba:
|
||||
access-key-id:
|
||||
access-key-secret:
|
||||
sign-name:
|
||||
template-code:
|
||||
unisms:
|
||||
# auth-mode: simple or hmac
|
||||
auth-mode: simple
|
||||
access-key-id: YOUR_ACCESS_KEY_ID
|
||||
# hmac mode need to fill in access-key-secret
|
||||
access-key-secret: YOUR_ACCESS_KEY_SECRET
|
||||
signature: YOUR_SMS_SIGNATURE
|
||||
template-id: YOUR_TEMPLATE_ID
|
||||
|
||||
scheduler:
|
||||
server:
|
||||
|
||||
@@ -51,6 +51,11 @@
|
||||
<property name="message"
|
||||
value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
|
||||
</module>
|
||||
<module name="AvoidEscapedUnicodeCharacters">
|
||||
<property name="allowEscapesForControlCharacters" value="true"/>
|
||||
<property name="allowByTailComment" value="true"/>
|
||||
<property name="allowNonPrintableEscapes" value="true"/>
|
||||
</module>
|
||||
<module name="OneTopLevelClass"/>
|
||||
<module name="NoLineWrap">
|
||||
<property name="tokens" value="PACKAGE_DEF, IMPORT, STATIC_IMPORT"/>
|
||||
|
||||
@@ -169,28 +169,6 @@ alerter:
|
||||
# alert inhibit ttl unit ms, default 14400000(4 hours)
|
||||
inhibit:
|
||||
ttl: 14400000
|
||||
sms:
|
||||
enable: false
|
||||
type: tencent
|
||||
tencent:
|
||||
secret-id:
|
||||
secret-key:
|
||||
app-id:
|
||||
sign-name:
|
||||
template-id:
|
||||
alibaba:
|
||||
access-key-id:
|
||||
access-key-secret:
|
||||
sign-name:
|
||||
template-code:
|
||||
unisms:
|
||||
# auth-mode: simple or hmac
|
||||
auth-mode: simple
|
||||
access-key-id: YOUR_ACCESS_KEY_ID
|
||||
# hmac mode need to fill in access-key-secret
|
||||
access-key-secret: YOUR_ACCESS_KEY_SECRET
|
||||
signature: YOUR_SMS_SIGNATURE
|
||||
template-id: YOUR_TEMPLATE_ID
|
||||
|
||||
scheduler:
|
||||
server:
|
||||
|
||||
@@ -72,7 +72,6 @@ resourceRole:
|
||||
# eg: /api/v1/source3===get means /api/v1/source3===get can be access by anyone, no need auth.
|
||||
excludedResource:
|
||||
- /api/alerts/report/**===*
|
||||
- /api/alert/sse/**===*
|
||||
- /api/account/auth/**===*
|
||||
- /api/i18n/**===get
|
||||
- /api/apps/hierarchy===get
|
||||
@@ -80,7 +79,6 @@ excludedResource:
|
||||
- /api/status/page/public/**===*
|
||||
# web ui resource
|
||||
- /===get
|
||||
- /assets/**===get
|
||||
- /dashboard/**===get
|
||||
- /monitors/**===get
|
||||
- /alert/**===get
|
||||
|
||||
@@ -165,28 +165,6 @@ alerter:
|
||||
# alert inhibit ttl unit ms, default 14400000(4 hours)
|
||||
inhibit:
|
||||
ttl: 14400000
|
||||
sms:
|
||||
enable: false
|
||||
type: tencent
|
||||
tencent:
|
||||
secret-id:
|
||||
secret-key:
|
||||
app-id:
|
||||
sign-name:
|
||||
template-id:
|
||||
alibaba:
|
||||
access-key-id:
|
||||
access-key-secret:
|
||||
sign-name:
|
||||
template-code:
|
||||
unisms:
|
||||
# auth-mode: simple or hmac
|
||||
auth-mode: simple
|
||||
access-key-id: YOUR_ACCESS_KEY_ID
|
||||
# hmac mode need to fill in access-key-secret
|
||||
access-key-secret: YOUR_ACCESS_KEY_SECRET
|
||||
signature: YOUR_SMS_SIGNATURE
|
||||
template-id: YOUR_TEMPLATE_ID
|
||||
|
||||
scheduler:
|
||||
server:
|
||||
|
||||
@@ -72,7 +72,6 @@ resourceRole:
|
||||
# eg: /api/v1/source3===get means /api/v1/source3===get can be access by anyone, no need auth.
|
||||
excludedResource:
|
||||
- /api/alerts/report/**===*
|
||||
- /api/alert/sse/**===*
|
||||
- /api/account/auth/**===*
|
||||
- /api/i18n/**===get
|
||||
- /api/apps/hierarchy===get
|
||||
@@ -80,7 +79,6 @@ excludedResource:
|
||||
- /api/status/page/public/**===*
|
||||
# web ui resource
|
||||
- /===get
|
||||
- /assets/**===get
|
||||
- /dashboard/**===get
|
||||
- /monitors/**===get
|
||||
- /alert/**===get
|
||||
|
||||
@@ -163,28 +163,6 @@ alerter:
|
||||
# alert inhibit ttl unit ms, default 14400000(4 hours)
|
||||
inhibit:
|
||||
ttl: 14400000
|
||||
sms:
|
||||
enable: false
|
||||
type: tencent
|
||||
tencent:
|
||||
secret-id:
|
||||
secret-key:
|
||||
app-id:
|
||||
sign-name:
|
||||
template-id:
|
||||
alibaba:
|
||||
access-key-id:
|
||||
access-key-secret:
|
||||
sign-name:
|
||||
template-code:
|
||||
unisms:
|
||||
# auth-mode: simple or hmac
|
||||
auth-mode: simple
|
||||
access-key-id: YOUR_ACCESS_KEY_ID
|
||||
# hmac mode need to fill in access-key-secret
|
||||
access-key-secret: YOUR_ACCESS_KEY_SECRET
|
||||
signature: YOUR_SMS_SIGNATURE
|
||||
template-id: YOUR_TEMPLATE_ID
|
||||
|
||||
scheduler:
|
||||
server:
|
||||
|
||||
@@ -72,7 +72,6 @@ resourceRole:
|
||||
# eg: /api/v1/source3===get means /api/v1/source3===get can be access by anyone, no need auth.
|
||||
excludedResource:
|
||||
- /api/alerts/report/**===*
|
||||
- /api/alert/sse/**===*
|
||||
- /api/account/auth/**===*
|
||||
- /api/i18n/**===get
|
||||
- /api/apps/hierarchy===get
|
||||
@@ -80,7 +79,6 @@ excludedResource:
|
||||
- /api/status/page/public/**===*
|
||||
# web ui resource
|
||||
- /===get
|
||||
- /assets/**===get
|
||||
- /dashboard/**===get
|
||||
- /monitors/**===get
|
||||
- /alert/**===get
|
||||
|
||||
@@ -162,28 +162,6 @@ alerter:
|
||||
# alert inhibit ttl unit ms, default 14400000(4 hours)
|
||||
inhibit:
|
||||
ttl: 14400000
|
||||
sms:
|
||||
enable: false
|
||||
type: tencent
|
||||
tencent:
|
||||
secret-id:
|
||||
secret-key:
|
||||
app-id:
|
||||
sign-name:
|
||||
template-id:
|
||||
alibaba:
|
||||
access-key-id:
|
||||
access-key-secret:
|
||||
sign-name:
|
||||
template-code:
|
||||
unisms:
|
||||
# auth-mode: simple or hmac
|
||||
auth-mode: simple
|
||||
access-key-id: YOUR_ACCESS_KEY_ID
|
||||
# hmac mode need to fill in access-key-secret
|
||||
access-key-secret: YOUR_ACCESS_KEY_SECRET
|
||||
signature: YOUR_SMS_SIGNATURE
|
||||
template-id: YOUR_TEMPLATE_ID
|
||||
|
||||
scheduler:
|
||||
server:
|
||||
|
||||
@@ -72,7 +72,6 @@ resourceRole:
|
||||
# eg: /api/v1/source3===get means /api/v1/source3===get can be access by anyone, no need auth.
|
||||
excludedResource:
|
||||
- /api/alerts/report/**===*
|
||||
- /api/alert/sse/**===*
|
||||
- /api/account/auth/**===*
|
||||
- /api/i18n/**===get
|
||||
- /api/apps/hierarchy===get
|
||||
@@ -80,7 +79,6 @@ excludedResource:
|
||||
- /api/status/page/public/**===*
|
||||
# web ui resource
|
||||
- /===get
|
||||
- /assets/**===get
|
||||
- /dashboard/**===get
|
||||
- /monitors/**===get
|
||||
- /alert/**===get
|
||||
|
||||
@@ -72,7 +72,6 @@ resourceRole:
|
||||
# eg: /api/v1/source3===get means /api/v1/source3===get can be access by anyone, no need auth.
|
||||
excludedResource:
|
||||
- /api/alerts/report/**===*
|
||||
- /api/alert/sse/**===*
|
||||
- /api/account/auth/**===*
|
||||
- /api/i18n/**===get
|
||||
- /api/apps/hierarchy===get
|
||||
@@ -80,7 +79,6 @@ excludedResource:
|
||||
- /api/status/page/public/**===*
|
||||
# web ui resource
|
||||
- /===get
|
||||
- /assets/**===get
|
||||
- /dashboard/**===get
|
||||
- /monitors/**===get
|
||||
- /alert/**===get
|
||||
|
||||
@@ -81,11 +81,4 @@ describe('Service: I18n', () => {
|
||||
expect(lang).toBe('en-US');
|
||||
});
|
||||
});
|
||||
it('should be trigger notify when changed language', () => {
|
||||
genModule();
|
||||
srv.use('pt-BR', {});
|
||||
srv.change.subscribe(lang => {
|
||||
expect(lang).toBe('pt-BR');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,8 +2,6 @@ import { Platform } from '@angular/cdk/platform';
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
import { HttpHeaders } from '@angular/common/http';
|
||||
import ngEn from '@angular/common/locales/en';
|
||||
import ngJa from '@angular/common/locales/ja';
|
||||
import ngPt from '@angular/common/locales/pt';
|
||||
import ngZh from '@angular/common/locales/zh';
|
||||
import ngZhTw from '@angular/common/locales/zh-Hant';
|
||||
import { Injectable } from '@angular/core';
|
||||
@@ -14,20 +12,12 @@ import {
|
||||
en_US as delonEnUS,
|
||||
SettingsService,
|
||||
zh_CN as delonZhCn,
|
||||
zh_TW as delonZhTw,
|
||||
ja_JP as delonJaJP
|
||||
zh_TW as delonZhTw
|
||||
} from '@delon/theme';
|
||||
import { AlainConfigService } from '@delon/util/config';
|
||||
import { enUS as dfEn, zhCN as dfZhCn, zhTW as dfZhTw, ja as dfJa, ptBR as dfPtBR } from 'date-fns/locale';
|
||||
import { enUS as dfEn, zhCN as dfZhCn, zhTW as dfZhTw } from 'date-fns/locale';
|
||||
import { NzSafeAny } from 'ng-zorro-antd/core/types';
|
||||
import {
|
||||
en_US as zorroEnUS,
|
||||
NzI18nService,
|
||||
zh_CN as zorroZhCN,
|
||||
zh_TW as zorroZhTW,
|
||||
ja_JP as zorroJaJP,
|
||||
pt_BR as zorroPtBR
|
||||
} from 'ng-zorro-antd/i18n';
|
||||
import { en_US as zorroEnUS, NzI18nService, zh_CN as zorroZhCN, zh_TW as zorroZhTW } from 'ng-zorro-antd/i18n';
|
||||
import { Observable, zip } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@@ -67,22 +57,6 @@ const LANGS: { [key: string]: LangConfigData } = {
|
||||
date: dfZhTw,
|
||||
delon: delonZhTw,
|
||||
abbr: '🇭🇰'
|
||||
},
|
||||
'ja-JP': {
|
||||
text: '日本語',
|
||||
ng: ngJa,
|
||||
zorro: zorroJaJP,
|
||||
date: dfJa,
|
||||
delon: delonJaJP,
|
||||
abbr: '🇯🇵'
|
||||
},
|
||||
'pt-BR': {
|
||||
text: 'Português (Brasil)',
|
||||
ng: ngPt,
|
||||
zorro: zorroPtBR,
|
||||
date: dfPtBR,
|
||||
delon: delonEnUS, // Usando en-US como fallback (ou crie um locale personalizado)
|
||||
abbr: '🇧🇷'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ import { GeneralConfigService } from '../../../service/general-config.service';
|
||||
ngClass="alain-default__nav-item-icon"
|
||||
(click)="toggleMute($event)"
|
||||
nz-tooltip
|
||||
[nzTooltipTitle]="(mute.mute ? 'common.unmute' : 'common.mute') | i18n"
|
||||
[nzTooltipTitle]="'common.mute' | i18n"
|
||||
></i>
|
||||
</nz-badge>
|
||||
</div>
|
||||
@@ -163,9 +163,6 @@ export class HeaderNotifyComponent implements OnInit, OnDestroy {
|
||||
if (this.refreshInterval) {
|
||||
clearInterval(this.refreshInterval);
|
||||
}
|
||||
if (this.eventSource) {
|
||||
this.eventSource.close();
|
||||
}
|
||||
}
|
||||
|
||||
onPopoverVisibleChange(visible: boolean): void {
|
||||
@@ -231,6 +228,24 @@ export class HeaderNotifyComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
updateAlertsStatus(alertIds: Set<number>, status: string) {
|
||||
const markAlertsStatus$ = this.alertSvc.applyGroupAlertsStatus(alertIds, status).subscribe(
|
||||
message => {
|
||||
markAlertsStatus$.unsubscribe();
|
||||
if (message.code === 0) {
|
||||
this.notifySvc.success(this.i18nSvc.fanyi('common.notify.mark-success'), '');
|
||||
this.loadData();
|
||||
} else {
|
||||
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.mark-fail'), message.msg);
|
||||
}
|
||||
},
|
||||
error => {
|
||||
markAlertsStatus$.unsubscribe();
|
||||
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.mark-fail'), error.msg);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
gotoAlertCenter(): void {
|
||||
this.popoverVisible = false;
|
||||
this.router.navigateByUrl(`/alert/center`);
|
||||
@@ -299,7 +314,7 @@ export class HeaderNotifyComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
this.eventSource.onerror = error => {
|
||||
console.error('SSE connection error:', error);
|
||||
this.eventSource.close();
|
||||
setTimeout(() => this.initSSEConnection(), 3000);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,9 @@
|
||||
*/
|
||||
|
||||
export class AlibabaSmsConfig {
|
||||
accessKeyId!: string;
|
||||
accessKeySecret!: string;
|
||||
secretId!: string;
|
||||
secretKey!: string;
|
||||
signName!: string;
|
||||
templateCode!: string;
|
||||
appId!: string;
|
||||
templateId!: string;
|
||||
}
|
||||
|
||||
@@ -19,16 +19,13 @@
|
||||
|
||||
import { AlibabaSmsConfig } from './AlibabaSmsConfig';
|
||||
import { TencentSmsConfig } from './TencentSmsConfig';
|
||||
import { UniSmsConfig } from './UniSmsConfig';
|
||||
import { SmsType } from './enums/sms-type.enum';
|
||||
|
||||
export class SmsNoticeSender {
|
||||
id!: number;
|
||||
type: SmsType = SmsType.TENCENT;
|
||||
tencent: TencentSmsConfig = new TencentSmsConfig();
|
||||
alibaba: AlibabaSmsConfig = new AlibabaSmsConfig();
|
||||
unisms: UniSmsConfig = new UniSmsConfig();
|
||||
enable: boolean = false;
|
||||
type!: string;
|
||||
tencent!: TencentSmsConfig;
|
||||
alibaba!: AlibabaSmsConfig;
|
||||
enable!: boolean;
|
||||
creator!: string;
|
||||
modifier!: string;
|
||||
gmtCreate!: number;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user