Compare commits

..
Author SHA1 Message Date
tomsun28 bf21538062 [improve] common metric data query design
Signed-off-by: tomsun28 <tomsun28@outlook.com>
2025-02-28 16:36:19 +08:00
86 changed files with 677 additions and 1745 deletions
-27
View File
@@ -2298,33 +2298,6 @@
"contributions": [
"code"
]
},
{
"login": "yasminvo",
"name": "yasminvo",
"avatar_url": "https://avatars.githubusercontent.com/u/107528848?v=4",
"profile": "https://github.com/yasminvo",
"contributions": [
"code"
]
},
{
"login": "notbugggg",
"name": "不关银渐层的事哦",
"avatar_url": "https://avatars.githubusercontent.com/u/147966331?v=4",
"profile": "https://github.com/notbugggg",
"contributions": [
"code"
]
},
{
"login": "yyahang",
"name": "yyahang",
"avatar_url": "https://avatars.githubusercontent.com/u/90464876?v=4",
"profile": "https://github.com/yyahang",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
+2 -5
View File
@@ -12,7 +12,7 @@
[![Discord](https://img.shields.io/badge/Chat-Discord-7289DA?logo=discord)](https://discord.gg/Fb6M73htGr)
[![Reddit](https://img.shields.io/badge/Reddit-Community-7289DA?logo=reddit)](https://www.reddit.com/r/hertzbeat/)
[![Twitter](https://img.shields.io/twitter/follow/hertzbeat1024?logo=twitter)](https://x.com/hertzbeat1024)
[![Twitter](https://img.shields.io/twitter/follow/hertzbeat1024?logo=twitter)](https://twitter.com/hertzbeat1024)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8139/badge)](https://www.bestpractices.dev/projects/8139)
[![codecov](https://codecov.io/gh/apache/HertzBeat/branch/master/graph/badge.svg)](https://app.codecov.io/gh/apache/hertzbeat)
[![Docker Pulls](https://img.shields.io/docker/pulls/apache/hertzbeat?style=%20for-the-badge&logo=docker&label=DockerHub%20Download)](https://hub.docker.com/r/apache/hertzbeat)
@@ -507,9 +507,6 @@ Thanks to these wonderful people, welcome to join us:
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/myangle1120"><img src="https://avatars.githubusercontent.com/u/19237013?v=4?s=100" width="100px;" alt="myangle1120"/><br /><sub><b>myangle1120</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=myangle1120" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yasminvo"><img src="https://avatars.githubusercontent.com/u/107528848?v=4?s=100" width="100px;" alt="yasminvo"/><br /><sub><b>yasminvo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yasminvo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yyahang"><img src="https://avatars.githubusercontent.com/u/90464876?v=4?s=100" width="100px;" alt="yyahang"/><br /><sub><b>yyahang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yyahang" title="Code">💻</a></td>
</tr>
</tbody>
</table>
@@ -537,7 +534,7 @@ WeChat Public : Search ID `usthecom`.
[Reddit Community](https://www.reddit.com/r/hertzbeat/)
[Follow Us Twitter](https://x.com/hertzbeat1024)
[Follow Us Twitter](https://twitter.com/hertzbeat1024)
[Subscribe YouTube](https://www.youtube.com/channel/UCri75zfWX0GHqJFPENEbLow)
+2 -5
View File
@@ -12,7 +12,7 @@
[![Discord](https://img.shields.io/badge/Chat-Discord-7289DA?logo=discord)](https://discord.gg/Fb6M73htGr)
[![Reddit](https://img.shields.io/badge/Reddit-Community-7289DA?logo=reddit)](https://www.reddit.com/r/hertzbeat/)
[![Twitter](https://img.shields.io/twitter/follow/hertzbeat1024?logo=twitter)](https://x.com/hertzbeat1024)
[![Twitter](https://img.shields.io/twitter/follow/hertzbeat1024?logo=twitter)](https://twitter.com/hertzbeat1024)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8139/badge)](https://www.bestpractices.dev/projects/8139)
[![codecov](https://codecov.io/gh/apache/HertzBeat/branch/master/graph/badge.svg)](https://app.codecov.io/gh/apache/hertzbeat)
[![Docker Pulls](https://img.shields.io/docker/pulls/apache/hertzbeat?style=%20for-the-badge&logo=docker&label=DockerHub%20Download)](https://hub.docker.com/r/apache/hertzbeat)
@@ -506,9 +506,6 @@ Thanks these wonderful people, welcome to join us:
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/myangle1120"><img src="https://avatars.githubusercontent.com/u/19237013?v=4?s=100" width="100px;" alt="myangle1120"/><br /><sub><b>myangle1120</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=myangle1120" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yasminvo"><img src="https://avatars.githubusercontent.com/u/107528848?v=4?s=100" width="100px;" alt="yasminvo"/><br /><sub><b>yasminvo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yasminvo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yyahang"><img src="https://avatars.githubusercontent.com/u/90464876?v=4?s=100" width="100px;" alt="yyahang"/><br /><sub><b>yyahang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yyahang" title="Code">💻</a></td>
</tr>
</tbody>
</table>
@@ -536,7 +533,7 @@ Thanks these wonderful people, welcome to join us:
[Reddit Community](https://www.reddit.com/r/hertzbeat/)
[Follow Us Twitter](https://x.com/hertzbeat1024)
[Follow Us Twitter](https://twitter.com/hertzbeat1024)
[Subscribe YouTube](https://www.youtube.com/channel/UCri75zfWX0GHqJFPENEbLow)
@@ -54,8 +54,4 @@ public class SmsConfig {
*/
private UniSmsProperties unisms;
/**
* Smslocal SMS configuration
*/
private SmslocalSmsProperties smslocal;
}
@@ -1,33 +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;
/**
* Smslocal SMS Properties
*/
@Data
public class SmslocalSmsProperties {
/**
* SmsLocal account api key
*/
private String apiKey;
}
@@ -20,7 +20,6 @@ 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.SmsLocalSmsClientImpl;
import org.apache.hertzbeat.alert.service.impl.TencentSmsClientImpl;
import org.apache.hertzbeat.alert.service.impl.UniSmsClientImpl;
import org.apache.hertzbeat.alert.service.impl.AlibabaSmsClientImpl;
@@ -34,7 +33,6 @@ 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;
import static org.apache.hertzbeat.common.constants.SmsConstants.SMSLOCAL;
/**
* SMS client factory
@@ -51,7 +49,9 @@ public class SmsClientFactory {
private volatile SmsClient currentSmsClient;
public SmsClientFactory(GeneralConfigDao generalConfigDao, ObjectMapper objectMapper, SmsConfig yamlSmsConfig) {
public SmsClientFactory(GeneralConfigDao generalConfigDao,
ObjectMapper objectMapper,
SmsConfig yamlSmsConfig) {
this.generalConfigDao = generalConfigDao;
this.objectMapper = objectMapper;
this.yamlSmsConfig = yamlSmsConfig;
@@ -133,9 +133,6 @@ public class SmsClientFactory {
case ALIBABA:
currentSmsClient = new AlibabaSmsClientImpl(smsConfig.getAlibaba());
break;
case SMSLOCAL:
currentSmsClient = new SmsLocalSmsClientImpl(smsConfig.getSmslocal());
break;
default:
log.warn("[SmsClientFactory] Unsupported SMS provider type: {}", smsConfig.getType());
break;
@@ -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.alert.service.impl;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.config.SmslocalSmsProperties;
import org.apache.hertzbeat.alert.service.SmsClient;
import org.apache.hertzbeat.common.constants.SmsConstants;
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 java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* Smslocal SMS Client Implement
*/
@Slf4j
public class SmsLocalSmsClientImpl implements SmsClient {
private static final String HOST = "secure.smslocal.com";
private static final String PATH = "/api/service/enterprise-service/external/sms";
private static final String FROM = "Hertzbeat";
private static final String SUCCESS_CODE = "200";
private final SmslocalSmsProperties config;
public SmsLocalSmsClientImpl(SmslocalSmsProperties smslocalSmsProperties) {
this.config = smslocalSmsProperties;
}
@Override
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
if (Objects.isNull(receiver) || Objects.isNull(alert)) {
log.warn("receiver and alert can not be null! receiver: {}, alert:{}", receiver, alert);
return;
}
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
String content = alert.getCommonAnnotations().get("summary");
if (Objects.isNull(content) || Objects.isNull(alert.getCommonAnnotations().get("description"))) {
content = alert.getAlerts().get(0).getContent();
}
SmsMessage smsMessage = new SmsMessage(FROM, receiver.getPhone(), content);
String payload = JsonUtil.toJson(smsMessage);
HttpPost httpPost = new HttpPost("https://" + HOST + PATH);
httpPost.setHeader("Content-Type", "application/json; charset=utf-8");
httpPost.setHeader("Token", config.getApiKey());
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 jsonNode = jsonResponse.get(0);
if (Objects.isNull(jsonNode)) {
log.warn("jsonResponse parse errorCode failed: {}", jsonResponse);
return;
}
String errorCode = jsonNode.get("errorCode").asText();
if (!SUCCESS_CODE.equals(errorCode)) {
String msgid = jsonNode.get("id").asText();
throw new SendMessageException(errorCode + ":" + msgid);
}
log.info("Successfully sent SMS to phone: {}", receiver.getPhone());
}
} catch (Exception e) {
log.error("Failed to send SMS: {}", e.getMessage());
throw new SendMessageException(e.getMessage());
}
}
@Override
public String getType() {
return SmsConstants.SMSLOCAL;
}
@Override
public boolean checkConfig() {
if (Objects.isNull(config) || Objects.isNull(config.getApiKey()) || config.getApiKey().isBlank()) {
log.warn("smslocal properties can not be null: {}", config);
return false;
}
return true;
}
@Getter
@Setter
private static class SmsMessage {
String from;
String to;
String content;
final int datacoding = 0;
final String direction = "mt";
public SmsMessage(String from, String to, String content) {
this.from = from;
this.to = to;
this.content = content;
}
}
}
@@ -88,7 +88,6 @@ public class JdbcCommonCollect extends AbstractCollect {
SshTunnel sshTunnel = jdbcProtocol.getSshTunnel();
int timeout = CollectUtil.getTimeout(jdbcProtocol.getTimeout());
boolean reuseConnection = Boolean.parseBoolean(jdbcProtocol.getReuseConnection());
Statement statement = null;
String databaseUrl;
try {
@@ -100,7 +99,7 @@ public class JdbcCommonCollect extends AbstractCollect {
}
statement = getConnection(jdbcProtocol.getUsername(),
jdbcProtocol.getPassword(), databaseUrl, timeout, reuseConnection);
jdbcProtocol.getPassword(), databaseUrl, timeout);
switch (jdbcProtocol.getQueryType()) {
case QUERY_TYPE_ONE_ROW -> queryOneRow(statement, jdbcProtocol.getSql(), metrics.getAliasFields(), builder, startTime);
case QUERY_TYPE_MULTI_ROW -> queryMultiRow(statement, jdbcProtocol.getSql(), metrics.getAliasFields(), builder, startTime);
@@ -143,20 +142,11 @@ public class JdbcCommonCollect extends AbstractCollect {
builder.setMsg("Query Error: " + errorMessage);
} finally {
if (statement != null) {
Connection connection = null;
try {
connection = statement.getConnection();
statement.close();
} catch (Exception e) {
log.error("Jdbc close statement error: {}", e.getMessage());
}
try {
if (!reuseConnection && connection != null) {
connection.close();
}
} catch (Exception e) {
log.error("Jdbc close connection error: {}", e.getMessage());
}
}
}
}
@@ -167,40 +157,38 @@ public class JdbcCommonCollect extends AbstractCollect {
}
private Statement getConnection(String username, String password, String url, Integer timeout, boolean reuseConnection) throws Exception {
private Statement getConnection(String username, String password, String url, Integer timeout) throws Exception {
CacheIdentifier identifier = CacheIdentifier.builder()
.ip(url)
.username(username).password(password).build();
Optional<AbstractConnection<?>> cacheOption = connectionCommonCache.getCache(identifier, true);
Statement statement = null;
if (reuseConnection) {
Optional<AbstractConnection<?>> cacheOption = connectionCommonCache.getCache(identifier, true);
if (cacheOption.isPresent()) {
JdbcConnect jdbcConnect = (JdbcConnect) cacheOption.get();
if (cacheOption.isPresent()) {
JdbcConnect jdbcConnect = (JdbcConnect) cacheOption.get();
try {
statement = jdbcConnect.getConnection().createStatement();
// set query timeout
int timeoutSecond = timeout / 1000;
timeoutSecond = timeoutSecond <= 0 ? 1 : timeoutSecond;
statement.setQueryTimeout(timeoutSecond);
// set query max row number
statement.setMaxRows(1000);
} catch (Exception e) {
log.info("The jdbc connect from cache, create statement error: {}", e.getMessage());
try {
statement = jdbcConnect.getConnection().createStatement();
// set query timeout
int timeoutSecond = timeout / 1000;
timeoutSecond = timeoutSecond <= 0 ? 1 : timeoutSecond;
statement.setQueryTimeout(timeoutSecond);
// set query max row number
statement.setMaxRows(1000);
} catch (Exception e) {
log.info("The jdbc connect from cache, create statement error: {}", e.getMessage());
try {
if (statement != null) {
statement.close();
}
jdbcConnect.close();
} catch (Exception e2) {
log.error(e2.getMessage());
if (statement != null) {
statement.close();
}
statement = null;
connectionCommonCache.removeCache(identifier);
jdbcConnect.close();
} catch (Exception e2) {
log.error(e2.getMessage());
}
statement = null;
connectionCommonCache.removeCache(identifier);
}
if (statement != null) {
return statement;
}
}
if (statement != null) {
return statement;
}
// renew connection when failed
Connection connection = DriverManager.getConnection(url, username, password);
@@ -210,10 +198,8 @@ public class JdbcCommonCollect extends AbstractCollect {
timeoutSecond = timeoutSecond <= 0 ? 1 : timeoutSecond;
statement.setQueryTimeout(timeoutSecond);
statement.setMaxRows(1000);
if (reuseConnection) {
JdbcConnect jdbcConnect = new JdbcConnect(connection);
connectionCommonCache.addCache(identifier, jdbcConnect);
}
JdbcConnect jdbcConnect = new JdbcConnect(connection);
connectionCommonCache.addCache(identifier, jdbcConnect);
return statement;
}
@@ -84,8 +84,6 @@ public class SnmpCollectImpl extends AbstractCollect {
+ "{1,choice,0#|1#1 hour, |1<{1,number,integer} hours, }"
+ "{2,choice,0#|1#1 minute, |1<{2,number,integer} minutes, }"
+ "{3,choice,0#|1#1 second, |1<{3,number,integer} seconds }";
private static final String DATE_AND_TIME_PATTERN = "%04d-%02d-%02d,%02d:%02d:%02d.%d";
private static final String TIME_ZONE_PATTERN = "%c%02d:%02d";
private final Map<Integer, Snmp> versionSnmpService = new ConcurrentHashMap<>(3);
@@ -307,9 +305,6 @@ public class SnmpCollectImpl extends AbstractCollect {
CharBuffer res = decoder.decode(ByteBuffer.wrap(bytes));
return res.toString();
} catch (Exception e) {
if (isDateAndTimeOctetString(binding)) {
return parseDateAndTime(clearHexStr);
}
return new String(bytes);
}
} catch (Exception e) {
@@ -320,68 +315,6 @@ public class SnmpCollectImpl extends AbstractCollect {
}
}
private static boolean isDateAndTimeOctetString(VariableBinding binding) {
if (!(binding.getVariable() instanceof OctetString)) {
return false;
}
byte[] bytes = HexFormat.of().parseHex(binding.toValueString().replaceAll(HEX_SPLIT, ""));
if (bytes.length != 8 && bytes.length != 11) return false;
int year = ((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF);
if (year < 1970 || year > 3000) return false;
int month = bytes[2] & 0xFF;
if (month < 1 || month > 12) return false;
int day = bytes[3] & 0xFF;
if (day < 1 || day > 31) return false;
int hour = bytes[4] & 0xFF;
if (hour > 23) return false;
int minute = bytes[5] & 0xFF;
if (minute > 59) return false;
int second = bytes[6] & 0xFF;
if (second > 59) return false;
int deciSecond = bytes[7] & 0xFF;
if (deciSecond > 99) return false;
if (bytes.length == 11) {
int tzSign = bytes[8] & 0xFF;
if (tzSign != 0x2B && tzSign != 0x2D) return false;
int tzHour = bytes[9] & 0xFF;
if (tzHour > 23) return false;
int tzMinute = bytes[10] & 0xFF;
if (tzMinute > 59) return false;
}
return true;
}
private static String parseDateAndTime(String hexString) {
byte[] bytes = HexFormat.of().parseHex(hexString);
int year = ((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF);
int month = bytes[2] & 0xFF;
int day = bytes[3] & 0xFF;
int hour = bytes[4] & 0xFF;
int minute = bytes[5] & 0xFF;
int second = bytes[6] & 0xFF;
int deciSeconds = bytes[7] & 0xFF;
String dateTime = String.format(DATE_AND_TIME_PATTERN,
year, month, day, hour, minute, second, deciSeconds);
if (bytes.length == 11) {
char sign = (char) bytes[8];
int tzHour = bytes[9] & 0xFF;
int tzMinute = bytes[10] & 0xFF;
dateTime += String.format(" " + TIME_ZONE_PATTERN, sign, tzHour, tzMinute);
}
return dateTime;
}
private void snmpClose(Snmp snmp, int version) throws IOException {
snmp.close();
versionSnmpService.remove(version);
@@ -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.constants;
/**
* Import/Export task constants
*/
public interface ImExportTaskConstant {
/**
* If the number of tasks exceeds 100, progress information will broadcast
*/
Integer IMPORT_TASK_PROCESS_THRESHOLD = 100;
}
@@ -1,39 +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;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
/**
* Manager Event Type Enum
*/
@Getter
@AllArgsConstructor
@ToString
public enum ManagerEventTypeEnum {
/**
* IMPORT_TASK_EVENT
*/
IMPORT_TASK_EVENT("IMPORT_TASK_EVENT");
private final String value;
}
@@ -1,40 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.common.constants;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
/**
* Notify Level Enum
*/
@Getter
@AllArgsConstructor
@ToString
public enum NotifyLevelEnum {
SUCCESS("SUCCESS"),
ERROR("ERROR"),
INFO("INFO"),
WARNING("WARNING"),
BLANK("BLANK");
private final String value;
}
@@ -23,13 +23,10 @@ package org.apache.hertzbeat.common.constants;
public interface SmsConstants {
// Tencent cloud SMS
String TENCENT = "tencent";
// Alibaba Cloud SMS
String ALIBABA = "alibaba";
// UniSMS
String UNISMS = "unisms";
// Smslocal SMS
String SMSLOCAL = "smslocal";
}
@@ -63,7 +63,6 @@ public class GroupAlert {
private Long id;
@Schema(title = "Group Key", example = "HighCPUUsage{alertname=\"HighCPUUsage\", instance=\"server1\"}")
@Column(length = 2048)
private String groupKey;
@Schema(title = "Status", example = "resolved")
@@ -59,9 +59,8 @@ public class SingleAlert {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Schema(title = "Threshold Id", example = "87584674384", accessMode = READ_ONLY)
private Long id;
@Schema(title = "Fingerprint", example = "alertname:demo")
@Column(length = 2048)
@Schema(title = "Fingerprint", example = "dxsdfdsf")
private String fingerprint;
@Schema(title = "Labels", example = "{\"alertname\": \"HighCPUUsage\", \"priority\": \"critical\", \"instance\": \"343483943\"}")
@@ -75,7 +74,6 @@ public class SingleAlert {
private Map<String, String> annotations;
@Schema(title = "Content", example = "CPU usage is above 80% for the last 5 minutes on instance server1.example.com.")
@Column(length = 4096)
private String content;
@Schema(title = "Status", example = "firing|resolved")
@@ -1,79 +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;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.hertzbeat.common.constants.ImportTaskStatusEnum;
import org.apache.hertzbeat.common.constants.ManagerEventTypeEnum;
import org.apache.hertzbeat.common.constants.NotifyLevelEnum;
import org.springframework.lang.Nullable;
/**
* Import task message
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class ImportTaskMessage extends ManagerMessage {
/**
* Task name
*/
@NotNull
private String taskName;
/**
* Progress, expressed as a percentage
*/
@Nullable
private Integer progress;
/**
* Task Status,
* @see ImportTaskStatusEnum
*/
@NotNull
private String status;
/**
* If Fail, the error message
*/
@Nullable
private String errMsg;
public ImportTaskMessage(String notifyLevel, String managerEventType, String taskName, @Nullable Integer progress, String status, @Nullable String errMsg){
super(notifyLevel, managerEventType);
this.taskName = taskName;
this.progress = progress;
this.status = status;
this.errMsg = errMsg;
}
public static ManagerMessage createInProgressMessage(String taskName, Integer process){
return new ImportTaskMessage(NotifyLevelEnum.INFO.getValue(), ManagerEventTypeEnum.IMPORT_TASK_EVENT.getValue(), taskName, process, ImportTaskStatusEnum.IN_PROGRESS.getValue(), null);
}
public static ManagerMessage createCompletedMessage(String taskName){
return new ImportTaskMessage(NotifyLevelEnum.SUCCESS.getValue(), ManagerEventTypeEnum.IMPORT_TASK_EVENT.getValue(), taskName, null, ImportTaskStatusEnum.COMPLETED.getValue(), null);
}
public static ManagerMessage createFailedMessage(String taskName, String errMsg){
return new ImportTaskMessage(NotifyLevelEnum.ERROR.getValue(), ManagerEventTypeEnum.IMPORT_TASK_EVENT.getValue(), taskName, null, ImportTaskStatusEnum.FAILED.getValue(), errMsg);
}
}
@@ -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.common.entity.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Manager Message Entity
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ManagerMessage {
/**
* Notify Level
* @see org.apache.hertzbeat.common.constants.NotifyLevelEnum
*/
private String notifyLevel;
/**
* Manager Event Type
* @see org.apache.hertzbeat.common.constants.ManagerEventTypeEnum
*/
private String managerEventType;
}
@@ -0,0 +1,81 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.common.entity.dto.query;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Metric History Range Query Data
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "Metric Query Data")
public class MetricQueryData {
@Schema(title = "Metric Schema")
private MetricSchema schema;
@Schema(title = "metrics row values, first is the timestamp-ts", example = "[[29,32,44],[32,34,true]]")
private List<List<Object>> values;
/**
* Metric Schema
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static final class MetricSchema {
@Schema(title = "Metrics Field")
private List<MetricField> fields;
@Schema(title = "Meta Information")
private Map<String, String> meta;
}
/**
* Metric Field
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static final class MetricField {
@Schema(title = "Metric Field Name")
private String name;
@Schema(title = "Field Type: number, string, time, bool")
private String type;
@Schema(title = "Field Unit: %, Mb, Kbps etc.")
private String unit;
@Schema(title = "Whether is a label")
private Boolean label;
}
}
@@ -71,10 +71,7 @@ public class JdbcProtocol implements CommonRequestProtocol, Protocol {
* DATABASE LINK URL eg: jdbc:mysql://localhost:3306/usthe
*/
private String url;
/**
* reuse connection session
*/
private String reuseConnection = "true";
/**
* ssh tunnel
*/
@@ -567,13 +567,6 @@ public final class CollectRep {
return new Builder();
}
public boolean equals(Object o) {
if(!(o instanceof Field))
return false;
Field other = (Field)o;
return other.name.equals(this.name);
}
public static class Builder {
private final Field instance;
@@ -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.common.entity.message;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Test case for {@link CollectRep}
*/
public class CollectRepTest {
@ParameterizedTest
@CsvSource(value = {
"name, name, true",
"name1, name3, false",
})
void testFieldEquals(String name1, String name2, boolean result) {
CollectRep.Field field1 = new CollectRep.Field();
field1.setName(name1);
CollectRep.Field field2 = new CollectRep.Field();
field2.setName(name2);
assertEquals(field1.equals(field2), result);
}
}
@@ -1,88 +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.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.constants.ManagerEventTypeEnum;
import org.apache.hertzbeat.common.entity.dto.ImportTaskMessage;
import org.apache.hertzbeat.common.entity.dto.ManagerMessage;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Manager SSE
*/
@Slf4j
@Component
public class ManagerSseManager {
private final Map<Long, SseEmitter> emitters = new ConcurrentHashMap<>();
public SseEmitter createEmitter(Long clientId) {
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
emitter.onCompletion(() -> removeEmitter(clientId));
emitter.onTimeout(() -> removeEmitter(clientId));
emitters.put(clientId, emitter);
return emitter;
}
@Async
public void broadcast(String eventName, String data) {
emitters.forEach((clientId, emitter) -> {
try {
emitter.send(SseEmitter.event()
.id(String.valueOf(System.currentTimeMillis()))
.name(eventName)
.data(data));
} catch (IOException | IllegalStateException e) {
emitter.complete();
removeEmitter(clientId);
} catch (Exception exception) {
log.error("Failed to broadcast manager message data to client: {}", exception.getMessage());
emitter.complete();
removeEmitter(clientId);
}
});
}
public void broadcastImportTaskInProgress(String taskName, Integer progress){
ManagerMessage managerMessage = ImportTaskMessage.createInProgressMessage(taskName, progress);
broadcast(ManagerEventTypeEnum.IMPORT_TASK_EVENT.getValue(), JsonUtil.toJson(managerMessage));
}
public void broadcastImportTaskSuccess(String taskName){
ManagerMessage managerMessage = ImportTaskMessage.createCompletedMessage(taskName);
broadcast(ManagerEventTypeEnum.IMPORT_TASK_EVENT.getValue(), JsonUtil.toJson(managerMessage));
}
public void broadcastImportTaskFail(String taskName, String errMsg){
ManagerMessage managerMessage = ImportTaskMessage.createFailedMessage(taskName, errMsg);
broadcast(ManagerEventTypeEnum.IMPORT_TASK_EVENT.getValue(), JsonUtil.toJson(managerMessage));
}
private void removeEmitter(Long clientId) {
emitters.remove(clientId);
}
}
@@ -1,50 +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.controller;
import org.apache.hertzbeat.common.util.SnowFlakeIdGenerator;
import org.apache.hertzbeat.manager.config.ManagerSseManager;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE;
/**
* SSE controller for manager
*/
@RestController
@RequestMapping(path = "/api/manager/sse", produces = {TEXT_EVENT_STREAM_VALUE})
public class ManagerSseController {
private final ManagerSseManager emitterManager;
public ManagerSseController(ManagerSseManager emitterManager) {
this.emitterManager = emitterManager;
}
@GetMapping(path = "/subscribe")
public SseEmitter subscribe() {
Long clientId = SnowFlakeIdGenerator.generateId();
return emitterManager.createEmitter(clientId);
}
}
@@ -39,7 +39,5 @@ public class SmsNoticeSender {
private SmsUniSmsConfig unisms;
private SmslocalConfig smslocal;
private boolean enable = true;
}
@@ -1,38 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.hertzbeat.manager.pojo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Smslocal SMS configuration
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SmslocalConfig {
/**
* Smslocal api key
*/
private String apiKey;
}
@@ -29,10 +29,9 @@ public interface ImExportService {
/**
* Import Configuration
* @param taskName task name
* @param is input stream
*/
void importConfig(String taskName, InputStream is);
void importConfig(InputStream is);
/**
* Export Configuration
@@ -22,20 +22,6 @@ import cn.afterturn.easypoi.excel.annotation.ExcelTarget;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.annotation.Resource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.constants.ImExportTaskConstant;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.apache.hertzbeat.common.entity.manager.Param;
import org.apache.hertzbeat.manager.config.ManagerSseManager;
import org.apache.hertzbeat.manager.pojo.dto.MonitorDto;
import org.apache.hertzbeat.manager.service.ImExportService;
import org.apache.hertzbeat.manager.service.MonitorService;
import org.apache.hertzbeat.manager.service.TagService;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.util.CollectionUtils;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDate;
@@ -43,6 +29,17 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.apache.hertzbeat.common.entity.manager.Param;
import org.apache.hertzbeat.manager.pojo.dto.MonitorDto;
import org.apache.hertzbeat.manager.service.ImExportService;
import org.apache.hertzbeat.manager.service.MonitorService;
import org.apache.hertzbeat.manager.service.TagService;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.util.CollectionUtils;
/**
* class AbstractImExportServiceImpl
@@ -57,30 +54,27 @@ public abstract class AbstractImExportServiceImpl implements ImExportService {
@Resource
private TagService tagService;
@Resource
private ManagerSseManager managerSseManager;
@Override
public void importConfig(String taskName, InputStream is) {
var formList = parseImport(is).stream().map(this::convert).toList();
public void importConfig(InputStream is) {
var formList = parseImport(is)
.stream()
.map(this::convert)
.toList();
if (!CollectionUtils.isEmpty(formList)) {
int totalElements = formList.size();
int progressInterval = Math.max(1, totalElements / 10);
for (int i = 0; i < totalElements; i++) {
MonitorDto monitorDto = formList.get(i);
formList.forEach(monitorDto -> {
monitorService.validate(monitorDto, false);
monitorService.addMonitor(monitorDto.getMonitor(), monitorDto.getParams(), monitorDto.getCollector(), monitorDto.getGrafanaDashboard());
if (totalElements >= ImExportTaskConstant.IMPORT_TASK_PROCESS_THRESHOLD && ((i + 1) % progressInterval == 0) && (i + 1 < totalElements)) {
managerSseManager.broadcastImportTaskInProgress(taskName, (int) ((i + 1) * 100.0 / totalElements));
}
}
managerSseManager.broadcastImportTaskSuccess(taskName);
});
}
}
@Override
public void exportConfig(OutputStream os, List<Long> configList) {
var monitorList = configList.stream().map(it -> monitorService.getMonitorDto(it)).filter(Objects::nonNull).map(this::convert).toList();
var monitorList = configList.stream()
.map(it -> monitorService.getMonitorDto(it))
.filter(Objects::nonNull)
.map(this::convert)
.toList();
writeOs(monitorList, os);
}
@@ -105,13 +99,15 @@ public abstract class AbstractImExportServiceImpl implements ImExportService {
var monitor = new MonitorDTO();
BeanUtils.copyProperties(dto.getMonitor(), monitor);
exportMonitor.setMonitor(monitor);
exportMonitor.setParams(dto.getParams().stream().map(it -> {
var param = new ParamDTO();
param.setField(it.getField());
param.setType(it.getType());
param.setValue(it.getParamValue());
return param;
}).toList());
exportMonitor.setParams(dto.getParams().stream()
.map(it -> {
var param = new ParamDTO();
param.setField(it.getField());
param.setType(it.getType());
param.setValue(it.getParamValue());
return param;
})
.toList());
exportMonitor.getMonitor().setCollector(dto.getCollector());
return exportMonitor;
}
@@ -124,7 +120,7 @@ public abstract class AbstractImExportServiceImpl implements ImExportService {
var monitorDto = new MonitorDto();
var monitor = new Monitor();
log.debug("exportMonitor.monitor{}", exportMonitor.monitor);
if (exportMonitor.monitor != null) {
if (exportMonitor.monitor != null) {
// Add one more null check
BeanUtils.copyProperties(exportMonitor.monitor, monitor);
}
@@ -133,13 +129,15 @@ public abstract class AbstractImExportServiceImpl implements ImExportService {
monitorDto.setCollector(exportMonitor.getMonitor().getCollector());
}
if (exportMonitor.params != null) {
monitorDto.setParams(exportMonitor.params.stream().map(it -> {
var param = new Param();
param.setField(it.field);
param.setType(it.type);
param.setParamValue(it.value);
return param;
}).toList());
monitorDto.setParams(exportMonitor.params.stream()
.map(it -> {
var param = new Param();
param.setField(it.field);
param.setType(it.type);
param.setParamValue(it.value);
return param;
})
.toList());
} else {
monitorDto.setParams(Collections.emptyList());
}
@@ -205,4 +203,5 @@ public abstract class AbstractImExportServiceImpl implements ImExportService {
@Excel(name = "Value")
private String value;
}
}
@@ -54,7 +54,6 @@ import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.common.util.SdMonitorOperator;
import org.apache.hertzbeat.common.util.SnowFlakeIdGenerator;
import org.apache.hertzbeat.grafana.service.DashboardService;
import org.apache.hertzbeat.manager.config.ManagerSseManager;
import org.apache.hertzbeat.manager.dao.CollectorDao;
import org.apache.hertzbeat.manager.dao.CollectorMonitorBindDao;
import org.apache.hertzbeat.manager.dao.MonitorBindDao;
@@ -139,8 +138,6 @@ public class MonitorServiceImpl implements MonitorService {
private WarehouseService warehouseService;
@Autowired
private DashboardService dashboardService;
@Autowired
private ManagerSseManager managerSseManager;
public MonitorServiceImpl(List<ImExportService> imExportServiceList) {
imExportServiceList.forEach(it -> imExportServiceMap.put(it.type(), it));
@@ -186,21 +183,17 @@ public class MonitorServiceImpl implements MonitorService {
@Override
public void importConfig(MultipartFile file) throws Exception {
var fileName = FileUtil.getFileName(file);
var type = FileUtil.getFileType(file);
try {
if (!imExportServiceMap.containsKey(type)) {
String errMsg = ExportFileConstants.FILE + " " + fileName + " is not supported.";
throw new RuntimeException(errMsg);
}
var imExportService = imExportServiceMap.get(type);
imExportService.importConfig(fileName, file.getInputStream());
} catch (Exception e){
managerSseManager.broadcastImportTaskFail(fileName, e.getMessage());
throw e;
if (!imExportServiceMap.containsKey(type)) {
throw new RuntimeException(ExportFileConstants.FILE + " " + fileName + " is not supported.");
}
var imExportService = imExportServiceMap.get(type);
imExportService.importConfig(file.getInputStream());
}
@Override
@Transactional(readOnly = true)
public void validate(MonitorDto monitorDto, Boolean isModify) throws IllegalArgumentException {
@@ -225,8 +225,6 @@ alerter:
access-key-secret: YOUR_ACCESS_KEY_SECRET
signature: YOUR_SMS_SIGNATURE
template-id: YOUR_TEMPLATE_ID
smslocal:
api-key: YOUR_API_KEY_HERE
scheduler:
server:
@@ -249,4 +247,4 @@ ai:
# api key
api-key:
#At present, only IFLYTEK large model needs to be filled in
api-secret:
api-secret:
@@ -64,13 +64,6 @@ params:
required: false
hide: true
defaultValue: 6000
- field: reuseConnection
name:
zh-CN: 复用连接
en-US: Reuse Connection
type: boolean
required: false
defaultValue: true
- field: database
name:
zh-CN: 服务名
@@ -165,7 +158,6 @@ metrics:
password: ^_^password^_^
database: ^_^database^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# SQL Query MethodoneRow, multiRow, columns
queryType: oneRow
# sql
@@ -221,8 +213,8 @@ metrics:
password: ^_^password^_^
database: ^_^database^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
queryType: multiRow
sql: select file_id, file_name, tablespace_name, status, bytes / 1024 / 1024 as bytes, blocks from dba_data_files
url: ^_^url^_^
@@ -246,7 +238,6 @@ metrics:
password: ^_^password^_^
database: ^_^database^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
queryType: oneRow
sql: select count(*) as count from v$session
url: ^_^url^_^
@@ -271,7 +262,6 @@ metrics:
password: ^_^password^_^
database: ^_^database^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
queryType: oneRow
sql: select count(*) as count from v$session where username is not null and status = 'ACTIVE'
url: ^_^url^_^
@@ -296,7 +286,6 @@ metrics:
password: ^_^password^_^
database: ^_^database^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
queryType: oneRow
sql: select count(*) as count from v$session where username is null
url: ^_^url^_^
@@ -327,7 +316,6 @@ metrics:
password: ^_^password^_^
database: ^_^database^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
queryType: oneRow
sql: SELECT username, count( username ) as count FROM v$session WHERE username IS NOT NULL GROUP BY username
url: ^_^url^_^
@@ -373,7 +361,6 @@ metrics:
password: ^_^password^_^
database: ^_^database^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
queryType: columns
sql: select metric_name, value from gv$sysmetric where metric_name = 'I/O Megabytes per Second' or metric_name = 'User Transaction Per Sec' or metric_name = 'I/O Requests per Second'
url: ^_^url^_^
@@ -427,7 +414,6 @@ metrics:
password: ^_^password^_^
database: ^_^database^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
queryType: multiRow
# DBA_TABLESPACE_USAGE_METRICS可以查出表空间used_max值,它的大小计算单位是block1kb=8block,把block*8/1024转化为MB单位
sql: "SELECT tablespace_name,ROUND ( (TABLESPACE_SIZE * 8 / 1024), 0) AS total,ROUND ( (USED_SPACE * 8 / 1024), 0) AS used,ROUND ( ( (TABLESPACE_SIZE * 8 / 1024) - (USED_SPACE * 8 / 1024)), 0) AS free,ROUND ( (USED_PERCENT), 0) AS used_percentage,100 - ROUND ( (USED_PERCENT), 0) AS free_percentage FROM sys.dba_tablespace_usage_metrics ORDER BY used_percent DESC"
@@ -453,7 +439,6 @@ metrics:
password: ^_^password^_^
database: ^_^database^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
queryType: oneRow
sql: select count(*) as process_count from v$process
url: ^_^url^_^
@@ -491,7 +476,6 @@ metrics:
password: ^_^password^_^
database: ^_^database^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
queryType: columns
sql: select metric_name, value from gv$sysmetric where metric_name = 'User Commits Per Sec' or metric_name = 'User Rollbacks Per Sec'
url: ^_^url^_^
@@ -577,7 +561,6 @@ metrics:
password: ^_^password^_^
database: ^_^database^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
queryType: columns
sql: select wait_class, sum(time_waited) total_wait_time from v$active_session_history where session_state = 'WAITING' GROUP BY wait_class ORDER BY total_wait_time DESC
url: ^_^url^_^
@@ -608,7 +591,6 @@ metrics:
password: ^_^password^_^
database: ^_^database^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
queryType: multiRow
sql: select stat_name as type, value as num from v$osstat where stat_name like '%CPU%' or stat_name like '%TIME'
url: ^_^url^_^
@@ -639,7 +621,6 @@ metrics:
password: ^_^password^_^
database: ^_^database^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
queryType: multiRow
sql: select stat_name as type, value as num from v$osstat where stat_name like '%BYTES'
url: ^_^url^_^
@@ -675,7 +656,6 @@ metrics:
password: ^_^password^_^
database: ^_^database^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
queryType: columns
sql: select metric_name, value from gv$sysmetric where metric_name like '%Cache Hit Ratio' order by end_time asc
url: ^_^url^_^
@@ -753,7 +733,6 @@ metrics:
password: ^_^password^_^
database: ^_^database^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
queryType: multiRow
sql: SELECT * FROM (SELECT sql_id, child_number, executions, ROUND(CASE WHEN executions = 0 THEN NULL ELSE elapsed_time / (executions*1000000) END,4) AS per_secs, cpu_time / 1000000 AS cpu_secs, buffer_gets, disk_reads, fetches, parse_calls, optimizer_cost, sql_text FROM v$sql ) where rownum <= 10 ORDER BY per_secs DESC
url: ^_^url^_^
@@ -78,7 +78,6 @@ excludedResource:
- /api/apps/hierarchy===get
- /api/push/**===*
- /api/status/page/public/**===*
- /api/manager/sse/**===*
# web ui resource
- /===get
- /assets/**===get
@@ -0,0 +1,72 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.warehouse.controller;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import org.apache.hertzbeat.common.entity.dto.Message;
import org.apache.hertzbeat.common.entity.dto.query.MetricQueryData;
import org.apache.hertzbeat.warehouse.service.MetricsDataQueryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* Metrics Data Query API
*/
@RestController
@RequestMapping(produces = {APPLICATION_JSON_VALUE})
@Tag(name = "Metrics Data Query API")
public class MetricsDataQueryController {
@Autowired
private MetricsDataQueryService queryService;
@GetMapping("/api/warehouse/query")
@Operation(summary = "Query Real Time Metrics Data")
public ResponseEntity<Message<List<MetricQueryData>>> queryMetricsData(
@Parameter(description = "Query PromQL expr list", example = "cpu")
@RequestParam List<String> queries,
@Parameter(description = "Query timestamp", example = "1725854804451")
@RequestParam long time) {
return ResponseEntity.ok(Message.success(queryService.query(queries, time)));
}
@GetMapping("/api/warehouse/query/range")
@Operation(summary = "Query Range Metrics Data")
public ResponseEntity<Message<List<MetricQueryData>>> queryMetricsDataRange(
@Parameter(description = "Query PromQL expr list", example = "cpu")
@RequestParam List<String> queries,
@Parameter(description = "Query start timestamp", example = "1725854804451")
@RequestParam long start,
@Parameter(description = "Query end timestamp", example = "1733630804452")
@RequestParam long end,
@Parameter(description = "Query step", example = "4m")
@RequestParam String step
) {
return ResponseEntity.ok(Message.success(queryService.queryRange(queries, start, end, step)));
}
}
@@ -15,35 +15,31 @@
* limitations under the License.
*/
package org.apache.hertzbeat.common.constants;
package org.apache.hertzbeat.warehouse.service;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import java.util.List;
import org.apache.hertzbeat.common.entity.dto.query.MetricQueryData;
/**
* Import/Export task status
* metrics data query service
*/
@Getter
@AllArgsConstructor
@ToString
public enum ImportTaskStatusEnum {
public interface MetricsDataQueryService {
/**
* Query metrics data
* @param queries query expr
* @param time time
* @return data
*/
List<MetricQueryData> query(List<String> queries, long time);
/**
* In progress
* Query metrics data range
* @param queries query expr
* @param start start
* @param end end
* @param step step
* @return data
*/
IN_PROGRESS("IN_PROGRESS"),
/**
* Completed
*/
COMPLETED("COMPLETED"),
/**
* Failed
*/
FAILED("FAILED");
private final String value;
List<MetricQueryData> queryRange(List<String> queries, long start, long end, String step);
}
@@ -0,0 +1,282 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.warehouse.service.impl.greptime;
import io.greptime.GreptimeDB;
import io.greptime.models.AuthInfo;
import io.greptime.models.DataType;
import io.greptime.models.Err;
import io.greptime.models.Result;
import io.greptime.models.Table;
import io.greptime.models.TableSchema;
import io.greptime.models.WriteOk;
import io.greptime.options.GreptimeOptions;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAmount;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.dto.Value;
import org.apache.hertzbeat.common.entity.dto.query.MetricQueryData;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.common.util.TimePeriodUtil;
import org.apache.hertzbeat.warehouse.service.MetricsDataQueryService;
import org.apache.hertzbeat.warehouse.store.history.greptime.GreptimeProperties;
import org.apache.hertzbeat.warehouse.store.history.vm.PromQlQueryContent;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
/**
* GreptimeDB data storage, only supports GreptimeDB version >= v0.5
*/
@Component
@ConditionalOnProperty(prefix = "warehouse.store.greptime", name = "enabled", havingValue = "true")
@Slf4j
public class GreptimeDbStorage implements MetricsDataQueryService, DisposableBean {
private static final String BASIC = "Basic";
private static final String QUERY_RANGE_PATH = "/v1/prometheus/api/v1/query_range";
private static final String LABEL_KEY_NAME = "__name__";
private static final String LABEL_KEY_FIELD = "__field__";
private static final String LABEL_KEY_INSTANCE = "instance";
private static final String SPILT = "_";
private GreptimeDB greptimeDb;
private final GreptimeProperties greptimeProperties;
private final RestTemplate restTemplate;
public GreptimeDbStorage(GreptimeProperties greptimeProperties, RestTemplate restTemplate) {
if (greptimeProperties == null) {
log.error("init error, please config Warehouse GreptimeDB props in application.yml");
throw new IllegalArgumentException("please config Warehouse GreptimeDB props");
}
this.restTemplate = restTemplate;
this.greptimeProperties = greptimeProperties;
initGreptimeDbClient(greptimeProperties);
}
private void initGreptimeDbClient(GreptimeProperties greptimeProperties) {
String endpoints = greptimeProperties.grpcEndpoints();
GreptimeOptions opts = GreptimeOptions.newBuilder(endpoints.split(","), greptimeProperties.database())
.writeMaxRetries(3)
.authInfo(new AuthInfo(greptimeProperties.username(), greptimeProperties.password()))
.routeTableRefreshPeriodSeconds(30)
.build();
this.greptimeDb = GreptimeDB.create(opts);
}
public void saveData(CollectRep.MetricsData metricsData) {
if (metricsData.getCode() != CollectRep.Code.SUCCESS) {
return;
}
if (metricsData.getValuesList().isEmpty()) {
log.info("[warehouse greptime] flush metrics data {} {} is null, ignore.", metricsData.getId(), metricsData.getMetrics());
return;
}
String monitorId = String.valueOf(metricsData.getId());
String tableName = getTableName(metricsData.getApp(), metricsData.getMetrics());
TableSchema.Builder tableSchemaBuilder = TableSchema.newBuilder(tableName);
tableSchemaBuilder.addTag("instance", DataType.String)
.addTimestamp("ts", DataType.TimestampMillisecond);
List<CollectRep.Field> fieldsList = metricsData.getFieldsList();
for (CollectRep.Field field : fieldsList) {
// handle field type
if (field.getLabel()) {
tableSchemaBuilder.addTag(field.getName(), DataType.String);
} else {
if (field.getType() == CommonConstants.TYPE_NUMBER) {
tableSchemaBuilder.addField(field.getName(), DataType.Float64);
} else if (field.getType() == CommonConstants.TYPE_STRING) {
tableSchemaBuilder.addField(field.getName(), DataType.String);
}
}
}
Table table = Table.from(tableSchemaBuilder.build());
try {
long now = System.currentTimeMillis();
Object[] values = new Object[2 + fieldsList.size()];
values[0] = monitorId;
values[1] = now;
for (CollectRep.ValueRow valueRow : metricsData.getValuesList()) {
for (int i = 0; i < fieldsList.size(); i++) {
if (!CommonConstants.NULL_VALUE.equals(valueRow.getColumns(i))) {
CollectRep.Field field = fieldsList.get(i);
if (field.getLabel()) {
values[2 + i] = valueRow.getColumns(i);
} else {
if (field.getType() == CommonConstants.TYPE_NUMBER) {
values[2 + i] = Double.parseDouble(valueRow.getColumns(i));
} else if (field.getType() == CommonConstants.TYPE_STRING) {
values[2 + i] = valueRow.getColumns(i);
}
}
} else {
values[2 + i] = null;
}
}
table.addRow(values);
}
CompletableFuture<Result<WriteOk, Err>> writeFuture = greptimeDb.write(table);
try {
Result<WriteOk, Err> result = writeFuture.get(10, TimeUnit.SECONDS);
if (result.isOk()) {
log.debug("[warehouse greptime]-Write successful");
} else {
log.warn("[warehouse greptime]--Write failed: {}", result.getErr());
}
} catch (Throwable throwable) {
log.error("[warehouse greptime]--Error occurred: {}", throwable.getMessage());
}
} catch (Exception e) {
log.error("[warehouse greptime]--Error: {}", e.getMessage(), e);
}
}
public Map<String, List<Value>> getHistoryMetricData(Long monitorId, String app, String metrics, String metric,
String label, String history) {
String name = getTableName(app, metrics);
String timeSeriesSelector = LABEL_KEY_NAME + "=\"" + name + "\""
+ "," + LABEL_KEY_INSTANCE + "=\"" + monitorId + "\"";
if (!CommonConstants.PROMETHEUS.equals(app)) {
timeSeriesSelector = timeSeriesSelector + "," + LABEL_KEY_FIELD + "=\"" + metric + "\"";
}
Map<String, List<Value>> instanceValuesMap = new HashMap<>(8);
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
if (StringUtils.hasText(greptimeProperties.username())
&& StringUtils.hasText(greptimeProperties.password())) {
String authStr = greptimeProperties.username() + ":" + greptimeProperties.password();
String encodedAuth = new String(Base64.encodeBase64(authStr.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
headers.add(HttpHeaders.AUTHORIZATION, BASIC + " " + encodedAuth);
}
Instant now = Instant.now();
long start;
try {
if (NumberUtils.isParsable(history)) {
start = NumberUtils.toLong(history);
start = (ZonedDateTime.now().toEpochSecond() - start);
} else {
TemporalAmount temporalAmount = TimePeriodUtil.parseTokenTime(history);
assert temporalAmount != null;
Instant dateTime = now.minus(temporalAmount);
start = dateTime.getEpochSecond();
}
} catch (Exception e) {
log.error("history time error: {}. use default: 6h", e.getMessage());
start = now.minus(6, ChronoUnit.HOURS).getEpochSecond();
}
long end = now.getEpochSecond();
String step = "60s";
if (end - start < Duration.ofDays(7).getSeconds() && end - start > Duration.ofDays(1).getSeconds()) {
step = "1h";
} else if (end - start >= Duration.ofDays(7).getSeconds()) {
step = "4h";
}
HttpEntity<Void> httpEntity = new HttpEntity<>(headers);
URI uri = UriComponentsBuilder.fromHttpUrl(greptimeProperties.httpEndpoint() + QUERY_RANGE_PATH)
.queryParam(URLEncoder.encode("query", StandardCharsets.UTF_8), URLEncoder.encode("{" + timeSeriesSelector + "}", StandardCharsets.UTF_8))
.queryParam("start", start)
.queryParam("end", end)
.queryParam("step", step)
.build(true).toUri();
ResponseEntity<PromQlQueryContent> responseEntity = restTemplate.exchange(uri,
HttpMethod.GET, httpEntity, PromQlQueryContent.class);
if (responseEntity.getStatusCode().is2xxSuccessful()) {
log.debug("query metrics data from victoria-metrics success. {}", uri);
if (responseEntity.getBody() != null && responseEntity.getBody().getData() != null
&& responseEntity.getBody().getData().getResult() != null) {
List<PromQlQueryContent.ContentData.Content> contents = responseEntity.getBody().getData().getResult();
for (PromQlQueryContent.ContentData.Content content : contents) {
Map<String, String> labels = content.getMetric();
labels.remove(LABEL_KEY_NAME);
labels.remove(LABEL_KEY_INSTANCE);
String labelStr = JsonUtil.toJson(labels);
if (content.getValues() != null && !content.getValues().isEmpty()) {
List<Value> valueList = instanceValuesMap.computeIfAbsent(labelStr, k -> new LinkedList<>());
for (Object[] valueArr : content.getValues()) {
long timestamp = ((Double) valueArr[0]).longValue();
String value = new BigDecimal(String.valueOf(valueArr[1])).setScale(4, RoundingMode.HALF_UP).stripTrailingZeros().toPlainString();
// read timestamp here is s unit
valueList.add(new Value(value, timestamp * 1000));
}
}
}
}
} else {
log.error("query metrics data from greptime failed. {}", responseEntity);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return instanceValuesMap;
}
private String getTableName(String app, String metrics) {
return app + SPILT + metrics;
}
@Override
public void destroy() {
if (this.greptimeDb != null) {
this.greptimeDb.shutdownGracefully();
this.greptimeDb = null;
}
}
@Override
public List<MetricQueryData> query(List<String> queries, long time) {
return List.of();
}
@Override
public List<MetricQueryData> queryRange(List<String> queries, long start, long end, String step) {
return List.of();
}
}
@@ -73,13 +73,10 @@ public class DataStorageDispatch {
if (metricsData == null) {
continue;
}
try {
calculateMonitorStatus(metricsData);
historyDataWriter.ifPresent(dataWriter -> dataWriter.saveData(metricsData));
pluginRunner.pluginExecute(PostCollectPlugin.class, ((postCollectPlugin, pluginContext) -> postCollectPlugin.execute(metricsData, pluginContext)));
} finally {
realTimeDataWriter.saveData(metricsData);
}
calculateMonitorStatus(metricsData);
historyDataWriter.ifPresent(dataWriter -> dataWriter.saveData(metricsData));
pluginRunner.pluginExecute(PostCollectPlugin.class, ((postCollectPlugin, pluginContext) -> postCollectPlugin.execute(metricsData, pluginContext)));
realTimeDataWriter.saveData(metricsData);
} catch (InterruptedException interruptedException) {
Thread.currentThread().interrupt();
} catch (Exception e) {
@@ -70,7 +70,6 @@ public class MemoryDataStorage extends AbstractRealTimeDataStorage {
Long monitorId = metricsData.getId();
String metrics = metricsData.getMetrics();
if (metricsData.getCode() != CollectRep.Code.SUCCESS) {
metricsData.close();
return;
}
Map<String, CollectRep.MetricsData> metricsDataMap =
@@ -63,7 +63,6 @@ public class RedisDataStorage extends AbstractRealTimeDataStorage {
String hashKey = metricsData.getMetrics();
if (metricsData.getCode() == CollectRep.Code.SUCCESS) {
redisCommandDelegate.operate().hset(key, hashKey, metricsData, future -> future.thenAccept(response -> {
metricsData.close();
if (response) {
log.debug("[warehouse] redis add new data {}:{}.", key, hashKey);
} else {
-122
View File
@@ -1,122 +0,0 @@
---
title: GSOC Google Summer of Code 2025 Recruitment is Underway | We Look Forward to Your Proposals
author: zhangshenghang
author_title: zhangshenghang
author_url: https://github.com/zhangshenghang
author_image_url: https://avatars.githubusercontent.com/u/29418975?s=400&v=4
tags: [opensource, practice]
keywords: [open source monitoring system, alerting system]
---
## The Origin and Purpose of GSOC
GSOC, namely Google Summer of Code, is a global initiative launched by Google. It aims to encourage students to participate in the development of open-source software projects. Here is a detailed introduction:
- GSOC started in 2005. Initially, it was a project established by Google to promote the development of the open-source community and encourage more students to engage in the development and innovation of open-source software. Through this project, Google hopes to inject fresh blood into the open-source world, enabling students to enhance their technical capabilities through practice, and at the same time, bring new ideas and contributions to open-source projects.
## Task: An AI Agent for Monitoring Information Interaction Based on the MCP Protocol
**Website**: `https://hertzbeat.apache.org/`
**Github**: `http://github.com/apache/hertzbeat/`
### Background
Apache HertzBeat is an open-source real-time monitoring tool that supports a wide range of monitoring targets, including web services, databases, middleware, and more. It is characterized by high performance, scalability, and security.
With the continuous advancement of artificial intelligence (AI) technology, integrating AI with monitoring systems can significantly improve their usability and interactivity. By developing an AI agent based on the Model Context Protocol (MCP), we aim to enable conversational interaction for querying monitoring information, adding new monitoring tasks, and retrieving monitoring metrics. This will provide a more user-friendly and intelligent monitoring management experience.
## Functional Requirements
### Query Monitoring and Alarm Information
1. **Monitoring Item Status Monitoring**
Be able to obtain the status of monitoring items through natural language;
For example: Which monitoring items are currently abnormal? It should inform the user which monitoring items are abnormal and the time when the abnormality occurred.
2. **Search for Metric Data**
Be able to obtain metric data through natural language;
For example: a. Which servers currently have a CPU usage rate exceeding 80%?
b. Display the CPU usage of server 192.168.1.1 in the last day in the form of a chart.
3. **Alarm Data**
Obtain alarm data through natural language;
For example: Which monitoring alarms have been more frequent recently?
### Add New Monitoring Tasks
1. **Add New Monitoring Targets**
Be able to add new monitoring targets through natural language;
For example: a. I want to monitor the response time of the web service.
b. Please help me add monitoring for the database connection pool.
2. **Configure Alarm Thresholds**
Be able to configure alarm thresholds through natural language;
For example: a. Trigger an alarm when the web service response time exceeds 5 seconds.
b. Send a notification when the database connection pool usage rate exceeds 90%.
### Considerations During Design
1. There are many data sources supported by the underlying storage. How to monitor? For example: Natural language -> SQL -> Query data source -> Large language model.
2. Compatibility with large language models, supporting commonly used models.
3. The usability of the solution, making it convenient for users to use.
### Requirement Analysis
- **Apache HertzBeat**: As the core backend of the monitoring system, it provides data collection, storage, and management functions.
- **MCP Protocol**: An open protocol that enables seamless integration between large language model (LLM) applications and external data sources and tools.
- **Front-end Interaction**: Develop a user-friendly interface that supports voice or text input and displays monitoring information and interaction results.
### Recommended Skills
- **Java + TypeScript**: Apache HertzBeat is developed based on this technology stack. Therefore, mastering these technologies is crucial for integrating with HertzBeat.
- **SpringAi**: It is recommended to use SpringAi to build the AI agent.
- **LLM + MCP**: You need to understand large language models (LLM) and the MCP protocol. SpringAi seems to support the MCP protocol, or you can consider using the mcp-sdk directly.
### Scale
- **Difficulty**: Difficult
- **Project Scale**: Approximately 350 hours
### Mentors
If you are interested, you can send an email to the mentors, including your solution and ideas.
- **Gong Chao**: `gongchao@apache.org`
- **Zhang Shenghang**: `shenghang@apache.org`
## How Can I Increase My Chances of Being Selected as a GSoC Contributor?
1. After the list of organizations is announced, please visit the program website to view these organizations and find several organizations that interest you.
2. Carefully read the "Project Ideas" list of these organizations.
3. If you see an idea that interests you, please contact the organization through the organization's preferred communication method (listed on the organization's page on the GSoC website).
4. Communicate with the mentors and community members to determine whether this project idea is something you are willing to implement during the program. If what you do is not something you are interested in, it will not be a pleasant summer vacation for you and your mentor.
5. During the communication with the mentors and other members of the organization's community, you have obtained some information. Use this information to write a proposal.
6. Please submit the proposal as early as possible so that the mentors have time to provide feedback. You need to have time to make revisions based on their feedback and resubmit it before the deadline. Do not wait until the last minute to submit the proposal!
## 2025 Google Summer of Code Timeline
All times are Coordinated Universal Time (UTC)
1. **January 27th - 18:00**: Mentor organizations can start submitting applications to Google.
2. **February 11th - 18:00**: Deadline for mentor organization applications.
3. **February 11th to 26th**: Google program administrators review organization applications.
4. **February 27th - 18:00**: Release the list of approved mentor organizations.
5. **February 27th to March 24th**: Potential GSoC contributors discuss proposal ideas with the mentoring organizations.
6. **March 24th - 18:00**: Start accepting applications from Google Summer of Code contributors (i.e., GSoC contributors).
7. **April 8th - 18:00**: Deadline for GSoC contributor applications.
8. **April 29th - 18:00**: Organization administrators need to determine the ranking of GSoC contributor proposals.
9. **May 8th - 18:00**: Announce the accepted GSoC contributor projects.
10. **May 8th to June 1st**: Community interaction period. Participating Google Summer of Code contributors (GSoC contributors) get to know the mentors, read the documentation, learn relevant information, and prepare for starting the project work.
11. **June 2nd**: Start coding officially!
12. **July 14th - 18:00**: Mentors and Google Summer of Code contributors can start submitting mid-term evaluations (applicable to standard 12-week programming projects).
13. **July 18th - 18:00**: Deadline for mid-term evaluations (standard coding cycle).
14. **July 14th to August 25th**: Working stage. Under the guidance of the mentors, GSoC contributors carry out the projects.
15. **August 25th to September 1st - 18:00**: Final week. GSoC contributors submit the final results and the final mentor evaluations (standard coding cycle).
16. **September 1st to 8th - 18:00**: Mentors submit the final evaluation results of GSoC contributors (standard coding cycle).
17. **September 1st to November 9th**: GSoC contributors with an extended schedule can continue coding.
18. **November 10th - 18:00**: Deadline for all Google Summer of Code contributors to submit the final results and final evaluations.
19. **November 17th - 18:00**: Final date for mentors to submit evaluations for GSoC contributor projects with an extended deadline.
## Frequently Asked Questions about Google Summer of Code
[https://developers.google.com/open-source/gsoc/faq?hl=zh-cn](https://developers.google.com/open-source/gsoc/faq?hl=zh-cn)
+1 -1
View File
@@ -20,6 +20,6 @@ WeChat Public : Search ID `usthecom`.
[Reddit Community](https://www.reddit.com/r/hertzbeat/)
[Follow Us Twitter](https://x.com/hertzbeat1024)
[Follow Us Twitter](https://twitter.com/hertzbeat1024)
[Subscribe YouTube](https://www.youtube.com/channel/UCri75zfWX0GHqJFPENEbLow)
+4 -2
View File
@@ -68,9 +68,11 @@ Even small corrections to typos are very welcome :)
4. Install Dependencies: `yarn install` or `yarn install --registry=https://registry.npmmirror.com` in `web-app`
5. After the local backend is started, start the local frontend in the web-app directory: `yarn start`
5. Install angular-cli globally: `yarn global add @angular/cli@15` or `yarn global add @angular/cli@15 --registry=https://registry.npmmirror.com`
6. Browser access to localhost:4200 to start, default account/password is *admin/hertzbeat*
6. After the local backend is started, start the local frontend in the web-app directory: `ng serve --open`
7. Browser access to localhost:4200 to start, default account/password is *admin/hertzbeat*
### Find tasks
+4 -2
View File
@@ -28,9 +28,11 @@ sidebar_label: Development
4. Install Dependencies: `yarn install` or `yarn install --registry=https://registry.npmmirror.com` in `web-app`
5. After the local backend is started, start the local frontend in the web-app directory: `yarn start`
5. Install angular-cli globally: `yarn global add @angular/cli@15` or `yarn global add @angular/cli@15 --registry=https://registry.npmmirror.com`
6. Browser access to localhost:4200 to start, default account/password is *admin/hertzbeat*
6. After the local backend is started, start the local frontend in the web-app directory: `ng serve --open`
7. Browser access to localhost:4200 to start, default account/password is *admin/hertzbeat*
## Build HertzBeat binary package
-194
View File
@@ -1,194 +0,0 @@
---
id: alert_sms
title: Alert SMS notification
sidebar_label: Alert SMS notification
keywords: [open source monitoring tool, open source alerter, open source SMS alert notification]
---
> After the threshold is triggered send alarm information and notify the recipient by SMS.
## SMS Service Configuration
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).
> ⚠️ 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.
### Tencent Cloud SMS Configuration
Add/Fill in the following Tencent Cloud SMS server configuration to `application.yml` (replace parameters with your own SMS server configuration):
```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
```
1. Create a signature (sign-name) in Tencent Cloud SMS
![image](https://github.com/apache/hertzbeat/assets/40455946/3a4c287d-b23d-4398-8562-4894296af485)
2. Create a message template (template-id) in Tencent Cloud SMS
```text
Monitor: {1}, Alert Level: {2}. Content: {3}
```
![image](https://github.com/apache/hertzbeat/assets/40455946/face71a6-46d5-452c-bed3-59d2a975afeb)
3. Create an application (app-id) in Tencent Cloud SMS
![image](https://github.com/apache/hertzbeat/assets/40455946/2732d710-37fa-4455-af64-48bba273c2f8)
4. Obtain Tencent Cloud Access Management credentials (secret-id, secret-key)
![image](https://github.com/apache/hertzbeat/assets/40455946/36f056f0-94e7-43db-8f07-82893c98024e)
### Alibaba Cloud SMS Configuration
To activate and use Alibaba Cloud SMS service, you can refer to the official Alibaba Cloud documentation: [SMS Getting Started Guide](https://help.aliyun.com/zh/sms/getting-started/get-started-with-sms)
You can configure the Alibaba Cloud SMS service either through the graphical interface or in the `application.yml` file.
To use `application.yml`, add/fill in the following Alibaba Cloud SMS configuration (replace parameters with your own SMS server configuration):
```yaml
alerter:
sms:
enable: true # Whether to enable
type: alibaba # SMS provider type, supports "alibaba"
alibaba: # Alibaba Cloud SMS configuration
access-key-id: # Your AccessKey ID
access-key-secret: # Your AccessKey Secret
sign-name: # SMS signature
template-code: # SMS template code
```
1. Create an Alibaba Cloud account and activate SMS service
- Visit [Alibaba Cloud SMS Console](https://dysms.console.aliyun.com/)
- Activate SMS service
2. Create a signature (sign-name)
- Log in to [SMS Console](https://dysms.console.aliyun.com/)
- Select Domestic/International SMS service
- Go to "Signature Management" page and click "Add Signature"
- Fill in signature information and submit for review
- Wait for signature approval
3. Create a message template (template-code)
- Go to "Template Management" page
- Click "Add Template"
- Create a template with the following format:
```text
Monitor: ${instance}, Alert Level: ${priority}. Content: ${content}
```
- Submit the template for review
4. Obtain Access Key credentials (access-key-id, access-key-secret)
:::tip
Alibaba Cloud officially recommends using RAM user AccessKey with minimal permissions.
:::
- [Go to RAM Access Control](https://ram.console.aliyun.com/users) to manage RAM users
- Create user and select "Access Key for API Access"
- Securely save the AccessKey ID and AccessKey Secret
- Grant SMS service permission "AliyunDysmsFullAccess" to the user
Now you can configure this information in your hertzbeat application.
### UniSMS Configuration
UniSMS is an aggregated SMS service platform. You can refer to [UniSMS Documentation](https://unisms.apistd.com/docs/tutorials) for configuration.
Add/Fill in the following UniSMS configuration to `application.yml` (replace parameters with your own SMS server configuration):
```yaml
alerter:
sms:
enable: true # Whether to enable
type: unisms # SMS provider type, set to unisms
unisms: # UniSMS configuration
# 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
```
1. Register UniSMS account
- Visit [UniSMS website](https://unisms.apistd.com/)
2. Create signature
- Log in to [UniSMS Console](https://unisms.apistd.com/console/)
- Go to "SMS Filing - Signature Management" page
- Click "Add Signature"
- Fill in signature information and submit for review
- Wait for signature approval
3. Create message template
- Go to "SMS Filing - Template Management" page
- Click "Add Template"
- Create a template with the following format:
```text
Monitor: {instance}, Alert Level: {priority}. Content: {content}
```
- Submit the template for review
4. Obtain `access-key-id` and `access-key-secret`
- Log in to [UniSMS Console](https://unisms.apistd.com/console/)
- Go to "Credential Management" page
- Get AccessKey ID and AccessKey Secret
- Securely save the AccessKey ID and AccessKey Secret
:::note
UniSMS provides two authentication methods for developers to choose from, which can be set in Console - Credential Management, with Simple Mode as default.
- Simple Mode [Default]: This mode only verifies AccessKey ID without request parameter signature, making it easier for developers to integrate quickly.
- HMAC Mode: This mode requires signing request parameters with AccessKey Secret to enhance the security and authenticity of requests.
:::
Now you can configure this information in your hertzbeat application.
### Smslocal SMS Configuration
SMSLocal is an all-in-one SMS service for businesses, with features like multi-way sending, strong security, and 24/7 support. You can refer to smslocal's [Developer Documentation](https://www.smslocal.com/developer/) for configuration.
Add/Fill in the following Smslocal configuration to `application.yml` (replace parameters with your own SMS server configuration):
```yaml
alerter:
sms:
enable: true # Whether to enable
type: smslocal # SMS provider type, set to smslocal
smslocal: # Smslocal configuration
api-key: YOUR_API_KEY_HERE
```
1. Register smslocal account
- Visit [Smslocal Website](https://www.smslocal.com/)
2. Obtain `api-key`
- Log in to [Smslocal Api Access](https://secure.smslocal.com/cpaas/pages/profile/settings/api-reference)
- Go to "API Access" page
- Click the eye button
- Copy the displayed access key
- Then you can configure the `application.yml` file
Now you can configure this information in your hertzbeat application.
## Operation steps
1. **【Alarm notification】->【Add new recipient】 ->【Select SMS notification method】**
2. **Configure the associated alarm notification strategy⚠️ 【Add new notification strategy】-> 【Associate the recipient just set】-> 【Confirm】**
> **Note⚠️ Adding a new recipient does not mean that it is effective to receive alarm information. It is also necessary to configure the associated alarm notification strategy, that is, to specify which messages are sent to which recipients.**
If you have any issues, please provide feedback through the communication group or ISSUE!
+1 -1
View File
@@ -9,7 +9,7 @@ slug: /
[![Discord](https://img.shields.io/badge/Chat-Discord-7289DA?logo=discord)](https://discord.gg/Fb6M73htGr)
[![Reddit](https://img.shields.io/badge/Reddit-Community-7289DA?logo=reddit)](https://www.reddit.com/r/hertzbeat/)
[![Twitter](https://img.shields.io/twitter/follow/hertzbeat1024?logo=twitter)](https://x.com/hertzbeat1024)
[![Twitter](https://img.shields.io/twitter/follow/hertzbeat1024?logo=twitter)](https://twitter.com/hertzbeat1024)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8139/badge)](https://www.bestpractices.dev/projects/8139)
[![Docker Pulls](https://img.shields.io/docker/pulls/apache/hertzbeat?style=%20for-the-badge&logo=docker&label=DockerHub%20Download)](https://hub.docker.com/r/apache/hertzbeat)
[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/hertzbeat)](https://artifacthub.io/packages/search?repo=hertzbeat)
+44 -3
View File
@@ -4,7 +4,7 @@ title: Advanced Params Config
sidebar_label: Advanced Params Config
---
Here it describes how to configure custom parameters for alerts, etc.
This describes how to configure the SMS server, the number of built-in availability alarm triggers, etc.
**Configuration file `application.yml` of `hertzbeat`**
@@ -14,7 +14,48 @@ Configuring the HertzBeat configuration file:
- **Docker Deployment:** ⚠️ When using a Docker container, the `application.yml` file must be mounted to the host machine
- **Installation Package Deployment:** Extract the package and modify the configuration file located at `hertzbeat/config/application.yml`
## 1. Configuring Custom Alert Parameters
## 1. Configuring the SMS Sending Service
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).
> ⚠️ 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.
### 1.1 Tencent Cloud SMS Configuration
Add the following Tencent Cloud SMS server configuration to `application.yml` (replace parameters with your own SMS server configuration):
```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
```
1. Create a signature (sign-name) in Tencent Cloud SMS
![image](https://github.com/apache/hertzbeat/assets/40455946/3a4c287d-b23d-4398-8562-4894296af485)
2. Create a message template (template-id) in Tencent Cloud SMS
```text
Monitor: {1}, Alert Level: {2}. Content: {3}
```
![image](https://github.com/apache/hertzbeat/assets/40455946/face71a6-46d5-452c-bed3-59d2a975afeb)
3. Create an application (app-id) in Tencent Cloud SMS
![image](https://github.com/apache/hertzbeat/assets/40455946/2732d710-37fa-4455-af64-48bba273c2f8)
4. Obtain Tencent Cloud Access Management credentials (secret-id, secret-key)
![image](https://github.com/apache/hertzbeat/assets/40455946/36f056f0-94e7-43db-8f07-82893c98024e)
## 2. Configuring Custom Alert Parameters
```yaml
alerter:
@@ -22,7 +63,7 @@ alerter:
console-url: https://console.tancloud.io
```
## 2. Using an External Redis Instead of In-Memory Storage for Real-Time Metric Data
## 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.
+1 -1
View File
@@ -213,7 +213,7 @@ module.exports = {
className: 'header-github-link'
},
{
href: 'https://x.com/hertzbeat1024',
href: 'https://twitter.com/hertzbeat1024',
position: 'right',
className: 'header-twitter-link'
},
@@ -1,127 +0,0 @@
---
title: GSOC谷歌编程之夏2025招募中|期待您的提案
author: zhangshenghang
author_title: zhangshenghang
author_url: https://github.com/zhangshenghang
author_image_url: https://avatars.githubusercontent.com/u/29418975?s=400&v=4
tags: [opensource, practice]
keywords: [open source monitoring system, alerting system]
---
## GSOC的起源与目的
GSOC 即 Google Summer of Code(谷歌编程之夏),是谷歌公司发起的一项全球性活动,旨在鼓励学生参与开源软件项目的开发,以下是其详细介绍:
- GSOC 始于 2005 年,最初是谷歌为了促进开源社区的发展,鼓励更多学生投身于开源软件的开发和创新而设立的项目。通过该项目,谷歌希望能够为开源世界注入新鲜血液,让学生们在实践中提升技术能力,同时也为开源项目带来新的思路和贡献。
## 任务:基于MCP协议的用于监控信息交互的AI代理
**网站**: `https://hertzbeat.apache.org/`
**Github**: `http://github.com/apache/hertzbeat/`
### 背景
Apache HertzBeat是一款开源的实时监控工具,支持广泛的监控目标,包括Web服务、数据库、中间件等等。它具有高性能、可扩展性和安全性的特点。
随着人工智能(AI)技术的不断进步,将人工智能与监控系统集成可以显著提高其可用性和交互性。通过开发一个基于模型上下文协议(MCP)的AI代理,我们旨在实现对话式交互,以便查询监控信息、添加新的监控任务以及检索监控指标。这将提供更加用户友好和智能的监控管理体验。
## 功能需求
### 查询监控和报警信息
1. **监控项状态监控**
通过自然语言能够获取监控项的状态;
如:现在哪些监控项存在异常?应该告诉用户哪些监控项存在异常,并且异常发生时间。
2. **搜索指标数据**
通过自然语言能够获取指标的数据;
如:a. 现在哪些服务器 CPU 使用率超过 80%
b. 服务器 192.168.1.1 最近一天 CPU 使用情况,用图表方式显示出来
3. **报警数据**
通过自然语言获取报警数据;
如:最近哪些监控报警比较多?
### 添加新的监控任务
1. **添加新的监控目标**
通过自然语言能够添加新的监控目标;
如:a. 我要监控Web服务的响应时间
b. 请帮我添加数据库连接池的监控
2. **配置警报阈值**
通过自然语言能够配置警报阈值;
如:a. 当Web服务响应时间超过5秒时触发警报
b. 数据库连接池使用率超过90%时发送通知
### 设计时需要考虑的事项
1. 底层存储支持数据源比较多,如何监控? 比如:自然语言 -> SQL -> 查询数据源 -> 大模型
2. 大模型的兼容性,支持常用模型
3. 方案的易用性,让用户能够方便使用
### 需求分析
- **Apache HertzBeat**:作为监控系统的核心后端,它提供数据收集、存储和管理功能。
- **MCP协议**:一种开放协议,可实现大语言模型(LLM)应用程序与外部数据源和工具之间的无缝集成。
- **前端交互**:开发一个用户友好的界面,支持语音或文本输入,并显示监控信息和交互结果。
### 推荐技能
- **Java + TypeScript**Apache HertzBeat是基于此技术栈开发的。因此,掌握这些技术对于与HertzBeat集成至关重要。
- **SpringAi**:建议使用SpringAi来构建AI代理。
- **LLM + MCP**:你需要了解大语言模型(LLM)和MCP协议。SpringAi似乎支持MCP协议,或者可以考虑直接使用mcp-sdk。
### 规模
- **难度**:困难
- **项目规模**:约350小时
### 导师
如果您有意向可邮件发送给导师,带上您的方案和想法。
- **宫超**`gongchao@apache.org`
- **张圣航**`shenghang@apache.org`
## 我该怎么做才能提高被选为 GSoC 贡献者的几率?
1. 在组织名单公布后,请访问计划网站查看这些组织,并找到几个您感兴趣的组织。
2. 仔细阅读这些组织的“项目想法”列表。
3. 如果您看到了感兴趣的想法,请通过组织的首选沟通方式(列在 GSoC 网站上的组织页面上)与该组织联系。
4. 与导师和社区成员沟通,确定这个项目想法是否是你在该计划期间乐于着手实施的。如果您所做的不是自己感兴趣的事情,那么对您和您的导师来说,这将不会是一个愉快的暑假。
5. 在与导师和其他组织社区成员沟通期间,您获得了一些信息,请利用这些信息撰写提案。
6. 请尽早提交提案,以便导师有时间提供反馈。您需要有时间根据他们的反馈进行修改,并在截止日期之前重新提交。请勿等到最后一刻才提交提案!
## 2025年Google编程之夏时间轴
所有时间均为世界协调时间(UTC
1. **1月27日 - 18:00**:导师组织可以开始向Google提交申请。
2. **2月11日 - 18:00**:导师组织申请截止日期。
3. **2月11日至26日**:Google计划管理员审核组织申请。
4. **2月27日 - 18:00**:发布已获批准的导师组织列表。
5. **2月27日至3月24日**:潜在的GSoC贡献者与指导组织讨论提案想法。
6. **3月24日 - 18:00**:开始接受Google全球服务学习贡献者(即GSoC贡献者)申请。
7. **4月8日 - 18:00**GSoC贡献者申请截止日期。
8. **4月29日 - 18:00**:组织管理员需要确定GSoC贡献者提案排名。
9. **5月8日 - 18:00**:公布已接受的GSoC贡献者项目。
10. **5月8日至6月1日**:社区互动期。参与Google开放式项目贡献者(GSoC贡献者)结识导师、阅读文档,了解相关信息,为开始项目工作做准备。
11. **6月2日**:正式开始编码!
12. **7月14日 - 18:00**:导师和Google开放式项目贡献者可以开始提交中期评估(适用于标准12周编程项目)。
13. **7月18日 - 18:00**:中期评估截止日期(标准编码周期)。
14. **7月14日至8月25日**:工作阶段。在导师的指导下,GSoC贡献者开展项目。
15. **8月25日至9月1日 - 18:00**:最后一周。GSoC贡献者提交最终成果和最终导师评估(标准编码周期)。
16. **9月1日至8日 - 18:00**:导师提交最终的GSoC贡献者评估结果(标准编码周期)。
17. **9月1日至11月9日**:有延长时间表的GSoC贡献者可以继续编码。
18. **11月10日 - 18:00**:所有Google开放式课程贡献者提交最终成果和最终评估的截止日期。
19. **11月17日 - 18:00**:导师针对延长截止日期的GSoC贡献者项目提交评估的最终日期 。
## Google 编程之夏常见问题解答
[https://developers.google.com/open-source/gsoc/faq?hl=zh-cn](https://developers.google.com/open-source/gsoc/faq?hl=zh-cn)
@@ -20,6 +20,6 @@ sidebar_label: 交流联系
[Reddit Community](https://www.reddit.com/r/hertzbeat/)
[Follow Us Twitter](https://x.com/hertzbeat1024)
[Follow Us Twitter](https://twitter.com/hertzbeat1024)
[Subscribe YouTube](https://www.youtube.com/channel/UCri75zfWX0GHqJFPENEbLow)
@@ -72,9 +72,11 @@ limitations under the License.
4. 在前端工程目录 `web-app` 下执行: `yarn install` or `yarn install --registry=https://registry.npmmirror.com` in `web-app`
5. 待本地后端启动后,在 web-app 目录下启动本地前端 `yarn start`
5. 全局安装 `angular-cli`: `yarn global add @angular/cli@15` or `yarn global add @angular/cli@15 --registry=https://registry.npmmirror.com`
6. 浏览器访问 localhost:4200 即可开始,默认账号密码 **admin/hertzbeat**
6. 待本地后端启动后,在 web-app 目录下启动本地前端 `ng serve --open`
7. 浏览器访问 localhost:4200 即可开始,默认账号密码 **admin/hertzbeat**
### 寻找任务
@@ -31,9 +31,11 @@ sidebar_label: 运行编译
4. 在前端工程目录 `web-app` 下执行: `yarn install` 或者 `yarn install --registry=https://registry.npmmirror.com`
5. 待本地后端启动后,在web-app目录下启动本地前端 `yarn start`
5. 全局安装 `angular-cli`: `yarn global add @angular/cli@15` or `yarn global add @angular/cli@15 --registry=https://registry.npmmirror.com`
6. 浏览器访问 localhost:4200 即可开始,默认账号密码 admin/hertzbeat
6. 待本地后端启动后,在web-app目录下启动本地前端 `ng serve --open`
7. 浏览器访问 localhost:4200 即可开始,默认账号密码 admin/hertzbeat
## 生成二进制包
@@ -1,193 +0,0 @@
---
id: alert_sms
title: 告警短信通知
sidebar_label: 告警短信通知
keywords: [开源监控系统, 开源告警系统, 开源短信告警通知]
---
> 当阈值触发后发送告警信息,通过短信方式通知接收人。
## 短信服务配置
只有成功配置了您自己的短信服务,监控系统内触发的告警短信才会正常发送。
hertzbeat有两种方式配置短信服务,一种是直接修改`application.yml`配置文件,另一种是通过hertzbeat前端界面(系统设置 > 消息服务配置)配置。
> 注意⚠️:两种方式配置的短信服务只能选择一种生效,当两种方式都配置并且开启时,hertzbeat将会优先使用前端界面配置的短信服务。
### 腾讯云短信配置
`application.yml`新增/填写如下腾讯平台短信服务器配置(参数需替换为您的短信服务器配置)
```yaml
alerter:
sms:
enable: true # 是否启用
type: tencent # 短信服务商类型,支持tencent、
tencent: # 腾讯云短信配置
secret-id: AKIDbQ4VhdMr89wDedFrIcgU2PaaMvOuBCzY
secret-key: PaXGl0ziY9UcWFjUyiFlCPMr77rLkJYlyA
app-id: 1435441637
sign-name: 赫兹跳动
template-id: 1343434
```
1. 腾讯云短信创建签名(sign-name)
![image](https://github.com/apache/hertzbeat/assets/40455946/3a4c287d-b23d-4398-8562-4894296af485)
2. 腾讯云短信创建正文模板(template-id
```text
监控:{1},告警级别:{2}。内容:{3}
```
![image](https://github.com/apache/hertzbeat/assets/40455946/face71a6-46d5-452c-bed3-59d2a975afeb)
3. 腾讯云短信创建应用(app-id)
![image](https://github.com/apache/hertzbeat/assets/40455946/2732d710-37fa-4455-af64-48bba273c2f8)
4. 腾讯云访问管理(secret-id、secret-key
![image](https://github.com/apache/hertzbeat/assets/40455946/36f056f0-94e7-43db-8f07-82893c98024e)
### 阿里云短信配置
开通使用阿里云短信服务,您可参考阿里云官方文档:[短信新手操作指引](https://help.aliyun.com/zh/sms/getting-started/get-started-with-sms)
您可以使用图形化界面配置阿里云短信服务,也可以在`application.yml`中配置阿里云短信服务。
使用`application.yml`需要新增/填写如下阿里云短信配置(参数需替换为您的短信服务器配置)
```yaml
alerter:
sms:
enable: true # 启用配置
type: alibaba # 短信服务商类型,设置为alibaba
alibaba: # 填写阿里云短信配置
access-key-id: # 您的AccessKey ID
access-key-secret: # 您的AccessKey Secret
sign-name: # 短信签名
template-code: # 短信模板CODE
```
1. 创建阿里云账号并开通短信服务
- 访问[阿里云短信服务控制台](https://dysms.console.aliyun.com/)
- 开通短信服务
2. 创建短信签名(sign-name
- 登录[短信服务控制台](https://dysms.console.aliyun.com/)
- 选择国内/国际短信服务
- 进入"签名管理"页面,点击"添加签名"
- 填写签名信息并提交审核
- 等待签名审核通过
3. 创建短信模板(template-code
- 进入"模板管理"页面
- 点击"添加模板"
- 创建如下格式的模板:
```text
监控项:${instance},告警级别:${priority}。内容:${content}
```
- 提交模板等待审核
4. 获取访问密钥(access-key-id、access-key-secret
:::tip
阿里云官方建议使用 RAM 用户 AccessKey,并进行最小化授权。
:::
- 进入[RAM访问控制](https://ram.console.aliyun.com/users)管理RAM用户
- 创建用户并选择"使用永久 AccessKey 访问"
- 安全保存AccessKey ID和AccessKey Secret
- 为用户授权短信服务权限"AliyunDysmsFullAccess"
现在您可以把这些信息配置到您的hertzbeat应用中。
### uni-sms配置
uni-sms是一个聚合短信服务平台,您可以参考[UniSMS合一短信文档](https://unisms.apistd.com/docs/tutorials)进行配置。
在`application.yml`新增/填写如下uni-sms短信服务配置(参数需替换为您的短信服务器配置)
```yaml
alerter:
sms:
enable: true # 启用配置
type: unisms # 短信服务商类型,设置为unisms
unisms: # 填写uni-sms短信配置
# 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
```
1. 注册uni-sms账号
- 访问[uni-sms官网](https://unisms.apistd.com/)
2. 创建短信签名(signature
- 登录[uni-sms控制台](https://unisms.apistd.com/console/)
- 进入"短信报备-签名管理"页面
- 点击"添加签名"
- 填写签名信息并提交审核
- 等待签名审核通过
3. 创建短信模板(template-id
- 进入"短信报备-模板管理"页面
- 点击"添加模板"
- 创建如下格式的模板:
```text
监控项:{instance},告警级别:{priority}。内容:{content}
```
- 提交模板等待审核
4. 获取`access-key-id`和`access-key-secret`
- 登录[uni-sms控制台](https://unisms.apistd.com/console/)
- 进入"凭证管理"页面
- 获取AccessKey ID和AccessKey Secret
- 安全保存AccessKey ID和AccessKey Secret
:::note
UniSMS 提供以下两种鉴权方式共开发者选择,可在控制台-凭证管理中设置,默认为简易模式。
- 简易模式 [默认]:此模式仅核验 AccessKey ID,不对请求参数进行验签,方便开发者快速接入。
- HMAC模式:此模式要求使用 AccessKey Secret 对请求参数进行验签,以加强保障请求的安全与真实性。
:::
现在您可以把这些信息配置到您的hertzbeat应用中。
### smslocal短信配置
smslocal是一款面向企业的一体化短信服务平台,具备诸如多种发送方式、强大的安全性以及全天候支持等特性。你可以参考 smslocal 的[开发者文档](https://www.smslocal.com/developer/)来进行配置。
在 `application.yml` 中添加/填写以下 smslocal 配置内容(请用你自己的短信服务器配置参数替换相关参数):
```yaml
alerter:
sms:
enable: true # 是否启用
type: smslocal # 短信服务提供商类型,设置为smslocal
smslocal: # smslocal配置
api-key: 在此处填入你的API密钥
```
1. 注册 smslocal 账号
- 访问 [smslocal官网](https://www.smslocal.com/)
2. 获取 `api-key`
- 登录 [smslocal API accessKey访问页面](https://secure.smslocal.com/cpaas/pages/profile/settings/api-reference)
- 进入 “API 访问” 页面
- 点击眼睛图标按钮
- 复制显示的访问密钥
- 然后你就可以配置 `application.yml` 文件了
现在你可以在你的 Hertzbeat 应用程序中配置这些信息。
## 操作步骤
1. **【告警通知】->【新增接收人】 ->【选择短信通知方式】**
2. **配置关联的告警通知策略⚠️ 【新增通知策略】-> 【关联刚才设置的接收人】-> 【确认】**
> **注意⚠️ 新增接收人并不代表就生效能收到告警信息,还需要配置关联的告警通知策略,即指定哪些消息发给哪些接收人。**
如有问题可通过交流群ISSUE反馈!
@@ -9,7 +9,7 @@ slug: /
[![Discord](https://img.shields.io/badge/Chat-Discord-7289DA?logo=discord)](https://discord.gg/Fb6M73htGr)
[![Reddit](https://img.shields.io/badge/Reddit-Community-7289DA?logo=reddit)](https://www.reddit.com/r/hertzbeat/)
[![Twitter](https://img.shields.io/twitter/follow/hertzbeat1024?logo=twitter)](https://x.com/hertzbeat1024)
[![Twitter](https://img.shields.io/twitter/follow/hertzbeat1024?logo=twitter)](https://twitter.com/hertzbeat1024)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8139/badge)](https://www.bestpractices.dev/projects/8139)
[![Docker Pulls](https://img.shields.io/docker/pulls/apache/hertzbeat?style=%20for-the-badge&logo=docker&label=DockerHub%20Download)](https://hub.docker.com/r/apache/hertzbeat)
[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/hertzbeat)](https://artifacthub.io/packages/search?repo=hertzbeat)
@@ -4,7 +4,7 @@ title: 常见参数配置
sidebar_label: 常见参数配置
---
这里描述了如何配置告警自定义参数等。
这里描述了如何配置短信服务,内置可用性告警触发次数等。
**`hertzbeat`的配置文件`application.yml`**
@@ -14,7 +14,47 @@ sidebar_label: 常见参数配置
- **Docker部署:** ⚠️docker容器方式需要将 `application.yml` 文件挂载到主机本地
- **安装包方式:** 解压修改位于 `hertzbeat/config/application.yml` 的配置文件即可
## 1. 配置告警自定义参数
## 1. 配置短信发送服务
只有成功配置了您自己的短信服务,监控系统内触发的告警短信才会正常发送。
hertzbeat有两种方式配置短信服务,一种是直接修改`application.yml`配置文件,另一种是通过hertzbeat前端界面(系统设置 > 消息服务配置)配置。
> 注意⚠️:两种方式配置的短信服务只能选择一种生效,当两种方式都配置并且开启时,hertzbeat将会优先使用前端界面配置的短信服务。
### 1.1 腾讯云短信配置
`application.yml`新增如下腾讯平台短信服务器配置(参数需替换为您的短信服务器配置)
```yaml
alerter:
sms:
enable: true # 是否启用
type: tencent # 短信服务商类型,支持tencent、
tencent: # 腾讯云短信配置
secret-id: AKIDbQ4VhdMr89wDedFrIcgU2PaaMvOuBCzY
secret-key: PaXGl0ziY9UcWFjUyiFlCPMr77rLkJYlyA
app-id: 1435441637
sign-name: 赫兹跳动
template-id: 1343434
```
1. 腾讯云短信创建签名(sign-name)
![image](https://github.com/apache/hertzbeat/assets/40455946/3a4c287d-b23d-4398-8562-4894296af485)
2. 腾讯云短信创建正文模板(template-id
```text
监控:{1},告警级别:{2}。内容:{3}
```
![image](https://github.com/apache/hertzbeat/assets/40455946/face71a6-46d5-452c-bed3-59d2a975afeb)
3. 腾讯云短信创建应用(app-id)
![image](https://github.com/apache/hertzbeat/assets/40455946/2732d710-37fa-4455-af64-48bba273c2f8)
4. 腾讯云访问管理(secret-id、secret-key
![image](https://github.com/apache/hertzbeat/assets/40455946/36f056f0-94e7-43db-8f07-82893c98024e)
## 2. 配置告警自定义参数
```yaml
alerter:
@@ -22,7 +62,7 @@ alerter:
console-url: https://console.tancloud.io
```
## 2. 使用外置redis代替内存存储实时指标数据
## 3. 使用外置redis代替内存存储实时指标数据
> 默认我们的指标实时数据存储在内存中,可以配置如下来使用redis代替内存存储。
@@ -20,6 +20,6 @@ sidebar_label: 交流联系
[Reddit Community](https://www.reddit.com/r/hertzbeat/)
[Follow Us Twitter](https://x.com/hertzbeat1024)
[Follow Us Twitter](https://twitter.com/hertzbeat1024)
[Subscribe YouTube](https://www.youtube.com/channel/UCri75zfWX0GHqJFPENEbLow)
@@ -9,7 +9,7 @@ slug: /
[![Discord](https://img.shields.io/badge/Chat-Discord-7289DA?logo=discord)](https://discord.gg/Fb6M73htGr)
[![Reddit](https://img.shields.io/badge/Reddit-Community-7289DA?logo=reddit)](https://www.reddit.com/r/hertzbeat/)
[![Twitter](https://img.shields.io/twitter/follow/hertzbeat1024?logo=twitter)](https://x.com/hertzbeat1024)
[![Twitter](https://img.shields.io/twitter/follow/hertzbeat1024?logo=twitter)](https://twitter.com/hertzbeat1024)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8139/badge)](https://www.bestpractices.dev/projects/8139)
[![Docker Pulls](https://img.shields.io/docker/pulls/apache/hertzbeat?style=%20for-the-badge&logo=docker&label=DockerHub%20Download)](https://hub.docker.com/r/apache/hertzbeat)
[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/hertzbeat)](https://artifacthub.io/packages/search?repo=hertzbeat)
-1
View File
@@ -260,7 +260,6 @@
"label": "notice",
"items": [
"help/alert_email",
"help/alert_sms",
"help/alert_webhook",
"help/alert_discord",
"help/alert_slack",
-3
View File
@@ -445,9 +445,6 @@ export default function () {
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/myangle1120"><img src="https://avatars.githubusercontent.com/u/19237013?v=4?s=100" width="100px;" alt="myangle1120"/><br /><sub><b>myangle1120</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=myangle1120" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yasminvo"><img src="https://avatars.githubusercontent.com/u/107528848?v=4?s=100" width="100px;" alt="yasminvo"/><br /><sub><b>yasminvo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yasminvo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yyahang"><img src="https://avatars.githubusercontent.com/u/90464876?v=4?s=100" width="100px;" alt="yyahang"/><br /><sub><b>yyahang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yyahang" title="Code">💻</a></td>
</tr>
</tbody>
</table>
-5
View File
@@ -123,11 +123,6 @@
"githubId": "61108539",
"gitUrl": "https://github.com/zuobiao-zhou",
"name": "Yuxuan Zhang"
},
{
"githubId": "91836599",
"gitUrl": "https://github.com/yunfan24",
"name": "Lixin Diao"
}
]
}
@@ -20,6 +20,6 @@ WeChat Public : Search ID `usthecom`.
[Reddit Community](https://www.reddit.com/r/hertzbeat/)
[Follow Us Twitter](https://x.com/hertzbeat1024)
[Follow Us Twitter](https://twitter.com/hertzbeat1024)
[Subscribe YouTube](https://www.youtube.com/channel/UCri75zfWX0GHqJFPENEbLow)
@@ -9,7 +9,7 @@ slug: /
[![Discord](https://img.shields.io/badge/Chat-Discord-7289DA?logo=discord)](https://discord.gg/Fb6M73htGr)
[![Reddit](https://img.shields.io/badge/Reddit-Community-7289DA?logo=reddit)](https://www.reddit.com/r/hertzbeat/)
[![Twitter](https://img.shields.io/twitter/follow/hertzbeat1024?logo=twitter)](https://x.com/hertzbeat1024)
[![Twitter](https://img.shields.io/twitter/follow/hertzbeat1024?logo=twitter)](https://twitter.com/hertzbeat1024)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8139/badge)](https://www.bestpractices.dev/projects/8139)
[![Docker Pulls](https://img.shields.io/docker/pulls/apache/hertzbeat?style=%20for-the-badge&logo=docker&label=DockerHub%20Download)](https://hub.docker.com/r/apache/hertzbeat)
[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/hertzbeat)](https://artifacthub.io/packages/search?repo=hertzbeat)
+1 -1
View File
@@ -110,7 +110,7 @@
<!-- hertzbeat related modules -->
<hertzbeat.version>2.0-SNAPSHOT</hertzbeat.version>
<springdoc.version>2.8.5</springdoc.version>
<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>
-2
View File
@@ -224,8 +224,6 @@ alerter:
access-key-secret: YOUR_ACCESS_KEY_SECRET
signature: YOUR_SMS_SIGNATURE
template-id: YOUR_TEMPLATE_ID
smslocal:
api-key: YOUR_API_KEY_HERE
scheduler:
server:
-8
View File
@@ -17,11 +17,3 @@
./home/i18n/zh-cn/docusaurus-plugin-content-docs/version-v1.5.x/help/sqlserver.md
./home/i18n/zh-cn/docusaurus-plugin-content-docs/version-v1.5.x/start/tdengine-init.md
./home/i18n/zh-cn/docusaurus-plugin-content-docs/current/start/tdengine-init.md
./home/versioned_docs/version-v1.5.x/introduce.md
./home/versioned_docs/version-v1.5.x/community/contact.md
./home/i18n/zh-cn/docusaurus-plugin-content-docs/current/introduce.md
./home/i18n/zh-cn/docusaurus-plugin-content-docs/current/community/contact.md
./home/i18n/zh-cn/docusaurus-plugin-content-docs/version-v1.5.x/community/contact.md
./home/i18n/zh-cn/docusaurus-plugin-content-docs/version-v1.5.x/introduce.md
./home/docs/introduce.md
./home/docs/community/contact.md
-1
View File
@@ -14,7 +14,6 @@
"aliveStatusCodes": [
0,
200,
400,
401,
403
]
@@ -191,8 +191,6 @@ alerter:
access-key-secret: YOUR_ACCESS_KEY_SECRET
signature: YOUR_SMS_SIGNATURE
template-id: YOUR_TEMPLATE_ID
smslocal:
api-key: YOUR_API_KEY_HERE
scheduler:
server:
@@ -78,7 +78,6 @@ excludedResource:
- /api/apps/hierarchy===get
- /api/push/**===*
- /api/status/page/public/**===*
- /api/manager/sse/**===*
# web ui resource
- /===get
- /assets/**===get
@@ -187,8 +187,6 @@ alerter:
access-key-secret: YOUR_ACCESS_KEY_SECRET
signature: YOUR_SMS_SIGNATURE
template-id: YOUR_TEMPLATE_ID
smslocal:
api-key: YOUR_API_KEY_HERE
scheduler:
server:
@@ -78,7 +78,6 @@ excludedResource:
- /api/apps/hierarchy===get
- /api/push/**===*
- /api/status/page/public/**===*
- /api/manager/sse/**===*
# web ui resource
- /===get
- /assets/**===get
@@ -185,8 +185,6 @@ alerter:
access-key-secret: YOUR_ACCESS_KEY_SECRET
signature: YOUR_SMS_SIGNATURE
template-id: YOUR_TEMPLATE_ID
smslocal:
api-key: YOUR_API_KEY_HERE
scheduler:
server:
@@ -78,7 +78,6 @@ excludedResource:
- /api/apps/hierarchy===get
- /api/push/**===*
- /api/status/page/public/**===*
- /api/manager/sse/**===*
# web ui resource
- /===get
- /assets/**===get
@@ -184,8 +184,6 @@ alerter:
access-key-secret: YOUR_ACCESS_KEY_SECRET
signature: YOUR_SMS_SIGNATURE
template-id: YOUR_TEMPLATE_ID
smslocal:
api-key: YOUR_API_KEY_HERE
scheduler:
server:
@@ -78,7 +78,6 @@ excludedResource:
- /api/apps/hierarchy===get
- /api/push/**===*
- /api/status/page/public/**===*
- /api/manager/sse/**===*
# web ui resource
- /===get
- /assets/**===get
-1
View File
@@ -78,7 +78,6 @@ excludedResource:
- /api/apps/hierarchy===get
- /api/push/**===*
- /api/status/page/public/**===*
- /api/manager/sse/**===*
# web ui resource
- /===get
- /assets/**===get
+3 -2
View File
@@ -9,8 +9,9 @@
1. Need `Node Yarn` Environment, Make sure `Node.js >= 18`
2. Install yarn if not existed `npm install -g yarn`
3. Execute `yarn install` or `yarn install --registry=https://registry.npmmirror.com` in `web-app`
4. Start After Backend Server Available : `yarn start`
3. Execute `yarn install` or `yarn install --registry=https://registry.npmmirror.com` in `web-app`
4. Install angular-cli : `yarn global add @angular/cli@15` or `yarn global add @angular/cli@15 --registry=https://registry.npmmirror.com`
5. Start After Backend Server Available : `yarn start`
### Build HertzBeat Install Package
@@ -156,8 +156,7 @@ export class HeaderNotifyComponent implements OnInit, OnDestroy {
}
);
this.loadData();
this.initAlertSSEConnection();
this.initManagerSSEConnection();
this.initSSEConnection();
}
ngOnDestroy() {
@@ -272,7 +271,7 @@ export class HeaderNotifyComponent implements OnInit, OnDestroy {
});
}
private initAlertSSEConnection(): void {
private initSSEConnection(): void {
const sseUrl = '/api/alert/sse/subscribe';
this.eventSource = new EventSource(sseUrl);
@@ -303,34 +302,4 @@ export class HeaderNotifyComponent implements OnInit, OnDestroy {
this.eventSource.close();
};
}
private initManagerSSEConnection(): void {
const sseUrl = '/api/manager/sse/subscribe';
this.eventSource = new EventSource(sseUrl);
this.eventSource.addEventListener('IMPORT_TASK_EVENT', (evt: MessageEvent) => {
let msg = JSON.parse(evt.data);
if (msg.notifyLevel === 'SUCCESS') {
this.notifySvc.success(
this.i18nSvc.fanyi('common.notice'),
this.i18nSvc.fanyi('common.notify.import-success-detail', { taskName: msg.taskName })
);
} else if (msg.notifyLevel === 'ERROR') {
this.notifySvc.error(
this.i18nSvc.fanyi('common.notice'),
this.i18nSvc.fanyi('common.notify.import-fail-detail', { taskName: msg.taskName, errMsg: msg.errMsg })
);
} else if (msg.notifyLevel === 'INFO') {
this.notifySvc.info(
this.i18nSvc.fanyi('common.notice'),
this.i18nSvc.fanyi('common.notify.import-progress', { taskName: msg.taskName, progress: msg.progress })
);
} else {
console.error('Parse message error, msg:', evt.data);
}
});
this.eventSource.onerror = error => {
console.error('Manager SSE connection error:', error);
this.eventSource.close();
};
}
}
-2
View File
@@ -18,7 +18,6 @@
*/
import { AlibabaSmsConfig } from './AlibabaSmsConfig';
import { SmslocalSmsConfig } from './SmslocalSmsConfig';
import { TencentSmsConfig } from './TencentSmsConfig';
import { UniSmsConfig } from './UniSmsConfig';
import { SmsType } from './enums/sms-type.enum';
@@ -29,7 +28,6 @@ export class SmsNoticeSender {
tencent: TencentSmsConfig = new TencentSmsConfig();
alibaba: AlibabaSmsConfig = new AlibabaSmsConfig();
unisms: UniSmsConfig = new UniSmsConfig();
smslocal: SmslocalSmsConfig = new SmslocalSmsConfig();
enable: boolean = false;
creator!: string;
modifier!: string;
-22
View File
@@ -1,22 +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.
*/
export class SmslocalSmsConfig {
apiKey!: string;
}
+1 -2
View File
@@ -20,8 +20,7 @@
export enum SmsType {
TENCENT = 'tencent',
ALIBABA = 'alibaba',
UNISMS = 'unisms',
SMSLOCAL = 'smslocal'
UNISMS = 'unisms'
}
export enum UniSmsAuthMode {
@@ -144,9 +144,7 @@ export class MonitorEditComponent implements OnInit {
}
}
define.name = this.i18nSvc.fanyi(`monitor.app.${this.monitor.app}.param.${define.field}`);
if (define.placeholder == null && this.i18nSvc.fanyi(`monitor.${define.field}.tip`) != `monitor.${define.field}.tip`) {
define.placeholder = this.i18nSvc.fanyi(`monitor.${define.field}.tip`);
}
define.placeholder = define.placeholder && this.i18nSvc.fanyi(`monitor.${define.field}.tip`);
if (define.hide) {
advancedParams.push(param);
advancedParamDefines.push(define);
@@ -265,20 +265,15 @@ export class MonitorListComponent implements OnInit, OnDestroy {
}
onImportMonitors(info: NzUploadChangeParam): void {
console.log(info.type);
if (info.type === 'start') {
this.notifySvc.info(
this.i18nSvc.fanyi('common.notice'),
this.i18nSvc.fanyi('common.notify.import-submitted', { taskName: info.file.name })
);
}
if (info.type === 'success' && info.file.response) {
if (info.file.response) {
this.tableLoading = true;
const message = info.file.response;
if (message.code === 0) {
this.notifySvc.success(this.i18nSvc.fanyi('common.notify.import-success'), '');
this.loadMonitorTable();
} else {
this.tableLoading = false;
this.notifySvc.error(this.i18nSvc.fanyi('common.notify.import-fail'), message.msg);
}
}
}
@@ -112,9 +112,7 @@ export class MonitorNewComponent implements OnInit {
}
}
define.name = this.i18nSvc.fanyi(`monitor.app.${this.monitor.app}.param.${define.field}`);
if (define.placeholder == null && this.i18nSvc.fanyi(`monitor.${define.field}.tip`) != `monitor.${define.field}.tip`) {
define.placeholder = this.i18nSvc.fanyi(`monitor.${define.field}.tip`);
}
define.placeholder = define.placeholder && this.i18nSvc.fanyi(`monitor.${define.field}.tip`);
if (define.hide) {
advancedParams.push(param);
advancedParamDefines.push(define);
@@ -139,7 +139,7 @@
</div>
</nz-modal>
<!-- sms server model -->
<!-- sms server modal -->
<nz-modal
[(nzVisible)]="isSmsServerModalVisible"
[nzTitle]="'settings.server.sms.setting' | i18n"
@@ -158,12 +158,9 @@
<nz-option [nzValue]="SmsType.TENCENT" nzLabel="{{ 'alert.notice.sender.sms.type.tencent' | i18n }}"></nz-option>
<nz-option [nzValue]="SmsType.ALIBABA" nzLabel="{{ 'alert.notice.sender.sms.type.alibaba' | i18n }}"></nz-option>
<nz-option [nzValue]="SmsType.UNISMS" nzLabel="{{ 'alert.notice.sender.sms.type.unisms' | i18n }}"></nz-option>
<nz-option [nzValue]="SmsType.SMSLOCAL" nzLabel="{{ 'alert.notice.sender.sms.type.smslocal' | i18n }}"></nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
<!-- Tencent SMS -->
<ng-container *ngIf="smsType === SmsType.TENCENT">
<nz-form-item>
<nz-form-label [nzSpan]="7" nzFor="secretId" nzRequired="true">
@@ -204,8 +201,6 @@
</nz-form-control>
</nz-form-item>
</ng-container>
<!-- Alibaba SMS -->
<ng-container *ngIf="smsType === SmsType.ALIBABA">
<nz-form-item>
<nz-form-label [nzSpan]="7" nzFor="accessKeyId" nzRequired="true">
@@ -261,8 +256,6 @@
</nz-form-control>
</nz-form-item>
</ng-container>
<!-- UniSMS -->
<ng-container *ngIf="smsType === SmsType.UNISMS">
<nz-form-item>
<nz-form-label [nzSpan]="7" nzFor="accessKeyId" nzRequired="true">
@@ -324,18 +317,6 @@
</nz-form-control>
</nz-form-item>
</ng-container>
<!-- Smslocal SMS -->
<ng-container *ngIf="smsType === SmsType.SMSLOCAL">
<nz-form-item>
<nz-form-label [nzSpan]="7" nzFor="apiKey" nzRequired="true">
{{ 'alert.notice.sender.sms.smslocal.apiKey' | i18n }}
</nz-form-label>
<nz-form-control [nzSpan]="12" [nzErrorTip]="'validation.required' | i18n">
<input [(ngModel)]="smsNoticeSender.smslocal.apiKey" nz-input required name="apiKey" type="password" id="apiKey" />
</nz-form-control>
</nz-form-item>
</ng-container>
<nz-form-item>
<nz-form-label nzSpan="7" nzFor="smsEnable" nzRequired="true">{{ 'common.enable' | i18n }}</nz-form-label>
<nz-form-control nzSpan="12">
@@ -31,7 +31,6 @@ import { UniSmsConfig } from 'src/app/pojo/UniSmsConfig';
import { SmsType, UniSmsAuthMode } from 'src/app/pojo/enums/sms-type.enum';
import { EmailNoticeSender } from '../../../../pojo/EmailNoticeSender';
import { SmslocalSmsConfig } from '../../../../pojo/SmslocalSmsConfig';
import { GeneralConfigService } from '../../../../service/general-config.service';
@Component({
@@ -143,7 +142,6 @@ export class MessageServerComponent implements OnInit {
this.smsNoticeSender.tencent = { ...new TencentSmsConfig(), ...message.data.tencent };
this.smsNoticeSender.alibaba = { ...new AlibabaSmsConfig(), ...message.data.alibaba };
this.smsNoticeSender.unisms = { ...new UniSmsConfig(), ...message.data.unisms };
this.smsNoticeSender.smslocal = { ...new SmslocalSmsConfig(), ...message.data.smslocal };
this.smsType = message.data.type || 'tencent';
} else {
this.smsNoticeSender = new SmsNoticeSender();
@@ -167,8 +165,7 @@ export class MessageServerComponent implements OnInit {
...this.smsNoticeSender,
tencent: { ...this.smsNoticeSender.tencent },
alibaba: { ...this.smsNoticeSender.alibaba },
unisms: { ...this.smsNoticeSender.unisms },
smslocal: { ...this.smsNoticeSender.smslocal }
unisms: { ...this.smsNoticeSender.unisms }
};
this.isSmsServerModalVisible = true;
}
@@ -179,8 +176,7 @@ export class MessageServerComponent implements OnInit {
...this.tempSmsNoticeSender,
tencent: { ...this.tempSmsNoticeSender.tencent },
alibaba: { ...this.tempSmsNoticeSender.alibaba },
unisms: { ...this.tempSmsNoticeSender.unisms },
smslocal: { ...this.tempSmsNoticeSender.smslocal }
unisms: { ...this.tempSmsNoticeSender.unisms }
};
this.isSmsServerModalVisible = false;
}
-6
View File
@@ -153,12 +153,10 @@
"alert.notice.sender.sms.unisms.signature": "UniSMS Signature",
"alert.notice.sender.sms.unisms.templateId": "UniSMS TemplateId",
"alert.notice.sender.sms.unisms.authMode": "UniSMS Authentication Mode",
"alert.notice.sender.sms.smslocal.apiKey": "Smslocal ApiKey",
"alert.notice.sender.sms.type": "Sms Type",
"alert.notice.sender.sms.type.alibaba": "Alibaba Sms",
"alert.notice.sender.sms.type.tencent": "Tencent Sms",
"alert.notice.sender.sms.type.unisms": "UniSMS",
"alert.notice.sender.sms.type.smslocal": "Smslocal Sms",
"alert.notice.template": "Notice Template",
"alert.notice.template.content": "Template Content",
"alert.notice.template.delete": "Delete Template",
@@ -462,10 +460,6 @@
"common.notify.export-success": "Export Success!",
"common.notify.import-fail": "Import Failed!",
"common.notify.import-success": "Import Success!",
"common.notify.import-fail-detail": "Import [{{taskName}}] Failed: [{{errMsg}}]",
"common.notify.import-success-detail": "Import [{{taskName}}] Success!",
"common.notify.import-submitted": "Import [{{taskName}}] Submitted!",
"common.notify.import-progress": "Importing [{{taskName}}], progress: {{progress}}%",
"common.notify.mark-fail": "Mark Failed!",
"common.notify.mark-success": "Mark Success!",
"common.notify.new-fail": "Add Failed!",
-6
View File
@@ -153,12 +153,10 @@
"alert.notice.sender.sms.unisms.signature": "UniSMS Signature",
"alert.notice.sender.sms.unisms.templateId": "UniSMS TemplateId",
"alert.notice.sender.sms.unisms.authMode": "UniSMS認証モード",
"alert.notice.sender.sms.smslocal.apiKey": "Smslocal ApiKey",
"alert.notice.sender.sms.type": "SMSタイプ",
"alert.notice.sender.sms.type.alibaba": "Alibaba Sms",
"alert.notice.sender.sms.type.tencent": "Tencent Sms",
"alert.notice.sender.sms.type.unisms": "UniSMS",
"alert.notice.sender.sms.type.smslocal": "Smslocal Sms",
"alert.notice.template": "通知テンプレート",
"alert.notice.template.content": "テンプレート内容",
"alert.notice.template.delete": "テンプレートを削除",
@@ -462,10 +460,6 @@
"common.notify.export-success": "エクスポートに成功しました!",
"common.notify.import-fail": "インポートに失敗しました!",
"common.notify.import-success": "インポートに成功しました!",
"common.notify.import-fail-detail": "[{{taskName}}]のインポートに失敗しました: [{{errMsg}}]",
"common.notify.import-success-detail": "[{{taskName}}]のインポートが成功しました!",
"common.notify.import-submitted": "インポートタスク[{{taskName}}]が送信されました!",
"common.notify.import-progress": "[{{taskName}}]のインポート中、進捗状況: {{progress}}%",
"common.notify.mark-fail": "マークに失敗しました!",
"common.notify.mark-success": "マークに成功しました!",
"common.notify.new-fail": "追加に失敗しました!",
-6
View File
@@ -153,12 +153,10 @@
"alert.notice.sender.sms.unisms.signature": "合一短信Signature",
"alert.notice.sender.sms.unisms.templateId": "合一短信TemplateId",
"alert.notice.sender.sms.unisms.authMode": "合一短信鉴权方式",
"alert.notice.sender.sms.smslocal.apiKey": "Smslocal短信鉴权方式",
"alert.notice.sender.sms.type": "短信类型",
"alert.notice.sender.sms.type.alibaba": "阿里短信",
"alert.notice.sender.sms.type.tencent": "腾讯短信",
"alert.notice.sender.sms.type.unisms": "合一短信(UniSMS",
"alert.notice.sender.sms.type.smslocal": "当地短信(Smslocal",
"alert.notice.template": "通知模板",
"alert.notice.template.content": "模板内容",
"alert.notice.template.delete": "删除通知模板",
@@ -462,10 +460,6 @@
"common.notify.export-success": "导出成功!",
"common.notify.import-fail": "导入失败!",
"common.notify.import-success": "导入成功!",
"common.notify.import-fail-detail": "导入 [{{taskName}}] 失败: [{{errMsg}}]",
"common.notify.import-success-detail": "导入 [{{taskName}}] 成功!",
"common.notify.import-submitted": "导入任务 [{{taskName}}] 已提交!",
"common.notify.import-progress": "正在导入 [{{taskName}}], 进度: {{progress}}%",
"common.notify.mark-fail": "标记失败!",
"common.notify.mark-success": "标记成功!",
"common.notify.new-fail": "新增失败!",
-6
View File
@@ -153,12 +153,10 @@
"alert.notice.sender.sms.unisms.signature": "合一簡訊Signature",
"alert.notice.sender.sms.unisms.templateId": "合一簡訊TemplateId",
"alert.notice.sender.sms.unisms.authMode": "合一簡訊驗證方式",
"alert.notice.sender.sms.smslocal.authMode": "Smslocal短訊ApiKey",
"alert.notice.sender.sms.type": "騰訊類型",
"alert.notice.sender.sms.type.alibaba": "阿裏短訊",
"alert.notice.sender.sms.type.tencent": "騰訊短訊",
"alert.notice.sender.sms.type.unisms": "合一簡訊(UniSMS",
"alert.notice.sender.sms.type.smslocal": "当地短訊(Smslocal",
"alert.notice.template": "通知模板",
"alert.notice.template.content": "模板内容",
"alert.notice.template.delete": "刪除通知模板",
@@ -462,10 +460,6 @@
"common.notify.export-success": "導出成功!",
"common.notify.import-fail": "導入失敗!",
"common.notify.import-success": "導入成功!",
"common.notify.import-fail-detail": "導入 [{{taskName}}] 失敗: [{{errMsg}}]",
"common.notify.import-success-detail": "導入 [{{taskName}}] 成功!",
"common.notify.import-submitted": "導入任務 [{{taskName}}] 已提交!",
"common.notify.import-progress": "正在導入 [{{taskName}}], 進度: {{progress}}%",
"common.notify.mark-fail": "標記失敗!",
"common.notify.mark-success": "標記成功!",
"common.notify.new-fail": "新增失敗!",