Compare commits

...
Author SHA1 Message Date
Duansg ad4028e284 Merge branch 'master' into update-release-doc 2026-01-11 13:03:04 +08:00
Prakash Kumar 0f1169ce36 [Fix] Fixed alignment of header social links on mobile (#3959) 2026-01-11 09:57:39 +08:00
Tomsun28 5a17f10c36 Merge branch 'master' into update-release-doc 2026-01-10 22:11:07 +08:00
KOYR 1c38d01abb style: widen table column to prevent label truncation (#3957) 2026-01-10 22:10:53 +08:00
Tomsun28 35a7154259 Merge branch 'master' into update-release-doc 2026-01-10 22:08:15 +08:00
tomsun28 485bca1a1f docs: update docs link 2026-01-10 22:07:02 +08:00
f82b5add11 [feature] Update CSP permissions to include Kapa.ai services (#3948)
Signed-off-by: Duansg <siguoduan@gmail.com>
Co-authored-by: Duansg <siguoduan@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-08 09:49:48 +08:00
KOYR 8e2c1f3b07 style: adjust grid layout width of form labels (#3950) 2026-01-07 15:06:45 +08:00
Logic 8ef92d70d2 [fix] Update overflow properties and enhance swiper container styles (#3947) 2026-01-07 08:10:37 +08:00
Yang Chen b5ce5c9e9d [fix] refactor system built-in labels validation logic (#3946) 2026-01-06 15:47:25 +08:00
P_Peaceful a19a399acd [bugfix&improve] Fix alert label persistence and improve custom label input (#3933) 2026-01-06 12:01:53 +08:00
529d161478 [improve] Optimize Tooltip display logic and support scrollable content (#3941)
Signed-off-by: Tomsun28 <tomsun28@outlook.com>
Co-authored-by: Tomsun28 <tomsun28@outlook.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-04 23:24:46 +08:00
168f78ef23 chore: optimzie docker compose docs hertzbeat version (#3929)
Signed-off-by: yuluo-yx <yuluo08290126@gmail.com>
Co-authored-by: aias00 <liuhongyu@apache.org>
Co-authored-by: Duansg <siguoduan@gmail.com>
2026-01-03 19:05:35 +08:00
KOYRandDuansg a159127572 [improve]: Optimize yAxis scale to better reflect data fluctuations (#3939)
Co-authored-by: Duansg <siguoduan@gmail.com>
2026-01-03 01:57:14 +08:00
88933ce0bb [feature] Update UI text and styles for website (#3934)
Signed-off-by: Logic <zqr10159@126.com>
Signed-off-by: Duansg <siguoduan@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Duansg <siguoduan@gmail.com>
Co-authored-by: aias00 <liuhongyu@apache.org>
2025-12-31 16:00:13 +08:00
Logic 6d703f7eae [feature] add Kapa widget script to docusaurus configuration (#3938) 2025-12-31 10:30:49 +08:00
Duansg f4a0fe3445 [fix] fixed CSP blocking algolia from loading on the page (#3935) 2025-12-30 09:56:58 +08:00
Sahil Shadwal b174c8622c [bugfix]: fix for the collector tab click effect error (#3932) 2025-12-28 18:42:04 +08:00
LathikaBaddam 98e0412ee6 [docs] improve English translation and technical terms for MySQL (#3928) 2025-12-27 20:20:04 +08:00
Logic a138238994 [bugfix] Enhance expiration time parsing to support strict day format (#3924) 2025-12-22 20:44:21 +08:00
Duansgandlynx009 01f5cb1fd6 [feat] Add Grafana to follow the system theme (#3920)
Co-authored-by: lynx009 <2030509072@qq.com>
2025-12-20 19:44:53 +08:00
Tomsun28andYang Chen 472554bc55 [chore] update docker compose hertzbeat version (#3916)
Signed-off-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: Yang Chen <1597081640@qq.com>
2025-12-19 14:15:28 +08:00
62 changed files with 1118 additions and 343 deletions
@@ -29,11 +29,14 @@ import org.apache.hertzbeat.alert.dao.AlertDefineDao;
import org.apache.hertzbeat.alert.service.AlertDefineImExportService;
import org.apache.hertzbeat.alert.service.AlertDefineService;
import org.apache.hertzbeat.alert.service.DataSourceService;
import org.apache.hertzbeat.base.dao.LabelDao;
import org.apache.hertzbeat.base.service.LabelService;
import org.apache.hertzbeat.common.cache.CacheFactory;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.constants.ExportFileConstants;
import org.apache.hertzbeat.common.constants.SignConstants;
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
import org.apache.hertzbeat.common.entity.manager.Label;
import org.apache.hertzbeat.common.util.FileUtil;
import org.apache.hertzbeat.common.util.JexlExpressionRunner;
import org.springframework.beans.factory.annotation.Autowired;
@@ -48,6 +51,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@@ -58,6 +62,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Alarm definition management interface implementation
@@ -69,16 +74,31 @@ public class AlertDefineServiceImpl implements AlertDefineService {
@Autowired
private AlertDefineDao alertDefineDao;
@Autowired
private PeriodicAlertRuleScheduler periodicAlertRuleScheduler;
@Resource
private LabelService labelService;
@Resource
private LabelDao labelDao;
private final DataSourceService dataSourceService;
private final Map<String, AlertDefineImExportService> alertDefineImExportServiceMap = new HashMap<>();
private static final String CONTENT_TYPE = MediaType.APPLICATION_OCTET_STREAM_VALUE + SignConstants.SINGLE_MARK + "charset=" + StandardCharsets.UTF_8;
private static final Set<String> SYSTEM_BUILT_IN_LABELS = Set.of(
CommonConstants.LABEL_INSTANCE,
CommonConstants.LABEL_DEFINE_ID,
CommonConstants.LABEL_ALERT_NAME,
CommonConstants.LABEL_INSTANCE_NAME,
CommonConstants.LABEL_ALERT_SEVERITY,
CommonConstants.ALERT_MODE_LABEL
);
public AlertDefineServiceImpl(List<AlertDefineImExportService> alertDefineImExportServiceList, DataSourceService dataSourceService) {
alertDefineImExportServiceList.forEach(it -> alertDefineImExportServiceMap.put(it.type(), it));
this.dataSourceService = dataSourceService;
@@ -93,7 +113,7 @@ public class AlertDefineServiceImpl implements AlertDefineService {
JexlExpressionRunner.compile(alertDefine.getExpr());
} catch (Exception e) {
throw new IllegalArgumentException("alert expr error: " + e.getMessage());
}
}
}
}
// the name of the alarm rule is unique
@@ -107,6 +127,7 @@ public class AlertDefineServiceImpl implements AlertDefineService {
@Override
public void addAlertDefine(AlertDefine alertDefine) throws RuntimeException {
saveNewCustomLabel(alertDefine);
alertDefine = alertDefineDao.saveAndFlush(alertDefine);
periodicAlertRuleScheduler.updateSchedule(alertDefine);
CacheFactory.clearAlertDefineCache();
@@ -114,11 +135,31 @@ public class AlertDefineServiceImpl implements AlertDefineService {
@Override
public void modifyAlertDefine(AlertDefine alertDefine) throws RuntimeException {
saveNewCustomLabel(alertDefine);
alertDefineDao.saveAndFlush(alertDefine);
periodicAlertRuleScheduler.updateSchedule(alertDefine);
CacheFactory.clearAlertDefineCache();
}
private void saveNewCustomLabel(AlertDefine alertDefine) {
Map<String, String> labels = alertDefine.getLabels();
if (labels == null) {
labels = new HashMap<>(8);
alertDefine.setLabels(labels);
}
Map<String, String> customLabels = labels.entrySet().stream()
.filter(entry -> !isSystemBuiltInLabel(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
List<Label> addLabels = labelService.determineNewLabels(customLabels.entrySet());
if (!addLabels.isEmpty()) {
labelDao.saveAll(addLabels);
}
}
private boolean isSystemBuiltInLabel(String labelKey) {
return SYSTEM_BUILT_IN_LABELS.contains(labelKey);
}
@Override
public void deleteAlertDefine(long alertId) throws RuntimeException {
alertDefineDao.deleteById(alertId);
@@ -256,7 +297,7 @@ public class AlertDefineServiceImpl implements AlertDefineService {
if (!StringUtils.hasText(type)) {
throw new IllegalArgumentException("Alert definition type cannot be null or empty");
}
switch (type) {
case CommonConstants.METRIC_ALERT_THRESHOLD_TYPE_REALTIME:
case CommonConstants.METRIC_ALERT_THRESHOLD_TYPE_PERIODIC:
@@ -267,7 +308,7 @@ public class AlertDefineServiceImpl implements AlertDefineService {
default:
throw new IllegalArgumentException("Unsupported alert definition type: " + type);
}
// Query enabled alert definitions by type
return alertDefineDao.findAlertDefinesByTypeAndEnableTrue(type);
}
@@ -21,6 +21,8 @@ import com.google.common.collect.Lists;
import org.apache.hertzbeat.alert.calculate.periodic.PeriodicAlertRuleScheduler;
import org.apache.hertzbeat.alert.dao.AlertDefineDao;
import org.apache.hertzbeat.alert.service.impl.AlertDefineServiceImpl;
import org.apache.hertzbeat.base.dao.LabelDao;
import org.apache.hertzbeat.base.service.LabelService;
import org.apache.hertzbeat.common.cache.CacheFactory;
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
import org.junit.jupiter.api.BeforeEach;
@@ -69,7 +71,13 @@ class AlertDefineServiceTest {
@Mock
private AlertDefineDao alertDefineDao;
@Mock
private LabelService labelService;
@Mock
private LabelDao labelDao;
@Mock
private PeriodicAlertRuleScheduler periodicAlertRuleScheduler;
@@ -86,6 +94,8 @@ class AlertDefineServiceTest {
void setUp() {
ReflectionTestUtils.setField(this.alertDefineService, "alertDefineDao", alertDefineDao);
ReflectionTestUtils.setField(this.alertDefineService, "periodicAlertRuleScheduler", periodicAlertRuleScheduler);
ReflectionTestUtils.setField(this.alertDefineService, "labelService", labelService);
ReflectionTestUtils.setField(this.alertDefineService, "labelDao", labelDao);
this.alertDefine = AlertDefine.builder()
.id(1L)
@@ -187,40 +197,40 @@ class AlertDefineServiceTest {
AlertDefine.builder().id(1L).type(METRIC_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build(),
AlertDefine.builder().id(2L).type(METRIC_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build()
);
try (MockedStatic<CacheFactory> cacheFactoryMock = Mockito.mockStatic(CacheFactory.class)) {
// Mock cache hit
cacheFactoryMock.when(CacheFactory::getMetricsAlertDefineCache).thenReturn(cachedAlertDefines);
List<AlertDefine> result = alertDefineService.getMetricsRealTimeAlertDefines();
assertNotNull(result);
assertEquals(2, result.size());
assertEquals(1L, result.get(0).getId());
assertEquals(2L, result.get(1).getId());
// Verify no database query was called
verify(alertDefineDao, times(0)).findAlertDefinesByTypeAndEnableTrue(any());
cacheFactoryMock.verify(() -> CacheFactory.setMetricsAlertDefineCache(any()), times(0));
}
// Test cache miss scenario
List<AlertDefine> dbAlertDefines = Lists.newArrayList(
AlertDefine.builder().id(3L).type(METRIC_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build()
);
try (MockedStatic<CacheFactory> cacheFactoryMock = Mockito.mockStatic(CacheFactory.class)) {
// Mock cache miss
cacheFactoryMock.when(CacheFactory::getMetricsAlertDefineCache).thenReturn(null);
when(alertDefineDao.findAlertDefinesByTypeAndEnableTrue(METRIC_ALERT_THRESHOLD_TYPE_REALTIME))
.thenReturn(dbAlertDefines);
List<AlertDefine> result = alertDefineService.getMetricsRealTimeAlertDefines();
assertNotNull(result);
assertEquals(1, result.size());
assertEquals(3L, result.get(0).getId());
// Verify database query and cache setting were called
verify(alertDefineDao, times(1)).findAlertDefinesByTypeAndEnableTrue(METRIC_ALERT_THRESHOLD_TYPE_REALTIME);
cacheFactoryMock.verify(() -> CacheFactory.setMetricsAlertDefineCache(dbAlertDefines), times(1));
@@ -234,40 +244,40 @@ class AlertDefineServiceTest {
AlertDefine.builder().id(4L).type(LOG_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build(),
AlertDefine.builder().id(5L).type(LOG_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build()
);
try (MockedStatic<CacheFactory> cacheFactoryMock = Mockito.mockStatic(CacheFactory.class)) {
// Mock cache hit
cacheFactoryMock.when(CacheFactory::getLogAlertDefineCache).thenReturn(cachedAlertDefines);
List<AlertDefine> result = alertDefineService.getLogRealTimeAlertDefines();
assertNotNull(result);
assertEquals(2, result.size());
assertEquals(4L, result.get(0).getId());
assertEquals(5L, result.get(1).getId());
// Verify no database query was called
verify(alertDefineDao, times(0)).findAlertDefinesByTypeAndEnableTrue(any());
cacheFactoryMock.verify(() -> CacheFactory.setLogAlertDefineCache(any()), times(0));
}
// Test cache miss scenario
List<AlertDefine> dbAlertDefines = Lists.newArrayList(
AlertDefine.builder().id(6L).type(LOG_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build()
);
try (MockedStatic<CacheFactory> cacheFactoryMock = Mockito.mockStatic(CacheFactory.class)) {
// Mock cache miss
cacheFactoryMock.when(CacheFactory::getLogAlertDefineCache).thenReturn(null);
when(alertDefineDao.findAlertDefinesByTypeAndEnableTrue(LOG_ALERT_THRESHOLD_TYPE_REALTIME))
.thenReturn(dbAlertDefines);
List<AlertDefine> result = alertDefineService.getLogRealTimeAlertDefines();
assertNotNull(result);
assertEquals(1, result.size());
assertEquals(6L, result.get(0).getId());
// Verify database query and cache setting were called
verify(alertDefineDao, times(1)).findAlertDefinesByTypeAndEnableTrue(LOG_ALERT_THRESHOLD_TYPE_REALTIME);
cacheFactoryMock.verify(() -> CacheFactory.setLogAlertDefineCache(dbAlertDefines), times(1));
@@ -280,58 +290,58 @@ class AlertDefineServiceTest {
AlertDefine.builder().id(7L).type(METRIC_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build(),
AlertDefine.builder().id(8L).type(METRIC_ALERT_THRESHOLD_TYPE_REALTIME).enable(true).build()
);
// Test valid metric realtime alert type
when(alertDefineDao.findAlertDefinesByTypeAndEnableTrue(METRIC_ALERT_THRESHOLD_TYPE_REALTIME))
.thenReturn(mockAlertDefines);
List<AlertDefine> result = alertDefineService.getAlertDefinesByType(METRIC_ALERT_THRESHOLD_TYPE_REALTIME);
assertNotNull(result);
assertEquals(2, result.size());
assertEquals(7L, result.get(0).getId());
assertEquals(8L, result.get(1).getId());
verify(alertDefineDao, times(1)).findAlertDefinesByTypeAndEnableTrue(METRIC_ALERT_THRESHOLD_TYPE_REALTIME);
// Test valid metric periodic alert type
reset(alertDefineDao);
when(alertDefineDao.findAlertDefinesByTypeAndEnableTrue(METRIC_ALERT_THRESHOLD_TYPE_PERIODIC))
.thenReturn(Lists.newArrayList());
result = alertDefineService.getAlertDefinesByType(METRIC_ALERT_THRESHOLD_TYPE_PERIODIC);
assertNotNull(result);
assertEquals(0, result.size());
verify(alertDefineDao, times(1)).findAlertDefinesByTypeAndEnableTrue(METRIC_ALERT_THRESHOLD_TYPE_PERIODIC);
// Test valid log realtime alert type
reset(alertDefineDao);
when(alertDefineDao.findAlertDefinesByTypeAndEnableTrue(LOG_ALERT_THRESHOLD_TYPE_REALTIME))
.thenReturn(mockAlertDefines);
result = alertDefineService.getAlertDefinesByType(LOG_ALERT_THRESHOLD_TYPE_REALTIME);
assertNotNull(result);
assertEquals(2, result.size());
verify(alertDefineDao, times(1)).findAlertDefinesByTypeAndEnableTrue(LOG_ALERT_THRESHOLD_TYPE_REALTIME);
// Test valid log periodic alert type
reset(alertDefineDao);
when(alertDefineDao.findAlertDefinesByTypeAndEnableTrue(LOG_ALERT_THRESHOLD_TYPE_PERIODIC))
.thenReturn(Lists.newArrayList());
result = alertDefineService.getAlertDefinesByType(LOG_ALERT_THRESHOLD_TYPE_PERIODIC);
assertNotNull(result);
assertEquals(0, result.size());
verify(alertDefineDao, times(1)).findAlertDefinesByTypeAndEnableTrue(LOG_ALERT_THRESHOLD_TYPE_PERIODIC);
// Test empty string type
assertThrows(IllegalArgumentException.class, () -> alertDefineService.getAlertDefinesByType(""));
// Test null type
assertThrows(IllegalArgumentException.class, () -> alertDefineService.getAlertDefinesByType(null));
// Test invalid type
assertThrows(IllegalArgumentException.class, () -> alertDefineService.getAlertDefinesByType("invalid_type"));
}
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.dao;
package org.apache.hertzbeat.base.dao;
import java.util.Optional;
import java.util.Set;
@@ -33,7 +33,7 @@ public interface LabelDao extends JpaRepository<Label, Long>, JpaSpecificationEx
* @param ids id list
*/
void deleteLabelsByIdIn(Set<Long> ids);
/**
* find Label by name and value
* @param name Label name
@@ -41,5 +41,5 @@ public interface LabelDao extends JpaRepository<Label, Long>, JpaSpecificationEx
* @return Label
*/
Optional<Label> findLabelByNameAndTagValue(String name, String value);
}
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.service;
package org.apache.hertzbeat.base.service;
import java.util.HashSet;
import java.util.List;
@@ -0,0 +1,50 @@
/*
* 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;
/**
* Theme Enum
*/
@Getter
@AllArgsConstructor
@ToString
public enum ThemeEnum {
COMPACT("compact"), DEFAULT("default"), LIGHT("light"), DARK("dark");
private final String value;
/**
* Convert theme to standard theme
*
* @param theme the input theme
* @return the converted theme
*/
public static String convert(String theme) {
if ((theme == null || theme.isEmpty())
|| ThemeEnum.COMPACT.getValue().equals(theme)
|| ThemeEnum.DEFAULT.getValue().equals(theme)) {
return ThemeEnum.LIGHT.getValue();
}
return theme;
}
}
@@ -34,7 +34,7 @@ import lombok.NoArgsConstructor;
@Entity
@Table(name = "hzb_grafana_dashboard")
@Data
@Builder
@Builder(toBuilder = true)
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "Grafana dashboard entity")
+2 -2
View File
@@ -32,10 +32,10 @@
</properties>
<dependencies>
<!-- hertzbeat common -->
<!-- hertzbeat base -->
<dependency>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-common</artifactId>
<artifactId>hertzbeat-base</artifactId>
</dependency>
<!-- hertzbeat warehouse -->
<dependency>
@@ -33,6 +33,8 @@ public interface GrafanaConstants {
String INSTANCE = "&var-instance=";
String THEME = "&theme=";
String CREATE_DASHBOARD_API = "/api/dashboards/db";
String DELETE_DASHBOARD_API = "/api/dashboards/uid/%s";
@@ -92,4 +94,4 @@ public interface GrafanaConstants {
static String generateUseDatasource(String datasourceName) {
return "&var-ds=" + datasourceName;
}
}
}
@@ -17,17 +17,15 @@
package org.apache.hertzbeat.grafana.service;
import static org.apache.hertzbeat.grafana.common.GrafanaConstants.CREATE_DASHBOARD_API;
import static org.apache.hertzbeat.grafana.common.GrafanaConstants.DELETE_DASHBOARD_API;
import static org.apache.hertzbeat.grafana.common.GrafanaConstants.INSTANCE;
import static org.apache.hertzbeat.grafana.common.GrafanaConstants.KIOSK;
import static org.apache.hertzbeat.grafana.common.GrafanaConstants.REFRESH;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
import org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum;
import org.apache.hertzbeat.common.constants.ThemeEnum;
import org.apache.hertzbeat.common.entity.grafana.GrafanaDashboard;
import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.grafana.common.GrafanaConstants;
import org.apache.hertzbeat.grafana.config.GrafanaProperties;
@@ -42,7 +40,17 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static org.apache.hertzbeat.grafana.common.GrafanaConstants.CREATE_DASHBOARD_API;
import static org.apache.hertzbeat.grafana.common.GrafanaConstants.DELETE_DASHBOARD_API;
import static org.apache.hertzbeat.grafana.common.GrafanaConstants.INSTANCE;
import static org.apache.hertzbeat.grafana.common.GrafanaConstants.KIOSK;
import static org.apache.hertzbeat.grafana.common.GrafanaConstants.REFRESH;
/**
* Service for managing Grafana dashboards.
@@ -66,6 +74,11 @@ public class DashboardService {
@Autowired
private DatasourceService datasourceService;
@Autowired
private GeneralConfigDao generalConfigDao;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
/**
* Creates or updates a dashboard in Grafana.
* The "id" field will be removed from the dashboard JSON before sending
@@ -225,7 +238,16 @@ public class DashboardService {
* @return GrafanaDashboard object
*/
public GrafanaDashboard getDashboardByMonitorId(Long monitorId) {
return dashboardDao.findByMonitorId(monitorId);
GrafanaDashboard dashboard = dashboardDao.findByMonitorId(monitorId);
if (null != dashboard
&& StringUtils.isNotBlank(dashboard.getUrl())
&& !dashboard.getUrl().contains(GrafanaConstants.THEME)) {
String theme = loadThemeConfig();
return dashboard.toBuilder()
.url(dashboard.getUrl() + GrafanaConstants.THEME + theme)
.build();
}
return dashboard;
}
/**
@@ -248,4 +270,21 @@ public class DashboardService {
log.warn("No Grafana dashboard record found for monitorId {} to disable.", monitorId);
}
}
}
private String loadThemeConfig() {
try {
GeneralConfig config = generalConfigDao.findByType(GeneralConfigTypeEnum.system.name());
if (config != null && config.getContent() != null) {
JsonNode root = OBJECT_MAPPER.readTree(config.getContent());
JsonNode node = root.get("theme");
if (node != null && !node.isNull()) {
return ThemeEnum.convert(node.asText());
}
return ThemeEnum.LIGHT.getValue();
}
} catch (Exception e) {
log.error("Failed to load database theme configuration", e);
}
return ThemeEnum.LIGHT.getValue();
}
}
@@ -17,6 +17,7 @@
package org.apache.hertzbeat.grafana.service;
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
import org.apache.hertzbeat.common.entity.grafana.GrafanaDashboard;
import org.apache.hertzbeat.grafana.config.GrafanaProperties;
import org.apache.hertzbeat.grafana.dao.DashboardDao;
@@ -34,10 +35,12 @@ import org.springframework.web.client.RestTemplate;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.lenient;
/**
* Test case for {@link DashboardService}
@@ -60,6 +63,9 @@ public class DashboardServiceTest {
@Mock
private DatasourceService datasourceService;
@Mock
private GeneralConfigDao generalConfigDao;
@InjectMocks
private DashboardService dashboardService;
@@ -73,25 +79,27 @@ public class DashboardServiceTest {
+ " \"version\": 3"
+ "}";
@BeforeEach
void setUp() {
when(datasourceService.getCurrentDatasourceName()).thenReturn("hertzbeat-vm-localhost-8428");
when(grafanaProperties.enabled()).thenReturn(Boolean.TRUE);
when(grafanaProperties.getPrefix()).thenReturn("");
when(grafanaProperties.getUrl()).thenReturn("http://127.0.0.1:3000");
when(grafanaProperties.exposeUrl()).thenReturn("http://127.0.0.1:3000");
when(serviceAccountService.getToken()).thenReturn("test-token");
lenient().when(datasourceService.getCurrentDatasourceName()).thenReturn("hertzbeat-vm-localhost-8428");
lenient().when(grafanaProperties.enabled()).thenReturn(Boolean.TRUE);
lenient().when(grafanaProperties.getPrefix()).thenReturn("");
lenient().when(grafanaProperties.getUrl()).thenReturn("http://127.0.0.1:3000");
lenient().when(grafanaProperties.exposeUrl()).thenReturn("http://127.0.0.1:3000");
lenient().when(serviceAccountService.getToken()).thenReturn("test-token");
}
@Test
void testCreateOrUpdateDashboard() {
ResponseEntity<String> responseEntity = new ResponseEntity<>(GRAFANA_API_RESULT, HttpStatus.OK);
when(restTemplate.postForEntity(
eq("http://127.0.0.1:3000/api/dashboards/db"), any(HttpEntity.class), eq(String.class)
)).thenReturn(responseEntity);
ArgumentCaptor<GrafanaDashboard> dashboardCaptor = ArgumentCaptor.forClass(GrafanaDashboard.class);
dashboardService.createOrUpdateDashboard("{\"id\":11}", 1L);
verify(dashboardDao).save(dashboardCaptor.capture());
@@ -109,7 +117,7 @@ public class DashboardServiceTest {
assertTrue(savedDashboard.getUrl().contains("refresh=15s"), "URL should contain refresh parameter");
assertTrue(savedDashboard.getUrl().contains("var-instance=1"), "URL should contain instance parameter");
}
@Test
void testCreateOrUpdateDashboardWithTrailingSlash() {
when(grafanaProperties.exposeUrl()).thenReturn("http://127.0.0.1:3000/");
@@ -138,4 +146,31 @@ public class DashboardServiceTest {
assertTrue(savedDashboard.getUrl().contains("var-instance=1"), "URL should contain instance parameter");
}
}
@Test
void testGetDashboardByMonitorId() {
Long monitorId = 1L;
String originalUrl = "http://localhost:3000/d/123/my-dashboard?kiosk";
GrafanaDashboard originalDashboard = GrafanaDashboard.builder()
.monitorId(monitorId)
.url(originalUrl)
.build();
when(dashboardDao.findByMonitorId(eq(monitorId))).thenReturn(originalDashboard);
String themeConfigJson = "{\"theme\":\"light\"}";
org.apache.hertzbeat.common.entity.manager.GeneralConfig runConfig = new org.apache.hertzbeat.common.entity.manager.GeneralConfig();
runConfig.setContent(themeConfigJson);
when(generalConfigDao.findByType(eq(org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum.system.name())))
.thenReturn(runConfig);
GrafanaDashboard resultDashboard = dashboardService.getDashboardByMonitorId(monitorId);
assertNotNull(resultDashboard);
assertTrue(resultDashboard.getUrl().contains("&theme=light"));
assertTrue(originalDashboard.getUrl().endsWith("?kiosk"));
assertFalse(originalDashboard.getUrl().contains("&theme="));
}
}
@@ -26,7 +26,7 @@ import java.util.HashSet;
import java.util.List;
import org.apache.hertzbeat.common.entity.dto.Message;
import org.apache.hertzbeat.common.entity.manager.Label;
import org.apache.hertzbeat.manager.service.LabelService;
import org.apache.hertzbeat.base.service.LabelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
@@ -46,7 +46,7 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(path = "/api/label", produces = {APPLICATION_JSON_VALUE})
public class LabelController {
@Autowired
private LabelService labelService;
@@ -31,7 +31,7 @@ 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.LabelService;
import org.apache.hertzbeat.base.service.LabelService;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.util.CollectionUtils;
@@ -31,8 +31,8 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.common.entity.manager.Label;
import org.apache.hertzbeat.manager.dao.LabelDao;
import org.apache.hertzbeat.manager.service.LabelService;
import org.apache.hertzbeat.base.dao.LabelDao;
import org.apache.hertzbeat.base.service.LabelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
@@ -50,7 +50,7 @@ import org.apache.hertzbeat.grafana.service.DashboardService;
import org.apache.hertzbeat.manager.component.validator.ParamValidatorManager;
import org.apache.hertzbeat.manager.dao.CollectorDao;
import org.apache.hertzbeat.manager.dao.CollectorMonitorBindDao;
import org.apache.hertzbeat.manager.dao.LabelDao;
import org.apache.hertzbeat.base.dao.LabelDao;
import org.apache.hertzbeat.manager.dao.MonitorBindDao;
import org.apache.hertzbeat.manager.dao.MonitorDao;
import org.apache.hertzbeat.manager.dao.ParamDao;
@@ -59,7 +59,7 @@ import org.apache.hertzbeat.manager.pojo.dto.MetricsInfo;
import org.apache.hertzbeat.manager.pojo.dto.MonitorDto;
import org.apache.hertzbeat.manager.scheduler.CollectJobScheduling;
import org.apache.hertzbeat.manager.service.AppService;
import org.apache.hertzbeat.manager.service.LabelService;
import org.apache.hertzbeat.base.service.LabelService;
import org.apache.hertzbeat.manager.service.MetricsFavoriteService;
import org.apache.hertzbeat.manager.service.MonitorService;
import org.apache.hertzbeat.manager.service.helper.MonitorImExportHelper;
@@ -25,7 +25,7 @@ name:
# The description and help of this monitoring type
help:
zh-CN: HertzBeat 使用 <a class='help_module_content' href='https://hertzbeat.apache.org/docs/advanced/extend-jdbc'> JDBC 协议</a> 通过配置 SQL 对 Mysql 数据库的通用性能指标(系统信息、性能状态、Innodb、缓存、事物、用户线程、慢SQL等)进行采集监控,支持版本为 Mysql5+。<br>您可以点击“<i>新建 Mysql 数据库</i>”并进行配置,或者选择“<i>更多操作</i>”,导入已有配置。
en-US: HertzBeat uses <a class='help_module_content' href='https://hertzbeat.apache.org/docs/advanced/extend-jdbc'> JDBC Protocol</a> to configure SQL for collecting general metrics of Mysql database (system information, performance status, Innodb, cache, things, user threads, slow SQL, etc.). Supported version is Mysql5+. <br>You can click "<i>New Mysql Database</i>" and configure it, or select "<i>More Operations</i>" to import the existing configuration.
en-US: HertzBeat uses <a class='help_module_content' href='https://hertzbeat.apache.org/docs/advanced/extend-jdbc'>JDBC Protocol</a> to collect general performance metrics of MySQL databases (system information, performance status, InnoDB, cache, transactions, user threads, slow SQL, etc.) by configuring SQL. Supported versions include MySQL 5.0 and above. <br>You can click "<i>New MySQL Database</i>" to configure it, or select "<i>More Operations</i>" to import an existing configuration.
zh-TW: HertzBeat 使用 <a class='help_module_content' href='https://hertzbeat.apache.org/docs/advanced/extend-jdbc'> JDBC 協議</a> 通過配置 SQL 對 Mysql 數據庫的通用性能指標(系統信息、性能狀態、Innodb、緩存、事物、用戶線程、慢SQL等)進行采集監控,支持版本爲 Mysql5+。<br>您可以點擊“<i>新建 Mysql 數據庫</i>”並進行配置,或者選擇“<i>更多操作</i>”,導入已有配置。
ja-JP: HertzBeat は <a class='help_module_content' href='https://hertzbeat.apache.org/docs/advanced/extend-jdbc'> JDBC プロトコル</a> を介して Mysql データベースの一般的なパフォーマンスのメトリクス(システム情報、パフォーマンスステータス、Innodb、キャッシュ、オブジェクト、ユーザースレッド、遅いSQLなど)収集監視を行う,サポートバージョンは Mysql5+。<br>クリックすることができます“<i>新規 Mysql データベース</i>”および構成,または“<i>その他の操作</i>”,既存の構成のインポートすることができます。
helpLink:
@@ -17,6 +17,7 @@
package org.apache.hertzbeat.manager.service;
import org.apache.hertzbeat.base.service.LabelService;
import org.junit.jupiter.api.Assertions;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -34,7 +35,7 @@ import java.util.Map;
import java.util.Optional;
import org.apache.hertzbeat.common.entity.manager.Label;
import org.apache.hertzbeat.manager.dao.LabelDao;
import org.apache.hertzbeat.base.dao.LabelDao;
import org.apache.hertzbeat.manager.service.impl.LabelServiceImpl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -35,6 +35,7 @@ import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import org.apache.hertzbeat.alert.dao.AlertDefineBindDao;
import org.apache.hertzbeat.base.service.LabelService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.job.Job;
import org.apache.hertzbeat.common.entity.job.Metrics;
@@ -27,7 +27,7 @@ import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.hertzbeat.common.entity.manager.Label;
import org.apache.hertzbeat.manager.dao.LabelDao;
import org.apache.hertzbeat.base.dao.LabelDao;
import org.apache.hertzbeat.startup.AbstractSpringIntegrationTest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -49,6 +49,8 @@ import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* data storage by duckdb
@@ -64,6 +66,9 @@ public class DuckdbDatabaseDataStorage extends AbstractHistoryDataStorage {
// Ideal number of data points for charting (avoids frontend lag)
private static final int TARGET_CHART_POINTS = 800;
// Regex to strictly match days format, e.g., "90d", "7D". Group 1 captures the digits.
private static final Pattern DAY_PATTERN = Pattern.compile("^(\\d+)[dD]$");
private final String expireTimeStr;
private final String dbPath;
private HikariDataSource dataSource;
@@ -136,11 +141,21 @@ public class DuckdbDatabaseDataStorage extends AbstractHistoryDataStorage {
log.info("[duckdb] start data cleaner and checkpoint...");
long expireTime;
try {
if (NumberUtils.isParsable(expireTimeStr)) {
expireTime = NumberUtils.toLong(expireTimeStr);
// Ensure no whitespace issues
String cleanExpireStr = expireTimeStr == null ? "" : expireTimeStr.trim();
Matcher dayMatcher = DAY_PATTERN.matcher(cleanExpireStr);
if (NumberUtils.isParsable(cleanExpireStr)) {
expireTime = NumberUtils.toLong(cleanExpireStr);
expireTime = (ZonedDateTime.now().toEpochSecond() - expireTime) * 1000L;
} else if (dayMatcher.matches()) {
// Strictly matched "90d" or "90D" format
long days = Long.parseLong(dayMatcher.group(1));
ZonedDateTime dateTime = ZonedDateTime.now().minus(Duration.ofDays(days));
expireTime = dateTime.toEpochSecond() * 1000L;
} else {
TemporalAmount temporalAmount = TimePeriodUtil.parseTokenTime(expireTimeStr);
// Fallback to existing utility for other units (h, m, s, etc.)
TemporalAmount temporalAmount = TimePeriodUtil.parseTokenTime(cleanExpireStr);
ZonedDateTime dateTime = ZonedDateTime.now().minus(temporalAmount);
expireTime = dateTime.toEpochSecond() * 1000L;
}
+13 -13
View File
@@ -71,7 +71,7 @@ some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
# At this time, a dialog box will pop up, asking you to enter the key for this gpg.
# At this time, a dialog box will pop up, asking you to enter the key for this gpg.
# you need to remember that it will be used in subsequent steps.
┌─────────────────────────────────────────────────────┐
│ Please enter this passphrase to │
@@ -146,7 +146,7 @@ $ svn co https://dist.apache.org/repos/dist/dev/hertzbeat
$ cd svn/dev/hertzbeat
# Append the KEY you generated to the file KEYS, and check if it is added correctly
$ (gpg --list-sigs muchunjin@apache.org && gpg --export --armor muchunjin@apache.org) >> KEYS
$ (gpg --list-sigs muchunjin@apache.org && gpg --export --armor muchunjin@apache.org) >> KEYS
$ svn ci -m "add gpg key for muchunjin"
```
@@ -161,7 +161,7 @@ $ svn co https://dist.apache.org/repos/dist/release/hertzbeat
$ cd svn/release/hertzbeat
# Append the KEY you generated to the file KEYS, and check if it is added correctly
$ (gpg --list-sigs muchunjin@apache.org && gpg --export --armor muchunjin@apache.org) >> KEYS
$ (gpg --list-sigs muchunjin@apache.org && gpg --export --armor muchunjin@apache.org) >> KEYS
$ svn ci -m "add gpg key for muchunjin"
```
@@ -310,15 +310,15 @@ apache-hertzbeat-collector-1.7.3-bin.tar.gz: OK
```shell
# Check out the dev directory of the Apache SVN to the svn/dev directory under dist in the root directory of the Apache HertzBeat™ project
svn co https://dist.apache.org/repos/dist/dev/incubator/hertzbeat svn/dev
svn co https://dist.apache.org/repos/dist/dev/hertzbeat svn/dev
svn co --depth empty https://dist.apache.org/repos/dist/dev/incubator/hertzbeat
svn co --depth empty https://dist.apache.org/repos/dist/dev/hertzbeat
```
- Copy the material package to the dev directory
Create a version number directory and name it in the form of ${release_version}-${RC_version}. RC_version starts from 1, that is, the candidate version starts from RC1. During the release process, there is a problem that causes the vote to fail.
If it needs to be corrected, it needs to iterate the RC version , the RC version number needs to be +1. For example: Vote for version 1.7.3-RC1. If the vote passes without any problems, the RC1 version material will be released as the final version material.
Create a version number directory and name it in the form of ${release_version}-${RC_version}. RC_version starts from 1, that is, the candidate version starts from RC1. During the release process, there is a problem that causes the vote to fail.
If it needs to be corrected, it needs to iterate the RC version , the RC version number needs to be +1. For example: Vote for version 1.7.3-RC1. If the vote passes without any problems, the RC1 version material will be released as the final version material.
If there is a problem (when the hertzbeat/incubator community votes, the voters will strictly check various release requirements and compliance issues) and need to be corrected, then re-initiate the vote after the correction, and the candidate version for the next vote is 1.7.3- RC2.
```shell
@@ -368,13 +368,13 @@ Release notes:
https://github.com/apache/hertzbeat/releases/tag/v1.7.3-rc1
The release candidates:
https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/1.7.3-RC1/
https://dist.apache.org/repos/dist/dev/hertzbeat/1.7.3-RC1/
Git tag for the release:
https://github.com/apache/hertzbeat/releases/tag/v1.7.3-rc1
The artifacts signed with PGP key [33545C76], corresponding to [muchunjin@apache.org], that can be found in keys file:
https://downloads.apache.org/incubator/hertzbeat/KEYS
https://downloads.apache.org/hertzbeat/KEYS
The vote will be open for at least 72 hours or until the necessary number of votes are reached.
@@ -492,17 +492,17 @@ The rename the release-1.7.3-rc1 branch to release-1.7.3.
Hi Community,
We are glad to announce the release of Apache HertzBeat™ 1.7.3.
Thanks again for your help.
Thanks again for your help.
Apache HertzBeat™ (https://hertzbeat.apache.org/) - A real-time observability system with agentless, performance cluster, prometheus-compatible, custom monitoring and status page building capabilities.
Download Link:
Download Link:
https://hertzbeat.apache.org/docs/download/
Release Note:
Release Note:
https://github.com/apache/hertzbeat/releases/tag/v1.7.3
Website:
Website:
https://hertzbeat.apache.org/
HertzBeat Resources:
+3 -3
View File
@@ -22,9 +22,9 @@ If the network is poor, downloading may be time-consuming. The download is compl
```shell
#If there is svn locally, you can clone to the local
$ svn co https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/${release_version}-${rc_version}/
$ svn co https://dist.apache.org/repos/dist/dev/hertzbeat/${release_version}-${rc_version}/
#or download the material file directly
$ wget https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/${release_version}-${rc_version}/xxx.xxx
$ wget https://dist.apache.org/repos/dist/dev/hertzbeat/${release_version}-${rc_version}/xxx.xxx
```
### 2. Verify that the uploaded version is compliant
@@ -47,7 +47,7 @@ First import the publisher's public key. Import KEYS from the svn repository to
##### 2.2.1 Import public key
```shell
curl https://downloads.apache.org/incubator/hertzbeat/KEYS > KEYS # Download KEYS
-curl https://downloads.apache.org/hertzbeat/KEYS > KEYS # Download KEYS
gpg --import KEYS # Import KEYS to local
```
+1 -1
View File
@@ -48,7 +48,7 @@ community members are welcome to comment and modify it.
|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **RE10** | Releases consist of source code, distributed using standard and open archive formats that are expected to stay readable in the long term. | **YES** Source release is distributed via [dist.apache.org](https://dist.apache.org/repos/dist/release/incubator/hertzbeat/) and linked from [download page](https://hertzbeat.apache.org/docs/download). |
| **RE20** | The project's PPMC (Project Management Committee, see CS10) approves each software release in order to make the release an act of the Foundation. | **YES** All releases have been voted at <dev@hertzbeat.apache.org> and <general@incubator.apache.org>, and have at least 3 PPMC member's votes. |
| **RE30** | Releases are signed and/or distributed along with digests that anyone can reliably use to validate the downloaded archives. | **YES** All releases are signed, and the [KEYS](https://dist.apache.org/repos/dist/release/incubator/hertzbeat/KEYS) are available. |
| **RE30** | Releases are signed and/or distributed along with digests that anyone can reliably use to validate the downloaded archives. | **YES** All releases are signed, and the [KEYS](https://dist.apache.org/repos/dist/release/hertzbeat/KEYS) are available. |
| **RE40** | The project can distribute convenience binaries alongside source code, but they are not Apache Releases, they are provided with no guarantee. | **YES** User can easily build binaries from source code, and we do not provide binaries as Apache Releases. |
| **RE50** | The project documents a repeatable release process so that someone new to the project can independently generate the complete set of artifacts required for a release. | **YES** We can follow the [Release guide](https://hertzbeat.apache.org/docs/community/how_to_release) to make a new Apache HertzBeat release, and so far we had 4 different release managers. |
+4 -4
View File
@@ -1,6 +1,6 @@
---
id: docker-compose-deploy
title: Install HertzBeat via Docker Compose
title: Install HertzBeat via Docker Compose
sidebar_label: Install via Docker Compose
---
@@ -21,7 +21,7 @@ Run the `docker compose version` command to check if you have a Docker Compose e
:::tip
- `apache-hertzbeat-xxx-docker-compose.tar.gz` contains multiple deployment solutions after decompression. Here we recommend choosing the `hertzbeat-postgresql-victoria-metrics` solution.
- `apache-hertzbeat-${version}-docker-compose.tar.gz` contains multiple deployment solutions after decompression. Here we recommend choosing the `hertzbeat-postgresql-victoria-metrics` solution.
- Other deployment methods, please read the README.md file of each deployment solution in detail. The MySQL solution requires you to prepare the MySQL driver package yourself.
:::
@@ -29,13 +29,13 @@ Run the `docker compose version` command to check if you have a Docker Compose e
- Unzip the script package
```shell
tar zxvf apache-hertzbeat-1.6.0-docker-compose.tar.gz
tar zxvf apache-hertzbeat-${version}-docker-compose.tar.gz
```
- Enter the decompression directory and select `HertzBeat + PostgreSQL + VictoriaMetrics` for one-click deployment
```shell
cd apache-hertzbeat-1.6.0-docker-compose
cd apache-hertzbeat-${version}-docker-compose
cd hertzbeat-postgresql-victoria-metrics
```
+12 -2
View File
@@ -279,7 +279,7 @@ module.exports = {
},
copyright:
`
<div style="text-align: left;margin-top:20px">
<div style="text-align: left;margin-top:20px">
<a href="https://apache.org/" style="display: flex; align-items: center; justify-content:center">
<img src="/img/icons/asf_logo.svg" alt="Apache logo" style="width:auto;height:140px">
</a>
@@ -399,5 +399,15 @@ module.exports = {
},
],
],
themes: ['@docusaurus/theme-live-codeblock']
themes: ['@docusaurus/theme-live-codeblock'],
scripts: [
{
src: "https://widget.kapa.ai/kapa-widget.bundle.js",
"data-website-id": "cb9dd0e9-5736-4572-be75-fd2ebe7443b0",
"data-project-name": "Apache HertzBeat",
"data-project-color": "#444FD9",
"data-project-logo": "https://hertzbeat.apache.org/zh-cn/assets/files/hertzbeat-logo-f92c500146ae43b9cb901600cd274d67.png",
async: true,
},
],
}
+6 -6
View File
@@ -200,13 +200,13 @@
"description": "The live editor label of the live codeblocks"
},
"convenient": {
"message": "Convenient Used"
"message": "Easy to Use"
},
"custom-multi-support": {
"message": "High Performance and Custom"
"message": "High Performance & Extensible"
},
"opensource": {
"message": "Opensource Friendly"
"message": "Open Source & Community Driven"
},
"theme.ErrorPageContent.title": {
"message": "This page crashed.",
@@ -257,13 +257,13 @@
"description": "The label used by the button on the collapsible TOC component"
},
"convenient-content": {
"message": "{docker} {br} Monitoring-alarm-notify all in one. Support monitoring web service, database, cache, os, web-server, middleware, bigdata, cloud-native, network etc. {br} Easy to use and agentless, full web-based operations with just a click of a mouse. {br} Security is the most important, encrypt the whole secret data."
"message": "{docker} {br} All-in-one monitoring, alerting, and notification system. Supports web services, databases, caching, OS, middleware, big data, cloud-native, and network monitoring. {br} Agentless with full web-based operations, simply point-and-click to get started. {br} Enterprise-grade security with end-to-end encryption for all sensitive data."
},
"custom-multi-support-content": {
"message": "Makes protocols such as Http, Jmx, Ssh, Snmp, Jdbc configurable, you can collect any metrics by simply configuring the yml online. {br} High performance, supports horizontal expansion of multi-collector clusters, multi-isolated network monitoring and cloud-edge collaboration. {br} Flexible threshold rules and timely notifications delivered via discord slack email webhook more."
"message": "Configurable protocols including HTTP, JMX, SSH, SNMP, and JDBC. Define custom metrics with simple online YAML configuration. {br} High-performance architecture with horizontal scaling for collector clusters, multi-network isolation, and cloud-edge collaboration. {br} Flexible alerting rules with timely notifications via Discord, Slack, email, Webhook, and more."
},
"opensource-content": {
"message": "Apache HertzBeat™ is open source, has an inclusive and open community. Unlimited and anyone who are interested in it are very welcome to contribute. No matter how small the contribution is, whether it is a code document or a typo, respect everyone and grow together. {br} Our code is being deployed on thousands of machines worldwide."
"message": "Apache HertzBeat™ is open source with an inclusive community. We welcome contributions of all sizes—code, documentation, or bug reports. {br} Respect every contributor and grow together. {br} Deployed on thousands of servers worldwide."
},
"slogan": {
"message": "AI-powered Open Source Observability System"
+6 -6
View File
@@ -1,12 +1,12 @@
{
"convenient": {
"message": "开箱即用"
"message": "简单易用"
},
"custom-multi-support": {
"message": "高性能与自定义"
"message": "高性能可扩展"
},
"opensource": {
"message": "拥抱开源"
"message": "开源社区"
},
"theme.ErrorPageContent.title": {
"message": "页面已崩溃。",
@@ -431,13 +431,13 @@
"description": "The live editor label of the live codeblocks"
},
"convenient-content": {
"message": "{docker} {br}集监控-告警-通知一体,支持应用服务,Web数据库缓存操作系统中间件大数据云原生网络等监控阈值告警通知一步到位。{br} 易用友好,无需Agent,全WEB页面操作,鼠标点一点就能监控告警,无需学习成本。{br}安全是最重要的,数据密钥全链路加密。"
"message": "{docker} {br}集监控告警通知一体,支持 Web 服务、数据库缓存操作系统中间件大数据云原生网络等全方位监控。{br}无需 Agent,全 Web 可视化操作,点点鼠标即可上手。{br}企业级安全保障,敏感数据全链路加密。"
},
"custom-multi-support-content": {
"message": "将 Http,Jmx,Ssh,Snmp,Jdbc 等协议规范可配置模板化,只需在线配置YML就可自定义监控指标。{br} 高性能,支持多采集器集群横向扩展,支持多隔离网络监控云边协同。{br}灵活的告警阈值规则,邮箱短信钉钉企业微信飞书Webhook等消息及时送达。{br} 您相信只需配置下就能适配新K8s监控类型吗?"
"message": "支持 HTTP、JMX、SSH、SNMP、JDBC 等协议可配置化,在线配置 YAML 即可自定义监控指标。{br}高性能架构,支持多采集器集群横向扩展、多网络隔离监控云边协同。{br}灵活的告警规则,支持邮箱短信钉钉企业微信飞书Webhook 等多种通知方式。"
},
"opensource-content": {
"message": "Apache HertzBeat™ 是开源,拥有一个包容开放的社区。{br}欢迎任何对此有兴趣的同学参与其中,无论是代码文档或者错别字,尊重社区的每一位,一起进步成长。{br}我们的代码正被部署全球成千上万机器上。"
"message": "Apache HertzBeat™ 是开源项目,拥有开放包容的社区。{br}欢迎任何形式的贡献,无论是代码文档还是问题反馈,尊重每位贡献者,共同成长。{br}已部署全球数千台服务器。"
},
"slogan": {
"message": "AI 驱动的下一代开源观测系统"
@@ -71,7 +71,7 @@ some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
# At this time, a dialog box will pop up, asking you to enter the key for this gpg.
# At this time, a dialog box will pop up, asking you to enter the key for this gpg.
# you need to remember that it will be used in subsequent steps.
┌─────────────────────────────────────────────────────┐
│ Please enter this passphrase to │
@@ -146,7 +146,7 @@ $ svn co https://dist.apache.org/repos/dist/dev/hertzbeat
$ cd svn/dev/hertzbeat
# 将生成的KEY追加到KEYS文件中,检查是否添加正确
$ (gpg --list-sigs muchunjin@apache.org && gpg --export --armor muchunjin@apache.org) >> KEYS
$ (gpg --list-sigs muchunjin@apache.org && gpg --export --armor muchunjin@apache.org) >> KEYS
$ svn ci -m "add gpg key for muchunjin"
```
@@ -161,7 +161,7 @@ $ svn co https://dist.apache.org/repos/dist/release/hertzbeat
$ cd svn/release/hertzbeat
# 将生成的KEY追加到KEYS文件中,检查是否添加正确
$ (gpg --list-sigs muchunjin@apache.org && gpg --export --armor muchunjin@apache.org) >> KEYS
$ (gpg --list-sigs muchunjin@apache.org && gpg --export --armor muchunjin@apache.org) >> KEYS
$ svn ci -m "add gpg key for muchunjin"
```
@@ -374,7 +374,7 @@ Git tag for the release:
https://github.com/apache/hertzbeat/releases/tag/v1.7.3-rc1
The artifacts signed with PGP key [33545C76], corresponding to [muchunjin@apache.org], that can be found in keys file:
https://downloads.apache.org/incubator/hertzbeat/KEYS
https://downloads.apache.org/hertzbeat/KEYS
The vote will be open for at least 72 hours or until the necessary number of votes are reached.
@@ -492,17 +492,17 @@ release note: xxx
Hi Community,
We are glad to announce the release of Apache HertzBeat 1.7.3.
Thanks again for your help.
Thanks again for your help.
Apache HertzBeat (https://hertzbeat.apache.org/) - a real-time observability system with agentless, performance cluster, prometheus-compatible, custom monitoring and status page building capabilities.
Download Link:
Download Link:
https://hertzbeat.apache.org/docs/download/
Release Note:
Release Note:
https://github.com/apache/hertzbeat/releases/tag/v1.7.3
Website:
Website:
https://hertzbeat.apache.org/
HertzBeat Resources:
@@ -16,9 +16,9 @@ sidebar_position: 4
```shell
#如果本地有svn,可以clone到本地
svn co https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/${release_version}-${rc_version}/
svn co https://dist.apache.org/repos/dist/dev/hertzbeat/${release_version}-${rc_version}/
#或者 直接下载物料文件
wget https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/${release_version}-${rc_version}/xxx.xxx
wget https://dist.apache.org/repos/dist/dev/hertzbeat/${release_version}-${rc_version}/xxx.xxx
```
@@ -42,7 +42,7 @@ wget https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/${release_versio
##### 2.2.1 导入公钥
```shell
curl https://downloads.apache.org/incubator/hertzbeat/KEYS > KEYS # 下载KEYS
curl https://downloads.apache.org/hertzbeat/KEYS > KEYS # 下载KEYS
gpg --import KEYS # 导入KEYS到本地
```
@@ -44,7 +44,7 @@ The following table is filled according to the [Apache Maturity Model](https://c
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **RE10** | Releases consist of source code, distributed using standard and open archive formats that are expected to stay readable in the long term. | **YES** Source release is distributed via [dist.apache.org](https://dist.apache.org/repos/dist/release/incubator/hertzbeat/) and linked from [download page](https://hertzbeat.apache.org/docs/download). |
| **RE20** | The project's PPMC (Project Management Committee, see CS10) approves each software release in order to make the release an act of the Foundation. | **YES** All releases have been voted at <dev@hertzbeat.apache.org> and <general@incubator.apache.org>, and have at least 3 PPMC member's votes. |
| **RE30** | Releases are signed and/or distributed along with digests that anyone can reliably use to validate the downloaded archives. | **YES** All releases are signed, and the [KEYS](https://dist.apache.org/repos/dist/release/incubator/hertzbeat/KEYS) are available. |
| **RE30** | Releases are signed and/or distributed along with digests that anyone can reliably use to validate the downloaded archives. | **YES** All releases are signed, and the [KEYS](https://dist.apache.org/repos/dist/release/hertzbeat/KEYS) are available. |
| **RE40** | The project can distribute convenience binaries alongside source code, but they are not Apache Releases, they are provided with no guarantee. | **YES** User can easily build binaries from source code, and we do not provide binaries as Apache Releases. |
| **RE50** | The project documents a repeatable release process so that someone new to the project can independently generate the complete set of artifacts required for a release. | **YES** We can follow the [Release guide](https://hertzbeat.apache.org/docs/community/how_to_release) to make a new Apache HertzBeat release, and so far we had 4 different release managers. |
@@ -29,7 +29,7 @@ keywords: [开源监控工具, 开源Java监控工具, 监控Nginx指标]
```shell
./configure --prefix=/usr/local/nginx --with-http_stub_status_module
make && make install
```
@@ -66,15 +66,15 @@ keywords: [开源监控工具, 开源Java监控工具, 监控Nginx指标]
```shell
# install `ngx_http_reqstat_module`
wget https://github.com/zls0424/ngx_req_status/archive/master.zip -O ngx_req_status.zip
unzip ngx_req_status.zip
patch -p1 < ../ngx_req_status-master/write_filter.patch
./configure --prefix=/usr/local/nginx --add-module=/path/to/ngx_req_status-master
make -j2
make install
```
@@ -87,9 +87,9 @@ keywords: [开源监控工具, 开源Java监控工具, 监控Nginx指标]
http {
req_status_zone server_name $server_name 256k;
req_status_zone server_addr $server_addr 256k;
req_status server_name server_addr;
server {
location /req-status {
req_status_show on;
@@ -108,6 +108,7 @@ keywords: [开源监控工具, 开源Java监控工具, 监控Nginx指标]
4. 在浏览器访问 `http://localhost/req-status` 即可查看 Nginx 监控状态信息。
<!-- markdown-link-check-disable -->
**参考文档: <https://blog.csdn.net/weixin_55985097/article/details/116722309>**
**⚠️注意监控模块的端点路径为 `/nginx-status` `/req-status`**
@@ -1,6 +1,6 @@
---
id: tomcat
title: 监控:Tomcat监控
id: tomcat
title: 监控:Tomcat监控
sidebar_label: Tomcat监控
keywords: [开源监控系统, 开源网站监控, Tomcat监控]
---
@@ -72,4 +72,5 @@ keywords: [开源监控系统, 开源网站监控, Tomcat监控]
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote -Djava.rmi.server.hostname=10.1.1.52 -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"
```
<!-- markdown-link-check-disable -->
参考: <https://blog.csdn.net/weixin_41924764/article/details/108694239>
@@ -21,7 +21,7 @@ sidebar_label: Docker Compose方式安装
:::tip
- `apache-hertzbeat-xxx-docker-compose.tar.gz` 解压后包含多个部署方案,这里我们推荐选择 `hertzbeat-postgresql-victoria-metrics` 方案。
- `apache-hertzbeat-${version}-docker-compose.tar.gz` 解压后包含多个部署方案,这里我们推荐选择 `hertzbeat-postgresql-victoria-metrics` 方案。
- 其它部署方式请详细阅读各个部署方案的 README.md 文件, MySQL 方案需要自行准备 MySQL 驱动包。
:::
@@ -29,13 +29,13 @@ sidebar_label: Docker Compose方式安装
- 解压脚本包
```shell
tar zxvf apache-hertzbeat-1.6.0-docker-compose.tar.gz
tar zxvf apache-hertzbeat-${version}-docker-compose.tar.gz
```
- 进入解压目录, 选择 `HertzBeat + PostgreSQL + VictoriaMetrics` 一键部署
```shell
cd apache-hertzbeat-1.6.0-docker-compose
cd apache-hertzbeat-${version}-docker-compose
cd hertzbeat-postgresql-victoria-metrics
```
@@ -71,7 +71,7 @@ some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
# At this time, a dialog box will pop up, asking you to enter the key for this gpg.
# At this time, a dialog box will pop up, asking you to enter the key for this gpg.
# you need to remember that it will be used in subsequent steps.
┌─────────────────────────────────────────────────────┐
│ Please enter this passphrase to │
@@ -142,11 +142,11 @@ gpg: unchanged: 1
$ mkdir -p svn/dev
$ cd svn/dev
$ svn co https://dist.apache.org/repos/dist/dev/incubator/hertzbeat
$ svn co https://dist.apache.org/repos/dist/dev/hertzbeat
$ cd svn/dev/hertzbeat
# 将生成的KEY追加到KEYS文件中,检查是否添加正确
$ (gpg --list-sigs muchunjin@apache.org && gpg --export --armor muchunjin@apache.org) >> KEYS
$ (gpg --list-sigs muchunjin@apache.org && gpg --export --armor muchunjin@apache.org) >> KEYS
$ svn ci -m "add gpg key for muchunjin"
```
@@ -161,7 +161,7 @@ $ svn co https://dist.apache.org/repos/dist/release/incubator/hertzbeat
$ cd svn/release/hertzbeat
# 将生成的KEY追加到KEYS文件中,检查是否添加正确
$ (gpg --list-sigs muchunjin@apache.org && gpg --export --armor muchunjin@apache.org) >> KEYS
$ (gpg --list-sigs muchunjin@apache.org && gpg --export --armor muchunjin@apache.org) >> KEYS
$ svn ci -m "add gpg key for muchunjin"
```
@@ -308,9 +308,9 @@ apache-hertzbeat-collector-1.6.0-incubating-bin.tar.gz: OK
```shell
# 检出 Apache SVN 仓库中的 dev/incubator/hertzbeat 目录到 Apache HertzBeat™ 项目根目录下的 svn/dev 目录下
svn co https://dist.apache.org/repos/dist/dev/incubator/hertzbeat svn/dev
svn co https://dist.apache.org/repos/dist/dev/hertzbeat svn/dev
svn co --depth empty https://dist.apache.org/repos/dist/dev/incubator/hertzbeat
svn co --depth empty https://dist.apache.org/repos/dist/dev/hertzbeat
```
- 复制物料包到 SVN 仓库
@@ -364,13 +364,13 @@ Release notes:
https://github.com/apache/hertzbeat/releases/tag/v1.6.0-rc1
The release candidates:
https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/1.6.0-RC1/
https://dist.apache.org/repos/dist/dev/hertzbeat/1.6.0-RC1/
Git tag for the release:
https://github.com/apache/hertzbeat/releases/tag/v1.6.0-rc1
The artifacts signed with PGP key [33545C76], corresponding to [muchunjin@apache.org], that can be found in keys file:
https://downloads.apache.org/incubator/hertzbeat/KEYS
https://downloads.apache.org/hertzbeat/KEYS
The vote will be open for at least 72 hours or until the necessary number of votes are reached.
@@ -460,13 +460,13 @@ Vote result thread:
https://lists.apache.org/thread/t5z58mvrs1drgzfyc48c9lhmd8skswn7
The release candidate:
https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/1.6.0-RC1/
https://dist.apache.org/repos/dist/dev/hertzbeat/1.6.0-RC1/
Git tag for the release:
https://github.com/apache/hertzbeat/releases/tag/v1.6.0-rc1
The artifacts signed with PGP key [33545C76], corresponding to [muchunjin@apache.org], that can be found in keys file:
https://downloads.apache.org/incubator/hertzbeat/KEYS
https://downloads.apache.org/hertzbeat/KEYS
The vote will be open for at least 72 hours or until the necessary number of votes are reached.
@@ -542,7 +542,7 @@ ChunJin Mu
### 4.1 迁移源代码和二进制包
```shell
svn mv https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/1.6.0-RC1 https://dist.apache.org/repos/dist/release/incubator/hertzbeat/1.6.0 -m "transfer packages for 1.6.0-RC1"
svn mv https://dist.apache.org/repos/dist/dev/hertzbeat/1.6.0-RC1 https://dist.apache.org/repos/dist/release/incubator/hertzbeat/1.6.0 -m "transfer packages for 1.6.0-RC1"
```
### 4.2 添加新版本下载地址到官网
@@ -592,17 +592,17 @@ release note: xxx
Hi Community,
We are glad to announce the release of Apache HertzBeat 1.6.0.
Thanks again for your help.
Thanks again for your help.
Apache HertzBeat (https://hertzbeat.apache.org/) - a real-time observability system with agentless, performance cluster, prometheus-compatible, custom monitoring and status page building capabilities.
Download Link:
Download Link:
https://hertzbeat.apache.org/docs/download/
Release Note:
Release Note:
https://github.com/apache/hertzbeat/releases/tag/v1.6.0
Website:
Website:
https://hertzbeat.apache.org/
HertzBeat Resources:
@@ -15,10 +15,10 @@ sidebar_position: 4
> 需要依赖gpg工具,如果没有,建议安装gpg2
```shell
#如果本地有svn,可以clone到本地
svn co https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/${release_version}-${rc_version}/
#如果本地有svn,可以clone到本地
svn co https://dist.apache.org/repos/dist/dev/hertzbeat/${release_version}-${rc_version}/
#或者 直接下载物料文件
wget https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/${release_version}-${rc_version}/xxx.xxx
wget https://dist.apache.org/repos/dist/dev/hertzbeat/${release_version}-${rc_version}/xxx.xxx
```
@@ -42,7 +42,7 @@ wget https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/${release_versio
##### 2.2.1 导入公钥
```shell
curl https://downloads.apache.org/incubator/hertzbeat/KEYS > KEYS # 下载KEYS
curl https://downloads.apache.org/hertzbeat/KEYS > KEYS # 下载KEYS
gpg --import KEYS # 导入KEYS到本地
```
@@ -70,9 +70,9 @@ Please decide how far you trust this user to correctly verify other users' keys
Your decision? 5 #选择5
Do you really want to set this key to ultimate trust? (y/N) y #选择y
gpg>
gpg>
```
##### 2.2.3 检查签名
@@ -174,7 +174,7 @@ I checked:
1. All download links are valid
2. Checksum and signature are OK
3. LICENSE and NOTICE are exist
4. Build successfully on macOS(Big Sur)
4. Build successfully on macOS(Big Sur)
5. ....
```
@@ -187,7 +187,7 @@ I checked:
1. All download links are valid
2. Checksum and signature are OK
3. LICENSE and NOTICE are exist
4. Build successfully on macOS(Big Sur)
4. Build successfully on macOS(Big Sur)
5. ....
```
@@ -9,7 +9,7 @@ sidebar_label: Download
:::tip
- 验证下载版本,请使用相应的哈希(sha512)、签名和[项目发布KEYS](https://downloads.apache.org/incubator/hertzbeat/KEYS)。
- 验证下载版本,请使用相应的哈希(sha512)、签名和[项目发布KEYS](https://downloads.apache.org/hertzbeat/KEYS)。
- 检查哈希和签名的方法参考 [如何验证](https://www.apache.org/dyn/closer.cgi#verify)。
:::
@@ -29,7 +29,7 @@ keywords: [开源监控工具, 开源Java监控工具, 监控Nginx指标]
```shell
./configure --prefix=/usr/local/nginx --with-http_stub_status_module
make && make install
```
@@ -66,15 +66,15 @@ keywords: [开源监控工具, 开源Java监控工具, 监控Nginx指标]
```shell
# install `ngx_http_reqstat_module`
wget https://github.com/zls0424/ngx_req_status/archive/master.zip -O ngx_req_status.zip
unzip ngx_req_status.zip
patch -p1 < ../ngx_req_status-master/write_filter.patch
./configure --prefix=/usr/local/nginx --add-module=/path/to/ngx_req_status-master
make -j2
make install
```
@@ -87,9 +87,9 @@ keywords: [开源监控工具, 开源Java监控工具, 监控Nginx指标]
http {
req_status_zone server_name $server_name 256k;
req_status_zone server_addr $server_addr 256k;
req_status server_name server_addr;
server {
location /req-status {
req_status_show on;
@@ -108,6 +108,7 @@ keywords: [开源监控工具, 开源Java监控工具, 监控Nginx指标]
4. 在浏览器访问 `http://localhost/req-status` 即可查看 Nginx 监控状态信息。
<!-- markdown-link-check-disable -->
**参考文档: <https://blog.csdn.net/weixin_55985097/article/details/116722309>**
**⚠️注意监控模块的端点路径为 `/nginx-status` `/req-status`**
@@ -1,6 +1,6 @@
---
id: tomcat
title: 监控:Tomcat监控
id: tomcat
title: 监控:Tomcat监控
sidebar_label: Tomcat监控
keywords: [开源监控系统, 开源网站监控, Tomcat监控]
---
@@ -72,4 +72,5 @@ keywords: [开源监控系统, 开源网站监控, Tomcat监控]
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote -Djava.rmi.server.hostname=10.1.1.52 -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"
```
<!-- markdown-link-check-disable -->
参考: <https://blog.csdn.net/weixin_41924764/article/details/108694239>
+131 -8
View File
@@ -1,24 +1,73 @@
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap');
@import url(navbar.css);
@import url(hero.css);
:root {
--md-primary: #6750A4;
--md-on-primary: #FFFFFF;
--md-primary-container: #EADDFF;
--md-on-primary-container: #21005D;
--md-secondary: #625B71;
--md-secondary-container: #E8DEF8;
--md-on-secondary-container: #1D192B;
--md-tertiary: #7D5260;
--md-tertiary-container: #FFD8E4;
--md-surface: #FFFBFE;
--md-surface-dim: #DDD8E1;
--md-surface-bright: #FFFBFE;
--md-surface-container-low: #F7F2FA;
--md-surface-container: #F3EDF7;
--md-surface-container-high: #ECE6F0;
--md-on-surface: #1C1B1F;
--md-on-surface-variant: #49454F;
--md-outline: #79747E;
--md-outline-variant: #CAC4D0;
--ifm-color-primary: #7228B5;
--ifm-color-primary-dark: #6228B5;
--ifm-color-primary-darker: #5228B5;
--ifm-color-primary-darkest: #5128B5;
--ifm-color-primary-light: #7228B5;
--ifm-color-primary-lighter: #8228B5;
--ifm-color-primary-lightest: #9228B5;
--ifm-color-primary-light: #8228C5;
--ifm-color-primary-lighter: #9228D5;
--ifm-color-primary-lightest: #A228E5;
--ifm-navbar-link-hover-color: var(--ifm-color-primary);
--ifm-code-font-size: 95%;
--ifm-font-family-base: Verdana, Arial, Helvetica, sans-serif;
--ifm-font-family-monospace: sans-serif;
--ifm-button-padding-horizontal: 0.55rem;
--ifm-button-padding-vertical: .7rem;
--ifm-navbar-height: 85px;
--ifm-font-family-base: 'Roboto', Verdana, Arial, Helvetica, sans-serif;
--ifm-font-family-monospace: 'Courier New', monospace;
--ifm-button-padding-horizontal: 1.5rem;
--ifm-button-padding-vertical: 0.75rem;
--ifm-button-border-radius: 9999px;
--ifm-button-border-width: 0;
--ifm-navbar-height: 85px;
--md-easing: cubic-bezier(0.2, 0, 0, 1);
--md-transition-fast: 200ms var(--md-easing);
--md-transition-medium: 300ms var(--md-easing);
}
html[data-theme='dark'] {
--md-primary: #D0BCFF;
--md-on-primary: #381E72;
--md-primary-container: #4F378B;
--md-on-primary-container: #EADDFF;
--md-secondary: #CCC2DC;
--md-secondary-container: #4A4458;
--md-on-secondary-container: #E8DEF8;
--md-tertiary: #EFB8C8;
--md-tertiary-container: #633B48;
--md-surface: #141218;
--md-surface-dim: #141218;
--md-surface-bright: #3B383E;
--md-surface-container-low: #1D1B20;
--md-surface-container: #211F26;
--md-surface-container-high: #2B2930;
--md-on-surface: #E6E1E5;
--md-on-surface-variant: #CAC4D0;
--md-outline: #938F99;
--md-outline-variant: #49454F;
}
h1, h2, h3 {
font-weight: 460;
}
@@ -165,3 +214,77 @@ article thead {
padding-left: 20px;
padding-right: 20px;
}
body {
background-color: var(--md-surface);
color: var(--md-on-surface);
}
.button {
border-radius: 9999px !important;
transition: all var(--md-transition-medium) !important;
font-weight: 500;
}
.button--primary {
background-color: var(--ifm-color-primary) !important;
color: var(--md-on-primary) !important;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
.button--primary:hover {
background-color: rgba(114, 40, 181, 0.9) !important;
box-shadow: 0 4px 12px rgba(114, 40, 181, 0.3) !important;
transform: translateY(-1px);
}
.button--primary:active {
transform: scale(0.95) translateY(0);
box-shadow: 0 2px 6px rgba(114, 40, 181, 0.2) !important;
}
.button:focus-visible {
outline: 2px solid var(--md-primary);
outline-offset: 2px;
}
h1, h2, h3, h4, h5, h6 {
font-weight: 500;
color: var(--md-on-surface);
}
h1 {
font-size: 2.5rem;
line-height: 1.2;
}
h2 {
font-size: 2rem;
line-height: 1.3;
}
h3 {
font-size: 1.5rem;
line-height: 1.4;
}
a, button {
transition: color var(--md-transition-fast),
background-color var(--md-transition-fast),
border-color var(--md-transition-fast),
box-shadow var(--md-transition-medium),
transform var(--md-transition-medium);
}
.card {
background-color: var(--md-surface-container);
border-radius: 24px;
padding: 1.5rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
transition: all var(--md-transition-medium);
}
.card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
transform: scale(1.01);
}
+161 -14
View File
@@ -1,34 +1,181 @@
.hero--primary {
--ifm-hero-background-color: #fff;
--ifm-hero-text-color: #000;
--ifm-hero-background-color: var(--md-surface);
--ifm-hero-text-color: var(--md-on-surface);
position: relative;
overflow: hidden;
min-height: 600px;
display: flex;
align-items: center;
}
html[data-theme=dark] .hero--primary {
--ifm-hero-background-color: #0c0c0c;
--ifm-hero-text-color: #fff;
--ifm-hero-background-color: #121212;
--ifm-hero-text-color: var(--md-surface);
}
.hero--primary::before,
.hero--primary::after {
content: '';
position: absolute;
border-radius: 50%;
filter: blur(80px);
opacity: 0.25;
z-index: 0;
pointer-events: none;
}
.hero--primary::before {
width: 500px;
height: 500px;
background: var(--md-primary);
top: -100px;
right: -150px;
animation: float 8s ease-in-out infinite;
}
.hero--primary::after {
width: 400px;
height: 400px;
background: var(--md-secondary-container);
bottom: -50px;
left: -100px;
animation: float 10s ease-in-out infinite reverse;
}
@keyframes float {
0%, 100% {
transform: translate(0, 0);
}
50% {
transform: translate(20px, -20px);
}
}
.hero--primary .container {
position: relative;
z-index: 1;
}
.hero__title {
background-size: 110%;
color: transparent;
width: 100%;
max-width: 600px;
max-width: 700px;
margin: 0 auto;
height: 280px;
height: auto;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
.hero__title img {
width: 100%;
max-width: 600px;
height: auto;
margin-top: 0;
filter: drop-shadow(0 4px 20px rgba(103, 80, 164, 0.15));
transition: transform var(--md-transition-medium);
}
.hero__title img:hover {
transform: scale(1.02);
}
.hero__subtitle {
font-size: 3em;
font-weight: bolder;
font-family: monospace;
color: #d97700;
font-size: 2.5rem;
font-weight: 700;
font-family: 'Roboto', sans-serif;
background: linear-gradient(135deg, var(--md-primary) 0%, #A228E5 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
width: 100%;
margin: 0 auto;
max-width: 1000px;
margin: 1.5rem auto 2rem;
text-align: center;
letter-spacing: -0.02em;
white-space: nowrap;
}
@media (max-width: 400px) {
@media (max-width: 996px) {
.hero--primary {
min-height: 500px;
}
.hero--primary::before {
width: 300px;
height: 300px;
top: -50px;
right: -100px;
}
.hero--primary::after {
width: 250px;
height: 250px;
bottom: -30px;
left: -80px;
}
.hero__title {
max-width: 500px;
min-height: 150px;
}
.hero__subtitle {
font-size: 1.3em;
font-weight: bolder;
font-size: 1.8rem;
padding: 0 1rem;
white-space: normal;
}
}
@media (max-width: 650px) {
.hero--primary {
min-height: 450px;
padding: 2rem 1rem 4rem;
}
.hero__title {
max-width: 350px;
min-height: 120px;
}
.hero__subtitle {
font-size: 1.4rem;
margin: 1rem auto 1.5rem;
}
.hero--primary::before,
.hero--primary::after {
opacity: 0.15;
}
}
.buttons {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 1rem;
margin: 2rem 0 0;
}
.buttons > a {
margin: 0;
min-width: 160px;
text-align: center;
}
@media (max-width: 650px) {
.buttons {
flex-direction: column;
width: 100%;
max-width: 300px;
margin: 1.5rem auto 0;
}
.buttons > a {
width: 100%;
margin: 0.25rem 0;
}
}
+9
View File
@@ -19,6 +19,15 @@
.header-discord-link {
padding-left: 0;
}
@media (max-width: 996px) {
.header-twitter-link,
.header-youtube-link,
.header-discord-link {
padding-left: 12px;
}
}
.header-github-link:before {
background: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") no-repeat;
}
+10 -4
View File
@@ -1,11 +1,17 @@
import React from 'react'
import clsx from 'clsx'
import styles from '../styles.module.css'
export default function Feature({ title, description }) {
export default function Feature({ title, description, index = 0 }) {
return (
<div className={clsx('col col--4')}>
<h3 style={{textAlign: 'center', fontSize: 'x-large'}}>{title}</h3>
<p>{description}</p>
<div
className={clsx('featureCard', styles.featureCard, styles.animateFadeInUp)}
style={{
animationDelay: `${index * 0.1}s`
}}
>
<h3 className={styles.featureTitle}>{title}</h3>
<p className={styles.featureDescription}>{description}</p>
</div>
)
}
+5 -4
View File
@@ -1,14 +1,15 @@
import React from 'react'
import clsx from 'clsx'
import styles from '../styles.module.css'
export default function Section({children }) {
export default function Section({ children, className }) {
return (
<section style={{padding: '4rem 0', width: '100%' }}>
<section className={clsx(styles.section, className)}>
<div className="container">
<div className="row">
<div className={styles.sectionRow}>
{children}
</div>
</div>
</section>
)
}
+62 -76
View File
@@ -7,16 +7,14 @@ import useBaseUrl from '@docusaurus/useBaseUrl'
import Translate from '@docusaurus/Translate'
import { Swiper, SwiperSlide } from 'swiper/react'
import { EffectFade, Navigation, Autoplay } from 'swiper'
import { Autoplay } from 'swiper'
import 'swiper/css'
import 'swiper/css/navigation'
import 'swiper/css/pagination'
import Feature from './components/Feature'
import Section from './components/Section'
import styles from './styles.module.css'
import {features} from '../constants'
import { features } from '../constants'
function Home() {
const context = useDocusaurusContext()
@@ -28,13 +26,13 @@ function Home() {
title={`${siteConfig.title} · ${siteConfig.tagline}`}
description={`${siteConfig.tagline}`}
>
{/* Hero Section */}
<header className={clsx('hero--primary', styles.heroBanner)}>
<div className="container">
<h1 className="hero__title">
<img
style={{ width: '500px', marginTop: '100px' }}
src={'/img/hertzbeat-brand.svg'}
alt={'#'}
alt={'HertzBeat Logo'}
/>
</h1>
<p className="hero__subtitle">
@@ -52,79 +50,67 @@ function Home() {
</div>
</div>
</header>
<main>
<div>
<Swiper
modules={[Autoplay, EffectFade, Navigation]}
watchSlidesProgress={true}
navigation={{
nextEl: '.user-swiper-button-next',
prevEl: '.user-swiper-button-prev',
}}
grabCursor
// effect will disable when auto scroll
// effect={'fade'}
// fadeEffect={{
// crossFade: true
// }}
// slidesPerView={1}
// auto scroll
loop={true}
speed={0}
autoplay={{
delay: 6000,
disableOnInteraction: false,
waitForTransition: false,
}}
>
<SwiperSlide>
<img
style={{ width: '1400px', display: 'block', margin: '0 auto' }}
src={useBaseUrl('/img/docs/hertzbeat-arch.png')}
/>
</SwiperSlide>
<SwiperSlide>
<img
style={{ width: '1400px', display: 'block', margin: '0 auto' }}
src={useBaseUrl('/img/home/status.png')}
/>
</SwiperSlide>
<SwiperSlide>
<img
style={{ width: '1400px', display: 'block', margin: '0 auto' }}
src={useBaseUrl('/img/home/0.png')}
/>
</SwiperSlide>
<SwiperSlide>
<img
style={{ width: '1400px', display: 'block', margin: '0 auto' }}
src={useBaseUrl('/img/home/1.png')}
/>
</SwiperSlide>
<SwiperSlide>
<img
style={{ width: '1400px', display: 'block', margin: '0 auto' }}
src={useBaseUrl('/img/home/9.png')}
/>
</SwiperSlide>
</Swiper>
</div>
<div
className="swiper-button-prev user-swiper-button-prev"
style={{ top: '880px', left: '50px', color: '#000033' }}
/>
<div
className="swiper-button-next user-swiper-button-next"
style={{ top: '880px', right: '50px', color: '#000033' }}
/>
<main>
{/* Features Section */}
{features && features.length > 0 && (
<Section>
{features.map((props, idx) => (
<Feature key={idx} {...props} />
))}
</Section>
<section className={styles.featuresSection}>
<div className="container">
<div className={styles.featuresRow}>
{features.map((props, idx) => (
<Feature key={idx} {...props} index={idx} />
))}
</div>
</div>
</section>
)}
{/* Swiper Carousel Section */}
<div className={styles.swiperContainer}>
<div className={styles.swiperInner}>
<Swiper
modules={[Autoplay]}
grabCursor
loop={true}
speed={600}
autoplay={{
delay: 6000,
disableOnInteraction: false,
}}
>
<SwiperSlide>
<img
src={useBaseUrl('/img/docs/hertzbeat-arch.png')}
alt="HertzBeat Architecture"
/>
</SwiperSlide>
<SwiperSlide>
<img
src={useBaseUrl('/img/home/status.png')}
alt="Monitoring Status"
/>
</SwiperSlide>
<SwiperSlide>
<img
src={useBaseUrl('/img/home/0.png')}
alt="Dashboard Overview"
/>
</SwiperSlide>
<SwiperSlide>
<img
src={useBaseUrl('/img/home/1.png')}
alt="Feature Demo"
/>
</SwiperSlide>
<SwiperSlide>
<img
src={useBaseUrl('/img/home/9.png')}
alt="Advanced Features"
/>
</SwiperSlide>
</Swiper>
</div>
</div>
</main>
</Layout>
</>
+212 -14
View File
@@ -5,8 +5,20 @@
* and scoped locally.
*/
.section {
padding: 4rem 0;
width: 100%;
background: var(--md-surface);
}
.sectionRow {
display: flex;
flex-wrap: wrap;
gap: 2rem;
}
.heroBanner {
padding: 1rem 0 5rem;
padding: 3rem 0 5rem;
text-align: center;
position: relative;
overflow: hidden;
@@ -14,28 +26,214 @@
@media screen and (max-width: 966px) {
.heroBanner {
padding: 2rem;
padding: 2rem 1rem 4rem;
}
}
.buttons {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
margin: 30px 0 0;
.swiperContainer {
position: relative;
background: linear-gradient(180deg,
var(--md-surface) 0%,
var(--md-surface-container) 100%);
padding: 3rem 0;
overflow: hidden;
}
.buttons > a {
margin: 0 5px;
.swiperInner {
width: 100%;
max-width: 1400px;
margin: 0 auto;
padding: 0 20px;
box-sizing: border-box;
}
.swiperContainer::before {
content: '';
position: absolute;
width: 600px;
height: 600px;
background: var(--md-secondary-container);
border-radius: 50%;
filter: blur(100px);
opacity: 0.2;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 0;
pointer-events: none;
}
.swiperContainer .swiper {
position: relative;
z-index: 1;
padding: 1rem 0;
}
.swiperContainer .swiper-slide {
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
width: 100%;
}
.swiperContainer .swiper-wrapper {
display: flex;
width: 100%;
}
.swiperContainer .swiper-slide img {
width: 100%;
max-width: 1200px;
height: auto;
border-radius: 24px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
transition: all var(--md-transition-medium);
}
.swiperContainer .swiper-slide img:hover {
box-shadow: 0 8px 30px rgba(103, 80, 164, 0.15);
transform: scale(1.01);
}
@media (max-width: 768px) {
.swiperContainer {
padding: 2rem 0;
}
}
.featuresSection {
background: var(--md-surface);
padding: 5rem 0;
position: relative;
overflow: hidden;
}
.featuresSection::before {
content: '';
position: absolute;
width: 400px;
height: 400px;
background: var(--md-tertiary-container);
border-radius: 50%;
filter: blur(90px);
opacity: 0.15;
top: 20%;
right: -100px;
pointer-events: none;
}
.featuresRow {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
position: relative;
z-index: 1;
}
@media (max-width: 996px) {
.featuresRow {
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
}
}
@media (max-width: 650px) {
.buttons > a {
margin: 10px 5px;
.featuresRow {
grid-template-columns: 1fr;
gap: 1rem;
}
}
.frameworkLogos img {
height: 45px;
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.featureCard {
background: var(--md-surface-container);
border-radius: 24px;
padding: 2rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
transition: all var(--md-transition-medium);
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
height: 100%;
position: relative;
overflow: hidden;
}
.featureCard::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg,
var(--md-primary) 0%,
var(--md-tertiary) 100%);
opacity: 0;
transition: opacity var(--md-transition-medium);
}
.featureCard:hover {
box-shadow: 0 8px 24px rgba(103, 80, 164, 0.15);
transform: translateY(-4px) scale(1.02);
background: var(--md-surface-container-low);
}
.featureCard:hover::before {
opacity: 1;
}
.featureCard:active {
transform: translateY(-2px) scale(1.01);
}
.featureTitle {
font-size: 1.35rem;
font-weight: 600;
color: var(--md-on-surface);
margin: 0 0 0.75rem 0;
line-height: 1.4;
}
.featureDescription {
font-size: 1rem;
line-height: 1.6;
color: var(--md-on-surface-variant);
margin: 0;
}
.featureDescription code {
background: var(--md-surface-container-high);
padding: 0.2rem 0.5rem;
border-radius: 8px;
font-size: 0.9em;
color: var(--md-primary);
font-family: 'Courier New', monospace;
}
@media (max-width: 768px) {
.featureCard {
padding: 1.5rem;
border-radius: 20px;
}
.featureTitle {
font-size: 1.2rem;
}
.featureDescription {
font-size: 0.95rem;
}
}
+2
View File
@@ -0,0 +1,2 @@
# CSP permissions for hertzbeat.apache.org - Adding 3rd party service Algolia & Kapa.ai
SetEnv CSP_PROJECT_DOMAINS "https://*.algolia.net/ https://*.algolianet.com/ https://*.algolia.io/ https://widget.kapa.ai https://proxy.kapa.ai https://kapa-widget-proxy-la7dkmplpq-uc.a.run.app https://metrics.kapa.ai https://www.google.com/recaptcha/ https://hcaptcha.com https://*.hcaptcha.com"
@@ -71,7 +71,7 @@ some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
# At this time, a dialog box will pop up, asking you to enter the key for this gpg.
# At this time, a dialog box will pop up, asking you to enter the key for this gpg.
# you need to remember that it will be used in subsequent steps.
┌─────────────────────────────────────────────────────┐
│ Please enter this passphrase to │
@@ -142,11 +142,11 @@ Or enter <https://keyserver.ubuntu.com/> address in the browser, enter the name
$ mkdir -p svn/dev
$ cd svn/dev
$ svn co https://dist.apache.org/repos/dist/dev/incubator/hertzbeat
$ svn co https://dist.apache.org/repos/dist/dev/hertzbeat
$ cd svn/dev/hertzbeat
# Append the KEY you generated to the file KEYS, and check if it is added correctly
$ (gpg --list-sigs muchunjin@apache.org && gpg --export --armor muchunjin@apache.org) >> KEYS
$ (gpg --list-sigs muchunjin@apache.org && gpg --export --armor muchunjin@apache.org) >> KEYS
$ svn ci -m "add gpg key for muchunjin"
```
@@ -161,7 +161,7 @@ $ svn co https://dist.apache.org/repos/dist/release/incubator/hertzbeat
$ cd svn/release/hertzbeat
# Append the KEY you generated to the file KEYS, and check if it is added correctly
$ (gpg --list-sigs muchunjin@apache.org && gpg --export --armor muchunjin@apache.org) >> KEYS
$ (gpg --list-sigs muchunjin@apache.org && gpg --export --armor muchunjin@apache.org) >> KEYS
$ svn ci -m "add gpg key for muchunjin"
```
@@ -306,15 +306,15 @@ apache-hertzbeat-collector-1.6.0-incubating-bin.tar.gz: OK
```shell
# Check out the dev directory of the Apache SVN to the svn/dev directory under dist in the root directory of the Apache HertzBeat project
svn co https://dist.apache.org/repos/dist/dev/incubator/hertzbeat svn/dev
svn co https://dist.apache.org/repos/dist/dev/hertzbeat svn/dev
svn co --depth empty https://dist.apache.org/repos/dist/dev/incubator/hertzbeat
svn co --depth empty https://dist.apache.org/repos/dist/dev/hertzbeat
```
- Copy the material package to the dev directory
Create a version number directory and name it in the form of ${release_version}-${RC_version}. RC_version starts from 1, that is, the candidate version starts from RC1. During the release process, there is a problem that causes the vote to fail.
If it needs to be corrected, it needs to iterate the RC version , the RC version number needs to be +1. For example: Vote for version 1.6.0-RC1. If the vote passes without any problems, the RC1 version material will be released as the final version material.
Create a version number directory and name it in the form of ${release_version}-${RC_version}. RC_version starts from 1, that is, the candidate version starts from RC1. During the release process, there is a problem that causes the vote to fail.
If it needs to be corrected, it needs to iterate the RC version , the RC version number needs to be +1. For example: Vote for version 1.6.0-RC1. If the vote passes without any problems, the RC1 version material will be released as the final version material.
If there is a problem (when the hertzbeat/incubator community votes, the voters will strictly check various release requirements and compliance issues) and need to be corrected, then re-initiate the vote after the correction, and the candidate version for the next vote is 1.6.0- RC2.
```shell
@@ -364,13 +364,13 @@ Release notes:
https://github.com/apache/hertzbeat/releases/tag/v1.6.0-rc1
The release candidates:
https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/1.6.0-RC1/
https://dist.apache.org/repos/dist/dev/hertzbeat/1.6.0-RC1/
Git tag for the release:
https://github.com/apache/hertzbeat/releases/tag/v1.6.0-rc1
The artifacts signed with PGP key [33545C76], corresponding to [muchunjin@apache.org], that can be found in keys file:
https://downloads.apache.org/incubator/hertzbeat/KEYS
https://downloads.apache.org/hertzbeat/KEYS
The vote will be open for at least 72 hours or until the necessary number of votes are reached.
@@ -460,13 +460,13 @@ Vote result thread:
https://lists.apache.org/thread/t5z58mvrs1drgzfyc48c9lhmd8skswn7
The release candidate:
https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/1.6.0-RC1/
https://dist.apache.org/repos/dist/dev/hertzbeat/1.6.0-RC1/
Git tag for the release:
https://github.com/apache/hertzbeat/releases/tag/v1.6.0-rc1
The artifacts signed with PGP key [33545C76], corresponding to [muchunjin@apache.org], that can be found in keys file:
https://downloads.apache.org/incubator/hertzbeat/KEYS
https://downloads.apache.org/hertzbeat/KEYS
The vote will be open for at least 72 hours or until the necessary number of votes are reached.
@@ -542,7 +542,7 @@ Wait a day to see if the tutor has any other comments, if not, send the followin
### 5.1 Migrating source and binary packages
```shell
svn mv https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/1.6.0-RC1 https://dist.apache.org/repos/dist/release/incubator/hertzbeat/1.6.0 -m "transfer packages for 1.6.0-RC1"
svn mv https://dist.apache.org/repos/dist/dev/hertzbeat/1.6.0-RC1 https://dist.apache.org/repos/dist/release/incubator/hertzbeat/1.6.0 -m "transfer packages for 1.6.0-RC1"
```
#### 4.2 Add the new version download address to the official website
@@ -592,17 +592,17 @@ The rename the release-1.6.0-rc1 branch to release-1.6.0.
Hi Community,
We are glad to announce the release of Apache HertzBeat 1.6.0.
Thanks again for your help.
Thanks again for your help.
Apache HertzBeat (https://hertzbeat.apache.org/) - a real-time observability system with agentless, performance cluster, prometheus-compatible, custom monitoring and status page building capabilities.
Download Link:
Download Link:
https://hertzbeat.apache.org/docs/download/
Release Note:
Release Note:
https://github.com/apache/hertzbeat/releases/tag/v1.6.0
Website:
Website:
https://hertzbeat.apache.org/
HertzBeat Resources:
@@ -22,9 +22,9 @@ If the network is poor, downloading may be time-consuming. The download is compl
```shell
#If there is svn locally, you can clone to the local
$ svn co https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/${release_version}-${rc_version}/
$ svn co https://dist.apache.org/repos/dist/dev/hertzbeat/${release_version}-${rc_version}/
#or download the material file directly
$ wget https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/${release_version}-${rc_version}/xxx.xxx
$ wget https://dist.apache.org/repos/dist/dev/hertzbeat/${release_version}-${rc_version}/xxx.xxx
```
### 2. Verify that the uploaded version is compliant
@@ -47,7 +47,7 @@ First import the publisher's public key. Import KEYS from the svn repository to
##### 2.2.1 Import public key
```shell
curl https://downloads.apache.org/incubator/hertzbeat/KEYS > KEYS # Download KEYS
-curl https://downloads.apache.org/hertzbeat/KEYS > KEYS # Download KEYS
gpg --import KEYS # Import KEYS to local
```
@@ -75,9 +75,9 @@ Please decide how far you trust this user to correctly verify other users' keys
Your decision? 5 #choose 5
Do you really want to set this key to ultimate trust? (y/N) y #choose y
gpg>
```
##### 2.2.3 Check the gpg signature
@@ -177,7 +177,7 @@ I checked:
2. Checksum and signature are OK
3. LICENSE and NOTICE are exist
4. Build successfully on macOS(Big Sur)
5.
5.
```
PPMC/IPMC member:
@@ -190,7 +190,7 @@ I checked:
2. Checksum and signature are OK
3. LICENSE and NOTICE are exist
4. Build successfully on macOS(Big Sur)
5.
5.
```
---
@@ -9,7 +9,7 @@ sidebar_label: Download
:::tip
- Please verify the release with corresponding hashes(sha512), signatures and [project release KEYS](https://downloads.apache.org/incubator/hertzbeat/KEYS).
- Please verify the release with corresponding hashes(sha512), signatures and [project release KEYS](https://downloads.apache.org/hertzbeat/KEYS).
- Refer to [How to Verify](https://www.apache.org/dyn/closer.cgi#verify) for how to check the hashes and signatures.
:::
@@ -63,7 +63,7 @@ services:
- hertzbeat
hertzbeat:
image: apache/hertzbeat:1.7.3
image: apache/hertzbeat:1.8.0
container_name: compose-hertzbeat
hostname: hertzbeat
restart: always
@@ -62,7 +62,7 @@ services:
- hertzbeat
hertzbeat:
image: apache/hertzbeat:1.7.3
image: apache/hertzbeat:1.8.0
container_name: compose-hertzbeat
hostname: hertzbeat
restart: always
@@ -62,7 +62,7 @@ services:
- hertzbeat
hertzbeat:
image: apache/hertzbeat:1.7.3
image: apache/hertzbeat:1.8.0
container_name: compose-hertzbeat
hostname: hertzbeat
restart: always
@@ -82,7 +82,7 @@ services:
- hertzbeat
hertzbeat:
image: apache/hertzbeat:nightly
image: apache/hertzbeat:1.8.0
container_name: compose-hertzbeat
hostname: hertzbeat
restart: always
@@ -65,7 +65,7 @@ services:
- hertzbeat
hertzbeat:
image: apache/hertzbeat:1.7.3
image: apache/hertzbeat:1.8.0
container_name: compose-hertzbeat
hostname: hertzbeat
restart: always
@@ -97,7 +97,7 @@
<tr>
<th nzAlign="center" nzLeft nzWidth="3%" [(nzChecked)]="checkedAll" (nzCheckedChange)="onAllChecked($event)"></th>
<th nzAlign="center" nzWidth="12%">{{ 'alert.setting.name' | i18n }}</th>
<th nzAlign="center" nzWidth="8%">{{ 'alert.setting.type' | i18n }}</th>
<th nzAlign="center" nzWidth="10%">{{ 'alert.setting.type' | i18n }}</th>
<th nzAlign="center" nzWidth="15%">{{ 'alert.setting.expr' | i18n }}</th>
<th nzAlign="center" nzWidth="15%">{{ 'alert.setting.template' | i18n }}</th>
<th nzAlign="center" nzWidth="15%">{{ 'label.bind' | i18n }}</th>
@@ -910,6 +910,9 @@
field: 'labels',
type: 'label-selector'
}"
[extra]="{
labelIsCustom: labelIsCustom
}"
[name]="'labels'"
[(ngModel)]="define.labels"
/>
@@ -577,6 +577,7 @@ export class AlertSettingComponent implements OnInit {
userExpr!: string;
severity!: string;
alertMode!: string;
labelIsCustom: boolean = true;
logFields: any[] = [];
editAlertDefine(alertDefineId: number) {
@@ -402,7 +402,7 @@
</span>
</ng-template>
<nz-card-tab>
<nz-tabset nzSize="small" [(nzSelectedIndex)]="collectorsTabSelectedIndex">
<nz-tabset nzSize="small" [(nzSelectedIndex)]="collectorsTabSelectedIndexes[i]">
<nz-tab [nzTitle]="'collector.status' | i18n" />
<nz-tab [nzTitle]="'collector.task' | i18n" />
<nz-tab [nzTitle]="'collector.start-time' | i18n" />
@@ -410,7 +410,7 @@
<nz-tab [nzTitle]="'collector.node' | i18n" />
</nz-tabset>
</nz-card-tab>
<ng-container *ngIf="collectorsTabSelectedIndex === 0">
<ng-container *ngIf="collectorsTabSelectedIndexes[i] === 0">
<div class="rounded-lg" style="text-align: center; width: 100%; background-color: transparent; padding: 2%">
<span [style]="'font-size: x-large; font-weight: bolder;' + (item.collector.status == 0 ? 'color: green' : 'color: red')">
{{
@@ -419,7 +419,7 @@
</span>
</div>
</ng-container>
<ng-container *ngIf="collectorsTabSelectedIndex === 1">
<ng-container *ngIf="collectorsTabSelectedIndexes[i] === 1">
<div class="rounded-lg" style="text-align: center; width: 100%; background-color: transparent; padding: 2%">
<span style="font-size: xxx-large; font-weight: bolder">
{{ item.pinMonitorNum + item.dispatchMonitorNum }}
@@ -431,19 +431,19 @@
>
</div>
</ng-container>
<ng-container *ngIf="collectorsTabSelectedIndex === 2">
<ng-container *ngIf="collectorsTabSelectedIndexes[i] === 2">
<div class="rounded-lg" style="text-align: center; width: 100%; background-color: transparent; padding: 2%">
<span style="font-size: x-large; font-weight: bolder">{{
(item.collector.gmtUpdate | date : 'YYYY-MM-dd HH:mm:ss')?.trim()
}}</span>
</div>
</ng-container>
<ng-container *ngIf="collectorsTabSelectedIndex === 3">
<ng-container *ngIf="collectorsTabSelectedIndexes[i] === 3">
<div class="rounded-lg" style="text-align: center; width: 100%; background-color: transparent; padding: 2%">
<span style="font-size: x-large; font-weight: bolder">{{ item.collector.ip }}</span>
</div>
</ng-container>
<ng-container *ngIf="collectorsTabSelectedIndex === 4">
<ng-container *ngIf="collectorsTabSelectedIndexes[i] === 4">
<div class="rounded-lg" style="text-align: center; width: 100%; background-color: transparent; padding: 2%">
<span style="font-size: x-large; font-weight: bolder">{{ item.collector.name }}</span>
</div>
@@ -174,7 +174,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
// collector list
collectorsLoading: boolean = false;
collectors!: CollectorSummary[];
collectorsTabSelectedIndex = 0;
collectorsTabSelectedIndexes: { [key: number]: number } = {};
// alert list
alerts!: SingleAlert[];
@@ -201,7 +201,12 @@ export class MonitorDataChartComponent implements OnInit, OnDestroy {
}
},
tooltip: {
trigger: 'axis'
trigger: 'axis',
extraCssText: 'max-width: 580px; max-height: 400px; overflow-y: auto;',
enterable: true,
renderMode: 'html',
appendToBody: true,
confine: false
},
grid: {
left: '2',
@@ -222,6 +227,7 @@ export class MonitorDataChartComponent implements OnInit, OnDestroy {
yAxis: {
type: 'value',
boundaryGap: [0, '100%'],
scale: true,
splitLine: {
show: true
}
@@ -97,7 +97,7 @@
<th nzAlign="center" nzLeft nzWidth="3%" [(nzChecked)]="checkedAll" (nzCheckedChange)="onAllChecked($event)"></th>
<th nzAlign="center" nzWidth="14%">{{ 'collector' | i18n }}</th>
<th nzAlign="center" nzWidth="9%">{{ 'collector.status' | i18n }}</th>
<th nzAlign="center" nzWidth="9%">{{ 'collector.mode' | i18n }}</th>
<th nzAlign="center" nzWidth="10%">{{ 'collector.mode' | i18n }}</th>
<th nzAlign="center" nzWidth="9%">{{ 'collector.task' | i18n }}</th>
<th nzAlign="center" nzWidth="9%">{{ 'collector.pinned' | i18n }}</th>
<th nzAlign="center" nzWidth="9%">{{ 'collector.dispatched' | i18n }}</th>
@@ -147,14 +147,14 @@
<div *nzModalContent class="-inner-content">
<form [formGroup]="pluginForm" nz-form>
<nz-form-item>
<nz-form-label [nzSpan]="6" nzRequired="true" nzFor="name">{{ 'plugin.name' | i18n }}</nz-form-label>
<nz-form-label [nzSpan]="8" nzRequired="true" nzFor="name">{{ 'plugin.name' | i18n }}</nz-form-label>
<nz-form-control [nzSpan]="14" [nzErrorTip]="'validation.required' | i18n">
<input nz-input formControlName="name" id="name" />
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSpan]="6" nzRequired="true" nzFor="jarFile">{{ 'plugin.jar.file' | i18n }}</nz-form-label>
<nz-form-label [nzSpan]="8" nzRequired="true" nzFor="jarFile">{{ 'plugin.jar.file' | i18n }}</nz-form-label>
<nz-form-control [nzSpan]="14" [nzErrorTip]="'validation.required' | i18n">
<nz-upload
nzAccept=".jar"
@@ -175,7 +175,7 @@
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSpan]="6" nzRequired="true" nzFor="enableStatus">{{ 'plugin.status' | i18n }}</nz-form-label>
<nz-form-label [nzSpan]="8" nzRequired="true" nzFor="enableStatus">{{ 'plugin.status' | i18n }}</nz-form-label>
<nz-form-control [nzSpan]="14">
<nz-switch formControlName="enableStatus" required></nz-switch>
</nz-form-control>
@@ -32,7 +32,13 @@
>
<nz-option *ngFor="let keyOption of labelKeys" [nzLabel]="keyOption" [nzValue]="keyOption"></nz-option>
<nz-option
*ngIf="labelIsCustom && customInputKey && !hasMatchingOption(customInputKey, labelKeys)"
*ngIf="
labelIsCustom &&
activeSearchIndex === i &&
activeSearchType === 'key' &&
customInputKey &&
!hasMatchingOption(customInputKey, labelKeys)
"
[nzLabel]="customInputKey"
[nzValue]="customInputKey"
></nz-option>
@@ -53,7 +59,13 @@
>
<nz-option *ngFor="let valueOption of getLabelValues(value[i].key)" [nzLabel]="valueOption" [nzValue]="valueOption"></nz-option>
<nz-option
*ngIf="labelIsCustom && customInputValue && !hasMatchingOption(customInputValue, getLabelValues(value[i].key))"
*ngIf="
labelIsCustom &&
activeSearchIndex === i &&
activeSearchType === 'value' &&
customInputValue &&
!hasMatchingOption(customInputValue, getLabelValues(value[i].key))
"
[nzLabel]="customInputValue"
[nzValue]="customInputValue"
></nz-option>
@@ -46,32 +46,105 @@ export class LabelSelectorComponent implements ControlValueAccessor, OnInit {
value: Array<{ key: string; value: string }> = [];
customInputKey: string = '';
customInputValue: string = '';
// Track the active search input to prevent cross-row pollution of custom options
activeSearchIndex: number | null = null;
activeSearchType: 'key' | 'value' | null = null;
_onChange = (_: any) => {};
_onTouched = () => {};
writeValue(value: any): void {
this.value = value && value.length > 0 ? value : [{ key: '', value: '' }];
// Validate and initialize: use valid array if provided, otherwise initialize with empty key-value pair
if (!value || !Array.isArray(value) || value.length === 0) {
this.value = [{ key: '', value: '' }];
return;
}
this.value = value;
// Build labelKeys and labelMap for autocomplete from the provided values
this.value.forEach(item => {
// Skip invalid items
if (!item || typeof item !== 'object' || !item.key) {
return;
}
const key = String(item.key).trim();
const val = item.value ? String(item.value).trim() : '';
// Skip empty keys
if (!key) {
return;
}
// Add new key to labelKeys
if (!this.labelKeys.includes(key)) {
this.labelKeys.push(key);
}
// Initialize value array for this key
if (!this.labelMap[key]) {
this.labelMap[key] = [];
}
// Add value to labelMap (with deduplication)
if (val && !this.labelMap[key].includes(val)) {
this.labelMap[key].push(val);
}
});
}
/**
* Handle key change: clear the corresponding value and add new key to labelKeys for reuse
*/
onKeyChange(index: number, value: string) {
// Clear the value when key changes
this.value[index].value = '';
// Add new key to labelKeys so other rows can see this custom key
if (value && !this.labelKeys.includes(value)) {
this.labelKeys.push(value);
}
// Initialize value array for this key
if (value && !this.labelMap[value]) {
this.labelMap[value] = [];
}
this._onChange(this.value);
this._onTouched();
}
/**
* Handle value change: add new value to the corresponding key's labelMap for reuse
*/
onValueChange(index: number, value: string) {
const key = this.value[index].key;
// Add new value to labelMap so other rows can see this custom value
if (key && value && this.labelMap[key] && !this.labelMap[key].includes(value)) {
this.labelMap[key].push(value);
}
this._onChange(this.value);
this._onTouched();
}
/**
* Handle search event: track the active input to ensure custom options only show in the corresponding field
*/
onSearch(value: string, index: number, type: 'key' | 'value'): void {
// Track the currently active search input
this.activeSearchIndex = index;
this.activeSearchType = type;
if (value) {
if (type === 'key') {
this.customInputKey = value;
} else {
this.customInputValue = value;
}
} else {
// Clear custom input value when search is empty
if (type === 'key') {
this.customInputKey = '';
} else {
this.customInputValue = '';
}
}
}